Skip to content

Commit

Permalink
feature: wip butterfly view
Browse files Browse the repository at this point in the history
  • Loading branch information
bric3 committed Apr 7, 2023
1 parent db92157 commit 0404aea
Show file tree
Hide file tree
Showing 13 changed files with 1,125 additions and 31 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package io.github.bric3.fireplace.jfr.tree;

import org.openjdk.jmc.flightrecorder.stacktrace.tree.AggregatableFrame;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

/**
* Unmodified copy of {@link org.openjdk.jmc.flightrecorder.stacktrace.tree.Node} as some
* of the methods are not accessible from a different package.
*/
public final class Node {
/**
* Integer uniquely identifying this node within our data structure.
*/
private final Integer nodeId;

/**
* The frame associated with this node.
*/
private final AggregatableFrame frame;

/**
* The weight when being the top frame.
*/
double weight;

/**
* The parent node; null when root.
*/
Node parent;

/**
* The child nodes; empty when leaf.
*/
final List<Node> children = new ArrayList<>();

/**
* The cumulative weight for all contributions.
*/
double cumulativeWeight;

public static Node newRootNode(AggregatableFrame rootFrame) {
return new Node(null, rootFrame);
}

public Node(Node parent, AggregatableFrame frame) {
this.nodeId = computeNodeId(parent, frame);
this.parent = parent;
this.frame = frame;
if (frame == null) {
throw new NullPointerException("Frame cannot be null!");
}
}

private static Integer computeNodeId(Node parent, AggregatableFrame frame) {
return Objects.hash(parent != null ? parent.getNodeId() : null, frame.hashCode());
}

/**
* @return the unique identifier associated with this node.
*/
public Integer getNodeId() {
return nodeId;
}

/**
* @return the weight of this node.
*/
public double getWeight() {
return weight;
}

/**
* @return the cumulative weight of this node.
*/
public double getCumulativeWeight() {
return cumulativeWeight;
}

/**
* @return the frame corresponding to this node.
*/
public AggregatableFrame getFrame() {
return frame;
}

/**
* @return the list of child nodes, in order of appearance.
*/
public List<Node> getChildren() {
return Collections.unmodifiableList(children);
}

/**
* @return the parent node or null when root.
*/
public Node getParent() {
return parent;
}

public boolean isRoot() {
return parent == null;
}

public boolean isLeaf() {
return children.isEmpty();
}

@Override
public int hashCode() {
// This will get a few extra collisions.
return frame.getMethod().hashCode();
}

@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Node other = (Node) obj;

return Objects.equals(nodeId, other.nodeId) && Objects.equals(frame, other.frame) && weight == other.weight
&& cumulativeWeight == other.cumulativeWeight;
}

