1 /* 2 * Copyright (c) 2011, 2018, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 25 package org.graalvm.compiler.core.test; 26 27 import static java.lang.Boolean.parseBoolean; 28 import static java.lang.Integer.getInteger; 29 import static java.lang.System.getProperty; 30 31 import java.io.PrintStream; 32 import java.lang.reflect.Field; 33 import java.util.ArrayList; 34 import java.util.Arrays; 35 import java.util.Collections; 36 import java.util.HashMap; 37 import java.util.List; 38 import java.util.Map; 39 40 import com.google.monitoring.runtime.instrumentation.AllocationRecorder; 41 import com.google.monitoring.runtime.instrumentation.Sampler; 42 43 /** 44 * Tool for analyzing allocations within a scope using the 45 * <a href="https://code.google.com/p/java-allocation-instrumenter/">Java Allocation 46 * Instrumenter</a>. Allocation records are aggregated per stack trace at an allocation site. The 47 * size of the stack trace is governed by the value of the "AllocSpy.ContextSize" system property 48 * (default is 5). 49 * <p> 50 * Using this facility requires using -javaagent on the command line. For example: 51 * 52 * <pre> 53 * mx --vm server unittest -javaagent:lib/java-allocation-instrumenter.jar -dsa -DAllocSpy.ContextSize=6 BC_iadd2 54 * </pre> 55 * 56 * @see #SampleBytes 57 * @see #SampleInstances 58 * @see #HistogramLimit 59 * @see #NameSize 60 * @see #BarSize 61 * @see #NumberSize 62 */ 63 public final class AllocSpy implements AutoCloseable { 64 65 static ThreadLocal<AllocSpy> current = new ThreadLocal<>(); 66 67 private static final boolean ENABLED; 68 69 static { 70 boolean enabled = false; 71 try { 72 Field field = AllocationRecorder.class.getDeclaredField("instrumentation"); 73 field.setAccessible(true); 74 enabled = field.get(null) != null; 75 } catch (Exception e) { 76 } catch (LinkageError e) { 77 } 78 ENABLED = enabled; 79 if (ENABLED) { 80 AllocationRecorder.addSampler(new GraalContextSampler()); 81 } 82 } 83 84 public static boolean isEnabled() { 85 return ENABLED; 86 } 87 88 static String prop(String sfx) { 89 return AllocSpy.class.getSimpleName() + "." + sfx; 90 } 91 92 /** 93 * Determines if bytes per allocation site are recorded. 94 */ 95 private static final boolean SampleBytes = parseBoolean(getProperty(prop("SampleBytes"), "true")); 96 97 /** 98 * Determines if allocations per allocation site are recorded. 99 */ 100 private static final boolean SampleInstances = parseBoolean(getProperty(prop("SampleInstances"), "true")); 101 102 /** 103 * The size of context to record for each allocation site in terms of Graal frames. 104 */ 105 private static final int ContextSize = getInteger(prop("ContextSize"), 5); 106 107 /** 108 * Only the {@code HistogramLimit} most frequent values are printed. 109 */ 110 private static final int HistogramLimit = getInteger(prop("HistogramLimit"), 40); 111 112 /** 113 * The width of the allocation context column. 114 */ 115 private static final int NameSize = getInteger(prop("NameSize"), 50); 116 117 /** 118 * The width of the histogram bar column. 119 */ 120 private static final int BarSize = getInteger(prop("BarSize"), 100); 121 122 /** 123 * The width of the frequency column. 124 */ 125 private static final int NumberSize = getInteger(prop("NumberSize"), 10); 126 127 final Object name; 128 final AllocSpy parent; 129 final Map<String, CountedValue> bytesPerGraalContext = new HashMap<>(); 130 final Map<String, CountedValue> instancesPerGraalContext = new HashMap<>(); 131 132 public static AllocSpy open(Object name) { 133 if (ENABLED) { 134 return new AllocSpy(name); 135 } 136 return null; 137 } 138 139 private AllocSpy(Object name) { 140 this.name = name; 141 parent = current.get(); 142 current.set(this); 143 } 144 145 @Override 146 public void close() { 147 current.set(parent); 148 PrintStream ps = System.out; 149 ps.println("\n\nAllocation histograms for " + name); 150 if (SampleBytes) { 151 print(ps, bytesPerGraalContext, "BytesPerGraalContext", HistogramLimit, NameSize + 60, BarSize); 152 } 153 if (SampleInstances) { 154 print(ps, instancesPerGraalContext, "InstancesPerGraalContext", HistogramLimit, NameSize + 60, BarSize); 155 } 156 } 157 158 private static void printLine(PrintStream printStream, char c, int lineSize) { 159 char[] charArr = new char[lineSize]; 160 Arrays.fill(charArr, c); 161 printStream.printf("%s%n", new String(charArr)); 162 } 163 164 private static void print(PrintStream ps, Map<String, CountedValue> map, String name, int limit, int nameSize, int barSize) { 165 if (map.isEmpty()) { 166 return; 167 } 168 169 List<CountedValue> list = new ArrayList<>(map.values()); 170 Collections.sort(list); 171 172 // Sum up the total number of elements. 173 int total = 0; 174 for (CountedValue cv : list) { 175 total += cv.getCount(); 176 } 177 178 // Print header. 179 ps.printf("%s has %d unique elements and %d total elements:%n", name, list.size(), total); 180 181 int max = list.get(0).getCount(); 182 final int lineSize = nameSize + NumberSize + barSize + 10; 183 printLine(ps, '-', lineSize); 184 String formatString = "| %-" + nameSize + "s | %-" + NumberSize + "d | %-" + barSize + "s |\n"; 185 for (int i = 0; i < list.size() && i < limit; ++i) { 186 CountedValue cv = list.get(i); 187 int value = cv.getCount(); 188 char[] bar = new char[(int) (((double) value / (double) max) * barSize)]; 189 Arrays.fill(bar, '='); 190 String[] lines = String.valueOf(cv.getValue()).split("\\n"); 191 192 String objectString = lines[0]; 193 if (objectString.length() > nameSize) { 194 objectString = objectString.substring(0, nameSize - 3) + "..."; 195 } 196 ps.printf(formatString, objectString, value, new String(bar)); 197 for (int j = 1; j < lines.length; j++) { 198 String line = lines[j]; 199 if (line.length() > nameSize) { 200 line = line.substring(0, nameSize - 3) + "..."; 201 } 202 ps.printf("| %-" + (nameSize - 2) + "s | %-" + NumberSize + "s | %-" + barSize + "s |%n", line, " ", " "); 203 204 } 205 } 206 printLine(ps, '-', lineSize); 207 } 208 209 CountedValue bytesPerGraalContext(String context) { 210 return getCounter(context, bytesPerGraalContext); 211 } 212 213 CountedValue instancesPerGraalContext(String context) { 214 return getCounter(context, instancesPerGraalContext); 215 } 216 217 protected static CountedValue getCounter(String desc, Map<String, CountedValue> map) { 218 CountedValue count = map.get(desc); 219 if (count == null) { 220 count = new CountedValue(0, desc); 221 map.put(desc, count); 222 } 223 return count; 224 } 225 226 private static final String[] Excluded = {AllocSpy.class.getName(), AllocationRecorder.class.getName()}; 227 228 private static boolean excludeFrame(String className) { 229 for (String e : Excluded) { 230 if (className.startsWith(e)) { 231 return true; 232 } 233 } 234 return false; 235 } 236 237 static class GraalContextSampler implements Sampler { 238 239 @Override 240 public void sampleAllocation(int count, String desc, Object newObj, long size) { 241 AllocSpy scope = current.get(); 242 if (scope != null) { 243 StringBuilder sb = new StringBuilder(200); 244 Throwable t = new Throwable(); 245 int remainingGraalFrames = ContextSize; 246 for (StackTraceElement e : t.getStackTrace()) { 247 if (remainingGraalFrames < 0) { 248 break; 249 } 250 String className = e.getClassName(); 251 boolean isGraalFrame = className.contains(".graal."); 252 if (sb.length() != 0) { 253 append(sb.append('\n'), e); 254 } else { 255 if (!excludeFrame(className)) { 256 sb.append("type=").append(desc); 257 if (count != -1) { 258 sb.append('[').append(count).append(']'); 259 } 260 append(sb.append('\n'), e); 261 } 262 } 263 if (isGraalFrame) { 264 remainingGraalFrames--; 265 } 266 } 267 String context = sb.toString(); 268 if (SampleBytes) { 269 scope.bytesPerGraalContext(context).add((int) size); 270 } 271 if (SampleInstances) { 272 scope.instancesPerGraalContext(context).inc(); 273 } 274 } 275 } 276 277 protected StringBuilder append(StringBuilder sb, StackTraceElement e) { 278 String className = e.getClassName(); 279 int period = className.lastIndexOf('.'); 280 if (period != -1) { 281 sb.append(className, period + 1, className.length()); 282 } else { 283 sb.append(className); 284 } 285 sb.append('.').append(e.getMethodName()); 286 if (e.isNativeMethod()) { 287 sb.append("(Native Method)"); 288 } else if (e.getFileName() != null && e.getLineNumber() >= 0) { 289 sb.append('(').append(e.getFileName()).append(':').append(e.getLineNumber()).append(")"); 290 } else { 291 sb.append("(Unknown Source)"); 292 } 293 return sb; 294 } 295 } 296 297 /** 298 * A value and a frequency. The ordering imposed by {@link #compareTo(CountedValue)} places 299 * values with higher frequencies first. 300 */ 301 static class CountedValue implements Comparable<CountedValue> { 302 303 private int count; 304 private final Object value; 305 306 CountedValue(int count, Object value) { 307 this.count = count; 308 this.value = value; 309 } 310 311 @Override 312 public int compareTo(CountedValue o) { 313 if (count < o.count) { 314 return 1; 315 } else if (count > o.count) { 316 return -1; 317 } 318 return 0; 319 } 320 321 @Override 322 public String toString() { 323 return count + " -> " + value; 324 } 325 326 public void inc() { 327 count++; 328 } 329 330 public void add(int n) { 331 count += n; 332 } 333 334 public int getCount() { 335 return count; 336 } 337 338 public Object getValue() { 339 return value; 340 } 341 } 342 }