001/* 002 * Copyright (c) 2011, 2014, Oracle and/or its affiliates. All rights reserved. 003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 004 * 005 * This code is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU General Public License version 2 only, as 007 * published by the Free Software Foundation. 008 * 009 * This code is distributed in the hope that it will be useful, but WITHOUT 010 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 011 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 012 * version 2 for more details (a copy is included in the LICENSE file that 013 * accompanied this code). 014 * 015 * You should have received a copy of the GNU General Public License version 016 * 2 along with this work; if not, write to the Free Software Foundation, 017 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 018 * 019 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 020 * or visit www.oracle.com if you need additional information or have any 021 * questions. 022 */ 023package com.oracle.graal.compiler.test; 024 025import static java.lang.Boolean.*; 026import static java.lang.Integer.*; 027import static java.lang.System.*; 028 029import java.io.*; 030import java.lang.reflect.*; 031import java.util.*; 032 033import com.google.monitoring.runtime.instrumentation.*; 034 035/** 036 * Tool for analyzing allocations within a scope using the <a 037 * href="https://code.google.com/p/java-allocation-instrumenter/">Java Allocation Instrumenter</a>. 038 * Allocation records are aggregated per stack trace at an allocation site. The size of the stack 039 * trace is governed by the value of the "AllocSpy.ContextSize" system property (default is 5). 040 * <p> 041 * Using this facility requires using -javaagent on the command line. For example: 042 * 043 * <pre> 044 * mx --vm server unittest -javaagent:lib/java-allocation-instrumenter.jar -dsa -DAllocSpy.ContextSize=6 BC_iadd2 045 * </pre> 046 * 047 * @see #SampleBytes 048 * @see #SampleInstances 049 * @see #HistogramLimit 050 * @see #NameSize 051 * @see #BarSize 052 * @see #NumberSize 053 */ 054public final class AllocSpy implements AutoCloseable { 055 056 static ThreadLocal<AllocSpy> current = new ThreadLocal<>(); 057 058 private static final boolean ENABLED; 059 static { 060 boolean enabled = false; 061 try { 062 Field field = AllocationRecorder.class.getDeclaredField("instrumentation"); 063 field.setAccessible(true); 064 enabled = field.get(null) != null; 065 } catch (Exception e) { 066 } 067 ENABLED = enabled; 068 if (ENABLED) { 069 AllocationRecorder.addSampler(new GraalContextSampler()); 070 } 071 } 072 073 public static boolean isEnabled() { 074 return ENABLED; 075 } 076 077 static String prop(String sfx) { 078 return AllocSpy.class.getSimpleName() + "." + sfx; 079 } 080 081 /** 082 * Determines if bytes per allocation site are recorded. 083 */ 084 private static final boolean SampleBytes = parseBoolean(getProperty(prop("SampleBytes"), "true")); 085 086 /** 087 * Determines if allocations per allocation site are recorded. 088 */ 089 private static final boolean SampleInstances = parseBoolean(getProperty(prop("SampleInstances"), "true")); 090 091 /** 092 * The size of context to record for each allocation site in terms of Graal frames. 093 */ 094 private static final int ContextSize = getInteger(prop("ContextSize"), 5); 095 096 /** 097 * Only the {@code HistogramLimit} most frequent values are printed. 098 */ 099 private static final int HistogramLimit = getInteger(prop("HistogramLimit"), 40); 100 101 /** 102 * The width of the allocation context column. 103 */ 104 private static final int NameSize = getInteger(prop("NameSize"), 50); 105 106 /** 107 * The width of the histogram bar column. 108 */ 109 private static final int BarSize = getInteger(prop("BarSize"), 100); 110 111 /** 112 * The width of the frequency column. 113 */ 114 private static final int NumberSize = getInteger(prop("NumberSize"), 10); 115 116 final Object name; 117 final AllocSpy parent; 118 final Map<String, CountedValue> bytesPerGraalContext = new HashMap<>(); 119 final Map<String, CountedValue> instancesPerGraalContext = new HashMap<>(); 120 121 public static AllocSpy open(Object name) { 122 if (ENABLED) { 123 return new AllocSpy(name); 124 } 125 return null; 126 } 127 128 private AllocSpy(Object name) { 129 this.name = name; 130 parent = current.get(); 131 current.set(this); 132 } 133 134 public void close() { 135 current.set(parent); 136 PrintStream ps = System.out; 137 ps.println("\n\nAllocation histograms for " + name); 138 if (SampleBytes) { 139 print(ps, bytesPerGraalContext, "BytesPerGraalContext", HistogramLimit, NameSize + 60, BarSize); 140 } 141 if (SampleInstances) { 142 print(ps, instancesPerGraalContext, "InstancesPerGraalContext", HistogramLimit, NameSize + 60, BarSize); 143 } 144 } 145 146 private static void printLine(PrintStream printStream, char c, int lineSize) { 147 char[] charArr = new char[lineSize]; 148 Arrays.fill(charArr, c); 149 printStream.printf("%s%n", new String(charArr)); 150 } 151 152 private static void print(PrintStream ps, Map<String, CountedValue> map, String name, int limit, int nameSize, int barSize) { 153 if (map.isEmpty()) { 154 return; 155 } 156 157 List<CountedValue> list = new ArrayList<>(map.values()); 158 Collections.sort(list); 159 160 // Sum up the total number of elements. 161 int total = 0; 162 for (CountedValue cv : list) { 163 total += cv.getCount(); 164 } 165 166 // Print header. 167 ps.printf("%s has %d unique elements and %d total elements:%n", name, list.size(), total); 168 169 int max = list.get(0).getCount(); 170 final int lineSize = nameSize + NumberSize + barSize + 10; 171 printLine(ps, '-', lineSize); 172 String formatString = "| %-" + nameSize + "s | %-" + NumberSize + "d | %-" + barSize + "s |\n"; 173 for (int i = 0; i < list.size() && i < limit; ++i) { 174 CountedValue cv = list.get(i); 175 int value = cv.getCount(); 176 char[] bar = new char[(int) (((double) value / (double) max) * barSize)]; 177 Arrays.fill(bar, '='); 178 String[] lines = String.valueOf(cv.getValue()).split("\\n"); 179 180 String objectString = lines[0]; 181 if (objectString.length() > nameSize) { 182 objectString = objectString.substring(0, nameSize - 3) + "..."; 183 } 184 ps.printf(formatString, objectString, value, new String(bar)); 185 for (int j = 1; j < lines.length; j++) { 186 String line = lines[j]; 187 if (line.length() > nameSize) { 188 line = line.substring(0, nameSize - 3) + "..."; 189 } 190 ps.printf("| %-" + (nameSize - 2) + "s | %-" + NumberSize + "s | %-" + barSize + "s |%n", line, " ", " "); 191 192 } 193 } 194 printLine(ps, '-', lineSize); 195 } 196 197 CountedValue bytesPerGraalContext(String context) { 198 return getCounter(context, bytesPerGraalContext); 199 } 200 201 CountedValue instancesPerGraalContext(String context) { 202 return getCounter(context, instancesPerGraalContext); 203 } 204 205 protected static CountedValue getCounter(String desc, Map<String, CountedValue> map) { 206 CountedValue count = map.get(desc); 207 if (count == null) { 208 count = new CountedValue(0, desc); 209 map.put(desc, count); 210 } 211 return count; 212 } 213 214 private static final String[] Excluded = {AllocSpy.class.getName(), AllocationRecorder.class.getName()}; 215 216 private static boolean excludeFrame(String className) { 217 for (String e : Excluded) { 218 if (className.startsWith(e)) { 219 return true; 220 } 221 } 222 return false; 223 } 224 225 static class GraalContextSampler implements Sampler { 226 227 public void sampleAllocation(int count, String desc, Object newObj, long size) { 228 AllocSpy scope = current.get(); 229 if (scope != null) { 230 StringBuilder sb = new StringBuilder(200); 231 Throwable t = new Throwable(); 232 int remainingGraalFrames = ContextSize; 233 for (StackTraceElement e : t.getStackTrace()) { 234 if (remainingGraalFrames < 0) { 235 break; 236 } 237 String className = e.getClassName(); 238 boolean isGraalFrame = className.contains(".graal."); 239 if (sb.length() != 0) { 240 append(sb.append('\n'), e); 241 } else { 242 if (!excludeFrame(className)) { 243 sb.append("type=").append(desc); 244 if (count != -1) { 245 sb.append('[').append(count).append(']'); 246 } 247 append(sb.append('\n'), e); 248 } 249 } 250 if (isGraalFrame) { 251 remainingGraalFrames--; 252 } 253 } 254 String context = sb.toString(); 255 if (SampleBytes) { 256 scope.bytesPerGraalContext(context).add((int) size); 257 } 258 if (SampleInstances) { 259 scope.instancesPerGraalContext(context).inc(); 260 } 261 } 262 } 263 264 protected StringBuilder append(StringBuilder sb, StackTraceElement e) { 265 String className = e.getClassName(); 266 int period = className.lastIndexOf('.'); 267 if (period != -1) { 268 sb.append(className, period + 1, className.length()); 269 } else { 270 sb.append(className); 271 } 272 sb.append('.').append(e.getMethodName()); 273 if (e.isNativeMethod()) { 274 sb.append("(Native Method)"); 275 } else if (e.getFileName() != null && e.getLineNumber() >= 0) { 276 sb.append('(').append(e.getFileName()).append(':').append(e.getLineNumber()).append(")"); 277 } else { 278 sb.append("(Unknown Source)"); 279 } 280 return sb; 281 } 282 } 283 284 /** 285 * A value and a frequency. The ordering imposed by {@link #compareTo(CountedValue)} places 286 * values with higher frequencies first. 287 */ 288 static class CountedValue implements Comparable<CountedValue> { 289 290 private int count; 291 private final Object value; 292 293 public CountedValue(int count, Object value) { 294 this.count = count; 295 this.value = value; 296 } 297 298 public int compareTo(CountedValue o) { 299 if (count < o.count) { 300 return 1; 301 } else if (count > o.count) { 302 return -1; 303 } 304 return 0; 305 } 306 307 @Override 308 public String toString() { 309 return count + " -> " + value; 310 } 311 312 public void inc() { 313 count++; 314 } 315 316 public void add(int n) { 317 count += n; 318 } 319 320 public int getCount() { 321 return count; 322 } 323 324 public Object getValue() { 325 return value; 326 } 327 } 328}