package com.insightfullogic.honest_profiler.core.profiles.lean; import java.util.HashMap; import java.util.Map; import com.insightfullogic.honest_profiler.core.profiles.lean.info.MethodInfo; import com.insightfullogic.honest_profiler.core.profiles.lean.info.ThreadInfo; /** * A {@link LeanProfile} is a profile which which collects stack trace samples, and stores the information with as * little redundancy as possible. The granularity is at stack frame level, keyed by class name, method name, BCI and * line number. * <p> * The profile is modeled as a collection of trees consisting of {@link LeanNode}s. * <p> * The root of each tree is a {@link LeanThreadNode} representing the thread-level information. The children of that * {@link LeanThreadNode} represent the methods called directly at the top-level of the thread (typically * {@link Thread#run()}, but there can be others too). * <p> * For the descendants, the following holds : whenever a stack trace sample has been added to the profile in which a * method A is called by a method B, the profile will contain a {@link LeanNode} representing B with as child a * {@link LeanNode} representing A. * <p> * Other than the {@link LeanNode} trees, the profile also contains the information for mapping thread and method ids to * the corresponding {@link ThreadInfo} and {@link MethodInfo} metadata. * <p> * Any aggregation other than counts and time addition at the lowest level is relegated to post-processing. */ public class LeanProfile { // Instance Properties private final Map<Long, MethodInfo> methodInfoMap; private final Map<Long, ThreadInfo> threadInfoMap; private final Map<Long, LeanThreadNode> threads; // Instance constructors /** * Constructor which specifies the maps containing the method id - {@link MethodInfo} and thread id - * {@link ThreadInfo} mappings, and a map containing the thread id - {@link LeanThreadNode} stack tree mappings. * <p> * @param methodMap a {@link Map} mapping the method id to the corresponding {@link MethodInfo} * @param threadMap a {@link Map} mapping the thread id to the corresponding {@link ThreadInfo} * @param threadData a {@link Map} mapping the thread id to the {@link LeanThreadNode} root of the {@link LeanNode} * tree containing the aggregated stack trace sample information for that thread */ public LeanProfile(Map<Long, MethodInfo> methodMap, Map<Long, ThreadInfo> threadMap, Map<Long, LeanThreadNode> threadData) { methodInfoMap = new HashMap<>(methodMap); threadInfoMap = new HashMap<>(threadMap); threads = new HashMap<>(); threadData.forEach((key, value) -> threads.put(key, value.copy())); } // Instance Accessors /** * Returns the mapping between method ids and their corresponding {@link MethodInfo} objects. * <p> * @return the mapping between method ids and their corresponding {@link MethodInfo} objects */ public Map<Long, MethodInfo> getMethodInfoMap() { return methodInfoMap; } /** * Returns the mapping between thread ids and their corresponding {@link ThreadInfo} objects. * <p> * @return the mapping between thread ids and their corresponding {@link ThreadInfo} objects */ public Map<Long, ThreadInfo> getThreadInfoMap() { return threadInfoMap; } /** * Returns the mapping between thread ids and their corresponding {@link ThreadInfo} objects. * <p> * @param id the thread id * @return the mapping between thread ids and their corresponding {@link ThreadInfo} objects */ public ThreadInfo getThreadInfo(long id) { return threadInfoMap.get(id); } /** * Returns the mapping between thread Ids and the root {@link LeanThreadNode} objects. * <p> * @return the mapping between thread Ids and the root {@link LeanThreadNode} objects */ public Map<Long, LeanThreadNode> getThreads() { return threads; } // Key and/or name Construction Methods /** * Calculates the FQMN. Introduced for hardening, based on a profile which contained a Method Id for which no * MethodInfo was available. * <p> * * @return the FQMN for the node */ public String getFqmn(LeanNode node) { long methodId = node.getFrame().getMethodId(); MethodInfo info = getMethodInfoMap().get(methodId); // Explicit NULL check because we encountered a profile where a particular MethodInfo wasn't present return info == null ? "<UNIDENTIFIED : Method Id = " + methodId + ">" : info.getFqmn(); } /** * Return the key for a {@link LeanNode} representing a frame, constructed by appending the line number to the FQMN, * separated by a colon. * <p> * @param node the node for which the key is calculated * @return the aggregation key for the node consisting of the FQMN and the line number */ public String getFqmnPlusLineNr(LeanNode node) { StringBuilder result = new StringBuilder(); result.append(getFqmn(node)).append(":").append(node.getFrame().getLineNr()); return result.toString(); } /** * Return the key for a {@link LeanNode} representing a frame, constructed by appending the BCI to the FQMN, * separated by a colon. * <p> * @param node the node for which the key is calculated * @return the aggregation key for the node consisting of the FQMN and the BCI */ public String getBciKey(LeanNode node) { StringBuilder result = new StringBuilder(); result.append(getFqmn(node)).append(":").append(node.getFrame().getBci()); return result.toString(); } /** * Return the key for a {@link LeanNode} representing a frame, constructed by prepending the method Id to the FQMN. * <p> * @param node the node for which the key is calculated * @return the aggregation key for the node consisting of the method Id and the FQMN */ public String getMethodIdKey(LeanNode node) { StringBuilder result = new StringBuilder(); result.append("(").append(Long.toString(node.getFrame().getMethodId())).append(") "); result.append(getFqmn(node)); return result.toString(); } /** * Returns the display name for the thread identified by the specified id. If there is no {@link ThreadInfo} for the * thread id, a name is constructed. * <p> * @param threadId the id of the thread * @return the calculated display name */ public String getThreadName(Long threadId) { ThreadInfo info = getThreadInfo(threadId); return info == null ? "Unknown <" + threadId + ">" : info.getIdentification(); } // Object Implementation @Override public String toString() { StringBuilder result = new StringBuilder(); result.append("LP :\n"); threads.forEach( (id, node) -> result.append(" Thread ") .append(threadInfoMap.get(id) == null ? "UNKNOWN" : threadInfoMap.get(id).getName()) .append(node.toDeepString(1, methodInfoMap))); return result.toString(); } }