1 /* 2 * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. 3 * 4 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 5 * 6 * The contents of this file are subject to the terms of either the Universal Permissive License 7 * v 1.0 as shown at http://oss.oracle.com/licenses/upl 8 * 9 * or the following license: 10 * 11 * Redistribution and use in source and binary forms, with or without modification, are permitted 12 * provided that the following conditions are met: 13 * 14 * 1. Redistributions of source code must retain the above copyright notice, this list of conditions 15 * and the following disclaimer. 16 * 17 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of 18 * conditions and the following disclaimer in the documentation and/or other materials provided with 19 * the distribution. 20 * 21 * 3. Neither the name of the copyright holder nor the names of its contributors may be used to 22 * endorse or promote products derived from this software without specific prior written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 25 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 26 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 27 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 29 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 30 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY 31 * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 */ 33 package org.openjdk.jmc.flightrecorder.ui.views.stacktrace; 34 35 import java.util.Arrays; 36 import java.util.List; 37 import java.util.Optional; 38 import java.util.concurrent.CompletableFuture; 39 import java.util.logging.Level; 40 import java.util.stream.Collectors; 41 import java.util.stream.IntStream; 42 import java.util.stream.Stream; 43 44 import org.eclipse.core.runtime.IAdapterFactory; 45 import org.eclipse.core.runtime.Platform; 46 import org.eclipse.jface.action.Action; 47 import org.eclipse.jface.action.IAction; 48 import org.eclipse.jface.action.IMenuManager; 49 import org.eclipse.jface.action.IToolBarManager; 50 import org.eclipse.jface.action.MenuManager; 51 import org.eclipse.jface.action.Separator; 52 import org.eclipse.jface.viewers.AbstractTreeViewer; 53 import org.eclipse.jface.viewers.ColumnLabelProvider; 54 import org.eclipse.jface.viewers.ColumnViewer; 55 import org.eclipse.jface.viewers.ColumnViewerToolTipSupport; 56 import org.eclipse.jface.viewers.ISelection; 57 import org.eclipse.jface.viewers.ISelectionChangedListener; 58 import org.eclipse.jface.viewers.IStructuredSelection; 59 import org.eclipse.jface.viewers.ITreeContentProvider; 60 import org.eclipse.jface.viewers.SelectionChangedEvent; 61 import org.eclipse.jface.viewers.StructuredSelection; 62 import org.eclipse.jface.viewers.StructuredViewer; 63 import org.eclipse.jface.viewers.TableViewer; 64 import org.eclipse.jface.viewers.TableViewerColumn; 65 import org.eclipse.jface.viewers.TreeViewer; 66 import org.eclipse.jface.viewers.TreeViewerColumn; 67 import org.eclipse.jface.viewers.ViewerCell; 68 import org.eclipse.jface.viewers.ViewerColumn; 69 import org.eclipse.jface.window.ToolTip; 70 import org.eclipse.swt.SWT; 71 import org.eclipse.swt.events.TraverseEvent; 72 import org.eclipse.swt.events.TraverseListener; 73 import org.eclipse.swt.graphics.Color; 74 import org.eclipse.swt.graphics.Image; 75 import org.eclipse.swt.graphics.RGB; 76 import org.eclipse.swt.widgets.Composite; 77 import org.eclipse.swt.widgets.Display; 78 import org.eclipse.swt.widgets.Event; 79 import org.eclipse.swt.widgets.Listener; 80 import org.eclipse.swt.widgets.TableColumn; 81 import org.eclipse.swt.widgets.TreeColumn; 82 import org.eclipse.ui.IEditorPart; 83 import org.eclipse.ui.IMemento; 84 import org.eclipse.ui.ISelectionListener; 85 import org.eclipse.ui.IViewSite; 86 import org.eclipse.ui.IWorkbenchPart; 87 import org.eclipse.ui.PartInitException; 88 import org.eclipse.ui.PlatformUI; 89 import org.eclipse.ui.forms.widgets.FormText; 90 import org.eclipse.ui.part.ViewPart; 91 92 import org.openjdk.jmc.common.IDisplayable; 93 import org.openjdk.jmc.common.IMCFrame; 94 import org.openjdk.jmc.common.IState; 95 import org.openjdk.jmc.common.collection.SimpleArray; 96 import org.openjdk.jmc.common.item.IItemCollection; 97 import org.openjdk.jmc.common.unit.UnitLookup; 98 import org.openjdk.jmc.common.util.StateToolkit; 99 import org.openjdk.jmc.flightrecorder.stacktrace.FrameSeparator; 100 import org.openjdk.jmc.flightrecorder.stacktrace.FrameSeparator.FrameCategorization; 101 import org.openjdk.jmc.flightrecorder.stacktrace.StacktraceFormatToolkit; 102 import org.openjdk.jmc.flightrecorder.stacktrace.StacktraceFrame; 103 import org.openjdk.jmc.flightrecorder.stacktrace.StacktraceModel; 104 import org.openjdk.jmc.flightrecorder.stacktrace.StacktraceModel.Branch; 105 import org.openjdk.jmc.flightrecorder.stacktrace.StacktraceModel.Fork; 106 import org.openjdk.jmc.flightrecorder.ui.FlightRecorderUI; 107 import org.openjdk.jmc.flightrecorder.ui.IPageContainer; 108 import org.openjdk.jmc.flightrecorder.ui.ItemCollectionToolkit; 109 import org.openjdk.jmc.flightrecorder.ui.common.ImageConstants; 110 import org.openjdk.jmc.flightrecorder.ui.messages.internal.Messages; 111 import org.openjdk.jmc.flightrecorder.ui.selection.IFlavoredSelection; 112 import org.openjdk.jmc.flightrecorder.ui.selection.SelectionStore; 113 import org.openjdk.jmc.flightrecorder.ui.selection.SelectionStoreActionToolkit; 114 import org.openjdk.jmc.flightrecorder.ui.selection.StacktraceFrameSelection; 115 import org.openjdk.jmc.ui.CoreImages; 116 import org.openjdk.jmc.ui.UIPlugin; 117 import org.openjdk.jmc.ui.accessibility.FocusTracker; 118 import org.openjdk.jmc.ui.common.util.AdapterUtil; 119 import org.openjdk.jmc.ui.handlers.ActionToolkit; 120 import org.openjdk.jmc.ui.handlers.CopySelectionAction; 121 import org.openjdk.jmc.ui.handlers.InFocusHandlerActivator; 122 import org.openjdk.jmc.ui.handlers.MCContextMenuManager; 123 import org.openjdk.jmc.ui.handlers.MethodFormatter; 124 import org.openjdk.jmc.ui.misc.AbstractStructuredContentProvider; 125 import org.openjdk.jmc.ui.misc.CompositeToolkit; 126 import org.openjdk.jmc.ui.misc.CopySettings; 127 import org.openjdk.jmc.ui.misc.DisplayToolkit; 128 import org.openjdk.jmc.ui.misc.FormatToolkit; 129 import org.openjdk.jmc.ui.misc.MementoToolkit; 130 import org.openjdk.jmc.ui.misc.SWTColorToolkit; 131 132 public class StacktraceView extends ViewPart implements ISelectionListener { 133 134 static { 135 // Adapt using IAdapterFactory to support object contribution for IMCMethod (e.g jump to source) 136 Platform.getAdapterManager().registerAdapters(new IAdapterFactory() { 137 138 @Override 139 public <T> T getAdapter(Object adaptableObject, Class<T> adapterType) { 140 if (adaptableObject instanceof StacktraceFrame && adapterType == IMCFrame.class) { 141 return adapterType.cast(((StacktraceFrame) adaptableObject).getFrame()); 142 } 143 return null; 144 } 145 146 @Override 147 public Class<?>[] getAdapterList() { 148 return new Class[] {IMCFrame.class}; 149 } 150 }, StacktraceFrame.class); 151 } 152 153 private class GroupByAction extends Action { 154 155 private final boolean fromThreadRootAction; 156 157 GroupByAction(boolean fromRoot) { 158 super(fromRoot ? Messages.STACKTRACE_VIEW_THREAD_ROOT : Messages.STACKTRACE_VIEW_LAST_FRAME, 159 IAction.AS_RADIO_BUTTON); 160 fromThreadRootAction = fromRoot; 161 setToolTipText(fromRoot ? Messages.STACKTRACE_VIEW_GROUP_TRACES_FROM_ROOT 162 : Messages.STACKTRACE_VIEW_GROUP_TRACES_FROM_LAST_FRAME); 163 setImageDescriptor(fromRoot ? CoreImages.THREAD : CoreImages.METHOD_NON_OPTIMIZED); 164 setChecked(fromRoot == threadRootAtTop); 165 } 166 167 @Override 168 public void run() { 169 boolean newValue = isChecked() == fromThreadRootAction; 170 if (newValue != threadRootAtTop) { 171 threadRootAtTop = newValue; 172 rebuildModel(); 173 } 174 } 175 } 176 177 private static final String HELP_CONTEXT_ID = FlightRecorderUI.PLUGIN_ID + ".StacktraceView"; //$NON-NLS-1$ 178 // FIXME: Define dynamic color (editable in preferences, to handle dark themes etc.) 179 private static final Color ALTERNATE_COLOR = SWTColorToolkit.getColor(new RGB(255, 255, 240)); 180 private static final String COUNT_IMG_KEY = "countColor"; //$NON-NLS-1$ 181 private static final Color COUNT_COLOR = SWTColorToolkit.getColor(new RGB(100, 200, 100)); 182 private static final String SIBLINGS_IMG_KEY = "siblingsColor"; //$NON-NLS-1$ 183 private static final Color SIBLINGS_COUNT_COLOR = SWTColorToolkit.getColor(new RGB(170, 250, 170)); 184 private static final int[] DEFAULT_COLUMN_WIDTHS = {700, 150}; 185 private static final String THREAD_ROOT_KEY = "threadRootAtTop"; //$NON-NLS-1$ 186 private static final String FRAME_OPTIMIZATION_KEY = "distinguishFramesByOptimization"; //$NON-NLS-1$ 187 private static final String FRAME_CATEGORIZATION_KEY = "distinguishFramesCategorization"; //$NON-NLS-1$ 188 private static final String TREE_LAYOUT_KEY = "treeLayout"; //$NON-NLS-1$ 189 private static final String REDUCED_TREE_KEY = "reducedTreeLayout"; //$NON-NLS-1$ 190 private static final String METHOD_FORMAT_KEY = "metodFormat"; //$NON-NLS-1$ 191 private static final String COLUMNS_KEY = "columns"; //$NON-NLS-1$ 192 private static final String COLUMNS_SEPARATOR = " "; //$NON-NLS-1$ 193 private ColumnViewer viewer; 194 private boolean treeLayout; 195 private boolean reducedTree; 196 private boolean threadRootAtTop; 197 private IItemCollection itemsToShow; 198 private MethodFormatter methodFormatter; 199 private FrameSeparatorManager frameSeparatorManager; 200 private GroupByAction[] groupByActions; 201 private IAction[] layoutActions; 202 private ViewerAction[] viewerActions; 203 private int[] columnWidths; 204 205 private static class StacktraceViewToolTipSupport extends ColumnViewerToolTipSupport { 206 207 StacktraceViewToolTipSupport(ColumnViewer viewer) { 208 super(viewer, ToolTip.NO_RECREATE, false); 209 } 210 211 @Override 212 protected Composite createViewerToolTipContentArea(Event event, ViewerCell cell, Composite parent) { 213 FormText formText = CompositeToolkit.createInfoFormText(parent); 214 formText.setImage(COUNT_IMG_KEY, SWTColorToolkit.getColorThumbnail(COUNT_COLOR.getRGB())); 215 formText.setImage(SIBLINGS_IMG_KEY, SWTColorToolkit.getColorThumbnail(SIBLINGS_COUNT_COLOR.getRGB())); 216 formText.setText(getText(event), true, false); 217 return formText; 218 } 219 220 } 221 222 private static class ViewerAction extends Action implements ISelectionChangedListener { 223 224 protected StructuredViewer provider = null; 225 226 public ViewerAction(String text) { 227 super(text); 228 setViewer(null); 229 setEnabled(false); 230 } 231 232 public void setViewer(StructuredViewer provider) { 233 this.provider = provider; 234 if (provider != null) { 235 provider.addSelectionChangedListener(this); 236 selectionChanged(getStructuredSelection()); 237 } else { 238 setEnabled(false); 239 } 240 } 241 242 @Override 243 public void selectionChanged(SelectionChangedEvent event) { 244 ISelection selection = event.getSelection(); 245 if (selection instanceof IStructuredSelection) { 246 selectionChanged((IStructuredSelection) selection); 247 } 248 } 249 250 protected void selectionChanged(IStructuredSelection selection) { 251 } 252 253 protected IStructuredSelection getStructuredSelection() { 254 if (provider != null) { 255 ISelection selection = provider.getSelection(); 256 if (selection instanceof IStructuredSelection) { 257 return (IStructuredSelection) selection; 258 } 259 } 260 return new StructuredSelection(); 261 } 262 263 } 264 265 static class SelectFrameGroupAction extends ViewerAction { 266 267 SelectFrameGroupAction() { 268 super(Messages.STACKTRACE_VIEW_FRAME_GROUP_CHOOSE); 269 setImageDescriptor( 270 FlightRecorderUI.getDefault().getMCImageDescriptor(ImageConstants.ICON_ARROW_FORK3_STAR)); 271 setAccelerator(SWT.CR); 272 } 273 274 @Override 275 public void setViewer(StructuredViewer provider) { 276 super.setViewer(provider); 277 if (provider != null) { 278 provider.addDoubleClickListener(e -> { 279 if (isEnabled()) { 280 run(); 281 } 282 }); 283 } 284 } 285 286 @Override 287 public void run() { 288 StacktraceFrame frame = (StacktraceFrame) getStructuredSelection().getFirstElement(); 289 // FIXME: Would like to move the table cursor after changing sibling state, not just the selection. 290 if (isInOpenFork(frame)) { 291 frame.getBranch().selectSibling(0); 292 } else { 293 frame.getBranch().selectSibling(null); 294 } 295 provider.getControl().setRedraw(false); 296 try { 297 provider.refresh(); 298 } finally { 299 provider.getControl().setRedraw(true); 300 } 301 provider.setSelection(new StructuredSelection(frame)); 302 303 } 304 305 @Override 306 public void selectionChanged(IStructuredSelection selection) { 307 setEnabled(selection.size() == 1 308 && isFirstInBranchWithSiblings((StacktraceFrame) selection.getFirstElement())); 309 } 310 311 } 312 313 static class NavigateAction extends ViewerAction implements TraverseListener { 314 315 private final int offset; 316 317 NavigateAction(boolean forward) { 318 super(forward ? Messages.STACKTRACE_VIEW_FRAME_GROUP_NEXT : Messages.STACKTRACE_VIEW_FRAME_GROUP_PREVIOUS); 319 setImageDescriptor( 320 forward ? FlightRecorderUI.getDefault().getMCImageDescriptor(ImageConstants.ICON_ARROW_FORK3_RIGHT) 321 : FlightRecorderUI.getDefault().getMCImageDescriptor(ImageConstants.ICON_ARROW_FORK3_LEFT)); 322 offset = forward ? 1 : -1; 323 setAccelerator(forward ? SWT.ARROW_RIGHT : SWT.ARROW_LEFT); 324 } 325 326 @Override 327 public void setViewer(StructuredViewer provider) { 328 super.setViewer(provider); 329 if (provider != null) { 330 provider.getControl().addTraverseListener(this); 331 } 332 } 333 334 @Override 335 public void run() { 336 Branch branch = ((StacktraceFrame) getStructuredSelection().getFirstElement()).getBranch(); 337 Branch selectedSibling = branch.selectSibling(offset); 338 provider.refresh(); 339 provider.setSelection(new StructuredSelection(selectedSibling.getFirstFrame())); 340 } 341 342 @Override 343 protected void selectionChanged(IStructuredSelection selection) { 344 setEnabled(selection.size() == 1 && isNavigationFrame((StacktraceFrame) selection.getFirstElement())); 345 } 346 347 @Override 348 public void keyTraversed(TraverseEvent e) { 349 if (isEnabled()) { 350 if (e.keyCode == getAccelerator()) { 351 run(); 352 e.detail = SWT.TRAVERSE_NONE; 353 e.doit = true; 354 } 355 } 356 } 357 358 }; 359 360 @Override 361 public void init(IViewSite site, IMemento memento) throws PartInitException { 362 super.init(site, memento); 363 IState state = MementoToolkit.asState(memento); 364 threadRootAtTop = StateToolkit.readBoolean(state, THREAD_ROOT_KEY, false); 365 groupByActions = new GroupByAction[] {new GroupByAction(false), new GroupByAction(true)}; 366 treeLayout = StateToolkit.readBoolean(state, TREE_LAYOUT_KEY, false); 367 reducedTree = StateToolkit.readBoolean(state, REDUCED_TREE_KEY, true); 368 369 IAction reducedTreeAction = ActionToolkit.checkAction(this::setReducedTree, 370 Messages.STACKTRACE_VIEW_REDUCE_TREE_DEPTH, null); 371 reducedTreeAction.setChecked(reducedTree); 372 IAction treeAction = ActionToolkit.checkAction(this::setTreeLayout, Messages.STACKTRACE_VIEW_SHOW_AS_TREE, 373 CoreImages.TREE_MODE); 374 treeAction.setChecked(treeLayout); 375 layoutActions = new IAction[] {treeAction, reducedTreeAction}; 376 377 NavigateAction forwardAction = new NavigateAction(true); 378 NavigateAction backwardAction = new NavigateAction(false); 379 SelectFrameGroupAction selectGroupAction = new SelectFrameGroupAction(); 380 viewerActions = new ViewerAction[] {selectGroupAction, forwardAction, backwardAction}; 381 382 try { 383 columnWidths = Optional.ofNullable(state) 384 .map(s -> Stream.of(s.getAttribute(COLUMNS_KEY).split(COLUMNS_SEPARATOR)) 385 .mapToInt(Integer::parseInt).toArray()) 386 .filter(widths -> widths.length == DEFAULT_COLUMN_WIDTHS.length 387 && Arrays.stream(widths).allMatch(w -> w >= 0)) 388 .orElse(DEFAULT_COLUMN_WIDTHS); 389 } catch (RuntimeException e) { 390 columnWidths = DEFAULT_COLUMN_WIDTHS; 391 } 392 393 FrameCategorization categorization = StateToolkit.readEnum(state, FRAME_CATEGORIZATION_KEY, 394 FrameCategorization.METHOD, FrameCategorization.class); 395 boolean byOptimization = StateToolkit.readBoolean(state, FRAME_OPTIMIZATION_KEY, false); 396 frameSeparatorManager = new FrameSeparatorManager(this::rebuildModel, 397 new FrameSeparator(categorization, byOptimization)); 398 methodFormatter = new MethodFormatter(memento == null ? null : memento.getChild(METHOD_FORMAT_KEY), 399 () -> viewer.refresh()); 400 IMenuManager siteMenu = site.getActionBars().getMenuManager(); 401 siteMenu.add(new Separator(MCContextMenuManager.GROUP_TOP)); 402 siteMenu.add(new Separator(MCContextMenuManager.GROUP_VIEWER_SETUP)); 403 addOptions(siteMenu); 404 IToolBarManager toolBar = site.getActionBars().getToolBarManager(); 405 toolBar.add(selectGroupAction); 406 toolBar.add(backwardAction); 407 toolBar.add(forwardAction); 408 toolBar.add(new Separator()); 409 toolBar.add(treeAction); 410 toolBar.add(new Separator()); 411 Stream.of(groupByActions).forEach(toolBar::add); 412 413 getSite().getPage().addSelectionListener(this); 414 } 415 416 @Override 417 public void dispose() { 418 getSite().getPage().removeSelectionListener(this); 419 super.dispose(); 420 } 421 422 @Override 423 public void createPartControl(Composite parent) { 424 buildViewer(parent); 425 } 426 427 private void setTreeLayout(boolean treeLayout) { 428 this.treeLayout = treeLayout; 429 rebuildViewer(); 430 } 431 432 private void setReducedTree(boolean reducedTree) { 433 this.reducedTree = reducedTree; 434 if (viewer instanceof TreeViewer) { 435 viewer.setContentProvider(createTreeContentProvider()); 436 } 437 } 438 439 private void rebuildViewer() { 440 boolean hasFocus = viewer.getControl().isFocusControl(); 441 ISelection oldSelection = viewer.getSelection(); 442 Fork oldInput = (Fork) viewer.getInput(); 443 Composite parent = viewer.getControl().getParent(); 444 viewer.getControl().dispose(); 445 buildViewer(parent); 446 if (hasFocus) { 447 viewer.getControl().setFocus(); 448 } 449 parent.layout(); 450 if (viewer instanceof TreeViewer) { 451 // Async set input to avoid drawing issue with tree 452 Display.getCurrent().asyncExec(() -> { 453 if (!viewer.getControl().isDisposed()) { 454 setViewerInput(oldInput); 455 if (reducedTree && oldInput != null) { 456 Branch selectedBranch = getLastSelectedBranch(oldInput); 457 if (selectedBranch != null) { 458 viewer.getControl().setRedraw(false); 459 ((TreeViewer) viewer).expandToLevel(selectedBranch.getLastFrame(), 460 AbstractTreeViewer.ALL_LEVELS); 461 viewer.getControl().setRedraw(true); 462 } 463 } 464 viewer.setSelection(oldSelection, true); 465 } 466 }); 467 } else { 468 Branch branch = null; 469 for (Object o : ((IStructuredSelection) oldSelection).toList()) { 470 if (branch == null) { 471 branch = ((StacktraceFrame) o).getBranch(); 472 } else if (branch != ((StacktraceFrame) o).getBranch()) { 473 branch = null; 474 break; 475 } 476 } 477 if (branch != null) { 478 branch.selectSibling(0); 479 } 480 setViewerInput(oldInput); 481 viewer.setSelection(oldSelection, true); 482 } 483 } 484 485 private void buildViewer(Composite parent) { 486 if (treeLayout) { 487 viewer = buildTree(parent); 488 } else { 489 viewer = buildTable(parent); 490 } 491 new StacktraceViewToolTipSupport(viewer); 492 MCContextMenuManager mm = MCContextMenuManager.create(viewer.getControl()); 493 CopySelectionAction copyAction = new CopySelectionAction(viewer, 494 FormatToolkit.selectionFormatter(stackTraceLabelProvider, countLabelProvider)); 495 InFocusHandlerActivator.install(viewer.getControl(), copyAction); 496 mm.appendToGroup(MCContextMenuManager.GROUP_EDIT, copyAction); 497 mm.appendToGroup(MCContextMenuManager.GROUP_EDIT, CopySettings.getInstance().createContributionItem()); 498 addOptions(mm); 499 getSite().registerContextMenu(mm, viewer); 500 if (!treeLayout) { 501 String navigateGroupName = "NAVIGATE"; //$NON-NLS-1$ 502 mm.insert(0, new Separator(navigateGroupName)); 503 Stream.of(viewerActions).forEach(a -> { 504 a.setViewer(viewer); 505 mm.appendToGroup(navigateGroupName, a); 506 }); 507 } else { 508 Stream.of(viewerActions).forEach(a -> a.setViewer(null)); 509 } 510 511 viewer.getControl().addListener(SWT.EraseItem, COUNT_BACKGROUND_DRAWER); 512 viewer.getControl().addDisposeListener(e -> columnWidths = getColumnWidths()); 513 514 buildColumn(viewer, Messages.STACKTRACE_VIEW_STACK_TRACE, SWT.NONE, columnWidths[0]) 515 .setLabelProvider(stackTraceLabelProvider); 516 buildColumn(viewer, Messages.STACKTRACE_VIEW_COUNT_COLUMN_NAME, SWT.RIGHT, columnWidths[1]) 517 .setLabelProvider(countLabelProvider); 518 519 PlatformUI.getWorkbench().getHelpSystem().setHelp(viewer.getControl(), HELP_CONTEXT_ID); 520 521 if (UIPlugin.getDefault().getAccessibilityMode()) { 522 if (treeLayout) { 523 FocusTracker.enableFocusTracking(((TreeViewer) viewer).getTree()); 524 } else { 525 FocusTracker.enableFocusTracking(((TableViewer) viewer).getTable()); 526 } 527 } 528 } 529 530 private static TableViewer buildTable(Composite parent) { 531 TableViewer tableViewer = new TableViewer(parent, 532 SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.FULL_SELECTION | SWT.BORDER); 533 tableViewer.setContentProvider(new AbstractStructuredContentProvider() { 534 @Override 535 public Object[] getElements(Object inputElement) { 536 SimpleArray<StacktraceFrame> trace = new SimpleArray<>(new StacktraceFrame[100]); 537 addSelectedBranches((Fork) inputElement, trace, false); 538 return trace.elements(); 539 } 540 }); 541 tableViewer.getTable().setHeaderVisible(true); 542 tableViewer.getTable().setLinesVisible(true); 543 return tableViewer; 544 } 545 546 private TreeViewer buildTree(Composite parent) { 547 TreeViewer treeViewer = new TreeViewer(parent, 548 SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.FULL_SELECTION | SWT.BORDER); 549 treeViewer.setContentProvider(createTreeContentProvider()); 550 treeViewer.getTree().setHeaderVisible(true); 551 treeViewer.getTree().setLinesVisible(true); 552 return treeViewer; 553 } 554 555 private static ViewerColumn buildColumn(ColumnViewer viewer, String text, int style, int width) { 556 if (viewer instanceof TableViewer) { 557 TableViewerColumn vc = new TableViewerColumn((TableViewer) viewer, style); 558 vc.getColumn().setWidth(width); 559 vc.getColumn().setText(text); 560 return vc; 561 } else { 562 TreeViewerColumn vc = new TreeViewerColumn((TreeViewer) viewer, style); 563 vc.getColumn().setWidth(width); 564 vc.getColumn().setText(text); 565 return vc; 566 } 567 } 568 569 private int[] getColumnWidths() { 570 if (!viewer.getControl().isDisposed()) { 571 if (viewer instanceof TableViewer) { 572 return Stream.of(((TableViewer) viewer).getTable().getColumns()).mapToInt(TableColumn::getWidth) 573 .toArray(); 574 } else { 575 return Stream.of(((TreeViewer) viewer).getTree().getColumns()).mapToInt(TreeColumn::getWidth).toArray(); 576 } 577 } 578 return columnWidths; 579 } 580 581 private void addOptions(IMenuManager menu) { 582 MenuManager groupMenu = new MenuManager(Messages.STACKTRACE_VIEW_GROUP_FROM); 583 Stream.of(groupByActions).forEach(groupMenu::add); 584 menu.appendToGroup(MCContextMenuManager.GROUP_TOP, groupMenu); 585 menu.appendToGroup(MCContextMenuManager.GROUP_TOP, frameSeparatorManager.createMenu()); 586 MenuManager layoutMenu = new MenuManager(Messages.STACKTRACE_VIEW_LAYOUT_OPTIONS); 587 Stream.of(layoutActions).forEach(layoutMenu::add); 588 menu.appendToGroup(MCContextMenuManager.GROUP_VIEWER_SETUP, layoutMenu); 589 menu.appendToGroup(MCContextMenuManager.GROUP_VIEWER_SETUP, methodFormatter.createMenu()); 590 SelectionStoreActionToolkit.addSelectionStoreActions(viewer, this::getSelectionStore, 591 this::getFlavoredSelection, menu); 592 } 593 594 private IFlavoredSelection getFlavoredSelection() { 595 ISelection selection = viewer.getSelection(); 596 if (selection instanceof IStructuredSelection && !selection.isEmpty()) { 597 List<?> selected = ((StructuredSelection) selection).toList(); 598 StacktraceFrame frame = (StacktraceFrame) selected.get(0); 599 return new StacktraceFrameSelection(frame.getFrame(), 600 ItemCollectionToolkit.build(Stream.of(frame.getItems().elements())), 601 Messages.STACKTRACE_VIEW_SELECTION); 602 } 603 return null; 604 } 605 606 private SelectionStore getSelectionStore() { 607 IEditorPart editorPart = null; 608 try { 609 PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().getActiveEditor(); 610 editorPart = getSite().getPage().getActiveEditor(); 611 } catch (Exception e) { 612 FlightRecorderUI.getDefault().getLogger().log(Level.INFO, 613 "Got exception while trying to get the active editor", e); //$NON-NLS-1$ 614 } 615 if (editorPart instanceof IPageContainer) { 616 return ((IPageContainer) editorPart).getSelectionStore(); 617 } 618 return null; 619 } 620 621 @Override 622 public void setFocus() { 623 viewer.getControl().setFocus(); 624 } 625 626 @Override 627 public void saveState(IMemento memento) { 628 memento.putString(COLUMNS_KEY, IntStream.of(getColumnWidths()).mapToObj(Integer::toString) 629 .collect(Collectors.joining(COLUMNS_SEPARATOR))); 630 methodFormatter.saveState(memento.createChild(METHOD_FORMAT_KEY)); 631 memento.putBoolean(THREAD_ROOT_KEY, threadRootAtTop); 632 memento.putBoolean(TREE_LAYOUT_KEY, treeLayout); 633 memento.putBoolean(REDUCED_TREE_KEY, reducedTree); 634 FrameSeparator frameSeparator = frameSeparatorManager.getFrameSeparator(); 635 memento.putBoolean(FRAME_OPTIMIZATION_KEY, frameSeparator.isDistinguishFramesByOptimization()); 636 memento.putString(FRAME_CATEGORIZATION_KEY, frameSeparator.getCategorization().name()); 637 } 638 639 @Override 640 public void selectionChanged(IWorkbenchPart part, ISelection selection) { 641 if (selection instanceof IStructuredSelection) { 642 Object first = ((IStructuredSelection) selection).getFirstElement(); 643 IItemCollection items = AdapterUtil.getAdapter(first, IItemCollection.class); 644 if (items != null && !items.equals(itemsToShow)) { 645 setItems(items); 646 } 647 } 648 } 649 650 private void setItems(IItemCollection items) { 651 itemsToShow = items; 652 rebuildModel(); 653 } 654 655 private StacktraceModel createStacktraceModel() { 656 return new StacktraceModel(threadRootAtTop, frameSeparatorManager.getFrameSeparator(), itemsToShow); 657 } 658 659 private void rebuildModel() { 660 // Release old model before building the new 661 setViewerInput(null); 662 CompletableFuture<StacktraceModel> modelPreparer = getModelPreparer(createStacktraceModel(), !treeLayout); 663 modelPreparer.thenAcceptAsync(this::setModel, DisplayToolkit.inDisplayThread()) 664 .exceptionally(StacktraceView::handleModelBuildException); 665 } 666 667 private static CompletableFuture<StacktraceModel> getModelPreparer( 668 StacktraceModel model, boolean materializeSelectedBranches) { 669 return CompletableFuture.supplyAsync(() -> { 670 Fork root = model.getRootFork(); 671 if (materializeSelectedBranches) { 672 Branch selectedBranch = getLastSelectedBranch(root); 673 if (selectedBranch != null) { 674 selectedBranch.getEndFork(); 675 } 676 } 677 return model; 678 }); 679 } 680 681 private static Void handleModelBuildException(Throwable ex) { 682 FlightRecorderUI.getDefault().getLogger().log(Level.SEVERE, "Failed to build stacktrace view model", ex); //$NON-NLS-1$ 683 return null; 684 } 685 686 private void setModel(StacktraceModel model) { 687 // Check that the model is up to date 688 if (model.equals(createStacktraceModel()) && !viewer.getControl().isDisposed()) { 689 setViewerInput(model.getRootFork()); 690 } 691 } 692 693 private void setViewerInput(Fork rootFork) { 694 // NOTE: will be slow for TreeViewer if number of roots or children of a node are more than ~1000 695 viewer.setInput(rootFork); 696 } 697 698 private ITreeContentProvider createTreeContentProvider() { 699 return reducedTree ? new StacktraceReducedTreeContentProvider() : new StacktraceTreeContentProvider(); 700 } 701 702 private static final Listener COUNT_BACKGROUND_DRAWER = new Listener() { 703 @Override 704 public void handleEvent(Event event) { 705 StacktraceFrame frame = (StacktraceFrame) event.item.getData(); 706 Fork rootFork = getRootFork(frame.getBranch().getParentFork()); 707 double total; 708 if (event.index == 1 && (total = rootFork.getItemsInFork()) > 0) { // index == 1 => count column 709 // Draw siblings 710 Fork parentFork = frame.getBranch().getParentFork(); 711 int forkOffset = parentFork.getItemOffset(); 712 int siblingsStart = (int) Math.floor(event.width * forkOffset / total); 713 int siblingsWidth = (int) Math.round(event.width * parentFork.getItemsInFork() / total); 714 event.gc.setBackground(SIBLINGS_COUNT_COLOR); 715 event.gc.fillRectangle(event.x + siblingsStart, event.y, siblingsWidth, event.height); 716 // Draw group 717 double offset = (forkOffset + frame.getBranch().getItemOffsetInFork()) / total; 718 double fraction = frame.getItemCount() / total; 719 event.gc.setBackground(COUNT_COLOR); 720 int startPixel = (int) Math.floor(event.width * offset); 721 int widthPixel = (int) Math.round(event.width * fraction); 722 event.gc.fillRectangle(event.x + startPixel, event.y, Math.max(widthPixel, 1), event.height); 723 event.detail &= ~SWT.BACKGROUND; 724 } 725 } 726 }; 727 728 private final ColumnLabelProvider countLabelProvider = new ColumnLabelProvider() { 729 @Override 730 public String getText(Object element) { 731 return Integer.toString(((StacktraceFrame) element).getItemCount()); 732 } 733 734 @Override 735 public String getToolTipText(Object element) { 736 StacktraceFrame frame = (StacktraceFrame) element; 737 Fork rootFork = getRootFork(frame.getBranch().getParentFork()); 738 int itemCount = frame.getItemCount(); 739 int totalCount = rootFork.getItemsInFork(); 740 Fork parentFork = frame.getBranch().getParentFork(); 741 int itemsInSiblings = parentFork.getItemsInFork() - frame.getBranch().getFirstFrame().getItemCount(); 742 String frameFraction = UnitLookup.PERCENT_UNITY.quantity(itemCount / (double) totalCount) 743 .displayUsing(IDisplayable.AUTO); 744 StringBuilder sb = new StringBuilder("<form>"); //$NON-NLS-1$ 745 sb.append("<li style='image' value='" + COUNT_IMG_KEY + "'>"); //$NON-NLS-1$ //$NON-NLS-2$ 746 sb.append(Messages.stackTraceMessage(itemCount, totalCount, frameFraction)); 747 sb.append("</li>"); //$NON-NLS-1$ 748 sb.append("<li style='image' value='" + SIBLINGS_IMG_KEY + "'>"); //$NON-NLS-1$ //$NON-NLS-2$ 749 sb.append(Messages.siblingMessage(itemsInSiblings, parentFork.getBranchCount() - 1)); 750 sb.append("</li>"); //$NON-NLS-1$ 751 sb.append("</form>"); //$NON-NLS-1$ 752 return sb.toString(); 753 } 754 }; 755 756 private final ColumnLabelProvider stackTraceLabelProvider = new ColumnLabelProvider() { 757 758 @Override 759 public String getText(Object element) { 760 IMCFrame frame = ((StacktraceFrame) element).getFrame(); 761 FrameSeparator frameSeparator = frameSeparatorManager.getFrameSeparator(); 762 return getText(frame, frameSeparator); 763 } 764 765 private String getText(IMCFrame frame, FrameSeparator frameSeparator) { 766 return StacktraceFormatToolkit.formatFrame(frame, frameSeparator, methodFormatter.showReturnValue(), 767 methodFormatter.showReturnValuePackage(), methodFormatter.showClassName(), 768 methodFormatter.showClassPackageName(), methodFormatter.showArguments(), 769 methodFormatter.showArgumentsPackage()); 770 } 771 772 @Override 773 public Image getImage(Object element) { 774 StacktraceFrame frame = (StacktraceFrame) element; 775 FlightRecorderUI plugin = FlightRecorderUI.getDefault(); 776 boolean isFirstInBranch = isFirstInBranchWithSiblings(frame); 777 boolean firstInOpenFork = isFirstInBranch && isInOpenFork(frame); 778 if (firstInOpenFork || treeLayout && (!reducedTree || isFirstInBranch)) { 779 return plugin.getImage( 780 threadRootAtTop ? ImageConstants.ICON_ARROW_CURVED_DOWN : ImageConstants.ICON_ARROW_CURVED_UP); 781 } else if (isFirstInBranchWithSiblings(frame)) { 782 return plugin.getImage( 783 threadRootAtTop ? ImageConstants.ICON_ARROW_FORK3_DOWN : ImageConstants.ICON_ARROW_FORK3_UP); 784 } else if (isLastFrame(frame)) { 785 return plugin.getImage(threadRootAtTop ? ImageConstants.ICON_ARROW_DOWN_END : ImageConstants.ICON_ARROW_UP_END); 786 } else { 787 return plugin.getImage(threadRootAtTop ? ImageConstants.ICON_ARROW_DOWN : ImageConstants.ICON_ARROW_UP); 788 } 789 } 790 791 @Override 792 public Color getBackground(Object element) { 793 if (treeLayout) { 794 return null; 795 } else { 796 int parentCount = 0; 797 Branch e = ((StacktraceFrame) element).getBranch(); 798 while (e != null) { 799 e = e.getParentFork().getParentBranch(); 800 parentCount++; 801 } 802 return parentCount % 2 == 0 ? null : ALTERNATE_COLOR; 803 } 804 } 805 }; 806 807 private static boolean isNavigationFrame(StacktraceFrame frame) { 808 return isFirstInBranchWithSiblings(frame) && !isInOpenFork(frame); 809 } 810 811 private static boolean isInOpenFork(StacktraceFrame frame) { 812 return frame.getBranch().getParentFork().getSelectedBranch() == null; 813 } 814 815 private static boolean isFirstInBranchWithSiblings(StacktraceFrame frame) { 816 return frame.getBranch().getFirstFrame() == frame && frame.getBranch().getParentFork().getBranchCount() > 1; 817 } 818 819 private static boolean isLastFrame(StacktraceFrame frame) { 820 return frame.getBranch().getLastFrame() == frame && frame.getBranch().getEndFork().getBranchCount() == 0; 821 } 822 823 /* 824 * FIXME: 'backwards' argument was used for displaying trace groups built from thread roots with 825 * the thread roots at the bottom. If we don't want to support that scenario then we can remove 826 * this argument. 827 */ 828 private static void addSelectedBranches(Fork fork, SimpleArray<StacktraceFrame> input, boolean backwards) { 829 Branch selectedBranch = fork.getSelectedBranch(); 830 if (selectedBranch == null) { 831 Stream.of(fork.getFirstFrames()).forEach(input::add); 832 } else if (backwards) { 833 addSelectedBranches(selectedBranch.getEndFork(), input, backwards); 834 StacktraceFrame[] tail = selectedBranch.getTailFrames(); 835 for (int i = tail.length; i > 0; i--) { 836 input.add(tail[i - 1]); 837 } 838 input.add(selectedBranch.getFirstFrame()); 839 } else { 840 input.add(selectedBranch.getFirstFrame()); 841 input.addAll(selectedBranch.getTailFrames()); 842 addSelectedBranches(selectedBranch.getEndFork(), input, backwards); 843 } 844 } 845 846 private static Branch getLastSelectedBranch(Fork fromFork) { 847 Branch lastSelectedBranch = null; 848 Branch branch = fromFork.getSelectedBranch(); 849 while (branch != null) { 850 lastSelectedBranch = branch; 851 branch = branch.getEndFork().getSelectedBranch(); 852 } 853 return lastSelectedBranch; 854 } 855 856 private static Fork getRootFork(Fork fork) { 857 while (fork.getParentBranch() != null) { 858 fork = fork.getParentBranch().getParentFork(); 859 } 860 return fork; 861 } 862 863 private static class StacktraceTreeContentProvider extends AbstractStructuredContentProvider 864 implements ITreeContentProvider { 865 866 @Override 867 public StacktraceFrame[] getElements(Object inputElement) { 868 return ((Fork) inputElement).getFirstFrames(); 869 } 870 871 @Override 872 public boolean hasChildren(Object element) { 873 StacktraceFrame frame = (StacktraceFrame) element; 874 return !isLastFrame(frame); 875 } 876 877 @Override 878 public StacktraceFrame[] getChildren(Object parentElement) { 879 StacktraceFrame frame = (StacktraceFrame) parentElement; 880 StacktraceFrame[] tailFrames = frame.getBranch().getTailFrames(); 881 if (frame.getIndexInBranch() == tailFrames.length) { 882 return frame.getBranch().getEndFork().getFirstFrames(); 883 } else { 884 return new StacktraceFrame[] {tailFrames[frame.getIndexInBranch()]}; 885 } 886 } 887 888 @Override 889 public StacktraceFrame getParent(Object element) { 890 StacktraceFrame frame = (StacktraceFrame) element; 891 int parentIndexInBranch = frame.getIndexInBranch() - 1; 892 if (parentIndexInBranch > 0) { 893 return frame.getBranch().getTailFrames()[parentIndexInBranch - 1]; 894 } else if (parentIndexInBranch == 0) { 895 return frame.getBranch().getFirstFrame(); 896 } else { 897 Branch parentBranch = frame.getBranch().getParentFork().getParentBranch(); 898 return parentBranch == null ? null : parentBranch.getLastFrame(); 899 } 900 } 901 }; 902 903 private static class StacktraceReducedTreeContentProvider extends AbstractStructuredContentProvider 904 implements ITreeContentProvider { 905 906 @Override 907 public StacktraceFrame[] getElements(Object inputElement) { 908 Fork rootFork = (Fork) inputElement; 909 if (rootFork.getBranchCount() == 1) { 910 Branch branch = rootFork.getBranch(0); 911 return Stream 912 .concat(Stream.concat(Stream.of(branch.getFirstFrame()), Stream.of(branch.getTailFrames())), 913 Stream.of(branch.getEndFork().getFirstFrames())) 914 .toArray(StacktraceFrame[]::new); 915 } else { 916 return rootFork.getFirstFrames(); 917 } 918 } 919 920 @Override 921 public boolean hasChildren(Object element) { 922 StacktraceFrame frame = (StacktraceFrame) element; 923 return isFirstInBranchWithSiblings(frame) && frame.getBranch().hasTail(); 924 } 925 926 @Override 927 public StacktraceFrame[] getChildren(Object parentElement) { 928 Stream<StacktraceFrame> children = Stream.empty(); 929 StacktraceFrame frame = (StacktraceFrame) parentElement; 930 if (isFirstInBranchWithSiblings(frame)) { 931 children = Stream.concat(Stream.of(frame.getBranch().getTailFrames()), 932 Stream.of(frame.getBranch().getEndFork().getFirstFrames())); 933 } 934 return children.toArray(StacktraceFrame[]::new); 935 } 936 937 @Override 938 public StacktraceFrame getParent(Object element) { 939 StacktraceFrame frame = (StacktraceFrame) element; 940 if (isFirstInBranchWithSiblings(frame) || frame.getBranch().getParentFork().getBranchCount() == 1) { 941 Branch parentBranch = frame.getBranch().getParentFork().getParentBranch(); 942 return parentBranch == null ? null : parentBranch.getFirstFrame(); 943 } else { 944 return frame.getBranch().getFirstFrame(); 945 } 946 } 947 } 948 949 }