Skip to content

Commit

Permalink
7041: Add Percentage column based on Duration in stack trace view
Browse files Browse the repository at this point in the history
Reviewed-by: aptmac
  • Loading branch information
Suchita Chaturvedi committed Jun 2, 2021
1 parent 7220aee commit fbf2d9b
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,7 @@ public class Messages extends NLS {
public static String SELECT_RANGE_WIZARD_TO_MUCH_SELECTED_WARNING;
public static String STACKTRACE_VIEW_COUNT_COLUMN_NAME;
public static String STACKTRACE_VIEW_PERCENTAGE_COLUMN_NAME;
public static String STACKTRACE_VIEW_PERCENTAGE_BY_DURATION_COLUMN_NAME;
public static String STACKTRACE_VIEW_DISTINGUISH_FRAMES_BY;
public static String STACKTRACE_VIEW_FRAME_GROUP_CHOOSE;
public static String STACKTRACE_VIEW_FRAME_GROUP_NEXT;
Expand All @@ -476,6 +477,7 @@ public class Messages extends NLS {
public static String STACKTRACE_VIEW_REDUCE_TREE_DEPTH;
public static String STACKTRACE_VIEW_SELECTION;
public static String STACKTRACE_VIEW_TREE_VIEW;
public static String STACKTRACE_VIEW_PERCENTAGE_BY_DURATION;
public static String STACKTRACE_VIEW_STACK_TRACE;
public static String STACKTRACE_VIEW_THREAD_ROOT;
public static String STACKTRACE_VIEW_TRACES_IN_GROUP;
Expand Down Expand Up @@ -590,4 +592,14 @@ public static String siblingMessage(int itemsInSiblings, int nSiblings) {
}
return NLS.bind(message, itemsInSiblings, nSiblings);
}

public static String stackTraceMessage(double duration, double totalDuration, String frameFraction) {
String message;
if (duration == 1) {
message = Messages.STACKTRACE_VIEW_TRACE_OF_TOTAL;
} else {
message = Messages.STACKTRACE_VIEW_TRACES_OF_TOTAL;
}
return NLS.bind(message, new Object[] {duration, frameFraction, totalDuration});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,15 @@
import org.openjdk.jmc.common.IMCFrame;
import org.openjdk.jmc.common.IState;
import org.openjdk.jmc.common.collection.SimpleArray;
import org.openjdk.jmc.common.item.IItem;
import org.openjdk.jmc.common.item.IItemCollection;
import org.openjdk.jmc.common.item.IMemberAccessor;
import org.openjdk.jmc.common.item.IType;
import org.openjdk.jmc.common.item.ItemCollectionToolkit;
import org.openjdk.jmc.common.unit.IQuantity;
import org.openjdk.jmc.common.unit.UnitLookup;
import org.openjdk.jmc.common.util.StateToolkit;
import org.openjdk.jmc.flightrecorder.JfrAttributes;
import org.openjdk.jmc.flightrecorder.stacktrace.FrameSeparator;
import org.openjdk.jmc.flightrecorder.stacktrace.FrameSeparator.FrameCategorization;
import org.openjdk.jmc.flightrecorder.stacktrace.StacktraceFormatToolkit;
Expand Down Expand Up @@ -185,12 +190,14 @@ public void run() {
private static final String FRAME_OPTIMIZATION_KEY = "distinguishFramesByOptimization"; //$NON-NLS-1$
private static final String FRAME_CATEGORIZATION_KEY = "distinguishFramesCategorization"; //$NON-NLS-1$
private static final String TREE_LAYOUT_KEY = "treeLayout"; //$NON-NLS-1$
private static final String PERCENTAGE_DURATION_KEY = "perDuration"; //$NON-NLS-1$
private static final String REDUCED_TREE_KEY = "reducedTreeLayout"; //$NON-NLS-1$
private static final String METHOD_FORMAT_KEY = "metodFormat"; //$NON-NLS-1$
private static final String COLUMNS_KEY = "columns"; //$NON-NLS-1$
private static final String COLUMNS_SEPARATOR = " "; //$NON-NLS-1$
private ColumnViewer viewer;
private boolean treeLayout;
private boolean perDuration;
private boolean reducedTree;
private IAction reducedTreeAction;
private boolean threadRootAtTop;
Expand Down Expand Up @@ -373,6 +380,7 @@ public void init(IViewSite site, IMemento memento) throws PartInitException {
threadRootAtTop = StateToolkit.readBoolean(state, THREAD_ROOT_KEY, false);
groupByActions = new GroupByAction[] {new GroupByAction(false), new GroupByAction(true)};
treeLayout = StateToolkit.readBoolean(state, TREE_LAYOUT_KEY, false);
perDuration = StateToolkit.readBoolean(state, PERCENTAGE_DURATION_KEY, false);
reducedTree = StateToolkit.readBoolean(state, REDUCED_TREE_KEY, true);

reducedTreeAction = ActionToolkit.checkAction(this::setReducedTree, Messages.STACKTRACE_VIEW_REDUCE_TREE_DEPTH,
Expand All @@ -384,6 +392,10 @@ public void init(IViewSite site, IMemento memento) throws PartInitException {
treeAction.setChecked(treeLayout);
layoutActions = new IAction[] {treeAction, reducedTreeAction};

IAction perByDurationAction = ActionToolkit.checkAction(this::setPerDuration,
Messages.STACKTRACE_VIEW_PERCENTAGE_BY_DURATION, CoreImages.TIMESPAN);
treeAction.setChecked(perDuration);

NavigateAction forwardAction = new NavigateAction(true);
NavigateAction backwardAction = new NavigateAction(false);
SelectFrameGroupAction selectGroupAction = new SelectFrameGroupAction();
Expand Down Expand Up @@ -418,6 +430,7 @@ public void init(IViewSite site, IMemento memento) throws PartInitException {
toolBar.add(new Separator());
toolBar.add(treeAction);
toolBar.add(new Separator());
toolBar.add(perByDurationAction);
Stream.of(groupByActions).forEach(toolBar::add);

getSite().getPage().addSelectionListener(this);
Expand All @@ -440,6 +453,12 @@ private void setTreeLayout(boolean treeLayout) {
updateReducedTreeOption();
}

private void setPerDuration(boolean perDuration) {
this.perDuration = perDuration;
rebuildModel();
rebuildViewer();
}

private void setReducedTree(boolean reducedTree) {
this.reducedTree = reducedTree;
if (viewer instanceof TreeViewer) {
Expand Down Expand Up @@ -504,10 +523,21 @@ private void buildViewer(Composite parent) {
}
new StacktraceViewToolTipSupport(viewer);
MCContextMenuManager mm = MCContextMenuManager.create(viewer.getControl());
List<String> headers = Arrays.asList(Messages.STACKTRACE_VIEW_STACK_TRACE,
Messages.STACKTRACE_VIEW_COUNT_COLUMN_NAME, Messages.STACKTRACE_VIEW_PERCENTAGE_COLUMN_NAME);
CopySelectionAction copyAction = new CopySelectionAction(viewer, FormatToolkit.selectionFormatter(headers,
stackTraceLabelProvider, countLabelProvider, percentageLabelProvider));
List<String> headers;
CopySelectionAction copyAction;
if (perDuration) {
headers = Arrays.asList(Messages.STACKTRACE_VIEW_STACK_TRACE, Messages.STACKTRACE_VIEW_COUNT_COLUMN_NAME,
Messages.STACKTRACE_VIEW_PERCENTAGE_COLUMN_NAME,
Messages.STACKTRACE_VIEW_PERCENTAGE_BY_DURATION_COLUMN_NAME);
copyAction = new CopySelectionAction(viewer,
FormatToolkit.selectionFormatter(headers, stackTraceLabelProvider, countLabelProvider,
percentageLabelProvider, percentageByDurationLabelProvider));
} else {
headers = Arrays.asList(Messages.STACKTRACE_VIEW_STACK_TRACE, Messages.STACKTRACE_VIEW_COUNT_COLUMN_NAME,
Messages.STACKTRACE_VIEW_PERCENTAGE_COLUMN_NAME);
copyAction = new CopySelectionAction(viewer, FormatToolkit.selectionFormatter(headers,
stackTraceLabelProvider, countLabelProvider, percentageLabelProvider));
}
InFocusHandlerActivator.install(viewer.getControl(), copyAction);
mm.appendToGroup(MCContextMenuManager.GROUP_EDIT, copyAction);
mm.appendToGroup(MCContextMenuManager.GROUP_EDIT, CopySettings.getInstance().createContributionItem());
Expand All @@ -525,6 +555,8 @@ private void buildViewer(Composite parent) {
}

viewer.getControl().addListener(SWT.EraseItem, PERCENTAGE_BACKGROUND_DRAWER);
if (perDuration)
viewer.getControl().addListener(SWT.EraseItem, PERCENTAGE_BY_DURATION_BACKGROUND_DRAWER);
viewer.getControl().addDisposeListener(e -> columnWidths = getColumnWidths());

buildColumn(viewer, Messages.STACKTRACE_VIEW_STACK_TRACE, SWT.NONE, columnWidths[0])
Expand All @@ -533,7 +565,9 @@ private void buildViewer(Composite parent) {
.setLabelProvider(countLabelProvider);
buildColumn(viewer, Messages.STACKTRACE_VIEW_PERCENTAGE_COLUMN_NAME, SWT.RIGHT, columnWidths[2])
.setLabelProvider(percentageLabelProvider);

if (perDuration)
buildColumn(viewer, Messages.STACKTRACE_VIEW_PERCENTAGE_BY_DURATION_COLUMN_NAME, SWT.RIGHT, 300)
.setLabelProvider(percentageByDurationLabelProvider);
PlatformUI.getWorkbench().getHelpSystem().setHelp(viewer.getControl(), HELP_CONTEXT_ID);

if (UIPlugin.getDefault().getAccessibilityMode()) {
Expand Down Expand Up @@ -648,6 +682,7 @@ public void saveState(IMemento memento) {
methodFormatter.saveState(memento.createChild(METHOD_FORMAT_KEY));
memento.putBoolean(THREAD_ROOT_KEY, threadRootAtTop);
memento.putBoolean(TREE_LAYOUT_KEY, treeLayout);
memento.putBoolean(PERCENTAGE_DURATION_KEY, perDuration);
memento.putBoolean(REDUCED_TREE_KEY, reducedTree);
FrameSeparator frameSeparator = frameSeparatorManager.getFrameSeparator();
memento.putBoolean(FRAME_OPTIMIZATION_KEY, frameSeparator.isDistinguishFramesByOptimization());
Expand Down Expand Up @@ -745,6 +780,32 @@ public void handleEvent(Event event) {
}
};

private static final Listener PERCENTAGE_BY_DURATION_BACKGROUND_DRAWER = new Listener() {
@Override
public void handleEvent(Event event) {
StacktraceFrame frame = (StacktraceFrame) event.item.getData();
Fork rootFork = getRootFork(frame.getBranch().getParentFork());
double total;
if (event.index == 3 && (total = rootFork.getItemsInFork()) > 0) { // index == 3 => percentage (by duration) column
// Draw siblings
Fork parentFork = frame.getBranch().getParentFork();
int forkOffset = parentFork.getItemOffset();
int siblingsStart = (int) Math.floor(event.width * forkOffset / total);
int siblingsWidth = (int) Math.round(event.width * parentFork.getItemsInFork() / total);
event.gc.setBackground(SIBLINGS_COUNT_COLOR);
event.gc.fillRectangle(event.x + siblingsStart, event.y, siblingsWidth, event.height);
// Draw group
double offset = (forkOffset + frame.getBranch().getItemOffsetInFork()) / total;
double fraction = frame.getItemCount() / total;
event.gc.setBackground(COUNT_COLOR);
int startPixel = (int) Math.floor(event.width * offset);
int widthPixel = (int) Math.round(event.width * fraction);
event.gc.fillRectangle(event.x + startPixel, event.y, Math.max(widthPixel, 1), event.height);
event.detail &= ~SWT.BACKGROUND;
}
}
};

private final ColumnLabelProvider percentageLabelProvider = new ColumnLabelProvider() {
@Override
public String getText(Object element) {
Expand Down Expand Up @@ -776,6 +837,58 @@ public String getToolTipText(Object element) {
}
};

private final ColumnLabelProvider percentageByDurationLabelProvider = new ColumnLabelProvider() {
@Override
public String getText(Object element) {
StacktraceFrame frame = (StacktraceFrame) element;
IQuantity duration = getDurationCount(frame.getItems());
IQuantity totalDuration = getDurationCount(
getRootFork(frame.getBranch().getParentFork()).getAllItemsInFork());
return UnitLookup.PERCENT_UNITY.quantity(duration.doubleValue() / (double) totalDuration.doubleValue())
.displayUsing(IDisplayable.AUTO);
}

@Override
public String getToolTipText(Object element) {
StacktraceFrame frame = (StacktraceFrame) element;
Fork rootFork = getRootFork(frame.getBranch().getParentFork());
IQuantity duration = getDurationCount(frame.getItems());
IQuantity totalDuration = getDurationCount(rootFork.getAllItemsInFork());
Fork parentFork = frame.getBranch().getParentFork();
int itemsInSiblings = parentFork.getItemsInFork() - frame.getBranch().getFirstFrame().getItemCount();
String frameFraction = UnitLookup.PERCENT_UNITY
.quantity(duration.doubleValue() / (double) totalDuration.doubleValue())
.displayUsing(IDisplayable.AUTO);
StringBuilder sb = new StringBuilder("<form>"); //$NON-NLS-1$
sb.append("<li style='image' value='" + COUNT_IMG_KEY + "'><span nowrap='true'>"); //$NON-NLS-1$ //$NON-NLS-2$
sb.append(Messages.stackTraceMessage(duration.doubleValue(), totalDuration.doubleValue(), frameFraction));
sb.append("</span></li>"); //$NON-NLS-1$
sb.append("<li style='image' value='" + SIBLINGS_IMG_KEY + "'><span nowrap='true'>"); //$NON-NLS-1$ //$NON-NLS-2$
sb.append(Messages.siblingMessage(itemsInSiblings, parentFork.getBranchCount() - 1));
sb.append("</span></li>"); //$NON-NLS-1$
sb.append("</form>"); //$NON-NLS-1$
return sb.toString();
}
};

private IQuantity getDurationCount(SimpleArray<IItem> simpleArray) {
IQuantity q = null;
for (IItem item : simpleArray.elements()) {
@SuppressWarnings("unchecked")
IType<IItem> type = (IType<IItem>) item.getType();
IMemberAccessor<IQuantity, IItem> durationAccessor = JfrAttributes.DURATION.getAccessor(type);
if (durationAccessor != null) {
if (q != null) {
q = q.add(durationAccessor.getMember(item));
} else {
q = durationAccessor.getMember(item);
}
}
}

return q;
}

private final ColumnLabelProvider countLabelProvider = new ColumnLabelProvider() {
@Override
public String getText(Object element) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,7 @@ STORE_AND_ACTIVATE_SELECTION_ACTION=Store and Set As Focused Selection

STACKTRACE_VIEW_COUNT_COLUMN_NAME=Count
STACKTRACE_VIEW_PERCENTAGE_COLUMN_NAME=Percentage
STACKTRACE_VIEW_PERCENTAGE_BY_DURATION_COLUMN_NAME=Percentage (By Duration)
STACKTRACE_VIEW_DISTINGUISH_FRAMES_BY=Distinguish Frames By
STACKTRACE_VIEW_FRAME_GROUP_CHOOSE=Choose Frame Group
STACKTRACE_VIEW_FRAME_GROUP_NEXT=Next Frame Group
Expand All @@ -453,6 +454,7 @@ STACKTRACE_VIEW_LAST_FRAME=Last Frame
STACKTRACE_VIEW_LAYOUT_OPTIONS=Layout Options
STACKTRACE_VIEW_SELECTION=Stack Trace View Selection
STACKTRACE_VIEW_TREE_VIEW=Tree View
STACKTRACE_VIEW_PERCENTAGE_BY_DURATION=Show Percentage By Duration
STACKTRACE_VIEW_STACK_TRACE=Stack Trace
STACKTRACE_VIEW_OPTIMIZATION_TYPE=Optimization Type
STACKTRACE_VIEW_REDUCE_TREE_DEPTH=Reduce Tree Depth
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved.
*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
Expand Down Expand Up @@ -417,6 +417,7 @@ public class Fork {
private final int itemOffset;
private final int itemsInFork;
private Integer selectedBranchIndex;
private final SimpleArray<IItem> allItems;

private Fork(Branch parentBranch) {
this(parentBranch.getLastFrame().getItems(),
Expand All @@ -437,15 +438,23 @@ private Fork(Iterable<? extends IItem> items, int itemOffset, Branch parentBranc
List<FrameEntry> branchHeadFrames = getDistinctFrames(countFramesOnOrAbove(parentBranch), items);
Collections.sort(branchHeadFrames, COUNT_CMP);
int itemsInFork = 0;

SimpleArray<IItem> allItems = new SimpleArray<IItem>(new IItem[0]);
SimpleArray<Branch> branches = new SimpleArray<>(new Branch[branchHeadFrames.size()]);
for (FrameEntry fe : branchHeadFrames) {
Branch b = new Branch(Fork.this, fe.items, fe.frame, branches.size(), itemsInFork);
itemsInFork += fe.items.size();
if (allItems != null) {
allItems.addAll(fe.items.elements());
} else {
allItems = new SimpleArray<IItem>(fe.items.elements());
}
branches.add(b);
}
selectedBranchIndex = branches.size() > 0 ? 0 : null; // To disable default branch selection: always set null
this.branches = branches.elements();
this.itemsInFork = itemsInFork;
this.allItems = allItems;
}

public int getItemOffset() {
Expand All @@ -456,6 +465,10 @@ public int getItemsInFork() {
return itemsInFork;
}

public SimpleArray<IItem> getAllItemsInFork() {
return allItems;
}

public Branch getParentBranch() {
return parentBranch;
}
Expand Down

0 comments on commit fbf2d9b

Please sign in to comment.