/* * Copyright (c) 2010-2012 Grid Dynamics Consulting Services, Inc, All Rights Reserved * http://www.griddynamics.com * * This library is free software; you can redistribute it and/or modify it under the terms of * the Apache License; either * version 2.0 of the License, or any later version. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.griddynamics.jagger.diagnostics.thread.sampling; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.MinMaxPriorityQueue; import com.google.common.primitives.Longs; import edu.uci.ics.jung.graph.DirectedGraph; import edu.uci.ics.jung.graph.DirectedSparseGraph; import edu.uci.ics.jung.graph.Graph; import java.io.Serializable; import java.util.Collection; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.regex.Pattern; public class RuntimeGraph implements Serializable { private static final Method pseudoRoot = new Method("#", "#"); private long observations = 0; private DirectedGraph<MethodStatistics, Invocation> graph = new DirectedSparseGraph<MethodStatistics, Invocation>(); private Map<Method, MethodStatistics> methodIndex = Maps.newHashMap(); private List<Pattern> includePatterns; private List<Pattern> excludePatterns; private static Comparator<MethodStatistics> selfTimeMethodSelector = new Comparator<MethodStatistics>() { public int compare(MethodStatistics s1, MethodStatistics s2) { return -Longs.compare(s1.getOnTopObservations(), s2.getOnTopObservations()); } }; private static Comparator<MethodStatistics> frequencyMethodSelector = new Comparator<MethodStatistics>() { public int compare(MethodStatistics s1, MethodStatistics s2) { return -Longs.compare(s1.getObservations(), s2.getObservations()); } }; public synchronized void clean() { this.methodIndex.clear(); locateMethodStatistics(pseudoRoot); } public RuntimeGraph() { clean(); } public void registerSnapshot(List<Method> callTree) { if (callTree == null || callTree.size() == 0) { return; } Method caller = pseudoRoot; MethodStatistics method = null; for (Method methodId : callTree) { if (isAppropriate(methodId)) { method = locateMethodStatistics(methodId); method.registerObservation(); Invocation invocation = locateInvocation(caller, methodId); invocation.registerObservation(); caller = methodId; } } if (method != null) { method.registerOnTopObservation(); } observations++; } private boolean isAppropriate(Method method) { boolean excluded = false; boolean included = false; if (excludePatterns != null) { for (Pattern pattern : excludePatterns) { if (method.matches(pattern)) { excluded = true; break; } } } if (includePatterns != null) { for (Pattern pattern : includePatterns) { if (method.matches(pattern)) { included = true; break; } } } if (includePatterns == null) { return !excluded; } else { return included || !excluded; } } public List<MethodProfile> getSelfTimeHotSpots(int maxSpots) { return getHotSpots(maxSpots, selfTimeMethodSelector); } public List<MethodProfile> getFrequentHotSpots(int maxSpots) { return getHotSpots(maxSpots, frequencyMethodSelector); } public Graph<MethodProfile, InvocationProfile> getNeighborhood(Method root, int maxCallDepth, int maxCallers) { DirectedGraph<MethodProfile, InvocationProfile> neighborhood = new DirectedSparseGraph<MethodProfile, InvocationProfile>(); MethodStatistics rootStatistics = methodIndex.get(root); if (rootStatistics != null) { collectCallers(rootStatistics, maxCallDepth, maxCallers, neighborhood); } return neighborhood; } private void collectCallers(MethodStatistics root, int maxCallDepth, int maxCallers, Graph<MethodProfile, InvocationProfile> neighborhood) { if (maxCallDepth > 0) { Collection<MethodStatistics> callers = selectMethodStatistics(graph.getPredecessors(root), maxCallers, frequencyMethodSelector); MethodProfile rootProfile = assembleProfile(root); for (MethodStatistics caller : callers) { if (!caller.getMethod().equals(pseudoRoot)) { MethodProfile callerProfile = assembleProfile(caller); neighborhood.addEdge(assembleProfile(graph.findEdge(caller, root)), callerProfile, rootProfile); } } for (MethodStatistics caller : callers) { collectCallers(caller, maxCallDepth - 1, maxCallers, neighborhood); } } } private List<MethodProfile> getHotSpots(int maxSpots, Comparator<MethodStatistics> comparator) { List<MethodProfile> result = Lists.newArrayList(); MinMaxPriorityQueue<MethodStatistics> hotSpots = MinMaxPriorityQueue .orderedBy(comparator) .maximumSize(maxSpots) .create(graph.getVertices()); int queueSize = hotSpots.size(); for (int i = 0; i < queueSize; i++) { result.add(assembleProfile(hotSpots.removeFirst())); } return result; } private List<MethodStatistics> selectMethodStatistics(Collection<MethodStatistics> statistics, int maxStatistics, Comparator<MethodStatistics> comparator) { List<MethodStatistics> result = Lists.newArrayList(); MinMaxPriorityQueue<MethodStatistics> selected = MinMaxPriorityQueue .orderedBy(comparator) .maximumSize(maxStatistics) .create(statistics); for (MethodStatistics method : selected) { result.add(method); } return result; } private MethodProfile assembleProfile(MethodStatistics method) { MethodProfile profile = new MethodProfile(method.getMethod()); profile.setObservations(method.getObservations()); profile.setInStackRatio(method.getObservations() / ((double) observations)); profile.setOnTopRatio(method.getOnTopObservations() / ((double) observations)); return profile; } private InvocationProfile assembleProfile(Invocation invocation) { InvocationProfile profile = new InvocationProfile(); profile.setObservations(invocation.getObservations()); return profile; } private MethodStatistics locateMethodStatistics(Method method) { MethodStatistics methodStatistics = methodIndex.get(method); if (methodStatistics == null) { methodStatistics = new MethodStatistics(method); methodIndex.put(method, methodStatistics); graph.addVertex(methodStatistics); } return methodStatistics; } private Invocation locateInvocation(Method caller, Method callee) { MethodStatistics callerMethod = locateMethodStatistics(caller); MethodStatistics calleeMethod = locateMethodStatistics(callee); Invocation invocation = graph.findEdge(callerMethod, calleeMethod); if (invocation == null) { invocation = new Invocation(); graph.addEdge(invocation, callerMethod, calleeMethod); } return invocation; } public List<Pattern> getIncludePatterns() { return includePatterns; } public void setIncludePatterns(List<Pattern> includePatterns) { this.includePatterns = includePatterns; } public List<Pattern> getExcludePatterns() { return excludePatterns; } public void setExcludePatterns(List<Pattern> excludePatterns) { this.excludePatterns = excludePatterns; } public long getObservations() { return observations; } public int getMethodCount() { return graph.getVertexCount(); } @Override public String toString() { return "RuntimeGraph{" + "observations=" + observations + ", graph=" + graph + ", methodIndex=" + methodIndex + ", includePatterns=" + includePatterns + ", excludePatterns=" + excludePatterns + '}'; } }