--- old/application/org.openjdk.jmc.flightrecorder.ui/src/main/java/org/openjdk/jmc/flightrecorder/ui/pages/ThreadsPage.java 2019-04-05 13:01:45.240836456 -0400 +++ new/application/org.openjdk.jmc.flightrecorder.ui/src/main/java/org/openjdk/jmc/flightrecorder/ui/pages/ThreadsPage.java 2019-04-05 13:01:45.173835745 -0400 @@ -76,7 +76,9 @@ import org.openjdk.jmc.flightrecorder.ui.common.ItemRow; import org.openjdk.jmc.flightrecorder.ui.common.ThreadGraphLanes; import org.openjdk.jmc.flightrecorder.ui.messages.internal.Messages; +import org.openjdk.jmc.ui.UIPlugin; import org.openjdk.jmc.ui.charts.IXDataRenderer; +import org.openjdk.jmc.ui.charts.QuantitySpanRenderer; import org.openjdk.jmc.ui.charts.RendererToolkit; import org.openjdk.jmc.ui.column.ColumnManager.SelectionState; import org.openjdk.jmc.ui.column.TableSettings; @@ -152,15 +154,23 @@ private class ThreadsPageUi extends ChartAndTableUI { private static final String THREADS_TABLE_FILTER = "threadsTableFilter"; //$NON-NLS-1$ + private static final String HIDE_THREAD = "hideThread"; //$NON-NLS-1$ + private static final String RESET_CHART = "resetChart"; //$NON-NLS-1$ + private IAction hideThreadAction; + private IAction resetChartAction; private ThreadGraphLanes lanes; private MCContextMenuManager mm; + private List threadRows; + private Boolean reloadThreads; + private Boolean isChartModified; + private Boolean isChartMenuActionsInit; ThreadsPageUi(Composite parent, FormToolkit toolkit, IPageContainer editor, IState state) { super(pageFilter, getDataSource(), parent, toolkit, editor, state, getName(), pageFilter, getIcon(), flavorSelectorState); mm = (MCContextMenuManager) chartCanvas.getContextMenu(); sash.setOrientation(SWT.HORIZONTAL); - mm.add(new Separator()); + addActionsToContextMenu(mm); // FIXME: The lanes field is initialized by initializeChartConfiguration which is called by the super constructor. This is too indirect for SpotBugs to resolve and should be simplified. lanes.updateContextMenu(mm, false); @@ -176,6 +186,86 @@ onFilterChange(tableFilter); } + /** + * Hides a thread from the chart and rebuilds the chart + */ + private void hideThread(String threadName) { + if (this.threadRows != null && this.threadRows.size() > 0) { + int index = indexOfThreadName(threadName); + if (index != -1) { + this.threadRows.remove(index); + this.reloadThreads = false; + buildChart(); + if (!this.isChartModified) { + this.isChartModified = true; + setResetChartActionEnablement(true); + } + } + if (this.threadRows.size() == 0) { + setHideThreadActionEnablement(false); + } + } + } + + /** + * Locates the index of the target Thread (by name) in the current selection list + * + * @param name + * the name of the Thread of interest + * @return the index of the Thread in the current selection, or -1 if not found + */ + private int indexOfThreadName(String name) { + for (int i = 0; i < this.threadRows.size() && name != null; i++) { + if (this.threadRows.get(i) instanceof QuantitySpanRenderer) { + if (name.equals(((QuantitySpanRenderer) this.threadRows.get(i)).getName())) { + return i; + } + } + } + return -1; + } + + /** + * Adds the hide thread and reset chart actions to the context menu + */ + private void addActionsToContextMenu(MCContextMenuManager mm) { + mm.add(new Separator()); + + IAction hideThreadAction = ActionToolkit.action(() -> this.hideThread(chartCanvas.getActiveScopeName()), + Messages.ThreadsPage_HIDE_THREAD_ACTION, + UIPlugin.getDefault().getMCImageDescriptor(UIPlugin.ICON_DELETE)); + hideThreadAction.setId(HIDE_THREAD); + this.hideThreadAction = hideThreadAction; + mm.add(hideThreadAction); + + IAction resetChartAction = ActionToolkit.action(() -> this.resetChartToSelection(), + Messages.ThreadsPage_RESET_CHART_TO_SELECTION_ACTION, + UIPlugin.getDefault().getMCImageDescriptor(UIPlugin.ICON_REFRESH)); + resetChartAction.setId(RESET_CHART); + resetChartAction.setEnabled(this.isChartModified); + this.resetChartAction = resetChartAction; + mm.add(resetChartAction); + + this.isChartMenuActionsInit = true; + } + + /** + * Redraws the chart, and disables the reset chart menu action + */ + private void resetChartToSelection() { + buildChart(); + this.isChartModified = false; + setResetChartActionEnablement(false); + setHideThreadActionEnablement(true); + } + + private void setHideThreadActionEnablement(Boolean enabled) { + this.hideThreadAction.setEnabled(enabled); + } + private void setResetChartActionEnablement(Boolean enabled) { + this.resetChartAction.setEnabled(enabled); + } + @Override protected ItemHistogram buildHistogram(Composite parent, IState state) { ItemHistogram build = HISTOGRAM.buildWithoutBorder(parent, JfrAttributes.EVENT_THREAD, @@ -198,15 +288,25 @@ } boolean useDefaultSelection = rows.size() > 1; if (lanes.getLaneDefinitions().stream().anyMatch(a -> a.isEnabled()) && selection.getRowCount() > 0) { - List threadRows = selection - .getSelectedRows((object, items) -> lanes.buildThreadRenderer(object, items)) - .collect(Collectors.toList()); + if (this.reloadThreads) { + this.threadRows = selection + .getSelectedRows((object, items) -> lanes.buildThreadRenderer(object, items)) + .collect(Collectors.toList()); + this.isChartModified = false; + if (this.isChartMenuActionsInit) { + setResetChartActionEnablement(false); + setHideThreadActionEnablement(true); + } + } else { + this.reloadThreads = true; + } + double threadsWeight = Math.sqrt(threadRows.size()) * 0.15; double otherRowWeight = Math.max(threadsWeight * 0.1, (1 - threadsWeight) / rows.size()); List weights = Stream .concat(Stream.generate(() -> otherRowWeight).limit(rows.size()), Stream.of(threadsWeight)) .collect(Collectors.toList()); - rows.add(RendererToolkit.uniformRows(threadRows)); + rows.add(RendererToolkit.uniformRows(this.threadRows)); useDefaultSelection = true; rows = Arrays.asList(RendererToolkit.weightedRows(rows, weights)); } @@ -236,6 +336,9 @@ @Override protected List initializeChartConfiguration(IState state) { + this.reloadThreads = true; + this.isChartModified = false; + this.isChartMenuActionsInit = false; lanes = new ThreadGraphLanes(() -> getDataSource(), () -> buildChart()); return lanes.initializeChartConfiguration(Stream.of(state.getChildren(THREAD_LANE))); }