--- /dev/null 2016-05-31 09:42:47.975716356 -0700 +++ new/src/jdk.vm.compiler/share/classes/org.graalvm.compiler.core.test/src/org/graalvm/compiler/core/test/AllocSpy.java 2016-12-07 13:48:12.992083749 -0800 @@ -0,0 +1,339 @@ +/* + * Copyright (c) 2011, 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.graalvm.compiler.core.test; + +import static java.lang.Boolean.parseBoolean; +import static java.lang.Integer.getInteger; +import static java.lang.System.getProperty; + +import java.io.PrintStream; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.google.monitoring.runtime.instrumentation.AllocationRecorder; +import com.google.monitoring.runtime.instrumentation.Sampler; + +/** + * Tool for analyzing allocations within a scope using the + * Java Allocation + * Instrumenter. Allocation records are aggregated per stack trace at an allocation site. The + * size of the stack trace is governed by the value of the "AllocSpy.ContextSize" system property + * (default is 5). + *

+ * Using this facility requires using -javaagent on the command line. For example: + * + *

+ * mx --vm server unittest -javaagent:lib/java-allocation-instrumenter.jar -dsa -DAllocSpy.ContextSize=6 BC_iadd2
+ * 
+ * + * @see #SampleBytes + * @see #SampleInstances + * @see #HistogramLimit + * @see #NameSize + * @see #BarSize + * @see #NumberSize + */ +public final class AllocSpy implements AutoCloseable { + + static ThreadLocal current = new ThreadLocal<>(); + + private static final boolean ENABLED; + + static { + boolean enabled = false; + try { + Field field = AllocationRecorder.class.getDeclaredField("instrumentation"); + field.setAccessible(true); + enabled = field.get(null) != null; + } catch (Exception e) { + } + ENABLED = enabled; + if (ENABLED) { + AllocationRecorder.addSampler(new GraalContextSampler()); + } + } + + public static boolean isEnabled() { + return ENABLED; + } + + static String prop(String sfx) { + return AllocSpy.class.getSimpleName() + "." + sfx; + } + + /** + * Determines if bytes per allocation site are recorded. + */ + private static final boolean SampleBytes = parseBoolean(getProperty(prop("SampleBytes"), "true")); + + /** + * Determines if allocations per allocation site are recorded. + */ + private static final boolean SampleInstances = parseBoolean(getProperty(prop("SampleInstances"), "true")); + + /** + * The size of context to record for each allocation site in terms of Graal frames. + */ + private static final int ContextSize = getInteger(prop("ContextSize"), 5); + + /** + * Only the {@code HistogramLimit} most frequent values are printed. + */ + private static final int HistogramLimit = getInteger(prop("HistogramLimit"), 40); + + /** + * The width of the allocation context column. + */ + private static final int NameSize = getInteger(prop("NameSize"), 50); + + /** + * The width of the histogram bar column. + */ + private static final int BarSize = getInteger(prop("BarSize"), 100); + + /** + * The width of the frequency column. + */ + private static final int NumberSize = getInteger(prop("NumberSize"), 10); + + final Object name; + final AllocSpy parent; + final Map bytesPerGraalContext = new HashMap<>(); + final Map instancesPerGraalContext = new HashMap<>(); + + public static AllocSpy open(Object name) { + if (ENABLED) { + return new AllocSpy(name); + } + return null; + } + + private AllocSpy(Object name) { + this.name = name; + parent = current.get(); + current.set(this); + } + + @Override + public void close() { + current.set(parent); + PrintStream ps = System.out; + ps.println("\n\nAllocation histograms for " + name); + if (SampleBytes) { + print(ps, bytesPerGraalContext, "BytesPerGraalContext", HistogramLimit, NameSize + 60, BarSize); + } + if (SampleInstances) { + print(ps, instancesPerGraalContext, "InstancesPerGraalContext", HistogramLimit, NameSize + 60, BarSize); + } + } + + private static void printLine(PrintStream printStream, char c, int lineSize) { + char[] charArr = new char[lineSize]; + Arrays.fill(charArr, c); + printStream.printf("%s%n", new String(charArr)); + } + + private static void print(PrintStream ps, Map map, String name, int limit, int nameSize, int barSize) { + if (map.isEmpty()) { + return; + } + + List list = new ArrayList<>(map.values()); + Collections.sort(list); + + // Sum up the total number of elements. + int total = 0; + for (CountedValue cv : list) { + total += cv.getCount(); + } + + // Print header. + ps.printf("%s has %d unique elements and %d total elements:%n", name, list.size(), total); + + int max = list.get(0).getCount(); + final int lineSize = nameSize + NumberSize + barSize + 10; + printLine(ps, '-', lineSize); + String formatString = "| %-" + nameSize + "s | %-" + NumberSize + "d | %-" + barSize + "s |\n"; + for (int i = 0; i < list.size() && i < limit; ++i) { + CountedValue cv = list.get(i); + int value = cv.getCount(); + char[] bar = new char[(int) (((double) value / (double) max) * barSize)]; + Arrays.fill(bar, '='); + String[] lines = String.valueOf(cv.getValue()).split("\\n"); + + String objectString = lines[0]; + if (objectString.length() > nameSize) { + objectString = objectString.substring(0, nameSize - 3) + "..."; + } + ps.printf(formatString, objectString, value, new String(bar)); + for (int j = 1; j < lines.length; j++) { + String line = lines[j]; + if (line.length() > nameSize) { + line = line.substring(0, nameSize - 3) + "..."; + } + ps.printf("| %-" + (nameSize - 2) + "s | %-" + NumberSize + "s | %-" + barSize + "s |%n", line, " ", " "); + + } + } + printLine(ps, '-', lineSize); + } + + CountedValue bytesPerGraalContext(String context) { + return getCounter(context, bytesPerGraalContext); + } + + CountedValue instancesPerGraalContext(String context) { + return getCounter(context, instancesPerGraalContext); + } + + protected static CountedValue getCounter(String desc, Map map) { + CountedValue count = map.get(desc); + if (count == null) { + count = new CountedValue(0, desc); + map.put(desc, count); + } + return count; + } + + private static final String[] Excluded = {AllocSpy.class.getName(), AllocationRecorder.class.getName()}; + + private static boolean excludeFrame(String className) { + for (String e : Excluded) { + if (className.startsWith(e)) { + return true; + } + } + return false; + } + + static class GraalContextSampler implements Sampler { + + @Override + public void sampleAllocation(int count, String desc, Object newObj, long size) { + AllocSpy scope = current.get(); + if (scope != null) { + StringBuilder sb = new StringBuilder(200); + Throwable t = new Throwable(); + int remainingGraalFrames = ContextSize; + for (StackTraceElement e : t.getStackTrace()) { + if (remainingGraalFrames < 0) { + break; + } + String className = e.getClassName(); + boolean isGraalFrame = className.contains(".graal."); + if (sb.length() != 0) { + append(sb.append('\n'), e); + } else { + if (!excludeFrame(className)) { + sb.append("type=").append(desc); + if (count != -1) { + sb.append('[').append(count).append(']'); + } + append(sb.append('\n'), e); + } + } + if (isGraalFrame) { + remainingGraalFrames--; + } + } + String context = sb.toString(); + if (SampleBytes) { + scope.bytesPerGraalContext(context).add((int) size); + } + if (SampleInstances) { + scope.instancesPerGraalContext(context).inc(); + } + } + } + + protected StringBuilder append(StringBuilder sb, StackTraceElement e) { + String className = e.getClassName(); + int period = className.lastIndexOf('.'); + if (period != -1) { + sb.append(className, period + 1, className.length()); + } else { + sb.append(className); + } + sb.append('.').append(e.getMethodName()); + if (e.isNativeMethod()) { + sb.append("(Native Method)"); + } else if (e.getFileName() != null && e.getLineNumber() >= 0) { + sb.append('(').append(e.getFileName()).append(':').append(e.getLineNumber()).append(")"); + } else { + sb.append("(Unknown Source)"); + } + return sb; + } + } + + /** + * A value and a frequency. The ordering imposed by {@link #compareTo(CountedValue)} places + * values with higher frequencies first. + */ + static class CountedValue implements Comparable { + + private int count; + private final Object value; + + CountedValue(int count, Object value) { + this.count = count; + this.value = value; + } + + @Override + public int compareTo(CountedValue o) { + if (count < o.count) { + return 1; + } else if (count > o.count) { + return -1; + } + return 0; + } + + @Override + public String toString() { + return count + " -> " + value; + } + + public void inc() { + count++; + } + + public void add(int n) { + count += n; + } + + public int getCount() { + return count; + } + + public Object getValue() { + return value; + } + } +}