@Override
public String toString() {
return String.format("%s %.2f (%.2f)", frame.getHumanReadableShortString(), weight, cumulativeWeight);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
/*
* Fireplace
*
* Copyright (c) 2021, Today - Brice Dutheil
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package io.github.bric3.fireplace.jfr.tree;

import org.openjdk.jmc.common.item.IAttribute;
import org.openjdk.jmc.common.item.IItemCollection;
import org.openjdk.jmc.common.unit.IQuantity;
import org.openjdk.jmc.flightrecorder.stacktrace.tree.AggregatableFrame;
import org.openjdk.jmc.flightrecorder.stacktrace.tree.StacktraceTreeModel;

import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Predicate;

public class StacktraceButterflyModel {
private final Node predecessorsRoot;
private final Node successorsRoot;
private final IItemCollection items;
private final IAttribute<IQuantity> attribute;

public StacktraceButterflyModel(StacktraceTreeModel stacktraceTreeModel, Predicate<AggregatableFrame> nodeSelector) {
Objects.requireNonNull(stacktraceTreeModel, "StacktraceTreeModel must not be null");
Objects.requireNonNull(nodeSelector, "Node selector must not be null");

items = stacktraceTreeModel.getItems();
attribute = stacktraceTreeModel.getAttribute();

predecessorsRoot = computePredecessorsTree(stacktraceTreeModel.getRoot(), nodeSelector);
successorsRoot = computeSuccessorsTree(stacktraceTreeModel.getRoot(), nodeSelector);

assert predecessorsRoot.getFrame().equals(successorsRoot.getFrame()) : "Predecessors and successors root should be the same frame";
}

/**
* Compute the predecessor frames, aka <em>callers</em> tree.
*
* @param flamegraphRoot The flamegraphRoot Node
* @param nodeSelector The node selector
* @return a tree representing the predecessors of the frame
*/
private Node computePredecessorsTree(
org.openjdk.jmc.flightrecorder.stacktrace.tree.Node flamegraphRoot,
Predicate<AggregatableFrame> nodeSelector
) {
AtomicReference<Node> predecessorsRoot = new AtomicReference<>();
findPredecessors(predecessorsRoot, flamegraphRoot, nodeSelector);

return predecessorsRoot.get();
}

private void findPredecessors(
AtomicReference<Node> predecessorsRootRef,
org.openjdk.jmc.flightrecorder.stacktrace.tree.Node node,
Predicate<AggregatableFrame> nodeSelector
) {
// if there's a match add children nodes
if (nodeSelector.test(node.getFrame())) {
predecessorsRootRef.compareAndSet(null, Node.newRootNode(node.getFrame()));

Node predecessorsRootNode = predecessorsRootRef.get();
predecessorsRootNode.weight += node.getWeight();
predecessorsRootNode.cumulativeWeight += node.getCumulativeWeight();

// Adds and merge predecessors in the callers tree
Node predecessorNode = predecessorsRootNode;
for (
org.openjdk.jmc.flightrecorder.stacktrace.tree.Node currentNode = node.getParent();
currentNode != null && currentNode.getParent() != null;
currentNode = currentNode.getParent()
) {
Node child = getOrCreateNode(predecessorNode, currentNode.getFrame());
child.weight += currentNode.getWeight();
child.cumulativeWeight += currentNode.getCumulativeWeight();
predecessorNode = child;
}
}

// regardless walk the current tree for matching nodes in children
for (org.openjdk.jmc.flightrecorder.stacktrace.tree.Node child : node.getChildren()) {
findPredecessors(predecessorsRootRef, child, nodeSelector);
}
}
private Node getOrCreateNode(Node parent, AggregatableFrame frame) {
return parent.children.stream()
// TODO: consider a map lookup instead of linear search
.filter(child -> child.getFrame().equals(frame)).findAny().orElseGet(() -> {
Node result = new Node(parent, frame);
parent.children.add(result);
return result;
});
}


/**
* Compute the successor frames, aka <em>callees</em> tree.
*
* @param flamegraphRoot The flamegraphRoot Node
* @param nodeSelector The node selector
* @return a tree representing the successors of the frame
*/
private Node computeSuccessorsTree(
org.openjdk.jmc.flightrecorder.stacktrace.tree.Node flamegraphRoot,
Predicate<AggregatableFrame> nodeSelector
) {
AtomicReference<Node> successorsRootRef = new AtomicReference<>();
findSuccessors(successorsRootRef, flamegraphRoot, nodeSelector);
mergeChildren(successorsRootRef.get());
return successorsRootRef.get();
}

private void findSuccessors(
AtomicReference<Node> successorsRootRef,
org.openjdk.jmc.flightrecorder.stacktrace.tree.Node node,
Predicate<AggregatableFrame> nodeSelector
) {
// if there's a match add children nodes
if (nodeSelector.test(node.getFrame())) {
successorsRootRef.compareAndSet(null, Node.newRootNode(node.getFrame()));

Node successorsRootNode = successorsRootRef.get();
successorsRootNode.weight += node.getWeight();
successorsRootNode.cumulativeWeight += node.getCumulativeWeight();
convertAndAddChildren(successorsRootNode, node);
}
// regardless look for matching nodes in children
for (org.openjdk.jmc.flightrecorder.stacktrace.tree.Node child : node.getChildren()) {
findSuccessors(successorsRootRef, child, nodeSelector);
}
}

/**
* Merge children with the same {@link AggregatableFrame}.
*
* @param node The node to process
*/
private void mergeChildren(Node node) {
Map<AggregatableFrame, Node> childrenMap = new HashMap<>();
for (Node child : node.getChildren()) {
childrenMap.merge(
child.getFrame(),
child,
(accumulatorNode, nodeToMerge) -> {
accumulatorNode.cumulativeWeight += child.cumulativeWeight;
accumulatorNode.weight += child.weight;
accumulatorNode.children.addAll(nodeToMerge.getChildren());
return accumulatorNode;
}
);
}
for (Node child : childrenMap.values()) {
mergeChildren(child);
}
node.children.clear();
node.children.addAll(childrenMap.values());
}

private static void convertAndAddChildren(Node successorsRootNode, org.openjdk.jmc.flightrecorder.stacktrace.tree.Node node) {
List<Node> list = new ArrayList<>();
for (org.openjdk.jmc.flightrecorder.stacktrace.tree.Node child : node.getChildren()) {
Node jmcTreeNode = toJmcTreeNode(successorsRootNode, child);
list.add(jmcTreeNode);
}
successorsRootNode.children.addAll(list);
}

private static Node toJmcTreeNode(Node convertedParent, org.openjdk.jmc.flightrecorder.stacktrace.tree.Node node) {
Node converted = new Node(
convertedParent,
node.getFrame()
);
converted.cumulativeWeight = node.getCumulativeWeight();
converted.weight = node.getCumulativeWeight();

convertAndAddChildren(converted, node);
return converted;
}

public Node getSuccessorsRoot() {
return successorsRoot;
}

public Node getPredecessorsRoot() {
return predecessorsRoot;
}

public AggregatableFrame getFocusedMethod() {
return successorsRoot.getFrame();
}

public IItemCollection getItems() {
return items;
}

public IAttribute<IQuantity> getAttribute() {
return attribute;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ package io.github.bric3.fireplace.jfr
import io.github.bric3.fireplace.jfr.support.JFRLoaderBinder
import io.github.bric3.fireplace.jfr.views.appDebug.AppSystemProperties
import io.github.bric3.fireplace.jfr.views.appDebug.AppUIManagerProperties
import io.github.bric3.fireplace.jfr.views.butterfly.ExperimentalButterfly
import io.github.bric3.fireplace.jfr.views.cpu.MethodCpuSample
import io.github.bric3.fireplace.jfr.views.events.EventBrowser
import io.github.bric3.fireplace.jfr.views.general.NativeLibraries
Expand All @@ -28,11 +29,13 @@ import javax.swing.tree.TreeSelectionModel

internal class ProfileContentPanel(private val jfrBinder: JFRLoaderBinder) : JPanel(BorderLayout()) {
private val views = buildList {
// TODO discover views automatically
add(MethodCpuSample(jfrBinder))
add(Allocations(jfrBinder))
add(SystemProperties(jfrBinder))
add(NativeLibraries(jfrBinder))
add(EventBrowser(jfrBinder))
add(ExperimentalButterfly(jfrBinder))

if (AppSystemProperties.isActive()) {
add(AppSystemProperties())
Expand Down
Loading

0 comments on commit 0404aea

Please sign in to comment.