1 /*
2  * Copyright (c) 2020, 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/chunklevel.hpp"
28 #include "memory/metaspace/commitLimiter.hpp"
29 #include "memory/metaspace/counters.hpp"
30 #include "memory/metaspace/freeChunkList.hpp"
31 #include "memory/metaspace/metachunk.hpp"
32 #include "memory/metaspace/metachunkList.hpp"
33 #include "memory/metaspace/metaspaceSettings.hpp"
34 #include "memory/metaspace/virtualSpaceNode.hpp"
35 #include "runtime/mutexLocker.hpp"
36 #include "utilities/debug.hpp"
37 //#define LOG_PLEASE
38 #include "metaspaceGtestCommon.hpp"
39 #include "metaspaceGtestRangeHelpers.hpp"
40 
41 using metaspace::chunklevel_t;
42 using metaspace::CommitLimiter;
43 using metaspace::FreeChunkListVector;
44 using metaspace::Metachunk;
45 using metaspace::MetachunkList;
46 using metaspace::VirtualSpaceNode;
47 using metaspace::Settings;
48 using metaspace::SizeCounter;
49 
50 class VirtualSpaceNodeTest {
51 
52   // These counters are updated by the Node.
53   SizeCounter _counter_reserved_words;
54   SizeCounter _counter_committed_words;
55   CommitLimiter _commit_limiter;
56   VirtualSpaceNode* _node;
57 
58   // These are my checks and counters.
59   const size_t _vs_word_size;
60   const size_t _commit_limit;
61 
62   MetachunkList _root_chunks;
63 
verify() const64   void verify() const {
65 
66     ASSERT_EQ(_root_chunks.count() * metaspace::chunklevel::MAX_CHUNK_WORD_SIZE,
67               _node->used_words());
68 
69     ASSERT_GE(_commit_limit,                      _counter_committed_words.get());
70     ASSERT_EQ(_commit_limiter.committed_words(),  _counter_committed_words.get());
71 
72     // Since we know _counter_committed_words serves our single node alone, the counter has to
73     // match the number of bits in the node internal commit mask.
74     ASSERT_EQ(_counter_committed_words.get(), _node->committed_words());
75 
76     ASSERT_EQ(_counter_reserved_words.get(), _vs_word_size);
77     ASSERT_EQ(_counter_reserved_words.get(), _node->word_size());
78 
79   }
80 
lock_and_verify_node()81   void lock_and_verify_node() {
82 #ifdef ASSERT
83     MutexLocker fcl(MetaspaceExpand_lock, Mutex::_no_safepoint_check_flag);
84     _node->verify_locked();
85 #endif
86   }
87 
alloc_root_chunk()88   Metachunk* alloc_root_chunk() {
89 
90     verify();
91 
92     const bool node_is_full = _node->used_words() == _node->word_size();
93     Metachunk* c = NULL;
94     {
95       MutexLocker fcl(MetaspaceExpand_lock, Mutex::_no_safepoint_check_flag);
96       c = _node->allocate_root_chunk();
97     }
98 
99     lock_and_verify_node();
100 
101     if (node_is_full) {
102 
103       EXPECT_NULL(c);
104 
105     } else {
106 
107       DEBUG_ONLY(c->verify();)
108       EXPECT_NOT_NULL(c);
109       EXPECT_TRUE(c->is_root_chunk());
110       EXPECT_TRUE(c->is_free());
111       EXPECT_EQ(c->word_size(), metaspace::chunklevel::MAX_CHUNK_WORD_SIZE);
112 
113       EXPECT_TRUE(c->is_fully_uncommitted());
114 
115       EXPECT_TRUE(_node->contains(c->base()));
116 
117       _root_chunks.add(c);
118 
119     }
120 
121     verify();
122 
123     return c;
124 
125   }
126 
commit_root_chunk(Metachunk * c,size_t request_commit_words)127   bool commit_root_chunk(Metachunk* c, size_t request_commit_words) {
128 
129     verify();
130 
131     const size_t committed_words_before = _counter_committed_words.get();
132 
133     bool rc = c->ensure_committed(request_commit_words);
134 
135     verify();
136     DEBUG_ONLY(c->verify();)
137 
138     lock_and_verify_node();
139 
140     if (rc == false) {
141 
142       // We must have hit the commit limit.
143       EXPECT_GE(committed_words_before + request_commit_words, _commit_limit);
144 
145     } else {
146 
147       // We should not have hit the commit limit.
148       EXPECT_LE(_counter_committed_words.get(), _commit_limit);
149 
150       // We do not know how much we really committed - maybe nothing if the
151       // chunk had been committed before - but we know the numbers should have
152       // risen or at least stayed equal.
153       EXPECT_GE(_counter_committed_words.get(), committed_words_before);
154 
155       // The chunk should be as far committed as was requested
156       EXPECT_GE(c->committed_words(), request_commit_words);
157 
158       // Zap committed portion.
159       DEBUG_ONLY(zap_range(c->base(), c->committed_words());)
160 
161     }
162 
163     verify();
164 
165     return rc;
166 
167   } // commit_root_chunk
168 
uncommit_chunk(Metachunk * c)169   void uncommit_chunk(Metachunk* c) {
170 
171     verify();
172 
173     const size_t committed_words_before = _counter_committed_words.get();
174     const size_t available_words_before = _commit_limiter.possible_expansion_words();
175 
176     c->uncommit();
177 
178     DEBUG_ONLY(c->verify();)
179 
180     lock_and_verify_node();
181 
182     EXPECT_EQ(c->committed_words(), (size_t)0);
183 
184     // Commit counter should have gone down (by exactly the size of the chunk) if chunk
185     // is larger than a commit granule.
186     // For smaller chunks, we do not know, but at least we know the commit size should not
187     // have gone up.
188     if (c->word_size() >= Settings::commit_granule_words()) {
189 
190       EXPECT_EQ(_counter_committed_words.get(), committed_words_before - c->word_size());
191 
192       // also, commit number in commit limiter should have gone down, so we have more space
193       EXPECT_EQ(_commit_limiter.possible_expansion_words(),
194                 available_words_before + c->word_size());
195 
196     } else {
197 
198       EXPECT_LE(_counter_committed_words.get(), committed_words_before);
199 
200     }
201 
202     verify();
203 
204   } // uncommit_chunk
205 
split_chunk_with_checks(Metachunk * c,chunklevel_t target_level,FreeChunkListVector * freelist)206   Metachunk* split_chunk_with_checks(Metachunk* c, chunklevel_t target_level, FreeChunkListVector* freelist) {
207 
208     DEBUG_ONLY(c->verify();)
209 
210     const chunklevel_t orig_level = c->level();
211     assert(orig_level < target_level, "Sanity");
212     DEBUG_ONLY(metaspace::chunklevel::check_valid_level(target_level);)
213 
214     const int total_num_chunks_in_freelist_before = freelist->num_chunks();
215     const size_t total_word_size_in_freelist_before = freelist->word_size();
216 
217    // freelist->print_on(tty);
218 
219     // Split...
220     {
221       MutexLocker fcl(MetaspaceExpand_lock, Mutex::_no_safepoint_check_flag);
222       _node->split(target_level, c, freelist);
223     }
224 
225     // freelist->print_on(tty);
226 
227     EXPECT_NOT_NULL(c);
228     EXPECT_EQ(c->level(), target_level);
229     EXPECT_TRUE(c->is_free());
230 
231     // ... check that we get the proper amount of splinters. For every chunk split we expect one
232     // buddy chunk to appear of level + 1 (aka, half size).
233     size_t expected_wordsize_increase = 0;
234     int expected_num_chunks_increase = 0;
235     for (chunklevel_t l = orig_level + 1; l <= target_level; l++) {
236       expected_wordsize_increase += metaspace::chunklevel::word_size_for_level(l);
237       expected_num_chunks_increase++;
238     }
239 
240     const int total_num_chunks_in_freelist_after = freelist->num_chunks();
241     const size_t total_word_size_in_freelist_after = freelist->word_size();
242 
243     EXPECT_EQ(total_num_chunks_in_freelist_after, total_num_chunks_in_freelist_before + expected_num_chunks_increase);
244     EXPECT_EQ(total_word_size_in_freelist_after, total_word_size_in_freelist_before + expected_wordsize_increase);
245 
246     return c;
247 
248   } // end: split_chunk_with_checks
249 
merge_chunk_with_checks(Metachunk * c,chunklevel_t expected_target_level,FreeChunkListVector * freelist)250   Metachunk* merge_chunk_with_checks(Metachunk* c, chunklevel_t expected_target_level, FreeChunkListVector* freelist) {
251 
252     const chunklevel_t orig_level = c->level();
253     assert(expected_target_level < orig_level, "Sanity");
254 
255     const int total_num_chunks_in_freelist_before = freelist->num_chunks();
256     const size_t total_word_size_in_freelist_before = freelist->word_size();
257 
258     //freelist->print_on(tty);
259 
260     Metachunk* result = NULL;
261     {
262       MutexLocker fcl(MetaspaceExpand_lock, Mutex::_no_safepoint_check_flag);
263       result = _node->merge(c, freelist);
264     }
265     EXPECT_NOT_NULL(result);
266     EXPECT_TRUE(result->level() == expected_target_level);
267 
268     //freelist->print_on(tty);
269 
270     // ... check that we merged in the proper amount of chunks. For every decreased level
271     // of the original chunk (each size doubling) we should see one buddy chunk swallowed up.
272     size_t expected_wordsize_decrease = 0;
273     int expected_num_chunks_decrease = 0;
274     for (chunklevel_t l = orig_level; l > expected_target_level; l --) {
275       expected_wordsize_decrease += metaspace::chunklevel::word_size_for_level(l);
276       expected_num_chunks_decrease++;
277     }
278 
279     const int total_num_chunks_in_freelist_after = freelist->num_chunks();
280     const size_t total_word_size_in_freelist_after = freelist->word_size();
281 
282     EXPECT_EQ(total_num_chunks_in_freelist_after, total_num_chunks_in_freelist_before - expected_num_chunks_decrease);
283     EXPECT_EQ(total_word_size_in_freelist_after, total_word_size_in_freelist_before - expected_wordsize_decrease);
284 
285     return result;
286 
287   } // end: merge_chunk_with_checks
288 
289 public:
290 
VirtualSpaceNodeTest(size_t vs_word_size,size_t commit_limit)291   VirtualSpaceNodeTest(size_t vs_word_size, size_t commit_limit) :
292     _counter_reserved_words(),
293     _counter_committed_words(),
294     _commit_limiter(commit_limit),
295     _node(NULL),
296     _vs_word_size(vs_word_size),
297     _commit_limit(commit_limit)
298   {
299     {
300       MutexLocker fcl(MetaspaceExpand_lock, Mutex::_no_safepoint_check_flag);
301       _node = VirtualSpaceNode::create_node(vs_word_size, &_commit_limiter,
302                                             &_counter_reserved_words, &_counter_committed_words);
303       EXPECT_EQ(_node->word_size(), vs_word_size);
304     }
305     EXPECT_TRUE(_commit_limiter.possible_expansion_words() == _commit_limit);
306     verify();
307   }
308 
~VirtualSpaceNodeTest()309   ~VirtualSpaceNodeTest() {
310     {
311       MutexLocker fcl(MetaspaceExpand_lock, Mutex::_no_safepoint_check_flag);
312       delete _node;
313     }
314     // After the node is deleted, counters should be back to zero
315     // (we cannot use ASSERT/EXPECT here in the destructor)
316     assert(_counter_reserved_words.get() == 0, "Sanity");
317     assert(_counter_committed_words.get() == 0, "Sanity");
318     assert(_commit_limiter.committed_words() == 0, "Sanity");
319   }
320 
test_simple()321   void test_simple() {
322     Metachunk* c = alloc_root_chunk();
323     commit_root_chunk(c, Settings::commit_granule_words());
324     commit_root_chunk(c, c->word_size());
325     uncommit_chunk(c);
326   }
327 
test_exhaust_node()328   void test_exhaust_node() {
329     Metachunk* c = NULL;
330     bool rc = true;
331     do {
332       c = alloc_root_chunk();
333       if (c != NULL) {
334         rc = commit_root_chunk(c, c->word_size());
335       }
336     } while (c != NULL && rc);
337   }
338 
test_arbitrary_commits()339   void test_arbitrary_commits() {
340 
341     assert(_commit_limit >= _vs_word_size, "For this test no commit limit.");
342 
343     // Get a root chunk to have a committable region
344     Metachunk* c = alloc_root_chunk();
345     ASSERT_NOT_NULL(c);
346 
347     if (c->committed_words() > 0) {
348       c->uncommit();
349     }
350 
351     ASSERT_EQ(_node->committed_words(), (size_t)0);
352     ASSERT_EQ(_counter_committed_words.get(), (size_t)0);
353 
354     TestMap testmap(c->word_size());
355     assert(testmap.get_num_set() == 0, "Sanity");
356 
357     for (int run = 0; run < 1000; run++) {
358 
359       const size_t committed_words_before = testmap.get_num_set();
360       ASSERT_EQ(_commit_limiter.committed_words(), committed_words_before);
361       ASSERT_EQ(_counter_committed_words.get(), committed_words_before);
362 
363       // A random range
364       SizeRange r = SizeRange(c->word_size()).random_aligned_subrange(Settings::commit_granule_words());
365 
366       const size_t committed_words_in_range_before =
367                    testmap.get_num_set(r.start(), r.end());
368 
369       const bool do_commit = IntRange(100).random_value() >= 50;
370       if (do_commit) {
371 
372         //LOG("c " SIZE_FORMAT "," SIZE_FORMAT, r.start(), r.end());
373 
374         bool rc = false;
375         {
376           MutexLocker fcl(MetaspaceExpand_lock, Mutex::_no_safepoint_check_flag);
377           rc = _node->ensure_range_is_committed(c->base() + r.start(), r.size());
378         }
379 
380         // Test-zap
381         zap_range(c->base() + r.start(), r.size());
382 
383         // We should never reach commit limit since it is as large as the whole area.
384         ASSERT_TRUE(rc);
385 
386         testmap.set_range(r.start(), r.end());
387 
388       } else {
389 
390         //LOG("u " SIZE_FORMAT "," SIZE_FORMAT, r.start(), r.end());
391 
392         {
393           MutexLocker fcl(MetaspaceExpand_lock, Mutex::_no_safepoint_check_flag);
394           _node->uncommit_range(c->base() + r.start(), r.size());
395         }
396 
397         testmap.clear_range(r.start(), r.end());
398 
399       }
400 
401       const size_t committed_words_after = testmap.get_num_set();
402 
403       ASSERT_EQ(_commit_limiter.committed_words(), committed_words_after);
404       ASSERT_EQ(_counter_committed_words.get(), committed_words_after);
405 
406       verify();
407     }
408   }
409 
410   // Helper function for test_splitting_chunks_1
check_chunk_is_committed_at_least_up_to(const Metachunk * c,size_t & word_size)411   static void check_chunk_is_committed_at_least_up_to(const Metachunk* c, size_t& word_size) {
412     if (word_size >= c->word_size()) {
413       EXPECT_TRUE(c->is_fully_committed());
414       word_size -= c->word_size();
415     } else {
416       EXPECT_EQ(c->committed_words(), word_size);
417       word_size = 0; // clear remaining size if there is.
418     }
419   }
420 
test_split_and_merge_chunks()421   void test_split_and_merge_chunks() {
422 
423     assert(_commit_limit >= _vs_word_size, "No commit limit here pls");
424 
425     // Allocate a root chunk and commit a random part of it. Then repeatedly split
426     // it and merge it back together; observe the committed regions of the split chunks.
427 
428     Metachunk* c = alloc_root_chunk();
429 
430     if (c->committed_words() > 0) {
431       c->uncommit();
432     }
433 
434     // To capture split-off chunks. Note: it is okay to use this here as a temp object.
435     FreeChunkListVector freelist;
436 
437     const int granules_per_root_chunk = (int)(c->word_size() / Settings::commit_granule_words());
438 
439     for (int granules_to_commit = 0; granules_to_commit < granules_per_root_chunk; granules_to_commit++) {
440 
441       const size_t words_to_commit = Settings::commit_granule_words() * granules_to_commit;
442 
443       c->ensure_committed(words_to_commit);
444 
445       ASSERT_EQ(c->committed_words(), words_to_commit);
446       ASSERT_EQ(_counter_committed_words.get(), words_to_commit);
447       ASSERT_EQ(_commit_limiter.committed_words(), words_to_commit);
448 
449       const size_t committed_words_before = c->committed_words();
450 
451       verify();
452 
453       for (chunklevel_t target_level = LOWEST_CHUNK_LEVEL + 1;
454            target_level <= HIGHEST_CHUNK_LEVEL; target_level++) {
455 
456         // Split:
457         Metachunk* c2 = split_chunk_with_checks(c, target_level, &freelist);
458         c2->set_in_use();
459 
460         // Split smallest leftover chunk.
461         if (c2->level() < HIGHEST_CHUNK_LEVEL) {
462 
463           Metachunk* c3 = freelist.remove_first(c2->level());
464           ASSERT_NOT_NULL(c3); // Must exist since c2 must have a splinter buddy now.
465 
466           Metachunk* c4 = split_chunk_with_checks(c3, HIGHEST_CHUNK_LEVEL, &freelist);
467           c4->set_in_use();
468 
469           // Merge it back. We expect this to merge up to c2's level, since c2 is in use.
470           c4->set_free();
471           Metachunk* c5 = merge_chunk_with_checks(c4, c2->level(), &freelist);
472           ASSERT_NOT_NULL(c5);
473           freelist.add(c5);
474 
475         }
476 
477         // Merge c2 back.
478         c2->set_free();
479         merge_chunk_with_checks(c2, LOWEST_CHUNK_LEVEL, &freelist);
480 
481         // After all this splitting and combining committed size should not have changed.
482         ASSERT_EQ(c2->committed_words(), committed_words_before);
483 
484       }
485 
486     }
487 
488   } // end: test_splitting_chunks
489 
490 };
491 
TEST_VM(metaspace,virtual_space_node_test_basics)492 TEST_VM(metaspace, virtual_space_node_test_basics) {
493 
494   MutexLocker fcl(MetaspaceExpand_lock, Mutex::_no_safepoint_check_flag);
495 
496   const size_t word_size = metaspace::chunklevel::MAX_CHUNK_WORD_SIZE * 10;
497 
498   SizeCounter scomm;
499   SizeCounter sres;
500   CommitLimiter cl (word_size * 2); // basically, no commit limiter.
501 
502   VirtualSpaceNode* node = VirtualSpaceNode::create_node(word_size, &cl, &sres, &scomm);
503   ASSERT_NOT_NULL(node);
504   ASSERT_EQ(node->committed_words(), (size_t)0);
505   ASSERT_EQ(node->committed_words(), scomm.get());
506   DEBUG_ONLY(node->verify_locked();)
507 
508   bool b = node->ensure_range_is_committed(node->base(), node->word_size());
509   ASSERT_TRUE(b);
510   ASSERT_EQ(node->committed_words(), word_size);
511   ASSERT_EQ(node->committed_words(), scomm.get());
512   DEBUG_ONLY(node->verify_locked();)
513   zap_range(node->base(), node->word_size());
514 
515   node->uncommit_range(node->base(), node->word_size());
516   ASSERT_EQ(node->committed_words(), (size_t)0);
517   ASSERT_EQ(node->committed_words(), scomm.get());
518   DEBUG_ONLY(node->verify_locked();)
519 
520   const int num_granules = (int)(word_size / Settings::commit_granule_words());
521   for (int i = 1; i < num_granules; i += 4) {
522     b = node->ensure_range_is_committed(node->base(), i * Settings::commit_granule_words());
523     ASSERT_TRUE(b);
524     ASSERT_EQ(node->committed_words(), i * Settings::commit_granule_words());
525     ASSERT_EQ(node->committed_words(), scomm.get());
526     DEBUG_ONLY(node->verify_locked();)
527     zap_range(node->base(), i * Settings::commit_granule_words());
528   }
529 
530   node->uncommit_range(node->base(), node->word_size());
531   ASSERT_EQ(node->committed_words(), (size_t)0);
532   ASSERT_EQ(node->committed_words(), scomm.get());
533   DEBUG_ONLY(node->verify_locked();)
534 
535 }
536 
537 // Note: we unfortunately need TEST_VM even though the system tested
538 // should be pretty independent since we need things like os::vm_page_size()
539 // which in turn need OS layer initialization.
TEST_VM(metaspace,virtual_space_node_test_1)540 TEST_VM(metaspace, virtual_space_node_test_1) {
541   VirtualSpaceNodeTest test(metaspace::chunklevel::MAX_CHUNK_WORD_SIZE,
542       metaspace::chunklevel::MAX_CHUNK_WORD_SIZE);
543   test.test_simple();
544 }
545 
TEST_VM(metaspace,virtual_space_node_test_2)546 TEST_VM(metaspace, virtual_space_node_test_2) {
547   // Should not hit commit limit
548   VirtualSpaceNodeTest test(3 * metaspace::chunklevel::MAX_CHUNK_WORD_SIZE,
549       3 * metaspace::chunklevel::MAX_CHUNK_WORD_SIZE);
550   test.test_simple();
551   test.test_exhaust_node();
552 }
553 
TEST_VM(metaspace,virtual_space_node_test_3)554 TEST_VM(metaspace, virtual_space_node_test_3) {
555   double d = os::elapsedTime();
556   // Test committing uncommitting arbitrary ranges
557   for (int run = 0; run < 100; run++) {
558     VirtualSpaceNodeTest test(metaspace::chunklevel::MAX_CHUNK_WORD_SIZE,
559         metaspace::chunklevel::MAX_CHUNK_WORD_SIZE);
560     test.test_split_and_merge_chunks();
561   }
562   double d2 = os::elapsedTime();
563   LOG("%f", (d2-d));
564 }
565 
TEST_VM(metaspace,virtual_space_node_test_4)566 TEST_VM(metaspace, virtual_space_node_test_4) {
567   // Should hit commit limit
568   VirtualSpaceNodeTest test(10 * metaspace::chunklevel::MAX_CHUNK_WORD_SIZE,
569       3 * metaspace::chunklevel::MAX_CHUNK_WORD_SIZE);
570   test.test_exhaust_node();
571 }
572 
TEST_VM(metaspace,virtual_space_node_test_5)573 TEST_VM(metaspace, virtual_space_node_test_5) {
574   // Test committing uncommitting arbitrary ranges
575   VirtualSpaceNodeTest test(metaspace::chunklevel::MAX_CHUNK_WORD_SIZE,
576       metaspace::chunklevel::MAX_CHUNK_WORD_SIZE);
577   test.test_arbitrary_commits();
578 }
579 
TEST_VM(metaspace,virtual_space_node_test_7)580 TEST_VM(metaspace, virtual_space_node_test_7) {
581   // Test large allocation and freeing.
582   {
583     VirtualSpaceNodeTest test(metaspace::chunklevel::MAX_CHUNK_WORD_SIZE * 100,
584         metaspace::chunklevel::MAX_CHUNK_WORD_SIZE * 100);
585     test.test_exhaust_node();
586   }
587   {
588     VirtualSpaceNodeTest test(metaspace::chunklevel::MAX_CHUNK_WORD_SIZE * 100,
589         metaspace::chunklevel::MAX_CHUNK_WORD_SIZE * 100);
590     test.test_exhaust_node();
591   }
592 
593 }
594