package com.insightfullogic.honest_profiler.core.profiles.lean; import static java.util.stream.Stream.concat; import static java.util.stream.Stream.of; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Stream; import com.insightfullogic.honest_profiler.core.parser.StackFrame; import com.insightfullogic.honest_profiler.core.parser.TraceStart; import com.insightfullogic.honest_profiler.core.profiles.lean.info.FrameInfo; import com.insightfullogic.honest_profiler.core.profiles.lean.info.MethodInfo; import com.insightfullogic.honest_profiler.core.profiles.lean.info.NumericInfo; /** * LeanNode is a tree node recording the aggregation of the numerical data for a thread or a frame inside a * {@link LeanProfile}. * <p> * Its children represent frames that were directly called from the represented frame or thread at some point. * <p> * It also has a unique id, needed in some case to prevent duplicate aggregation. */ public class LeanNode { // Class Properties private static final AtomicInteger ID_GENERATOR = new AtomicInteger(0); private final int id; private final FrameInfo frame; private final NumericInfo data; private LeanNode parent; private final Map<FrameInfo, LeanNode> childMap; /** * "Non-self constructor" which sets the {@link FrameInfo} and the parent LeanNode, used for constructing a new * frame LeanNode which is has no self time (i.e. the stack trace sample still has more descendant frames), or a * {@link LeanThreadNode}. * <p> * @param frame the {@link FrameInfo} for this LeanNode * @param parent the parent LeanNode */ protected LeanNode(FrameInfo frame, LeanNode parent) { id = ID_GENERATOR.getAndIncrement(); this.frame = frame; // The use of the NumericInfo constructor sets all values, such as sample count, to 0. This is OK because this // LeanNode constructor is only called for frames (or threads) which are known to have at least one descendant // in the stack trace sample being processed. When the child is processed, the add() method will update the // values of this LeanNode. data = new NumericInfo(); this.parent = parent; childMap = new HashMap<>(); } /** * Copy constructor. * <p> * @param source the source LeanNode which is being copied * @param newParent the parent of the copy (which itself generally is a copy) */ protected LeanNode(LeanNode source, LeanNode newParent) { this.id = source.id; this.frame = source.frame; this.data = source.data.copy(); this.parent = newParent; this.childMap = new HashMap<>(); // The FrameInfo key is an immutable object, no need to copy it. source.childMap.forEach((key, value) -> this.childMap.put(key, new LeanNode(value, this))); } // Instance Accessors /** * Returns the unique id of this LeanNode. * <p> * @return the unique id of this LeanNode */ public int getId() { return id; } /** * Returns the {@link FrameInfo} associated with this LeanNode, or null if this LeanNode represents a thread and is * in fact a {@link LeanThreadNode}. * <p> * @return the {@link FrameInfo} associated with this LeanNode, or null if this LeanNode represents a thread */ public FrameInfo getFrame() { return frame; } /** * Returns the {@link NumericInfo} for this LeanNode * <p> * @return the {@link NumericInfo} for this LeanNode */ public NumericInfo getData() { return data; } /** * Returns the parent of this LeanNode * <p> * @return the parent of this LeanNode */ public LeanNode getParent() { return parent; } /** * Returns a {@link Collection} containing the children of this LeanNode. * <p> * @return a {@link Collection} containing the children of this LeanNode */ public Collection<LeanNode> getChildren() { return childMap.values(); } /** * Returns a boolean indicating whether this node represents a thread and is a {@link LeanThreadNode}. * <p> * @return a boolean indicating whether this node represents a thread and is a {@link LeanThreadNode} */ public boolean isThreadNode() { return false; } // Aggregation Methods /** * Aggregates the information from a child {@link StackFrame} into the children of this LeanNode, updating the total * data for this LeanNode in the process. * <p> * The add method is called from the LogCollector, on the parent with the next (potentially last) child from the * trace. * <p> * @param nanos the number of nanoseconds between the {@link TraceStart} preceding the stack trace sample and the * {@link TraceStart} following it * @param child the child {@link FrameInfo} of the current LeanNode * @param last a boolean indicating if the child is the last in the stack trace sample * @return the aggregated child {@link LeanNode} */ public LeanNode add(long nanos, FrameInfo child, boolean last) { // Non-self add, which updates total time and sample count only. data.add(nanos, false); LeanNode childNode = childMap.computeIfAbsent(child, k -> new LeanNode(k, this)); if (last) { childNode.addSelf(nanos); } return childNode; } /** * Aggregate the self and total data in the {@link NumericInfo} for this LeanNode. * <p> * @param nanos the self time for the frame * @return this object */ private LeanNode addSelf(long nanos) { data.add(nanos, true); return this; } // Tree-related Methods /** * Returns a {@link Stream} containing this LeanNode and all its descendants. * <p> * @return a {@link Stream} containing this LeanNode and all its descendants */ public Stream<LeanNode> flatten() { return concat(of(this), childMap.values().stream().flatMap(LeanNode::flatten)); } // Debug Methods /** * Returns a String representation of the frame represented by this node, including descendant information, indented * according to the specified indentation level. * <p> * @param level the level of the indentation * @param methodMap the {@link Map} with the {@link MethodInfo} objects used to print the method info in the frame * @return a String containing the frame information of this LeanNode and its descendants. */ public String toDeepString(int level, Map<Long, MethodInfo> methodMap) { StringBuilder result = new StringBuilder(); for (int i = 0; i < level; i++) { result.append(" "); } result.append(toString()).append(" (") .append(frame == null ? "--" : methodMap.get(frame.getMethodId()).getFqmn()) .append(")\n"); childMap.values().forEach(child -> result.append(child.toDeepString(level + 1, methodMap))); return result.toString(); } // Object Implementation @Override public boolean equals(Object other) { return other instanceof LeanNode && ((LeanNode)other).id == id; } @Override public int hashCode() { return id; } @Override public String toString() { return "LN [" + frame + ":" + data + "]"; } }