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