59 import org.openjdk.jmc.flightrecorder.JfrAttributes; 60 import org.openjdk.jmc.flightrecorder.jdk.JdkAttributes; 61 import org.openjdk.jmc.flightrecorder.jdk.JdkTypeIDs; 62 import org.openjdk.jmc.flightrecorder.rules.util.JfrRuleTopics; 63 import org.openjdk.jmc.flightrecorder.ui.FlightRecorderUI; 64 import org.openjdk.jmc.flightrecorder.ui.IDataPageFactory; 65 import org.openjdk.jmc.flightrecorder.ui.IDisplayablePage; 66 import org.openjdk.jmc.flightrecorder.ui.IPageContainer; 67 import org.openjdk.jmc.flightrecorder.ui.IPageDefinition; 68 import org.openjdk.jmc.flightrecorder.ui.IPageUI; 69 import org.openjdk.jmc.flightrecorder.ui.StreamModel; 70 import org.openjdk.jmc.flightrecorder.ui.common.AbstractDataPage; 71 import org.openjdk.jmc.flightrecorder.ui.common.FlavorSelector.FlavorSelectorState; 72 import org.openjdk.jmc.flightrecorder.ui.common.ImageConstants; 73 import org.openjdk.jmc.flightrecorder.ui.common.ItemHistogram; 74 import org.openjdk.jmc.flightrecorder.ui.common.ItemHistogram.HistogramSelection; 75 import org.openjdk.jmc.flightrecorder.ui.common.ItemHistogram.ItemHistogramBuilder; 76 import org.openjdk.jmc.flightrecorder.ui.common.ItemRow; 77 import org.openjdk.jmc.flightrecorder.ui.common.ThreadGraphLanes; 78 import org.openjdk.jmc.flightrecorder.ui.messages.internal.Messages; 79 import org.openjdk.jmc.ui.charts.IXDataRenderer; 80 import org.openjdk.jmc.ui.charts.RendererToolkit; 81 import org.openjdk.jmc.ui.column.ColumnManager.SelectionState; 82 import org.openjdk.jmc.ui.column.TableSettings; 83 import org.openjdk.jmc.ui.handlers.ActionToolkit; 84 import org.openjdk.jmc.ui.handlers.MCContextMenuManager; 85 86 public class ThreadsPage extends AbstractDataPage { 87 88 public static class ThreadsPageFactory implements IDataPageFactory { 89 90 @Override 91 public String getName(IState state) { 92 return Messages.ThreadsPage_NAME; 93 } 94 95 @Override 96 public String[] getTopics(IState state) { 97 return new String[] {JfrRuleTopics.THREADS_TOPIC}; 98 } 99 135 * to all other columns. 136 */ 137 HISTOGRAM.addColumn(THREAD_END_COL, 138 max(Messages.JavaApplicationPage_COLUMN_THREAD_END, Messages.JavaApplicationPage_COLUMN_THREAD_END_DESC, 139 JdkTypeIDs.JAVA_THREAD_END, JfrAttributes.EVENT_TIMESTAMP)); 140 HISTOGRAM.addColumn(THREAD_DURATION_COL, ic -> { 141 IQuantity threadStart = ic.apply(ItemFilters.type(JdkTypeIDs.JAVA_THREAD_START)) 142 .getAggregate((IAggregator<IQuantity, ?>) Aggregators.min(JfrAttributes.EVENT_TIMESTAMP)); 143 IQuantity threadEnd = ic.apply(ItemFilters.type(JdkTypeIDs.JAVA_THREAD_END)) 144 .getAggregate((IAggregator<IQuantity, ?>) Aggregators.max(JfrAttributes.EVENT_TIMESTAMP)); 145 if (threadStart != null && threadEnd != null) { 146 return threadEnd.subtract(threadStart); 147 } 148 return null; 149 }, Messages.JavaApplicationPage_COLUMN_THREAD_DURATION, 150 Messages.JavaApplicationPage_COLUMN_THREAD_DURATION_DESC); 151 } 152 153 private class ThreadsPageUi extends ChartAndTableUI { 154 private static final String THREADS_TABLE_FILTER = "threadsTableFilter"; //$NON-NLS-1$ 155 private ThreadGraphLanes lanes; 156 private MCContextMenuManager mm; 157 158 ThreadsPageUi(Composite parent, FormToolkit toolkit, IPageContainer editor, IState state) { 159 super(pageFilter, getDataSource(), parent, toolkit, editor, state, getName(), pageFilter, getIcon(), 160 flavorSelectorState); 161 mm = (MCContextMenuManager) chartCanvas.getContextMenu(); 162 sash.setOrientation(SWT.HORIZONTAL); 163 mm.add(new Separator()); 164 // 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. 165 lanes.updateContextMenu(mm, false); 166 167 form.getToolBarManager() 168 .add(ActionToolkit.action(() -> lanes.openEditLanesDialog(mm, false), Messages.ThreadsPage_EDIT_LANES, 169 FlightRecorderUI.getDefault().getMCImageDescriptor(ImageConstants.ICON_LANES_EDIT))); 170 form.getToolBarManager().update(true); 171 chartLegend.getControl().dispose(); 172 buildChart(); 173 table.getManager().setSelectionState(histogramSelectionState); 174 tableFilterComponent.loadState(state.getChild(THREADS_TABLE_FILTER)); 175 chart.setVisibleRange(visibleRange.getStart(), visibleRange.getEnd()); 176 onFilterChange(tableFilter); 177 } 178 179 @Override 180 protected ItemHistogram buildHistogram(Composite parent, IState state) { 181 ItemHistogram build = HISTOGRAM.buildWithoutBorder(parent, JfrAttributes.EVENT_THREAD, 182 TableSettings.forState(state)); 183 return build; 184 } 185 186 @Override 187 protected IXDataRenderer getChartRenderer(IItemCollection itemsInTable, HistogramSelection tableSelection) { 188 List<IXDataRenderer> rows = new ArrayList<>(); 189 190 IItemCollection selectedItems; 191 HistogramSelection selection; 192 if (tableSelection.getRowCount() == 0) { 193 selectedItems = itemsInTable; 194 selection = table.getAllRows(); 195 } else { 196 selectedItems = tableSelection.getItems(); 197 selection = tableSelection; 198 } 199 boolean useDefaultSelection = rows.size() > 1; 200 if (lanes.getLaneDefinitions().stream().anyMatch(a -> a.isEnabled()) && selection.getRowCount() > 0) { 201 List<IXDataRenderer> threadRows = selection 202 .getSelectedRows((object, items) -> lanes.buildThreadRenderer(object, items)) 203 .collect(Collectors.toList()); 204 double threadsWeight = Math.sqrt(threadRows.size()) * 0.15; 205 double otherRowWeight = Math.max(threadsWeight * 0.1, (1 - threadsWeight) / rows.size()); 206 List<Double> weights = Stream 207 .concat(Stream.generate(() -> otherRowWeight).limit(rows.size()), Stream.of(threadsWeight)) 208 .collect(Collectors.toList()); 209 rows.add(RendererToolkit.uniformRows(threadRows)); 210 useDefaultSelection = true; 211 rows = Arrays.asList(RendererToolkit.weightedRows(rows, weights)); 212 } 213 IXDataRenderer root = rows.size() == 1 ? rows.get(0) : RendererToolkit.uniformRows(rows); 214 // We don't use the default selection when there is only one row. This is to get the correct payload. 215 return useDefaultSelection ? new ItemRow(root, selectedItems.apply(lanes.getEnabledLanesFilter())) : root; 216 } 217 218 @Override 219 protected void onFilterChange(IItemFilter filter) { 220 super.onFilterChange(filter); 221 tableFilter = filter; 222 } 223 224 @Override 225 public void saveTo(IWritableState state) { 226 super.saveTo(state); 227 tableFilterComponent.saveState(state.createChild(THREADS_TABLE_FILTER)); 228 saveToLocal(); 229 } 230 231 private void saveToLocal() { 232 flavorSelectorState = flavorSelector.getFlavorSelectorState(); 233 histogramSelectionState = table.getManager().getSelectionState(); 234 visibleRange = chart.getVisibleRange(); 235 } 236 237 @Override 238 protected List<IAction> initializeChartConfiguration(IState state) { 239 lanes = new ThreadGraphLanes(() -> getDataSource(), () -> buildChart()); 240 return lanes.initializeChartConfiguration(Stream.of(state.getChildren(THREAD_LANE))); 241 } 242 } 243 244 private FlavorSelectorState flavorSelectorState; 245 private SelectionState histogramSelectionState; 246 private IItemFilter tableFilter; 247 private IRange<IQuantity> visibleRange; 248 249 public ThreadsPage(IPageDefinition definition, StreamModel model, IPageContainer editor) { 250 super(definition, model, editor); 251 visibleRange = editor.getRecordingRange(); 252 } 253 254 @Override 255 public IPageUI display(Composite parent, FormToolkit toolkit, IPageContainer editor, IState state) { 256 return new ThreadsPageUi(parent, toolkit, editor, state); 257 } 258 | 59 import org.openjdk.jmc.flightrecorder.JfrAttributes; 60 import org.openjdk.jmc.flightrecorder.jdk.JdkAttributes; 61 import org.openjdk.jmc.flightrecorder.jdk.JdkTypeIDs; 62 import org.openjdk.jmc.flightrecorder.rules.util.JfrRuleTopics; 63 import org.openjdk.jmc.flightrecorder.ui.FlightRecorderUI; 64 import org.openjdk.jmc.flightrecorder.ui.IDataPageFactory; 65 import org.openjdk.jmc.flightrecorder.ui.IDisplayablePage; 66 import org.openjdk.jmc.flightrecorder.ui.IPageContainer; 67 import org.openjdk.jmc.flightrecorder.ui.IPageDefinition; 68 import org.openjdk.jmc.flightrecorder.ui.IPageUI; 69 import org.openjdk.jmc.flightrecorder.ui.StreamModel; 70 import org.openjdk.jmc.flightrecorder.ui.common.AbstractDataPage; 71 import org.openjdk.jmc.flightrecorder.ui.common.FlavorSelector.FlavorSelectorState; 72 import org.openjdk.jmc.flightrecorder.ui.common.ImageConstants; 73 import org.openjdk.jmc.flightrecorder.ui.common.ItemHistogram; 74 import org.openjdk.jmc.flightrecorder.ui.common.ItemHistogram.HistogramSelection; 75 import org.openjdk.jmc.flightrecorder.ui.common.ItemHistogram.ItemHistogramBuilder; 76 import org.openjdk.jmc.flightrecorder.ui.common.ItemRow; 77 import org.openjdk.jmc.flightrecorder.ui.common.ThreadGraphLanes; 78 import org.openjdk.jmc.flightrecorder.ui.messages.internal.Messages; 79 import org.openjdk.jmc.ui.UIPlugin; 80 import org.openjdk.jmc.ui.charts.IXDataRenderer; 81 import org.openjdk.jmc.ui.charts.QuantitySpanRenderer; 82 import org.openjdk.jmc.ui.charts.RendererToolkit; 83 import org.openjdk.jmc.ui.column.ColumnManager.SelectionState; 84 import org.openjdk.jmc.ui.column.TableSettings; 85 import org.openjdk.jmc.ui.handlers.ActionToolkit; 86 import org.openjdk.jmc.ui.handlers.MCContextMenuManager; 87 88 public class ThreadsPage extends AbstractDataPage { 89 90 public static class ThreadsPageFactory implements IDataPageFactory { 91 92 @Override 93 public String getName(IState state) { 94 return Messages.ThreadsPage_NAME; 95 } 96 97 @Override 98 public String[] getTopics(IState state) { 99 return new String[] {JfrRuleTopics.THREADS_TOPIC}; 100 } 101 137 * to all other columns. 138 */ 139 HISTOGRAM.addColumn(THREAD_END_COL, 140 max(Messages.JavaApplicationPage_COLUMN_THREAD_END, Messages.JavaApplicationPage_COLUMN_THREAD_END_DESC, 141 JdkTypeIDs.JAVA_THREAD_END, JfrAttributes.EVENT_TIMESTAMP)); 142 HISTOGRAM.addColumn(THREAD_DURATION_COL, ic -> { 143 IQuantity threadStart = ic.apply(ItemFilters.type(JdkTypeIDs.JAVA_THREAD_START)) 144 .getAggregate((IAggregator<IQuantity, ?>) Aggregators.min(JfrAttributes.EVENT_TIMESTAMP)); 145 IQuantity threadEnd = ic.apply(ItemFilters.type(JdkTypeIDs.JAVA_THREAD_END)) 146 .getAggregate((IAggregator<IQuantity, ?>) Aggregators.max(JfrAttributes.EVENT_TIMESTAMP)); 147 if (threadStart != null && threadEnd != null) { 148 return threadEnd.subtract(threadStart); 149 } 150 return null; 151 }, Messages.JavaApplicationPage_COLUMN_THREAD_DURATION, 152 Messages.JavaApplicationPage_COLUMN_THREAD_DURATION_DESC); 153 } 154 155 private class ThreadsPageUi extends ChartAndTableUI { 156 private static final String THREADS_TABLE_FILTER = "threadsTableFilter"; //$NON-NLS-1$ 157 private static final String HIDE_THREAD = "hideThread"; //$NON-NLS-1$ 158 private static final String RESET_CHART = "resetChart"; //$NON-NLS-1$ 159 private IAction hideThreadAction; 160 private IAction resetChartAction; 161 private ThreadGraphLanes lanes; 162 private MCContextMenuManager mm; 163 private List<IXDataRenderer> threadRows; 164 private Boolean reloadThreads; 165 private Boolean isChartModified; 166 private Boolean isChartMenuActionsInit; 167 168 ThreadsPageUi(Composite parent, FormToolkit toolkit, IPageContainer editor, IState state) { 169 super(pageFilter, getDataSource(), parent, toolkit, editor, state, getName(), pageFilter, getIcon(), 170 flavorSelectorState); 171 mm = (MCContextMenuManager) chartCanvas.getContextMenu(); 172 sash.setOrientation(SWT.HORIZONTAL); 173 addActionsToContextMenu(mm); 174 // 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. 175 lanes.updateContextMenu(mm, false); 176 177 form.getToolBarManager() 178 .add(ActionToolkit.action(() -> lanes.openEditLanesDialog(mm, false), Messages.ThreadsPage_EDIT_LANES, 179 FlightRecorderUI.getDefault().getMCImageDescriptor(ImageConstants.ICON_LANES_EDIT))); 180 form.getToolBarManager().update(true); 181 chartLegend.getControl().dispose(); 182 buildChart(); 183 table.getManager().setSelectionState(histogramSelectionState); 184 tableFilterComponent.loadState(state.getChild(THREADS_TABLE_FILTER)); 185 chart.setVisibleRange(visibleRange.getStart(), visibleRange.getEnd()); 186 onFilterChange(tableFilter); 187 } 188 189 /** 190 * Hides a thread from the chart and rebuilds the chart 191 */ 192 private void hideThread(String threadName) { 193 if (this.threadRows != null && this.threadRows.size() > 0) { 194 int index = indexOfThreadName(threadName); 195 if (index != -1) { 196 this.threadRows.remove(index); 197 this.reloadThreads = false; 198 buildChart(); 199 if (!this.isChartModified) { 200 this.isChartModified = true; 201 setResetChartActionEnablement(true); 202 } 203 } 204 if (this.threadRows.size() == 0) { 205 setHideThreadActionEnablement(false); 206 } 207 } 208 } 209 210 /** 211 * Locates the index of the target Thread (by name) in the current selection list 212 * 213 * @param name 214 * the name of the Thread of interest 215 * @return the index of the Thread in the current selection, or -1 if not found 216 */ 217 private int indexOfThreadName(String name) { 218 for (int i = 0; i < this.threadRows.size() && name != null; i++) { 219 if (this.threadRows.get(i) instanceof QuantitySpanRenderer) { 220 if (name.equals(((QuantitySpanRenderer) this.threadRows.get(i)).getName())) { 221 return i; 222 } 223 } 224 } 225 return -1; 226 } 227 228 /** 229 * Adds the hide thread and reset chart actions to the context menu 230 */ 231 private void addActionsToContextMenu(MCContextMenuManager mm) { 232 mm.add(new Separator()); 233 234 IAction hideThreadAction = ActionToolkit.action(() -> this.hideThread(chartCanvas.getActiveScopeName()), 235 Messages.ThreadsPage_HIDE_THREAD_ACTION, 236 UIPlugin.getDefault().getMCImageDescriptor(UIPlugin.ICON_DELETE)); 237 hideThreadAction.setId(HIDE_THREAD); 238 this.hideThreadAction = hideThreadAction; 239 mm.add(hideThreadAction); 240 241 IAction resetChartAction = ActionToolkit.action(() -> this.resetChartToSelection(), 242 Messages.ThreadsPage_RESET_CHART_TO_SELECTION_ACTION, 243 UIPlugin.getDefault().getMCImageDescriptor(UIPlugin.ICON_REFRESH)); 244 resetChartAction.setId(RESET_CHART); 245 resetChartAction.setEnabled(this.isChartModified); 246 this.resetChartAction = resetChartAction; 247 mm.add(resetChartAction); 248 249 this.isChartMenuActionsInit = true; 250 } 251 252 /** 253 * Redraws the chart, and disables the reset chart menu action 254 */ 255 private void resetChartToSelection() { 256 buildChart(); 257 this.isChartModified = false; 258 setResetChartActionEnablement(false); 259 setHideThreadActionEnablement(true); 260 } 261 262 private void setHideThreadActionEnablement(Boolean enabled) { 263 this.hideThreadAction.setEnabled(enabled); 264 } 265 private void setResetChartActionEnablement(Boolean enabled) { 266 this.resetChartAction.setEnabled(enabled); 267 } 268 269 @Override 270 protected ItemHistogram buildHistogram(Composite parent, IState state) { 271 ItemHistogram build = HISTOGRAM.buildWithoutBorder(parent, JfrAttributes.EVENT_THREAD, 272 TableSettings.forState(state)); 273 return build; 274 } 275 276 @Override 277 protected IXDataRenderer getChartRenderer(IItemCollection itemsInTable, HistogramSelection tableSelection) { 278 List<IXDataRenderer> rows = new ArrayList<>(); 279 280 IItemCollection selectedItems; 281 HistogramSelection selection; 282 if (tableSelection.getRowCount() == 0) { 283 selectedItems = itemsInTable; 284 selection = table.getAllRows(); 285 } else { 286 selectedItems = tableSelection.getItems(); 287 selection = tableSelection; 288 } 289 boolean useDefaultSelection = rows.size() > 1; 290 if (lanes.getLaneDefinitions().stream().anyMatch(a -> a.isEnabled()) && selection.getRowCount() > 0) { 291 if (this.reloadThreads) { 292 this.threadRows = selection 293 .getSelectedRows((object, items) -> lanes.buildThreadRenderer(object, items)) 294 .collect(Collectors.toList()); 295 this.isChartModified = false; 296 if (this.isChartMenuActionsInit) { 297 setResetChartActionEnablement(false); 298 setHideThreadActionEnablement(true); 299 } 300 } else { 301 this.reloadThreads = true; 302 } 303 304 double threadsWeight = Math.sqrt(threadRows.size()) * 0.15; 305 double otherRowWeight = Math.max(threadsWeight * 0.1, (1 - threadsWeight) / rows.size()); 306 List<Double> weights = Stream 307 .concat(Stream.generate(() -> otherRowWeight).limit(rows.size()), Stream.of(threadsWeight)) 308 .collect(Collectors.toList()); 309 rows.add(RendererToolkit.uniformRows(this.threadRows)); 310 useDefaultSelection = true; 311 rows = Arrays.asList(RendererToolkit.weightedRows(rows, weights)); 312 } 313 IXDataRenderer root = rows.size() == 1 ? rows.get(0) : RendererToolkit.uniformRows(rows); 314 // We don't use the default selection when there is only one row. This is to get the correct payload. 315 return useDefaultSelection ? new ItemRow(root, selectedItems.apply(lanes.getEnabledLanesFilter())) : root; 316 } 317 318 @Override 319 protected void onFilterChange(IItemFilter filter) { 320 super.onFilterChange(filter); 321 tableFilter = filter; 322 } 323 324 @Override 325 public void saveTo(IWritableState state) { 326 super.saveTo(state); 327 tableFilterComponent.saveState(state.createChild(THREADS_TABLE_FILTER)); 328 saveToLocal(); 329 } 330 331 private void saveToLocal() { 332 flavorSelectorState = flavorSelector.getFlavorSelectorState(); 333 histogramSelectionState = table.getManager().getSelectionState(); 334 visibleRange = chart.getVisibleRange(); 335 } 336 337 @Override 338 protected List<IAction> initializeChartConfiguration(IState state) { 339 this.reloadThreads = true; 340 this.isChartModified = false; 341 this.isChartMenuActionsInit = false; 342 lanes = new ThreadGraphLanes(() -> getDataSource(), () -> buildChart()); 343 return lanes.initializeChartConfiguration(Stream.of(state.getChildren(THREAD_LANE))); 344 } 345 } 346 347 private FlavorSelectorState flavorSelectorState; 348 private SelectionState histogramSelectionState; 349 private IItemFilter tableFilter; 350 private IRange<IQuantity> visibleRange; 351 352 public ThreadsPage(IPageDefinition definition, StreamModel model, IPageContainer editor) { 353 super(definition, model, editor); 354 visibleRange = editor.getRecordingRange(); 355 } 356 357 @Override 358 public IPageUI display(Composite parent, FormToolkit toolkit, IPageContainer editor, IState state) { 359 return new ThreadsPageUi(parent, toolkit, editor, state); 360 } 361 |