Skip to content

Commit

Permalink
chore: Updates test code for successors
Browse files Browse the repository at this point in the history
  • Loading branch information
bric3 committed Apr 12, 2023
1 parent 1a5f2e0 commit 08266e3
Show file tree
Hide file tree
Showing 4 changed files with 229 additions and 133 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,62 +9,105 @@
*/
package io.github.bric3.fireplace.jfr.tree;

import org.openjdk.jmc.common.IMCFrame;
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.common.util.MCFrame;
import org.openjdk.jmc.flightrecorder.stacktrace.FrameSeparator;
import org.openjdk.jmc.flightrecorder.stacktrace.tree.AggregatableFrame;
import org.openjdk.jmc.flightrecorder.stacktrace.tree.StacktraceTreeModel;

import java.lang.reflect.Field;
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;
private final FrameSeparator frameSeparator;

public StacktraceButterflyModel(StacktraceTreeModel stacktraceTreeModel, Predicate<AggregatableFrame> nodeSelector) {
/**
* A special marker object that indicates a hand-crafted frame at the root of the tree.
* <p>
* We need to create this frame as a parent to all branches of the tree we want to represent.
*/
private static final IMCFrame ROOT_FRAME = new MCFrame(null, null, null, IMCFrame.Type.UNKNOWN);

private StacktraceButterflyModel(Node predecessorsRoot, Node successorsRoot, IItemCollection items, IAttribute<IQuantity> attribute, FrameSeparator frameSeparator) {
this.predecessorsRoot = predecessorsRoot;
this.successorsRoot = successorsRoot;
this.items = items;
this.attribute = attribute;
this.frameSeparator = frameSeparator;
}

/**
* Create a butterfly model, that is focused on a specific frame.
* This model allows to model predecessors and successors of the frame as merged trees.
*
* @param stacktraceTreeModel The {@link StacktraceTreeModel}
* @param frameSelector The frame selector, this selector should only select a single frame otherwise the result is undefined.
* @return A stacktrace Butterfly model.
*/
public static StacktraceButterflyModel from(
StacktraceTreeModel stacktraceTreeModel,
Predicate<AggregatableFrame> frameSelector
) {
Objects.requireNonNull(stacktraceTreeModel, "StacktraceTreeModel must not be null");
Objects.requireNonNull(nodeSelector, "Node selector must not be null");
Objects.requireNonNull(frameSelector, "Frame selector must not be null");

items = stacktraceTreeModel.getItems();
attribute = stacktraceTreeModel.getAttribute();
// TODO modify StacktraceTreeModel to expose 'frameSeparator', 'invertedStacks'
if (StacktraceTreeModelAccessor.isInvertedStacks(stacktraceTreeModel)) {
throw new IllegalArgumentException("Inverted stacks are not supported");
}
IItemCollection items = stacktraceTreeModel.getItems();
IAttribute<IQuantity> attribute = stacktraceTreeModel.getAttribute();
FrameSeparator frameSeparator = StacktraceTreeModelAccessor.getFrameSeparator(stacktraceTreeModel);

Node predecessorsRoot = computePredecessorsTree(stacktraceTreeModel.getRoot(), frameSeparator, frameSelector);
Node successorsRoot = computeSuccessorsTree(stacktraceTreeModel.getRoot(), frameSeparator, frameSelector);

predecessorsRoot = computePredecessorsTree(stacktraceTreeModel.getRoot(), nodeSelector);
successorsRoot = computeSuccessorsTree(stacktraceTreeModel.getRoot(), nodeSelector);
assert predecessorsRoot.getChildren().size() == 1 && successorsRoot.getChildren().size() == 1 :
"Predecessors and successors root should have a single child";
assert predecessorsRoot.getChildren().get(0).getFrame().equals(successorsRoot.getChildren().get(0).getFrame()) :
"Predecessors and successors root should be the same frame";

assert predecessorsRoot.getFrame().equals(successorsRoot.getFrame()) : "Predecessors and successors root should be the same frame";
return new StacktraceButterflyModel(predecessorsRoot, successorsRoot, items, attribute, frameSeparator);
}

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

findPredecessors(predecessorsRoot, flamegraphRoot, nodeSelector);

return predecessorsRoot.get();
return predecessorsRoot;
}

