--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/gtest/metaspace/test_chunkManager.cpp Tue Sep 10 09:24:05 2019 +0200
@@ -0,0 +1,315 @@
+/*
+ * Copyright (c) 2019, SAP SE. All rights reserved.
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+
+#include "precompiled.hpp"
+#include "metaspace/metaspaceTestsCommon.hpp"
+
+class ChunkManagerTest {
+
+ static const int max_num_chunks = 0x100;
+
+ // These counters are updated by the Node.
+ CommitLimiter _commit_limiter;
+ VirtualSpaceList* _vs_list;
+
+ ChunkManager* _cm;
+
+ Metachunk* _elems[max_num_chunks];
+ SizeCounter _word_size_allocated;
+ IntCounter _num_allocated;
+
+ // Note: [min_level ... max_level] (inclusive max)
+ static chklvl_t get_random_level(chklvl_t min_level, chklvl_t max_level) {
+ int range = max_level - min_level + 1; // [ ]
+ chklvl_t l = min_level + (os::random() % range);
+ return l;
+ }
+
+ struct chklvl_range_t { chklvl_t from; chklvl_t to; };
+
+ // Note: [min_level ... max_level] (inclusive max)
+ static void get_random_level_range(chklvl_t min_level, chklvl_t max_level, chklvl_range_t* out) {
+ chklvl_t l1 = get_random_level(min_level, max_level);
+ chklvl_t l2 = get_random_level(min_level, max_level);
+ if (l1 > l2) {
+ chklvl_t l = l2;
+ l1 = l2;
+ l2 = l;
+ }
+ out->from = l1; out->to = l2;
+ }
+
+ bool attempt_free_at(size_t index) {
+
+ LOG("attempt_free_at " SIZE_FORMAT ".", index);
+
+ if (_elems[index] == NULL) {
+ return false;
+ }
+
+ Metachunk* c = _elems[index];
+ const size_t chunk_word_size = c->word_size();
+ _cm->return_chunk(c);
+
+ _elems[index] = NULL;
+
+ DEBUG_ONLY(_vs_list->verify(true);)
+ DEBUG_ONLY(_cm->verify(true);)
+
+ _word_size_allocated.decrement_by(chunk_word_size);
+ _num_allocated.decrement();
+
+ return true;
+ }
+
+ bool attempt_allocate_at(size_t index, chklvl_t max_level, chklvl_t pref_level, bool fully_commit = false) {
+
+ LOG("attempt_allocate_at " SIZE_FORMAT ". (" CHKLVL_FORMAT "-" CHKLVL_FORMAT ")", index, max_level, pref_level);
+
+ if (_elems[index] != NULL) {
+ return false;
+ }
+
+ Metachunk* c = _cm->get_chunk(max_level, pref_level);
+
+ EXPECT_NOT_NULL(c);
+ EXPECT_TRUE(c->is_in_use());
+ EXPECT_LE(c->level(), max_level);
+ EXPECT_GE(c->level(), pref_level);
+
+ _elems[index] = c;
+
+ DEBUG_ONLY(c->verify(true);)
+ DEBUG_ONLY(_vs_list->verify(true);)
+ DEBUG_ONLY(_cm->verify(true);)
+
+ _word_size_allocated.increment_by(c->word_size());
+ _num_allocated.increment();
+
+ if (fully_commit) {
+ c->ensure_fully_committed();
+ }
+
+ return true;
+ }
+
+ // Note: [min_level ... max_level] (inclusive max)
+ void allocate_n_random_chunks(int n, chklvl_t min_level, chklvl_t max_level) {
+
+ assert(n <= max_num_chunks, "Sanity");
+
+ for (int i = 0; i < n; i ++) {
+ chklvl_range_t r;
+ get_random_level_range(min_level, max_level, &r);
+ attempt_allocate_at(i, r.to, r.from);
+ }
+
+ }
+
+ void free_all_chunks() {
+ for (int i = 0; i < max_num_chunks; i ++) {
+ attempt_free_at(i);
+ }
+ assert(_num_allocated.get() == 0, "Sanity");
+ assert(_word_size_allocated.get() == 0, "Sanity");
+ }
+
+ void random_alloc_free(int iterations, chklvl_t min_level, chklvl_t max_level) {
+ for (int i = 0; i < iterations; i ++) {
+ int index = os::random() % max_num_chunks;
+ if ((os::random() % 100) > 50) {
+ attempt_allocate_at(index, max_level, min_level);
+ } else {
+ attempt_free_at(index);
+ }
+ }
+ }
+
+public:
+
+ ChunkManagerTest()
+ : _commit_limiter(50 * M), _vs_list(NULL), _cm(NULL), _word_size_allocated()
+ {
+ _vs_list = new VirtualSpaceList("test_vs_lust", &_commit_limiter);
+ _cm = new ChunkManager("test_cm", _vs_list);
+ memset(_elems, 0, sizeof(_elems));
+ }
+
+ void test(int iterations, chklvl_t min_level, chklvl_t max_level) {
+ for (int run = 0; run < iterations; run ++) {
+ allocate_n_random_chunks(max_num_chunks, min_level, max_level);
+ random_alloc_free(iterations, min_level, max_level);
+ free_all_chunks();
+ }
+ }
+
+ void test_enlarge_chunk() {
+ // On an empty state, request a chunk of the smallest possible size from chunk manager; then,
+ // attempt to enlarge it in place. Since all splinters should be free, this should work until
+ // we are back at root chunk size.
+ assert(_cm->total_num_chunks() == 0, "Call this on an empty cm.");
+ Metachunk* c = _cm->get_chunk(HIGHEST_CHUNK_LEVEL, HIGHEST_CHUNK_LEVEL);
+ ASSERT_NOT_NULL(c);
+ ASSERT_EQ(c->level(), HIGHEST_CHUNK_LEVEL);
+
+ int num_splinter_chunks = _cm->total_num_chunks();
+
+ // Getting a chunk of the smallest size there is should have yielded us one splinter for every level beyond 0.
+ ASSERT_EQ(num_splinter_chunks, NUM_CHUNK_LEVELS - 1);
+
+ // Now enlarge n-1 times until c is of root chunk level size again.
+ for (chklvl_t l = HIGHEST_CHUNK_LEVEL; l > LOWEST_CHUNK_LEVEL; l --) {
+ bool rc = _cm->attempt_enlarge_chunk(c);
+ ASSERT_TRUE(rc);
+ ASSERT_EQ(c->level(), l - 1);
+ num_splinter_chunks --;
+ ASSERT_EQ(num_splinter_chunks, _cm->total_num_chunks());
+ }
+ }
+
+ void test_recommit_chunk() {
+
+ // test that if a chunk is committed again, already committed content stays.
+ assert(_cm->total_num_chunks() == 0, "Call this on an empty cm.");
+ const chklvl_t lvl = metaspace::chklvl::level_fitting_word_size(Settings::commit_granule_words());
+ Metachunk* c = _cm->get_chunk(lvl, lvl);
+ ASSERT_NOT_NULL(c);
+ ASSERT_EQ(c->level(), lvl);
+
+ // clean slate.
+ c->set_free();
+ c->uncommit();
+ c->set_in_use();
+
+ c->ensure_committed(10);
+
+ const size_t committed_words_1 = c->committed_words();
+ fill_range_with_pattern(c->base(), (uintx) this, committed_words_1);
+
+ // enlarge chunk, then recommit again, which will make sure we
+ bool rc = _cm->attempt_enlarge_chunk(c);
+ ASSERT_TRUE(rc);
+ rc = _cm->attempt_enlarge_chunk(c);
+ ASSERT_TRUE(rc);
+ rc = _cm->attempt_enlarge_chunk(c);
+ ASSERT_TRUE(rc);
+ ASSERT_EQ(c->level(), lvl - 3);
+
+ c->ensure_committed(c->word_size());
+ check_range_for_pattern(c->base(), (uintx) this, committed_words_1);
+
+ }
+
+ void test_wholesale_reclaim() {
+
+ // test that if a chunk is committed again, already committed content stays.
+ assert(_num_allocated.get() == 0, "Call this on an empty cm.");
+
+ // Get a number of random sized but large chunks, be sure to cover multiple vsnodes.
+ // Also, commit those chunks.
+ const size_t min_words_to_allocate = 4 * Settings::virtual_space_node_default_word_size(); // about 16 m
+
+ while (_num_allocated.get() < max_num_chunks &&
+ _word_size_allocated.get() < min_words_to_allocate) {
+ const chklvl_t lvl = LOWEST_CHUNK_LEVEL + os::random() % 4;
+ attempt_allocate_at(_num_allocated.get(), lvl, lvl, true); // < fully committed please
+ }
+
+//_cm->print_on(tty);
+//_vs_list->print_on(tty);
+ DEBUG_ONLY(_cm->verify(true);)
+ DEBUG_ONLY(_vs_list->verify(true);)
+
+ // Return about three quarter of the chunks.
+ for (int i = 0; i < max_num_chunks; i ++) {
+ if (os::random() % 100 < 75) {
+ attempt_free_at(i);
+ }
+ }
+
+ // Now do a reclaim.
+ _cm->wholesale_reclaim();
+
+//_cm->print_on(tty);
+//_vs_list->print_on(tty);
+ DEBUG_ONLY(_cm->verify(true);)
+ DEBUG_ONLY(_vs_list->verify(true);)
+
+ // Return all chunks
+ free_all_chunks();
+
+ // Now do a second reclaim.
+ _cm->wholesale_reclaim();
+
+//_cm->print_on(tty);
+//_vs_list->print_on(tty);
+ DEBUG_ONLY(_cm->verify(true);)
+ DEBUG_ONLY(_vs_list->verify(true);)
+
+ // All space should be gone now, if the settings are not preventing reclaim
+ if (Settings::delete_nodes_on_purge()) {
+ ASSERT_EQ(_vs_list->reserved_words(), (size_t)0);
+ }
+ if (Settings::uncommit_on_purge() || Settings::delete_nodes_on_purge()) {
+ ASSERT_EQ(_vs_list->committed_words(), (size_t)0);
+ }
+
+ }
+
+};
+
+// Note: we unfortunately need TEST_VM even though the system tested
+// should be pretty independent since we need things like os::vm_page_size()
+// which in turn need OS layer initialization.
+TEST_VM(metaspace, chunkmanager_test_whole_range) {
+ ChunkManagerTest ct;
+ ct.test(100, LOWEST_CHUNK_LEVEL, HIGHEST_CHUNK_LEVEL);
+}
+
+TEST_VM(metaspace, chunkmanager_test_small_chunks) {
+ ChunkManagerTest ct;
+ ct.test(100, HIGHEST_CHUNK_LEVEL / 2, HIGHEST_CHUNK_LEVEL);
+}
+
+TEST_VM(metaspace, chunkmanager_test_large_chunks) {
+ ChunkManagerTest ct;
+ ct.test(100, LOWEST_CHUNK_LEVEL, HIGHEST_CHUNK_LEVEL / 2);
+}
+
+TEST_VM(metaspace, chunkmanager_test_enlarge_chunk) {
+ ChunkManagerTest ct;
+ ct.test_enlarge_chunk();
+}
+
+TEST_VM(metaspace, chunkmanager_test_recommit_chunk) {
+ ChunkManagerTest ct;
+ ct.test_recommit_chunk();
+}
+
+TEST_VM(metaspace, chunkmanager_test_wholesale_reclaim) {
+ ChunkManagerTest ct;
+ ct.test_wholesale_reclaim();
+}
+