1 /*
2  * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
3  * Copyright (c) 2018, 2019, Google and/or its affiliates. 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 package MyPackage;
26 
27 import java.lang.management.ManagementFactory;
28 import java.util.ArrayList;
29 import java.util.List;
30 
31 import com.sun.management.HotSpotDiagnosticMXBean;
32 import com.sun.management.VMOption;
33 
34 /** API for handling the underlying heap sampling monitoring system. */
35 public class HeapMonitor {
36   private static int[][] arrays;
37   private static int allocationIterations = 1000;
38 
39   static {
40     try {
41       System.loadLibrary("HeapMonitorTest");
42     } catch (UnsatisfiedLinkError ule) {
43       System.err.println("Could not load HeapMonitor library");
44       System.err.println("java.library.path: " + System.getProperty("java.library.path"));
45       throw ule;
46     }
47   }
48 
49   /** Set a specific sampling interval, 0 samples every allocation. */
setSamplingInterval(int interval)50   public native static void setSamplingInterval(int interval);
enableSamplingEvents()51   public native static void enableSamplingEvents();
enableSamplingEventsForTwoThreads(Thread firstThread, Thread secondThread)52   public native static boolean enableSamplingEventsForTwoThreads(Thread firstThread, Thread secondThread);
disableSamplingEvents()53   public native static void disableSamplingEvents();
54 
55   /**
56    * Allocate memory but first create a stack trace.
57    *
58    * @return list of frames for the allocation.
59    */
allocate()60   public static List<Frame> allocate() {
61     int sum = 0;
62     List<Frame> frames = new ArrayList<Frame>();
63     allocate(frames);
64     frames.add(new Frame("allocate", "()Ljava/util/List;", "HeapMonitor.java", 63));
65     return frames;
66   }
67 
allocate(List<Frame> frames)68   private static void allocate(List<Frame> frames) {
69     int sum = 0;
70     for (int j = 0; j < allocationIterations; j++) {
71       sum += actuallyAllocate();
72     }
73     frames.add(new Frame("actuallyAllocate", "()I", "HeapMonitor.java", 98));
74     frames.add(new Frame("allocate", "(Ljava/util/List;)V", "HeapMonitor.java", 71));
75   }
76 
repeatAllocate(int max)77   public static List<Frame> repeatAllocate(int max) {
78     List<Frame> frames = null;
79     for (int i = 0; i < max; i++) {
80       frames = allocate();
81     }
82     frames.add(new Frame("repeatAllocate", "(I)Ljava/util/List;", "HeapMonitor.java", 80));
83     return frames;
84   }
85 
actuallyAllocate()86   private static int actuallyAllocate() {
87     int sum = 0;
88 
89     // Let us assume that a 1-element array is 24 bytes of memory and we want
90     // 2MB allocated.
91     int iterations = (1 << 19) / 6;
92 
93     if (arrays == null) {
94       arrays = new int[iterations][];
95     }
96 
97     for (int i = 0; i < iterations; i++) {
98       int tmp[] = new int[1];
99       // Force it to be kept and, at the same time, wipe out any previous data.
100       arrays[i] = tmp;
101       sum += arrays[0][0];
102     }
103     return sum;
104   }
105 
106   private static long oneElementSize;
getSize(Frame[] frames, boolean checkLines)107   private static native long getSize(Frame[] frames, boolean checkLines);
getSize(Frame[] frames)108   private static long getSize(Frame[] frames) {
109     return getSize(frames, getCheckLines());
110   }
111 
112   // Calculate the size of a 1-element array in order to assess sampling interval
113   // via the HeapMonitorStatIntervalTest.
114   // This is done by allocating a 1-element array and then looking in the heap monitoring
115   // samples for the size of an object collected.
calculateOneElementSize()116   public static void calculateOneElementSize() {
117     enableSamplingEvents();
118 
119     List<Frame> frameList = allocate();
120     disableSamplingEvents();
121 
122     frameList.add(new Frame("calculateOneElementSize", "()V", "HeapMonitor.java", 119));
123     Frame[] frames = frameList.toArray(new Frame[0]);
124 
125     // Get the actual size.
126     oneElementSize = getSize(frames);
127     System.out.println("Element size is: " + oneElementSize);
128 
129     if (oneElementSize == 0) {
130       throw new RuntimeException("Could get the size of a 1-element array.");
131     }
132   }
133 
allocateSize(int totalSize)134   public static int allocateSize(int totalSize) {
135     if (oneElementSize == 0) {
136       throw new RuntimeException("Size of a 1-element array was not calculated.");
137     }
138 
139     int sum = 0;
140     int iterations = (int) (totalSize / oneElementSize);
141 
142     if (arrays == null || arrays.length < iterations) {
143       arrays = new int[iterations][];
144     }
145 
146     System.out.println("Allocating for " + iterations);
147     for (int i = 0; i < iterations; i++) {
148       int tmp[] = new int[1];
149 
150       // Force it to be kept and, at the same time, wipe out any previous data.
151       arrays[i] = tmp;
152       sum += arrays[0][0];
153     }
154 
155     return sum;
156   }
157 
158   /** Remove the reference to the global array to free data at the next GC. */
freeStorage()159   public static void freeStorage() {
160     arrays = null;
161   }
162 
sampleEverything()163   public static int[][][] sampleEverything() {
164     enableSamplingEvents();
165     setSamplingInterval(0);
166 
167     // Loop around an allocation loop and wait until the tlabs have settled.
168     final int maxTries = 10;
169     int[][][] result = new int[maxTries][][];
170     for (int i = 0; i < maxTries; i++) {
171       final int maxInternalTries = 400;
172       result[i] = new int[maxInternalTries][];
173 
174       resetEventStorage();
175       for (int j = 0; j < maxInternalTries; j++) {
176         final int size = 1000;
177         result[i][j] = new int[size];
178       }
179 
180       int sampledEvents = sampledEvents();
181       if (sampledEvents == maxInternalTries) {
182         return result;
183       }
184     }
185 
186     throw new RuntimeException("Could not set the sampler");
187   }
188 
allocateAndCheckFrames(boolean shouldFindFrames, boolean enableSampling)189   public static Frame[] allocateAndCheckFrames(boolean shouldFindFrames,
190       boolean enableSampling) {
191     if (!eventStorageIsEmpty()) {
192       throw new RuntimeException("Statistics should be null to begin with.");
193     }
194 
195     // Put sampling rate to 100k to ensure samples are collected.
196     setSamplingInterval(100 * 1024);
197 
198     if (enableSampling) {
199       enableSamplingEvents();
200     }
201 
202     List<Frame> frameList = allocate();
203     frameList.add(new Frame("allocateAndCheckFrames", "(ZZ)[LMyPackage/Frame;", "HeapMonitor.java",
204           202));
205     Frame[] frames = frameList.toArray(new Frame[0]);
206 
207     boolean foundLive = obtainedEvents(frames);
208     boolean foundGarbage = garbageContains(frames);
209     if (shouldFindFrames) {
210       if (!foundLive && !foundGarbage) {
211         throw new RuntimeException("No expected events were found: "
212             + foundLive + ", " + foundGarbage);
213       }
214     } else {
215       if (foundLive || foundGarbage) {
216         throw new RuntimeException("Were not expecting events, but found some: "
217             + foundLive + ", " + foundGarbage);
218       }
219     }
220 
221     return frames;
222   }
223 
allocateAndCheckFrames()224   public static Frame[] allocateAndCheckFrames() {
225     return allocateAndCheckFrames(true, true);
226   }
227 
sampledEvents()228   public native static int sampledEvents();
obtainedEvents(Frame[] frames, boolean checkLines)229   public native static boolean obtainedEvents(Frame[] frames, boolean checkLines);
garbageContains(Frame[] frames, boolean checkLines)230   public native static boolean garbageContains(Frame[] frames, boolean checkLines);
eventStorageIsEmpty()231   public native static boolean eventStorageIsEmpty();
resetEventStorage()232   public native static void resetEventStorage();
getEventStorageElementCount()233   public native static int getEventStorageElementCount();
forceGarbageCollection()234   public native static void forceGarbageCollection();
enableVMEvents()235   public native static boolean enableVMEvents();
236 
getCheckLines()237   private static boolean getCheckLines() {
238     boolean checkLines = true;
239 
240     // Do not check lines for Graal since it is not always "precise" with BCIs at uncommon traps.
241     try {
242       HotSpotDiagnosticMXBean bean = ManagementFactory.getPlatformMXBean(HotSpotDiagnosticMXBean.class);
243 
244       VMOption enableJVMCI = bean.getVMOption("EnableJVMCI");
245       VMOption useJVMCICompiler = bean.getVMOption("UseJVMCICompiler");
246       String compiler = System.getProperty("jvmci.Compiler");
247 
248       checkLines = !(enableJVMCI.getValue().equals("true")
249           && useJVMCICompiler.getValue().equals("true") && compiler.equals("graal"));
250     } catch (Exception e) {
251       // NOP.
252     }
253 
254     return checkLines;
255   }
256 
obtainedEvents(Frame[] frames)257   public static boolean obtainedEvents(Frame[] frames) {
258     return obtainedEvents(frames, getCheckLines());
259   }
260 
garbageContains(Frame[] frames)261   public static boolean garbageContains(Frame[] frames) {
262     return garbageContains(frames, getCheckLines());
263   }
264 
statsHaveExpectedNumberSamples(int expected, int acceptedErrorPercentage)265   public static boolean statsHaveExpectedNumberSamples(int expected, int acceptedErrorPercentage) {
266     double actual = sampledEvents();
267     double diffPercentage = 100 * Math.abs(actual - expected) / expected;
268 
269     if (diffPercentage >= acceptedErrorPercentage) {
270       System.err.println("Unexpected high difference percentage: " + diffPercentage
271           + " due to the count being " + actual + " instead of " + expected);
272     }
273     return diffPercentage < acceptedErrorPercentage;
274   }
275 
setAllocationIterations(int iterations)276   public static void setAllocationIterations(int iterations) {
277     allocationIterations = iterations;
278   }
279 }
280