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