File: test_chunkManager_stress.cpp

package info (click to toggle)
openjdk-25 25.0.1%2B8-1~deb13u1
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 825,408 kB
  • sloc: java: 5,585,680; cpp: 1,333,948; xml: 1,321,242; ansic: 488,034; asm: 404,003; objc: 21,088; sh: 15,106; javascript: 13,265; python: 8,319; makefile: 2,518; perl: 357; awk: 351; pascal: 103; exp: 83; sed: 72; jsp: 24
file content (287 lines) | stat: -rw-r--r-- 9,741 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
/*
 * Copyright (c) 2020, 2023 SAP SE. All rights reserved.
 * Copyright (c) 2020, 2025, 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 "memory/metaspace/chunkManager.hpp"
#include "memory/metaspace/metaspaceSettings.hpp"
#include "memory/metaspace/virtualSpaceList.hpp"
//#define LOG_PLEASE
#include "metaspaceGtestCommon.hpp"
#include "metaspaceGtestContexts.hpp"
#include "metaspaceGtestRangeHelpers.hpp"
#include "metaspaceGtestSparseArray.hpp"

using metaspace::ChunkManager;
using metaspace::Settings;

class ChunkManagerRandomChunkAllocTest {

  static const size_t max_footprint_words = 8 * M;

  ChunkGtestContext _context;

  // All allocated live chunks
  typedef SparseArray<Metachunk*> SparseArrayOfChunks;
  SparseArrayOfChunks _chunks;

  const ChunkLevelRange _chunklevel_range;
  const float _commit_factor;

  // Depending on a probability pattern, come up with a reasonable limit to number of live chunks
  static int max_num_live_chunks(ChunkLevelRange r, float commit_factor) {
    // Assuming we allocate only the largest type of chunk, committed to the fullest commit factor,
    // how many chunks can we accomodate before hitting max_footprint_words?
    const size_t largest_chunk_size = word_size_for_level(r.lowest());
    int max_chunks = (int)((max_footprint_words * commit_factor) / (float) largest_chunk_size);
    // .. but cap at (min) 50 and (max) 1000
    max_chunks = MIN2(1000, max_chunks);
    max_chunks = MAX2(50, max_chunks);
    return max_chunks;
  }

  // Return true if, after an allocation error happened, a reserve error seems possible.
  bool could_be_reserve_error() {
    return _context.reserve_limit() < max_uintx;
  }

  // Return true if, after an allocation error happened, a commit error seems likely.
  bool could_be_commit_error(size_t additional_word_size) {

    // could it be commit limit hit?

    // Note that this is difficult to verify precisely, since there are
    // several layers of truth:
    // a) at the lowest layer (RootChunkArea) we have a bitmap of committed granules;
    // b) at the vslist layer, we keep running counters of committed/reserved words;
    // c) at the chunk layer, we keep a commit watermark (committed_words).
    //
    // (a) should mirror reality.
    // (a) and (b) should be precisely in sync. This is tested by
    // VirtualSpaceList::verify().
    // (c) can be, by design, imprecise (too low).
    //
    // Here, I check (b) and trust it to be correct. We also call vslist::verify().
    DEBUG_ONLY(_context.verify();)

    const size_t commit_add = align_up(additional_word_size, Settings::commit_granule_words());
    if (_context.commit_limit() <= (commit_add + _context.vslist().committed_words())) {
      return true;
    }

    return false;

  }

  // Given a chunk level and a factor, return a random commit size.
  static size_t random_committed_words(chunklevel_t lvl, float commit_factor) {
    const size_t sz = (size_t)((float)word_size_for_level(lvl) * commit_factor);
    if (sz < 2) {
      return 0;
    }
    return MIN2(SizeRange(sz).random_value(), sz);
  }

  //// Chunk allocation ////

  // Given an slot index, allocate a random chunk and set it into that slot. Slot must be empty.
  // Returns false if allocation fails.
  bool allocate_random_chunk_at(int slot) {

    DEBUG_ONLY(_chunks.check_slot_is_null(slot);)

    const ChunkLevelRange r = _chunklevel_range.random_subrange();
    const chunklevel_t pref_level = r.lowest();
    const chunklevel_t max_level = r.highest();
    const size_t min_committed = random_committed_words(max_level, _commit_factor);

    Metachunk* c = nullptr;
    _context.alloc_chunk(&c, r.lowest(), r.highest(), min_committed);
    if (c == nullptr) {
      EXPECT_TRUE(could_be_reserve_error() ||
                  could_be_commit_error(min_committed));
      LOG("Alloc chunk at %d failed.", slot);
      return false;
    }

    _chunks.set_at(slot, c);

    LOG("Allocated chunk at %d: " METACHUNK_FORMAT ".", slot, METACHUNK_FORMAT_ARGS(c));

    return true;

  }

  // Allocates a random number of random chunks
  bool allocate_random_chunks() {
    int to_alloc = 1 + IntRange(MAX2(1, _chunks.size() / 8)).random_value();
    bool success = true;
    int slot = _chunks.first_null_slot();
    while (to_alloc > 0 && slot != -1 && success) {
      success = allocate_random_chunk_at(slot);
      slot = _chunks.next_null_slot(slot);
      to_alloc --;
    }
    return success && to_alloc == 0;
  }

  bool fill_all_slots_with_random_chunks() {
    bool success = true;
    for (int slot = _chunks.first_null_slot();
         slot != -1 && success; slot = _chunks.next_null_slot(slot)) {
      success = allocate_random_chunk_at(slot);
    }
    return success;
  }

  //// Chunk return ////

  // Given an slot index, return the chunk in that slot to the chunk manager.
  void return_chunk_at(int slot) {
    Metachunk* c = _chunks.at(slot);
    LOG("Returning chunk at %d: " METACHUNK_FORMAT ".", slot, METACHUNK_FORMAT_ARGS(c));
    _context.return_chunk(c);
    _chunks.set_at(slot, nullptr);
  }

  // return a random number of chunks (at most a quarter of the full slot range)
  void return_random_chunks() {
    int to_free = 1 + IntRange(MAX2(1, _chunks.size() / 8)).random_value();
    int index = _chunks.first_non_null_slot();
    while (to_free > 0 && index != -1) {
      return_chunk_at(index);
      index = _chunks.next_non_null_slot(index);
      to_free --;
    }
  }

  void return_all_chunks() {
    for (int slot = _chunks.first_non_null_slot();
         slot != -1; slot = _chunks.next_non_null_slot(slot)) {
      return_chunk_at(slot);
    }
  }

  // adjust test if we change levels
  STATIC_ASSERT(HIGHEST_CHUNK_LEVEL == CHUNK_LEVEL_1K);
  STATIC_ASSERT(LOWEST_CHUNK_LEVEL == CHUNK_LEVEL_16M);

  void one_test() {

    fill_all_slots_with_random_chunks();
    _chunks.shuffle();

    IntRange rand(100);

    for (int j = 0; j < 750; j++) {

      bool force_alloc = false;
      bool force_free = true;

      bool do_alloc =
          force_alloc ? true :
              (force_free ? false : rand.random_value() >= 50);
      force_alloc = force_free = false;

      if (do_alloc) {
        if (!allocate_random_chunks()) {
          force_free = true;
        }
      } else {
        return_random_chunks();
      }

      _chunks.shuffle();

    }

    return_all_chunks();

  }

public:

  // A test with no limits
  ChunkManagerRandomChunkAllocTest(ChunkLevelRange r, float commit_factor) :
    _context(),
    _chunks(max_num_live_chunks(r, commit_factor)),
    _chunklevel_range(r),
    _commit_factor(commit_factor)
  {}

  // A test with no reserve limit but commit limit
  ChunkManagerRandomChunkAllocTest(size_t commit_limit,
                                   ChunkLevelRange r, float commit_factor) :
    _context(commit_limit),
    _chunks(max_num_live_chunks(r, commit_factor)),
    _chunklevel_range(r),
    _commit_factor(commit_factor)
  {}

  // A test with both reserve and commit limit
  // ChunkManagerRandomChunkAllocTest(size_t commit_limit, size_t reserve_limit,
  //                                  ChunkLevelRange r, float commit_factor)
  // : _helper(commit_limit, reserve_limit),
  // _chunks(max_num_live_chunks(r, commit_factor)),
  // _chunklevel_range(r),
  // _commit_factor(commit_factor)
  // {}

  void do_tests() {
    const int num_runs = 3;
    for (int n = 0; n < num_runs; n++) {
      one_test();
    }
  }

};

#define DEFINE_TEST(name, range, commit_factor) \
TEST_VM(metaspace, chunkmanager_random_alloc_##name) { \
  ChunkManagerRandomChunkAllocTest test(range, commit_factor); \
  test.do_tests(); \
}

DEFINE_TEST(test_nolimit_1, ChunkLevelRanges::small_chunks(), 0.0f)
DEFINE_TEST(test_nolimit_2, ChunkLevelRanges::small_chunks(), 0.5f)
DEFINE_TEST(test_nolimit_3, ChunkLevelRanges::small_chunks(), 1.0f)

DEFINE_TEST(test_nolimit_4, ChunkLevelRanges::all_chunks(), 0.0f)
DEFINE_TEST(test_nolimit_5, ChunkLevelRanges::all_chunks(), 0.5f)
DEFINE_TEST(test_nolimit_6, ChunkLevelRanges::all_chunks(), 1.0f)

#define DEFINE_TEST_2(name, range, commit_factor) \
TEST_VM(metaspace, chunkmanager_random_alloc_##name) { \
  const size_t commit_limit = 256 * K; \
  ChunkManagerRandomChunkAllocTest test(commit_limit, range, commit_factor); \
  test.do_tests(); \
}

DEFINE_TEST_2(test_with_limit_1, ChunkLevelRanges::small_chunks(), 0.0f)
DEFINE_TEST_2(test_with_limit_2, ChunkLevelRanges::small_chunks(), 0.5f)
DEFINE_TEST_2(test_with_limit_3, ChunkLevelRanges::small_chunks(), 1.0f)

DEFINE_TEST_2(test_with_limit_4, ChunkLevelRanges::all_chunks(), 0.0f)
DEFINE_TEST_2(test_with_limit_5, ChunkLevelRanges::all_chunks(), 0.5f)
DEFINE_TEST_2(test_with_limit_6, ChunkLevelRanges::all_chunks(), 1.0f)