1 /*
  2  * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
  3  * Copyright (c) 2020 SAP SE. All rights reserved.
  4  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  5  *
  6  * This code is free software; you can redistribute it and/or modify it
  7  * under the terms of the GNU General Public License version 2 only, as
  8  * published by the Free Software Foundation.
  9  *
 10  * This code is distributed in the hope that it will be useful, but WITHOUT
 11  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 12  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 13  * version 2 for more details (a copy is included in the LICENSE file that
 14  * accompanied this code).
 15  *
 16  * You should have received a copy of the GNU General Public License version
 17  * 2 along with this work; if not, write to the Free Software Foundation,
 18  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 19  *
 20  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 21  * or visit www.oracle.com if you need additional information or have any
 22  * questions.
 23  *
 24  */
 25 
 26 #include "precompiled.hpp"
 27 #include "memory/metaspace/chunkManager.hpp"
 28 #include "memory/metaspace/counters.hpp"
 29 #include "memory/metaspace/metaspaceArena.hpp"
 30 #include "memory/metaspace/metaspaceArenaGrowthPolicy.hpp"
 31 #include "memory/metaspace/metaspaceSettings.hpp"
 32 #include "memory/metaspace/metaspaceStatistics.hpp"
 33 #include "runtime/mutexLocker.hpp"
 34 #include "utilities/debug.hpp"
 35 #include "utilities/globalDefinitions.hpp"
 36 //#define LOG_PLEASE
 37 #include "metaspaceGtestCommon.hpp"
 38 #include "metaspaceGtestContexts.hpp"
 39 #include "metaspaceGtestSparseArray.hpp"
 40 
 41 using metaspace::AllocationAlignmentByteSize;
 42 using metaspace::ArenaGrowthPolicy;
 43 using metaspace::ChunkManager;
 44 using metaspace::IntCounter;
 45 using metaspace::MemRangeCounter;
 46 using metaspace::MetaspaceArena;
 47 using metaspace::SizeAtomicCounter;
 48 using metaspace::ArenaStats;
 49 using metaspace::InUseChunkStats;
 50 
 51 // Little randomness helper
 52 static bool fifty_fifty() {
 53   return IntRange(100).random_value() < 50;
 54 }
 55 
 56 // A MetaspaceArenaTestBed contains a single MetaspaceArena and its lock.
 57 // It keeps track of allocations done from this MetaspaceArena.
 58 class MetaspaceArenaTestBed : public CHeapObj<mtInternal> {
 59 
 60   MetaspaceArena* _arena;
 61 
 62   const SizeRange _allocation_range;
 63   size_t _size_of_last_failed_allocation;
 64 
 65   // We keep track of all allocations done thru the MetaspaceArena to
 66   // later check for overwriters.
 67   struct allocation_t {
 68     allocation_t* next;
 69     MetaWord* p; // nullptr if deallocated
 70     size_t word_size;
 71     void mark() {
 72       mark_range(p, word_size);
 73     }
 74     void verify() const {
 75       if (p != nullptr) {
 76         check_marked_range(p, word_size);
 77       }
 78     }
 79   };
 80 
 81   allocation_t* _allocations;
 82 
 83   // We count how much we did allocate and deallocate
 84   MemRangeCounter _alloc_count;
 85   MemRangeCounter _dealloc_count;
 86 
 87   // Check statistics returned by MetaspaceArena::add_to_statistics() against what
 88   // we know we allocated. This is a bit flaky since MetaspaceArena has internal
 89   // overhead.
 90   void verify_arena_statistics() const {
 91 
 92     ArenaStats stats;
 93     _arena->add_to_statistics(&stats);
 94     InUseChunkStats in_use_stats = stats.totals();
 95 
 96     assert(_dealloc_count.total_size() <= _alloc_count.total_size() &&
 97            _dealloc_count.count() <= _alloc_count.count(), "Sanity");
 98 
 99     // Check consistency of stats
100     ASSERT_GE(in_use_stats._word_size, in_use_stats._committed_words);
101     ASSERT_EQ(in_use_stats._committed_words,
102               in_use_stats._used_words + in_use_stats._free_words + in_use_stats._waste_words);
103     ASSERT_GE(in_use_stats._used_words, stats._free_blocks_word_size);
104 
105     // Note: reasons why the outside alloc counter and the inside used counter can differ:
106     // - alignment/padding of allocations
107     // - inside used counter contains blocks in free list
108     // - free block list splinter threshold
109     // - if +MetaspaceGuardAllocations, guard costs
110 
111     // Since what we deallocated may have been given back to us in a following allocation,
112     // we only know fore sure we allocated what we did not give back.
113     const size_t at_least_allocated = _alloc_count.total_size() - _dealloc_count.total_size();
114 
115     // At most we allocated this:
116     const size_t max_word_overhead_per_alloc =
117         4 + (metaspace::Settings::use_allocation_guard() ? 4 : 0);
118     const size_t at_most_allocated = _alloc_count.total_size() + max_word_overhead_per_alloc * _alloc_count.count();
119 
120     ASSERT_LE(at_least_allocated, in_use_stats._used_words - stats._free_blocks_word_size);
121     ASSERT_GE(at_most_allocated, in_use_stats._used_words - stats._free_blocks_word_size);
122 
123   }
124 
125 public:
126 
127   MetaspaceArena* arena() { return _arena; }
128 
129   MetaspaceArenaTestBed(ChunkManager* cm, const ArenaGrowthPolicy* alloc_sequence,
130                         SizeAtomicCounter* used_words_counter, SizeRange allocation_range) :
131     _arena(nullptr),
132     _allocation_range(allocation_range),
133     _size_of_last_failed_allocation(0),
134     _allocations(nullptr),
135     _alloc_count(),
136     _dealloc_count()
137   {
138     _arena = new MetaspaceArena(cm, alloc_sequence, used_words_counter, "gtest-MetaspaceArenaTestBed-sm");
139   }
140 
141   ~MetaspaceArenaTestBed() {
142 
143     verify_arena_statistics();
144 
145     allocation_t* a = _allocations;
146     while (a != nullptr) {
147       allocation_t* b = a->next;
148       a->verify();
149       FREE_C_HEAP_OBJ(a);
150       a = b;
151     }
152 
153     DEBUG_ONLY(_arena->verify();)
154 
155     // Delete MetaspaceArena. That should clean up all metaspace.
156     delete _arena;
157 
158   }
159 
160   size_t words_allocated() const        { return _alloc_count.total_size(); }
161   int num_allocations() const           { return _alloc_count.count(); }
162 
163   size_t size_of_last_failed_allocation() const { return _size_of_last_failed_allocation; }
164 
165   // Allocate a random amount. Return false if the allocation failed.
166   bool checked_random_allocate() {
167     size_t word_size = 1 + _allocation_range.random_value();
168     MetaWord* p = _arena->allocate(word_size);
169     if (p != nullptr) {
170       EXPECT_TRUE(is_aligned(p, AllocationAlignmentByteSize));
171 
172       allocation_t* a = NEW_C_HEAP_OBJ(allocation_t, mtInternal);
173       a->word_size = word_size;
174       a->p = p;
175       a->mark();
176       a->next = _allocations;
177       _allocations = a;
178       _alloc_count.add(word_size);
179       if ((_alloc_count.count() % 20) == 0) {
180         verify_arena_statistics();
181         DEBUG_ONLY(_arena->verify();)
182       }
183       return true;
184     } else {
185       _size_of_last_failed_allocation = word_size;
186     }
187     return false;
188   }
189 
190   // Deallocate a random allocation
191   void checked_random_deallocate() {
192     allocation_t* a = _allocations;
193     while (a && a->p != nullptr && os::random() % 10 != 0) {
194       a = a->next;
195     }
196     if (a != nullptr && a->p != nullptr) {
197       a->verify();
198       _arena->deallocate(a->p, a->word_size);
199       _dealloc_count.add(a->word_size);
200       a->p = nullptr; a->word_size = 0;
201       if ((_dealloc_count.count() % 20) == 0) {
202         verify_arena_statistics();
203         DEBUG_ONLY(_arena->verify();)
204       }
205     }
206   }
207 
208 }; // End: MetaspaceArenaTestBed
209 
210 class MetaspaceArenaTest {
211 
212   MetaspaceGtestContext _context;
213 
214   SizeAtomicCounter _used_words_counter;
215 
216   SparseArray<MetaspaceArenaTestBed*> _testbeds;
217   IntCounter _num_beds;
218 
219   //////// Bed creation, destruction ///////
220 
221   void create_new_test_bed_at(int slotindex, const ArenaGrowthPolicy* growth_policy, SizeRange allocation_range) {
222     DEBUG_ONLY(_testbeds.check_slot_is_null(slotindex));
223     MetaspaceArenaTestBed* bed = new MetaspaceArenaTestBed(&_context.cm(), growth_policy,
224                                                        &_used_words_counter, allocation_range);
225     _testbeds.set_at(slotindex, bed);
226     _num_beds.increment();
227   }
228 
229   void create_random_test_bed_at(int slotindex) {
230     SizeRange allocation_range(1, 100); // randomize too?
231     const ArenaGrowthPolicy* growth_policy = ArenaGrowthPolicy::policy_for_space_type(
232         (fifty_fifty() ? Metaspace::StandardMetaspaceType : Metaspace::ReflectionMetaspaceType),
233          fifty_fifty());
234     create_new_test_bed_at(slotindex, growth_policy, allocation_range);
235    }
236 
237   // Randomly create a random test bed at a random slot, and return its slot index
238   // (returns false if we reached max number of test beds)
239   bool create_random_test_bed() {
240     const int slot = _testbeds.random_null_slot_index();
241     if (slot != -1) {
242       create_random_test_bed_at(slot);
243     }
244     return slot;
245   }
246 
247   // Create test beds for all slots
248   void create_all_test_beds() {
249     for (int slot = 0; slot < _testbeds.size(); slot++) {
250       if (_testbeds.slot_is_null(slot)) {
251         create_random_test_bed_at(slot);
252       }
253     }
254   }
255 
256   void delete_test_bed_at(int slotindex) {
257     DEBUG_ONLY(_testbeds.check_slot_is_not_null(slotindex));
258     MetaspaceArenaTestBed* bed = _testbeds.at(slotindex);
259     delete bed; // This will return all its memory to the chunk manager
260     _testbeds.set_at(slotindex, nullptr);
261     _num_beds.decrement();
262   }
263 
264   // Randomly delete a random test bed at a random slot
265   // Return false if there are no test beds to delete.
266   bool delete_random_test_bed() {
267     const int slotindex = _testbeds.random_non_null_slot_index();
268     if (slotindex != -1) {
269       delete_test_bed_at(slotindex);
270       return true;
271     }
272     return false;
273   }
274 
275   // Delete all test beds.
276   void delete_all_test_beds() {
277     for (int slot = _testbeds.first_non_null_slot(); slot != -1; slot = _testbeds.next_non_null_slot(slot)) {
278       delete_test_bed_at(slot);
279     }
280   }
281 
282   //////// Allocating metaspace from test beds ///////
283 
284   bool random_allocate_from_testbed(int slotindex) {
285     DEBUG_ONLY(_testbeds.check_slot_is_not_null(slotindex);)
286     MetaspaceArenaTestBed* bed = _testbeds.at(slotindex);
287     bool success = bed->checked_random_allocate();
288     if (success == false) {
289       // We must have hit a limit.
290       EXPECT_LT(_context.commit_limiter().possible_expansion_words(),
291                 metaspace::get_raw_word_size_for_requested_word_size(bed->size_of_last_failed_allocation()));
292     }
293     return success;
294   }
295 
296   // Allocate multiple times random sizes from a single MetaspaceArena.
297   bool random_allocate_multiple_times_from_testbed(int slotindex, int num_allocations) {
298     bool success = true;
299     int n = 0;
300     while (success && n < num_allocations) {
301       success = random_allocate_from_testbed(slotindex);
302       n++;
303     }
304     return success;
305   }
306 
307   // Allocate multiple times random sizes from a single random MetaspaceArena.
308   bool random_allocate_random_times_from_random_testbed() {
309     int slot = _testbeds.random_non_null_slot_index();
310     bool success = false;
311     if (slot != -1) {
312       const int n = IntRange(5, 20).random_value();
313       success = random_allocate_multiple_times_from_testbed(slot, n);
314     }
315     return success;
316   }
317 
318   /////// Deallocating from testbed ///////////////////
319 
320   void deallocate_from_testbed(int slotindex) {
321     DEBUG_ONLY(_testbeds.check_slot_is_not_null(slotindex);)
322     MetaspaceArenaTestBed* bed = _testbeds.at(slotindex);
323     bed->checked_random_deallocate();
324   }
325 
326   void deallocate_from_random_testbed() {
327     int slot = _testbeds.random_non_null_slot_index();
328     if (slot != -1) {
329       deallocate_from_testbed(slot);
330     }
331   }
332 
333   /////// Stats ///////////////////////////////////////
334 
335   int get_total_number_of_allocations() const {
336     int sum = 0;
337     for (int i = _testbeds.first_non_null_slot(); i != -1; i = _testbeds.next_non_null_slot(i)) {
338       sum += _testbeds.at(i)->num_allocations();
339     }
340     return sum;
341   }
342 
343   size_t get_total_words_allocated() const {
344     size_t sum = 0;
345     for (int i = _testbeds.first_non_null_slot(); i != -1; i = _testbeds.next_non_null_slot(i)) {
346       sum += _testbeds.at(i)->words_allocated();
347     }
348     return sum;
349   }
350 
351 public:
352 
353   MetaspaceArenaTest(size_t commit_limit, int num_testbeds)
354     : _context(commit_limit),
355       _testbeds(num_testbeds),
356       _num_beds()
357   {}
358 
359   ~MetaspaceArenaTest () {
360 
361     delete_all_test_beds();
362 
363   }
364 
365   //////////////// Tests ////////////////////////
366 
367   void test() {
368 
369     // In a big loop, randomly chose one of these actions
370     // - creating a test bed (simulates a new loader creation)
371     // - allocating from a test bed (simulates allocating metaspace for a loader)
372     // - (rarely) deallocate (simulates metaspace deallocation, e.g. class redefinitions)
373     // - delete a test bed (simulates collection of a loader and subsequent return of metaspace to freelists)
374 
375     const int iterations = 2500;
376 
377     // Lets have a ceiling on number of words allocated (this is independent from the commit limit)
378     const size_t max_allocation_size = 8 * M;
379 
380     bool force_bed_deletion = false;
381 
382     for (int niter = 0; niter < iterations; niter++) {
383 
384       const int r = IntRange(100).random_value();
385 
386       if (force_bed_deletion || r < 10) {
387 
388         force_bed_deletion = false;
389         delete_random_test_bed();
390 
391       } else if (r < 20 || _num_beds.get() < (unsigned)_testbeds.size() / 2) {
392 
393         create_random_test_bed();
394 
395       } else if (r < 95) {
396 
397         // If allocation fails, we hit the commit limit and should delete some beds first
398         force_bed_deletion = ! random_allocate_random_times_from_random_testbed();
399 
400       } else {
401 
402         // Note: does not affect the used words counter.
403         deallocate_from_random_testbed();
404 
405       }
406 
407       // If we are close to our quota, start bed deletion
408       if (_used_words_counter.get() >= max_allocation_size) {
409 
410         force_bed_deletion = true;
411 
412       }
413 
414     }
415 
416   }
417 
418 };
419 
420 // 32 parallel MetaspaceArena objects, random allocating without commit limit
421 TEST_VM(metaspace, MetaspaceArena_random_allocs_32_beds_no_commit_limit) {
422   MetaspaceArenaTest test(max_uintx, 32);
423   test.test();
424 }
425 
426 // 32 parallel Metaspace arena objects, random allocating with commit limit
427 TEST_VM(metaspace, MetaspaceArena_random_allocs_32_beds_with_commit_limit) {
428   MetaspaceArenaTest test(2 * M, 32);
429   test.test();
430 }
431 
432 // A single MetaspaceArena, random allocating without commit limit. This should exercise
433 //  chunk enlargement since allocation is undisturbed.
434 TEST_VM(metaspace, MetaspaceArena_random_allocs_1_bed_no_commit_limit) {
435   MetaspaceArenaTest test(max_uintx, 1);
436   test.test();
437 }
438