private void findPredecessors(
AtomicReference<Node> predecessorsRootRef,
private static void findPredecessors(
Node predecessorsRoot,
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 = getOrCreateFocusedMethodNode(predecessorsRoot, node.getFrame());

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

Expand All @@ -75,6 +118,10 @@ private void findPredecessors(
currentNode != null && currentNode.getParent() != null;
currentNode = currentNode.getParent()
) {
if (currentNode.getParent().isRoot()) {
continue;
}

Node child = getOrCreateNode(predecessorNode, currentNode.getFrame());
child.weight += currentNode.getWeight();
child.cumulativeWeight += currentNode.getCumulativeWeight();
Expand All @@ -84,63 +131,72 @@ private void findPredecessors(

// 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);
findPredecessors(predecessorsRoot, 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 frameSeparator The frame separator
* @param nodeSelector The node selector
* @return a tree representing the successors of the frame
*/
private Node computeSuccessorsTree(
private static Node computeSuccessorsTree(
org.openjdk.jmc.flightrecorder.stacktrace.tree.Node flamegraphRoot,
Predicate<AggregatableFrame> nodeSelector
FrameSeparator frameSeparator, Predicate<AggregatableFrame> nodeSelector
) {
AtomicReference<Node> successorsRootRef = new AtomicReference<>();
findSuccessors(successorsRootRef, flamegraphRoot, nodeSelector);
mergeChildren(successorsRootRef.get());
return successorsRootRef.get();
AggregatableFrame rootFrame = new AggregatableFrame(frameSeparator, ROOT_FRAME);
Node root = Node.newRootNode(rootFrame);
findSuccessors(root, flamegraphRoot, nodeSelector);
mergeChildren(root);
return root;
}

private void findSuccessors(
AtomicReference<Node> successorsRootRef,
private static void findSuccessors(
Node successorsRoot,
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();
Node successorsRootNode = getOrCreateFocusedMethodNode(successorsRoot, node.getFrame());
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);
findSuccessors(successorsRoot, child, nodeSelector);
}
}

private static Node getOrCreateFocusedMethodNode(Node parent, AggregatableFrame frame) {
if (!parent.children.isEmpty() && !parent.children.get(0).getFrame().equals(frame)) {
throw new IllegalArgumentException("frameSelector matched more than one frame");
}
return getOrCreateNode(parent, frame);
}

private static 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;
});
}

/**
* Merge children with the same {@link AggregatableFrame}.
*
* @param node The node to process
*/
private void mergeChildren(Node node) {
private static void mergeChildren(Node node) {
Map<AggregatableFrame, Node> childrenMap = new HashMap<>();
for (Node child : node.getChildren()) {
childrenMap.merge(
Expand Down Expand Up @@ -201,4 +257,40 @@ public IItemCollection getItems() {
public IAttribute<IQuantity> getAttribute() {
return attribute;
}

public FrameSeparator getFrameSeparator() {
return frameSeparator;
}

private static abstract class StacktraceTreeModelAccessor {
private final static Field frameSeparatorField;
private final static Field invertedStacksField;

static {
try {
frameSeparatorField = StacktraceTreeModel.class.getDeclaredField("frameSeparator");
frameSeparatorField.setAccessible(true);
invertedStacksField = StacktraceTreeModel.class.getDeclaredField("invertedStacks");
invertedStacksField.setAccessible(true);
} catch (NoSuchFieldException e) {
throw new RuntimeException("Fields have changed in 'StacktraceTreeModel'", e);
}
}

static FrameSeparator getFrameSeparator(StacktraceTreeModel model) {
try {
return (FrameSeparator) frameSeparatorField.get(model);
} catch (IllegalAccessException e) {
throw new RuntimeException("StacktraceTreeModel.frameSeparator field cannot be accessed", e);
}
}

static boolean isInvertedStacks(StacktraceTreeModel model) {
try {
return (boolean) invertedStacksField.get(model);
} catch (IllegalAccessException e) {
throw new RuntimeException("StacktraceTreeModel.invertedStacks field cannot be accessed", e);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ fun IItemCollection.stacktraceTreeModel(nodeWeightAttribute: IAttribute<IQuantit
}

fun IItemCollection.stacktraceButterflyModel(nodeWeightAttribute: IAttribute<IQuantity>? = null, nodeSelector: Predicate<AggregatableFrame>): StacktraceButterflyModel {
return StacktraceButterflyModel(
return StacktraceButterflyModel.from(
this.stacktraceTreeModel(nodeWeightAttribute),
nodeSelector,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ class ExperimentalButterfly(private val jfrBinder: JFRLoaderBinder) : ViewPanel

jfrBinder.bindEvents(JfrAnalyzer::executionSamples) {
val stacktraceButterflyModel = it.stacktraceButterflyModel(nodeSelector = { frame ->
frame.method.methodName.contains("computeUnderProgress") && frame.method.type.typeName.contains("CoreProgressManager")
(frame.method.type.typeName == "StupidMain" && frame.method.methodName == "work") ||
(frame.method.type.typeName.contains("CoreProgressManager") && frame.method.methodName.contains("computeUnderProgress"))
})

butterflyPane.setStacktraceButterflyModel(stacktraceButterflyModel)
Expand Down
Loading

0 comments on commit 08266e3

Please sign in to comment.