/* * Copyright 2012 Kantega AS * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.kantega.revoc.registry; import org.kantega.revoc.report.HtmlReport; import org.kantega.revoc.source.CompondSourceSource; import org.kantega.revoc.source.MavenProjectSourceSource; import org.kantega.revoc.source.MavenSourceArtifactSourceSource; import sun.misc.Unsafe; import java.io.*; import java.lang.invoke.*; import java.lang.reflect.Field; import java.net.URL; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicIntegerArray; import java.util.concurrent.atomic.AtomicLongArray; /** * */ public abstract class Registry { private static int classCount = 0; private static final int INITIAL_NUM_CLASSES = 1000; private static String[] classNames; private static ConcurrentMap<Integer, ClassNameMap> classNamesMap; private static String[] sourceFiles; private static int[] classLoaders; public static int[][] lines; public static AtomicLongArray[] lineVisits; public static UnsafeAtomicLongArray[] methodVisits; public static int[][][] methodLines; public static AtomicLongArray[] lineTimes; public static AtomicIntegerArray classTouches; private static BranchPoint[][] branchPoints; public static volatile long time = System.currentTimeMillis(); public static final int CHECK_RESOLUTION_MILLIS = 100; public static final int NOTIFY_CHANGE_RESOLUTION_MILLIS = 1000; public static final int TIME_RESOLUTION_MILLIS = 50; private final static List<ChangeListener> changeListeners = new ArrayList<ChangeListener>(); private static final Object monitor = new Object(); private static final ThreadMap threadMap = new ThreadMap(); private static final ThreadLocal<FrameMap> threadLocalMap = new ThreadLocal<FrameMap>() { @Override protected FrameMap initialValue() { FrameMap frameMap = new FrameMap(); threadMap.put(Thread.currentThread().getId(), frameMap); return frameMap; } }; private static String[][] methodNames; private static String[][] methodDescs; private static List<NamedClassLoader> classLoaderList = new ArrayList<NamedClassLoader>(); public static int[][] methodFirstLines; private static final Unsafe unsafe; private static final long offset; static { try { Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); theUnsafe.setAccessible(true); unsafe = (Unsafe) theUnsafe.get(null); offset = unsafe.objectFieldOffset(Thread.class.getDeclaredField("target")); } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (NoSuchFieldException e) { throw new RuntimeException(e); } } public static Collection<Frame> getFrames() { Frame top = new Frame(-1); for (FrameMap map : threadMap.values()) { for (Frame frame : map.values()) { frame.mergeIntoParent(top); } } return top.getChildren(); } public static ClassLoader getClassLoader(int classLoader) { return classNamesMap.get(classLoader).getClassLoader(); } public interface ChangeListener { void onChange(BitSet bs); } static class ClassNameMap extends ConcurrentHashMap<String, Integer> { private final ClassLoader classLoader; public ClassNameMap(ClassLoader classLoader) { this.classLoader = classLoader; } public ClassLoader getClassLoader() { return classLoader; } } static { try { resetRegistry(); startTimerThread(); startChangeThread(); URL resource = Registry.class.getClassLoader().getResource("revoc-instrumentation.properties"); if (resource != null) { Properties props = new Properties(); props.load(resource.openStream()); String registry = props.getProperty("registry"); if (registry != null) { FileInputStream fis = new FileInputStream(registry); load(fis); fis.close(); } String report = props.getProperty("report"); if (report != null) { addReportShutdownHook(report); } } } catch (IOException e) { throw new RuntimeException(e); } } public static class NamedClassLoader { private String name; private ClassLoader classLoader; NamedClassLoader(ClassLoader classLoader) { this.classLoader = classLoader; this.name = classLoader.toString().replaceAll("\n",", ").replaceAll("\r",""); } public ClassLoader getClassLoader() { return classLoader; } public String getName() { return name; } } public static void addClassLoader(ClassLoader classLoader) { synchronized (monitor) { classLoaderList.add(new NamedClassLoader(classLoader)); Collections.sort(classLoaderList, new Comparator<NamedClassLoader>() { @Override public int compare(NamedClassLoader classLoader, NamedClassLoader classLoader1) { return classLoader.getName().toLowerCase().compareTo(classLoader1.getName().toLowerCase()); } }); } } public static List<NamedClassLoader> getClassLoaders() { synchronized (monitor) { return new ArrayList<NamedClassLoader>(classLoaderList); } } private static void startTimerThread() { new Thread("Revoc time ticker") { { setDaemon(true); } @Override public void run() { while (true) { try { Thread.sleep(TIME_RESOLUTION_MILLIS); time = System.currentTimeMillis(); } catch (InterruptedException e) { break; } } } }.start(); } public static void addChangeListener(ChangeListener changeListener) { synchronized (changeListeners) { changeListeners.add(changeListener); } } public static void removeChangeListener(ChangeListener changeListener) { synchronized (changeListeners) { changeListeners.remove(changeListener); } } private static void startChangeThread() { new Thread("Revoc change detector") { { setDaemon(true); } private long lastInvoke = 0; @Override public void run() { BitSet bs = new BitSet(); while (true) { AtomicIntegerArray classes = classTouches; for(int i = 0; i < classes.length(); i++) { int last = classes.get(i); if(last != 0) { bs.set(i); classes.set(i, 0); } } long now = System.currentTimeMillis(); long sincelast = now - lastInvoke; if (bs.cardinality() > 0 && sincelast > NOTIFY_CHANGE_RESOLUTION_MILLIS ) { List<ChangeListener> listeners = new ArrayList<ChangeListener>(); synchronized (changeListeners) { listeners.addAll(changeListeners); } for (ChangeListener listener : listeners) { try { listener.onChange(bs); } catch (Exception e) { e.printStackTrace(); } } bs.clear(); lastInvoke = now; } try { Thread.sleep(CHECK_RESOLUTION_MILLIS); } catch (InterruptedException e) { break; } } } }.start(); } private static void addReportShutdownHook(final String report) { Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { try { File reportFile = new File(report); reportFile.getParentFile().mkdirs(); final FileWriter writer = new FileWriter(reportFile); new HtmlReport().run(getCoverageData(), null, writer, new CompondSourceSource(new MavenProjectSourceSource(), new MavenSourceArtifactSourceSource())); writer.close(); } catch (IOException e) { throw new RuntimeException(e); } } }); } public static void load(InputStream inputStream) { try { ObjectInputStream in = new ObjectInputStream(inputStream); Save s = (Save) in.readObject(); classCount = s.classCount; classNames = s.classNames; classLoaders = s.classLoaders; methodNames = s.methodNames; methodDescs = s.methodDescs; classNamesMap = new ConcurrentHashMap<Integer, ClassNameMap>(); for (int i = 0; i < classCount; i++) { if(!classNamesMap.containsKey(classLoaders[i])) { classNamesMap.putIfAbsent(classLoaders[i], new ClassNameMap(null)); } classNamesMap.get(classLoaders[i]).put(classNames[i], i); } sourceFiles = s.sourceFiles; lines = s.lines; lineVisits = s.lineVisits; branchPoints = s.branchPoints; in.close(); } catch (IOException e) { throw new RuntimeException(e); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } } public static void save(OutputStream outputStream) { try { ObjectOutputStream out = new ObjectOutputStream(outputStream); Save s = new Save(classCount, classNames, classLoaders, methodNames, methodDescs, sourceFiles, lines, lineVisits, branchPoints); out.writeObject(s); out.close(); } catch (IOException e) { throw new RuntimeException(e); } } public static void registerLineVisited(int classId, int lineId) { lineVisits[classId].incrementAndGet(lineId); } public static ThreadLocalBuffer getThreadLocalBufferInc(long methodId) { ThreadLocalBuffer buffer = getThreadLocalBuffer(); buffer.pushStack(methodId); return buffer; } public static ThreadLocalBuffer getThreadLocalBuffer() { Thread thread = Thread.currentThread(); Object counter = getRunnable(thread); if(counter != null) { try { return (ThreadLocalBuffer) counter; } catch (Exception e) { return newBuffer(thread); } } return newBuffer(thread); } private static Object getRunnable(Thread thread) { return unsafe.getObject(thread, offset); } private static ThreadLocalBuffer newBuffer(Thread thread) { return putRunnable(thread, new ThreadLocalBuffer()); } private static ThreadLocalBuffer putRunnable(Thread thread, ThreadLocalBuffer buffer) { unsafe.putObject(thread, offset, buffer); return buffer; } public static void registerLineTimeVisited(AtomicLongArray lineVisits, AtomicLongArray lineTimes, int lineId, int numvisits, long time) { if(numvisits != 0) { lineVisits.addAndGet(lineId, numvisits); lineTimes.lazySet(lineId, time); } } public static void registerLineTimeVisitedMV(AtomicLongArray lineVisits, AtomicLongArray lineTimes, int lineId, int numvisits, long time) { if(numvisits != 0) { lineVisits.addAndGet(lineId, numvisits); } if(numvisits != -1) { lineTimes.set(lineId, time); } } public static void registerOneLineVisited(long methodId) { //getThreadLocalBuffer().visitLine(methodId, 1, 1); getThreadLocalBuffer().visitMethod(methodId); } public static void registerLineVisited(AtomicLongArray lineVisits, int lineId, int numvisits) { //lineVisits.addAndGet(lineId, numvisits); } public static void registerLineVisitedArray(AtomicLongArray lineVisits, int[] methodVisited, int firstLine) { for (int i = 0; i < methodVisited.length; i++) { lineVisits.addAndGet(i + firstLine, methodVisited[i]); } } public static void registerLineTimeVisitedArray(AtomicLongArray lineVisits, AtomicLongArray timeVisits, int[] methodVisited, long[] methodTimes, int firstLine) { for (int i = 0; i < methodVisited.length; i++) { int visits = methodVisited[i]; final int classLine = i + firstLine; lineVisits.addAndGet(classLine, visits); if (visits > 0) { timeVisits.set(classLine, methodTimes[i]); } } } public static CallSite lineVisitBootstrap(MethodHandles.Lookup lookup, String name, MethodType methodType, int classId, int lineIndex) throws NoSuchMethodException, IllegalAccessException { AtomicLongArray visitStore = Registry.lineVisits[classId]; MethodHandle registerLine = lookup.findStatic(Registry.class, "registerLineVisited", MethodType.methodType(void.class, AtomicLongArray.class, int.class, int.class)); MethodHandle methodHandle = MethodHandles.insertArguments(registerLine, 0, visitStore, lineIndex); return new ConstantCallSite(methodHandle); } public static CallSite methodVisitBootstrap(MethodHandles.Lookup lookup, String name, MethodType methodType, int classId, int methodIndex) throws NoSuchMethodException, IllegalAccessException { UnsafeAtomicLongArray visitsArray = Registry.methodVisits[classId]; MethodHandle incrementHandle = lookup.findVirtual(UnsafeAtomicLongArray.class, "incrementOffset", MethodType.methodType(void.class, int.class)); MethodHandle incr = MethodHandles.insertArguments(incrementHandle, 1, methodIndex); MethodHandle bound = incr.bindTo(visitsArray); return new ConstantCallSite(bound); } public static void linesTouched(int classId) { classTouches.set(classId, 1); } public static boolean isClassRegistered(String name, ClassLoader classLoader) { synchronized (monitor) { ClassNameMap classNameMap = classNamesMap.get(System.identityHashCode(classLoader)); return classNameMap != null && classNameMap.containsKey(name); } } public static int registerClass(String name, ClassLoader classLoader, String source) { synchronized (monitor) { ensureLineRegistryCapacity(); int classId = classCount; classNames[classId] = name; sourceFiles[classId] = source; classLoaders[classId] = System.identityHashCode(classLoader); classCount++; if(!classNamesMap.containsKey(classLoaders[classId])) { addClassLoader(classLoader); classNamesMap.putIfAbsent(classLoaders[classId], new ClassNameMap(classLoader)); } classNamesMap.get(classLoaders[classId]).put(classNames[classId], classId); return classId; } } public static void registerLines(int classId, int[] lines) { Registry.lineVisits[classId] = new AtomicLongArray(lines.length); Registry.lineTimes[classId] = new AtomicLongArray(lines.length); Registry.lines[classId] = new int[lines.length]; System.arraycopy(lines, 0, Registry.lines[classId], 0, lines.length); } private static void ensureLineRegistryCapacity() { if (classCount + 1 > classNames.length) { { String[] old = classNames; String[] classNames = new String[old.length * 2]; System.arraycopy(old, 0, classNames, 0, old.length); Registry.classNames = classNames; } { int[] old = classLoaders; int[] classLoaders = new int[old.length * 2]; System.arraycopy(old, 0, classLoaders, 0, old.length); Registry.classLoaders = classLoaders; } { String[][] old = methodNames; String[][] methodNames = new String[old.length * 2][]; System.arraycopy(old, 0, methodNames, 0, old.length); Registry.methodNames = methodNames; } { String[][] old = methodDescs; String[][] methodDescs = new String[old.length * 2][]; System.arraycopy(old, 0, methodDescs, 0, old.length); Registry.methodDescs = methodDescs; } { String[] old = sourceFiles; String[] sourceFiles = new String[old.length * 2]; System.arraycopy(old, 0, sourceFiles, 0, old.length); Registry.sourceFiles = sourceFiles; } { UnsafeAtomicLongArray[] old = methodVisits; UnsafeAtomicLongArray[] methodVisits = new UnsafeAtomicLongArray[old.length * 2]; System.arraycopy(old, 0, methodVisits, 0, old.length); Registry.methodVisits = methodVisits; } { int[][] old = methodFirstLines; int[][] methodFirstLines = new int[old.length * 2][]; System.arraycopy(old, 0, methodFirstLines, 0, old.length); Registry.methodFirstLines = methodFirstLines; } { int[][][] old = methodLines; int[][][] methodLines = new int[old.length * 2][][]; System.arraycopy(old, 0, methodLines, 0, old.length); Registry.methodLines = methodLines; } { AtomicLongArray[] old = lineVisits; AtomicLongArray[] lineVisits = new AtomicLongArray[old.length * 2]; System.arraycopy(old, 0, lineVisits, 0, old.length); Registry.lineVisits = lineVisits; } { AtomicLongArray[] old = lineTimes; AtomicLongArray[] lineTimes = new AtomicLongArray[old.length * 2]; System.arraycopy(old, 0, lineTimes, 0, old.length); Registry.lineTimes = lineTimes; } { int[][] old = lines; int[][] lines = new int[old.length * 2][]; System.arraycopy(old, 0, lines, 0, old.length); Registry.lines = lines; } { BranchPoint[][] old = branchPoints; BranchPoint[][] branchPoints = new BranchPoint[old.length * 2][]; System.arraycopy(old, 0, branchPoints, 0, old.length); Registry.branchPoints = branchPoints; } classTouches = new AtomicIntegerArray(classNames.length); } } public static CoverageData getCoverageData() { synchronized (monitor) { final String[] classNames = new String[classCount]; System.arraycopy(Registry.classNames, 0, classNames, 0, classNames.length); final int[] classLoaders = new int[classCount]; System.arraycopy(Registry.classLoaders, 0, classLoaders, 0, classLoaders.length); final String[][] methodNames = new String[classCount][]; System.arraycopy(Registry.methodNames, 0, methodNames, 0, methodNames.length); final String[][] methodDescs = new String[classCount][]; System.arraycopy(Registry.methodDescs, 0, methodDescs, 0, methodDescs.length); final String[] sourceFiles = new String[classCount]; System.arraycopy(Registry.sourceFiles, 0, sourceFiles, 0, sourceFiles.length); final long[][] lineVisits = new long[classCount][]; final long[][] lineTimes = new long[classCount][]; for (int c = 0; c < classCount; c++) { AtomicLongArray registryVisits = Registry.lineVisits[c]; int[] lines = Registry.lines[c]; int maxLine = 0; for (int l = 0; l < lines.length; l++) { maxLine = Math.max(maxLine, lines[l]); } lineVisits[c] = new long[maxLine]; Arrays.fill(lineVisits[c], -1); lineTimes[c] = new long[maxLine]; Arrays.fill(lineTimes[c], -1); synchronized (Registry.lineVisits[c]) { for (int l = 0; l < registryVisits.length(); l++) { int lineNumber = lines[l]; lineVisits[c][lineNumber - 1] = registryVisits.get(l); } final AtomicLongArray registryTimes = Registry.lineTimes[c]; for (int l = 0; l < registryTimes.length(); l++) { int lineNumber = lines[l]; lineTimes[c][lineNumber - 1] = registryTimes.get(l); } int[][] methodLines = Registry.methodLines[c]; if(methodLines == null) { continue; } else { for(int methodIndex = 0; methodIndex < methodLines.length; methodIndex++) { long methodTimes = methodVisits[c].get(methodIndex); for(int l = 0; l < methodLines[methodIndex].length; l++) { int idx = methodLines[methodIndex][l]; if(idx >= lines.length) { break; } int lineNumber = lines[idx]; lineVisits[c][lineNumber-1] += methodTimes; } } } } } final BranchPoint[][] branchPoints = new BranchPoint[classCount][]; System.arraycopy(Registry.branchPoints, 0, branchPoints, 0, branchPoints.length); return new CoverageData() { public long[] getLinesVisited(int classId) { return lineVisits[classId]; } public long[] getLinesVisitTimes(int classId) { return lineTimes[classId]; } public String[] getClassNames() { return classNames; } @Override public int[] getClassLoaders() { return classLoaders; } @Override public ClassLoader getClassLoader(int i) { return Registry.getClassLoader(i); } public String[] getSourceFiles() { return sourceFiles; } public String[][] getMethodNames() { return methodNames; } public String[][] getMethodDescriptions() { return methodDescs; } public BranchPoint[] getBranchPoints(int classId) { return branchPoints[classId]; } public BranchPoint[] getBranchPointsForLine(int classId, int lineNumber) { List<BranchPoint> points = new ArrayList<BranchPoint>(); for (BranchPoint b : branchPoints[classId]) { if (b.getLinenumber() == lineNumber) { points.add(b); } } return points.toArray(new BranchPoint[points.size()]); } }; } } public static void registerMethods(int classId, List<String> methodNames, List<String> methodDescs, Map<Integer, List<Integer>> methodLineNumbers) { methodVisits[classId] = new UnsafeAtomicLongArray(methodNames.size()); Registry.methodNames[classId] = new String[methodNames.size()]; Registry.methodDescs[classId] = new String[methodNames.size()]; for (int i = 0; i < methodNames.size(); i++) { Registry.methodNames[classId][i] = methodNames.get(i); Registry.methodDescs[classId][i] = methodDescs.get(i); } int[] firstLines = new int[methodLineNumbers.size()]; methodFirstLines[classId] = firstLines; for(Integer methodIndex : methodLineNumbers.keySet()) { firstLines[methodIndex] = methodLineNumbers.get(methodIndex).get(0); } methodLines[classId] = new int[methodLineNumbers.size()][]; for (Integer methodindex : methodLineNumbers.keySet()) { List<Integer> lines = methodLineNumbers.get(methodindex); methodLines[classId][methodindex] = new int[lines.size()]; for(int i = 0; i < lines.size(); i++) { methodLines[classId][methodindex][i] = lines.get(i); } } } public static void registerBranchPoints(int classId, List<BranchPoint> branchPoints) { Registry.branchPoints[classId] = branchPoints.toArray(new BranchPoint[branchPoints.size()]); } public static void registerBranchPointVisits(int classId, int index, int numBefore, int numAfter) { final BranchPoint branchPoint = branchPoints[classId][index]; branchPoint.before(numBefore); branchPoint.after(numAfter); } public static void registerBranchPointVisitsArray(int classId, int[] beforeVisits, int[] afterVisits, int firstIndex) { for (int i = 0; i < beforeVisits.length; i++) { final BranchPoint branchPoint = branchPoints[classId][firstIndex + i]; branchPoint.before(beforeVisits[i]); branchPoint.after(afterVisits[i]); } } public static int newClassId(String className, ClassLoader classLoader) { synchronized (monitor) { ClassNameMap classNameMap = classNamesMap.get(System.identityHashCode(classLoader)); if (classNameMap != null && classNameMap.containsKey(className)) { return classNameMap.get(className); } else { return classCount; } } } public static void resetRegistry() { synchronized (monitor) { classCount = 0; classNames = new String[INITIAL_NUM_CLASSES]; classLoaders = new int[INITIAL_NUM_CLASSES]; classTouches = new AtomicIntegerArray(INITIAL_NUM_CLASSES); classNamesMap = new ConcurrentHashMap<Integer, ClassNameMap>(INITIAL_NUM_CLASSES); methodNames = new String[INITIAL_NUM_CLASSES][]; methodDescs = new String[INITIAL_NUM_CLASSES][]; sourceFiles = new String[INITIAL_NUM_CLASSES]; lines = new int[INITIAL_NUM_CLASSES][]; lineVisits = new AtomicLongArray[INITIAL_NUM_CLASSES]; methodLines = new int[INITIAL_NUM_CLASSES][][]; methodVisits = new UnsafeAtomicLongArray[INITIAL_NUM_CLASSES]; methodFirstLines = new int[INITIAL_NUM_CLASSES][]; lineTimes = new AtomicLongArray[INITIAL_NUM_CLASSES]; branchPoints = new BranchPoint[INITIAL_NUM_CLASSES][]; getThreadLocalBuffer().reset(); } } public static void resetVisits() { synchronized (monitor) { for (int i = 0; i < classCount; i++) { { AtomicLongArray lvs = lineVisits[i]; for (int l = 0; l < lvs.length(); l++) { if (lvs.get(l) >= 0) { lvs.set(l, 0); } } } { UnsafeAtomicLongArray lvs = methodVisits[i]; for (int l = 0; l < lvs.length(); l++) { if (lvs.get(l) >= 0) { lvs.set(l, 0); } } } BranchPoint[] bps = branchPoints[i]; for (int b = 0; bps != null && b < bps.length; b++) { bps[b].reset(); } } } } public static FrameMap registerMethodEnter(long methodId) { FrameMap frameMap = threadLocalMap.get(); Frame myFrame = frameMap.getFrameForMethod(methodId); frameMap.setTopFrame(myFrame); return frameMap; } private static FrameMap getFrameMapForThread() { return threadLocalMap.get(); } public static void registerMethodExit(FrameMap frameMap, long exitTime, long startTime) { Frame topFrame = frameMap.getTopFrame(); Frame parentFrame = topFrame.getParentFrame(); frameMap.setTopFrame(parentFrame); topFrame.visit(exitTime - startTime); } public static void registerMethodExit(FrameMap frameMap) { Frame topFrame = frameMap.getTopFrame(); Frame parentFrame = topFrame.getParentFrame(); frameMap.setTopFrame(parentFrame); topFrame.visit(); } private static class Save implements Serializable { final int classCount; final String[] classNames; final int[] classLoaders; final String[][] methodNames; final String[][] methodDescs; final String[] sourceFiles; final int[][] lines; final AtomicLongArray[] lineVisits; final BranchPoint[][] branchPoints; public Save(int classCount, String[] classNames, int[] classLoaders, String[][] methodNames, String[][] methodDescs, String[] sourceFiles, int[][] lines, AtomicLongArray[] lineVisits, BranchPoint[][] branchPoints) { this.classCount = classCount; this.classNames = classNames; this.classLoaders = classLoaders; this.methodNames = methodNames; this.methodDescs = methodDescs; this.sourceFiles = sourceFiles; this.lines = lines; this.lineVisits = lineVisits; this.branchPoints = branchPoints; } } public static void dumpFrames() { Collection<Frame> frames = getFrames(); synchronized (frames) { for (Frame frame : frames) { dumpFrame(frame, 0); } } } private static void dumpFrame(Frame frame, int level) { for (int i = 0; i < level; i++) { System.out.print(" "); } int classId = getClassId(frame.methodId); int methodIdx = getMethodIndex(frame.methodId); String className = classNames[classId]; String methodName = methodNames[classId][methodIdx]; FrameData data = frame.getData(); long selfTime = data.getTime(); for (Frame child : frame.getChildren()) { selfTime -= child.getData().getTime(); } System.out.println(className + "." + methodName + " Total time: " + (TimeUnit.NANOSECONDS.toMillis(data.getTime())) + ", visits: " + data.getVisits() + ", self time: " + TimeUnit.NANOSECONDS.toMillis(selfTime)); for (Frame child : frame.getChildren()) { dumpFrame(child, ++level); } } public static int getClassId(long methodId) { return (int) (methodId >> 32); } public static int getMethodIndex(long methodId) { return (int) (methodId & 0xFFFFFFFFl); } public static class Frame extends ConcurrentHashMap<Long, Frame> { private final long methodId; private final Frame parentFrame; private long time; private int visits; private Frame(long methodId, Frame parentFrame) { this.methodId = methodId; this.parentFrame = parentFrame; } private Frame(long methodId) { this(methodId, null); } public void addChild(Frame frame) { put(frame.getMethodId(), frame); } public Frame getChild(long methodId) { return get(methodId); } public Frame getParentFrame() { return parentFrame; } public long getMethodId() { return methodId; } public Collection<Frame> getChildren() { return new ArrayList<Frame>(values()); } public synchronized void visit() { this.visits++; } public synchronized void visit(long time) { this.time += time; this.visits++; } public synchronized void visits(long visits, long time) { this.time += time; this.visits += visits; } public synchronized FrameData getData() { return new FrameData(time, visits); } public void mergeIntoParent(Frame parent) { Frame myFrame = parent.getChild(getMethodId()); if (myFrame == null) { parent.addChild(myFrame = new Frame(getMethodId())); } FrameData data = getData(); long time = data.getTime(); int visits = data.getVisits(); myFrame.visits(visits, time); for (Frame child : getChildren()) { child.mergeIntoParent(myFrame); } } } public static class FrameData { private final long time; private final int visits; public FrameData(long time, int visits) { this.time = time; this.visits = visits; } public long getTime() { return time; } public int getVisits() { return visits; } } static class ThreadMap extends ConcurrentHashMap<Long, FrameMap> { } public static class FrameMap extends ConcurrentHashMap<Long, Frame> { private Frame topFrame; public Frame getTopFrame() { return topFrame; } public void setTopFrame(Frame topFrame) { this.topFrame = topFrame; } public Frame getFrameForMethod(long methodId) { Frame parentFrame = getTopFrame(); Frame myFrame; if (parentFrame != null) { myFrame = parentFrame.get(methodId); if (myFrame == null) { myFrame = new Frame(methodId, parentFrame); Frame put = parentFrame.putIfAbsent(methodId, myFrame); if(put != null) { return put; } return myFrame; } else { return myFrame; } } else { myFrame = get(methodId); if (myFrame == null) { myFrame = new Frame(methodId); Frame put = putIfAbsent(methodId, myFrame); if(put != null) { return put; } else { return myFrame; } } else { return myFrame; } } } } }