| /* |
| * Copyright (c) 2020, 2023 SAP SE. All rights reserved. |
| * Copyright (c) 2020, 2024, 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. |
| * |
| */ |
| |
| import jdk.test.whitebox.WhiteBox; |
| |
| import java.util.HashSet; |
| |
| public class MetaspaceTestContext { |
| |
| long context; |
| |
| final long commitLimit; |
| final long reserveLimit; |
| |
| int numArenasCreated; |
| int numArenasDestroyed; |
| |
| HashSet<MetaspaceTestArena> arenaList = new HashSet<>(); |
| |
| long allocatedBytes; |
| long numAllocated; |
| long deallocatedBytes; |
| long numDeallocated; |
| long allocationFailures; |
| |
| public MetaspaceTestContext(long commitLimit, long reserveLimit) { |
| this.commitLimit = commitLimit; |
| this.reserveLimit = reserveLimit; |
| WhiteBox wb = WhiteBox.getWhiteBox(); |
| context = wb.createMetaspaceTestContext(commitLimit, reserveLimit); |
| if (context == 0) { |
| throw new RuntimeException("Failed to create context"); |
| } |
| } |
| |
| // no limits |
| public MetaspaceTestContext() { |
| this(0, 0); |
| } |
| |
| public void destroy() { |
| if (context != 0) { |
| WhiteBox wb = WhiteBox.getWhiteBox(); |
| wb.destroyMetaspaceTestContext(context); |
| context = 0; |
| } |
| } |
| |
| public void purge() { |
| if (context != 0) { |
| WhiteBox wb = WhiteBox.getWhiteBox(); |
| wb.purgeMetaspaceTestContext(context); |
| } |
| } |
| |
| public MetaspaceTestArena createArena(boolean is_micro, long ceiling) { |
| MetaspaceTestArena arena = null; |
| if (context != 0) { |
| WhiteBox wb = WhiteBox.getWhiteBox(); |
| long arena0 = wb.createArenaInTestContext(context, is_micro); |
| if (arena0 == 0) { |
| throw new RuntimeException("Failed to create arena"); |
| } |
| numArenasCreated++; |
| arena = new MetaspaceTestArena(arena0, ceiling); |
| arenaList.add(arena); |
| } |
| return arena; |
| } |
| |
| public void destroyArena(MetaspaceTestArena a) { |
| if (context != 0) { |
| if (a.isLive()) { |
| WhiteBox wb = WhiteBox.getWhiteBox(); |
| wb.destroyMetaspaceTestArena(a.arena); |
| numArenasDestroyed++; |
| } |
| arenaList.remove(a); |
| } |
| } |
| |
| public long committedBytes() { |
| long l = 0; |
| if (context != 0) { |
| WhiteBox wb = WhiteBox.getWhiteBox(); |
| l = wb.getTotalCommittedBytesInMetaspaceTestContext(context); |
| } |
| return l; |
| } |
| |
| public long usedBytes() { |
| long l = 0; |
| if (context != 0) { |
| WhiteBox wb = WhiteBox.getWhiteBox(); |
| l = wb.getTotalUsedBytesInMetaspaceTestContext(context); |
| } |
| return l; |
| } |
| |
| public int numLiveArenas() { |
| return arenaList.size(); |
| } |
| |
| public void updateTotals() { |
| allocatedBytes = deallocatedBytes = numAllocated = numDeallocated = 0; |
| for (MetaspaceTestArena a : arenaList) { |
| allocatedBytes += a.allocatedBytes; |
| deallocatedBytes += a.deallocatedBytes; |
| numAllocated += a.numAllocated; |
| numDeallocated += a.numDeallocated; |
| allocationFailures += a.numAllocationFailures; |
| } |
| } |
| |
| public void printToTTY() { |
| if (context != 0) { |
| WhiteBox wb = WhiteBox.getWhiteBox(); |
| wb.printMetaspaceTestContext(context); |
| } |
| } |
| |
| /** |
| * Given usage and some context information for current live arenas, do a heuristic about whether the |
| * Usage seems right for this case. |
| */ |
| public void checkStatistics() { |
| |
| // Note: |
| // Estimating Used and Committed is fuzzy, and we only have limited information here |
| // (we know the current state, but not the history, which determines fragmentation and |
| // freelist occupancy). |
| // |
| // We do not want test which constantly generate false positives, so these checks are |
| // somewhat loose and only meant to check for clear outliers, e.g. leaks. |
| |
| ///// used ///// |
| |
| updateTotals(); |
| |
| long usageMeasured = usedBytes(); |
| long committedMeasured = committedBytes(); |
| |
| System.out.println("context used bytes " + usageMeasured + ", committed bytes " + committedMeasured |
| + "."); |
| |
| if (usageMeasured > committedMeasured) { |
| throw new RuntimeException("Weirdness."); |
| } |
| |
| if (deallocatedBytes > allocatedBytes) { |
| throw new RuntimeException("Weirdness."); |
| } |
| |
| // If no arenas are alive, usage should be zero and committed too |
| if (numLiveArenas() == 0) { |
| if (usageMeasured > 0) { |
| throw new RuntimeException("Usage > 0, expected 0"); |
| } |
| if (committedMeasured > 0) { |
| throw new RuntimeException("Committed > 0, expected 0"); |
| } |
| } |
| |
| long expectedMinUsage = allocatedBytes - deallocatedBytes; |
| |
| if (usageMeasured < expectedMinUsage) { |
| throw new RuntimeException("Usage too low: " + usageMeasured + " expected at least " + expectedMinUsage); |
| } |
| |
| long expectedMaxUsage = allocatedBytes; |
| |
| // This is necessary a bit fuzzy, since Metaspace usage consists of: |
| // - whatever we allocated |
| // - deallocated blocks in fbl |
| // - remains of retired chunks in fbl |
| // - overhead per allocation (padding for alignment) |
| |
| // Overhead per allocation (see metaspaceArena.cpp, get_raw_allocation_word_size() ) |
| // Any allocation is 3 words least |
| expectedMaxUsage += (numAllocated * 3 * Settings.WORD_SIZE); |
| |
| // Lets add a overhead per arena. Each arena carries a free block list containing |
| // deallocated/retired blocks. We do not know how much. In general, the free block list should not |
| // accumulate a lot of memory but be drained in the course of allocating memory from the arena. |
| long overheadPerArena = 1024 * 1024 * numLiveArenas() * Settings.WORD_SIZE; |
| expectedMaxUsage += overheadPerArena; |
| |
| if (expectedMaxUsage < usageMeasured) { |
| throw new RuntimeException("Usage seems high: " + usageMeasured + " expected at most " + expectedMaxUsage); |
| } |
| |
| ///// Committed ////// |
| |
| if (committedMeasured < expectedMinUsage) { |
| throw new RuntimeException("Usage too low: " + usageMeasured + " expected at least " + expectedMinUsage); |
| } |
| |
| // Max committed: |
| // This is difficult to estimate, so just a rough guess. |
| // |
| // Committed space depends on: |
| // 1) Usage (how much we allocated + overhead per allocation + free block list content) |
| // 2) free space in used chunks |
| // 3) committed chunks in freelist. |
| // |
| // Having only live usage numbers without history, (2) and (3) can only be roughly estimated. Since these |
| // are stress tests, |
| // |
| long expectedMaxCommitted = usageMeasured; |
| expectedMaxCommitted += Settings.ROOT_CHUNK_WORD_SIZE * Settings.WORD_SIZE; |
| expectedMaxCommitted *= 10.0; |
| |
| if (committedMeasured > expectedMaxCommitted) { |
| throw new RuntimeException("Committed seems high: " + committedMeasured + " expected at most " + expectedMaxCommitted); |
| } |
| |
| } |
| |
| @java.lang.Override |
| public java.lang.String toString() { |
| return "MetaspaceTestContext{" + |
| "context=" + context + |
| ", commitLimit=" + commitLimit + |
| ", reserveLimit=" + reserveLimit + |
| ", numArenasCreated=" + numArenasCreated + |
| ", numArenasDestroyed=" + numArenasDestroyed + |
| ", numLiveArenas=" + numLiveArenas() + |
| ", allocatedBytes=" + allocatedBytes + |
| ", numAllocated=" + numAllocated + |
| ", deallocatedBytes=" + deallocatedBytes + |
| ", numDeallocated=" + numDeallocated + |
| ", allocationFailures=" + allocationFailures + |
| '}'; |
| } |
| } |