1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.ide.eclipse.gltrace.editors; 18 19 import com.android.ddmuilib.AbstractBufferFindTarget; 20 import com.android.ddmuilib.FindDialog; 21 import com.android.ide.eclipse.gltrace.GLProtoBuf.GLMessage.Function; 22 import com.android.ide.eclipse.gltrace.GlTracePlugin; 23 import com.android.ide.eclipse.gltrace.SwtUtils; 24 import com.android.ide.eclipse.gltrace.TraceFileParserTask; 25 import com.android.ide.eclipse.gltrace.editors.DurationMinimap.ICallSelectionListener; 26 import com.android.ide.eclipse.gltrace.editors.GLCallGroups.GLCallNode; 27 import com.android.ide.eclipse.gltrace.model.GLCall; 28 import com.android.ide.eclipse.gltrace.model.GLFrame; 29 import com.android.ide.eclipse.gltrace.model.GLTrace; 30 import com.android.ide.eclipse.gltrace.views.FrameSummaryViewPage; 31 import com.android.ide.eclipse.gltrace.views.detail.DetailsPage; 32 import com.google.common.base.Charsets; 33 import com.google.common.io.Files; 34 35 import org.eclipse.core.runtime.IProgressMonitor; 36 import org.eclipse.core.runtime.IStatus; 37 import org.eclipse.core.runtime.Status; 38 import org.eclipse.core.runtime.jobs.Job; 39 import org.eclipse.jface.action.Action; 40 import org.eclipse.jface.dialogs.ErrorDialog; 41 import org.eclipse.jface.dialogs.MessageDialog; 42 import org.eclipse.jface.dialogs.ProgressMonitorDialog; 43 import org.eclipse.jface.resource.ImageDescriptor; 44 import org.eclipse.jface.viewers.CellLabelProvider; 45 import org.eclipse.jface.viewers.ColumnLabelProvider; 46 import org.eclipse.jface.viewers.ISelection; 47 import org.eclipse.jface.viewers.ISelectionChangedListener; 48 import org.eclipse.jface.viewers.ISelectionProvider; 49 import org.eclipse.jface.viewers.ITreeContentProvider; 50 import org.eclipse.jface.viewers.TreeViewer; 51 import org.eclipse.jface.viewers.TreeViewerColumn; 52 import org.eclipse.jface.viewers.Viewer; 53 import org.eclipse.jface.viewers.ViewerCell; 54 import org.eclipse.jface.viewers.ViewerFilter; 55 import org.eclipse.swt.SWT; 56 import org.eclipse.swt.dnd.Clipboard; 57 import org.eclipse.swt.dnd.TextTransfer; 58 import org.eclipse.swt.dnd.Transfer; 59 import org.eclipse.swt.events.ControlAdapter; 60 import org.eclipse.swt.events.ControlEvent; 61 import org.eclipse.swt.events.ModifyEvent; 62 import org.eclipse.swt.events.ModifyListener; 63 import org.eclipse.swt.events.SelectionAdapter; 64 import org.eclipse.swt.events.SelectionEvent; 65 import org.eclipse.swt.events.SelectionListener; 66 import org.eclipse.swt.graphics.Color; 67 import org.eclipse.swt.graphics.Image; 68 import org.eclipse.swt.layout.GridData; 69 import org.eclipse.swt.layout.GridLayout; 70 import org.eclipse.swt.widgets.Combo; 71 import org.eclipse.swt.widgets.Composite; 72 import org.eclipse.swt.widgets.Display; 73 import org.eclipse.swt.widgets.FileDialog; 74 import org.eclipse.swt.widgets.Label; 75 import org.eclipse.swt.widgets.Scale; 76 import org.eclipse.swt.widgets.ScrollBar; 77 import org.eclipse.swt.widgets.Shell; 78 import org.eclipse.swt.widgets.Spinner; 79 import org.eclipse.swt.widgets.Text; 80 import org.eclipse.swt.widgets.ToolBar; 81 import org.eclipse.swt.widgets.ToolItem; 82 import org.eclipse.swt.widgets.Tree; 83 import org.eclipse.swt.widgets.TreeColumn; 84 import org.eclipse.swt.widgets.TreeItem; 85 import org.eclipse.ui.IActionBars; 86 import org.eclipse.ui.IEditorInput; 87 import org.eclipse.ui.IEditorSite; 88 import org.eclipse.ui.ISharedImages; 89 import org.eclipse.ui.IURIEditorInput; 90 import org.eclipse.ui.PartInitException; 91 import org.eclipse.ui.PlatformUI; 92 import org.eclipse.ui.actions.ActionFactory; 93 import org.eclipse.ui.part.EditorPart; 94 95 import java.io.File; 96 import java.io.IOException; 97 import java.lang.reflect.InvocationTargetException; 98 import java.util.ArrayList; 99 import java.util.List; 100 import java.util.regex.Matcher; 101 import java.util.regex.Pattern; 102 103 /** Display OpenGL function trace in a tabular view. */ 104 public class GLFunctionTraceViewer extends EditorPart implements ISelectionProvider { 105 public static final String ID = "com.android.ide.eclipse.gltrace.GLFunctionTrace"; //$NON-NLS-1$ 106 107 private static final String DEFAULT_FILTER_MESSAGE = "Filter list of OpenGL calls. Accepts Java regexes."; 108 private static final String NEWLINE = System.getProperty("line.separator"); //$NON-NLS-1$ 109 110 private static Image sExpandAllIcon; 111 112 private static String sLastExportedToFolder; 113 114 private String mFilePath; 115 private Scale mFrameSelectionScale; 116 private Spinner mFrameSelectionSpinner; 117 118 private GLTrace mTrace; 119 120 private TreeViewer mFrameTreeViewer; 121 private List<GLCallNode> mTreeViewerNodes; 122 123 private Text mFilterText; 124 private GLCallFilter mGLCallFilter; 125 126 private Color mGldrawTextColor; 127 private Color mGlCallErrorColor; 128 129 /** 130 * Job to refresh the tree view & frame summary view. 131 * 132 * When the currently displayed frame is changed, either via the {@link #mFrameSelectionScale} 133 * or via {@link #mFrameSelectionSpinner}, we need to update the displayed tree of calls for 134 * that frame, and the frame summary view. Both these operations need to happen on the UI 135 * thread, but are time consuming. This works out ok if the frame selection is not changing 136 * rapidly (i.e., when the spinner or scale is moved to the target frame in a single action). 137 * However, if the spinner is constantly pressed, then the user is scrolling through a sequence 138 * of frames, and rather than refreshing the details for each of the intermediate frames, 139 * we create a job to refresh the details and schedule the job after a short interval 140 * {@link #TREE_REFRESH_INTERVAL}. This allows us to stay responsive to the spinner/scale, 141 * and not do the costly refresh for each of the intermediate frames. 142 */ 143 private Job mTreeRefresherJob; 144 private final Object mTreeRefresherLock = new Object(); 145 private static final int TREE_REFRESH_INTERVAL_MS = 250; 146 147 private int mCurrentFrame; 148 149 // Currently displayed frame's start and end call indices. 150 private int mCallStartIndex; 151 private int mCallEndIndex; 152 153 private DurationMinimap mDurationMinimap; 154 private ScrollBar mVerticalScrollBar; 155 156 private Combo mContextSwitchCombo; 157 private boolean mShowContextSwitcher; 158 private int mCurrentlyDisplayedContext = -1; 159 160 private StateViewPage mStateViewPage; 161 private FrameSummaryViewPage mFrameSummaryViewPage; 162 private DetailsPage mDetailsPage; 163 164 private ToolItem mExpandAllToolItem; 165 private ToolItem mCollapseAllToolItem; 166 private ToolItem mSaveAsToolItem; 167 GLFunctionTraceViewer()168 public GLFunctionTraceViewer() { 169 mGldrawTextColor = Display.getDefault().getSystemColor(SWT.COLOR_BLUE); 170 mGlCallErrorColor = Display.getDefault().getSystemColor(SWT.COLOR_RED); 171 } 172 173 @Override doSave(IProgressMonitor monitor)174 public void doSave(IProgressMonitor monitor) { 175 } 176 177 @Override doSaveAs()178 public void doSaveAs() { 179 } 180 181 @Override init(IEditorSite site, IEditorInput input)182 public void init(IEditorSite site, IEditorInput input) throws PartInitException { 183 // we use a IURIEditorInput to allow opening files not within the workspace 184 if (!(input instanceof IURIEditorInput)) { 185 throw new PartInitException("GL Function Trace View: unsupported input type."); 186 } 187 188 setSite(site); 189 setInput(input); 190 mFilePath = ((IURIEditorInput) input).getURI().getPath(); 191 192 // set the editor part name to be the name of the file. 193 File f = new File(mFilePath); 194 setPartName(f.getName()); 195 } 196 197 @Override isDirty()198 public boolean isDirty() { 199 return false; 200 } 201 202 @Override isSaveAsAllowed()203 public boolean isSaveAsAllowed() { 204 return false; 205 } 206 207 @Override createPartControl(Composite parent)208 public void createPartControl(Composite parent) { 209 Composite c = new Composite(parent, SWT.NONE); 210 c.setLayout(new GridLayout(1, false)); 211 GridData gd = new GridData(GridData.FILL_BOTH); 212 c.setLayoutData(gd); 213 214 setInput(parent.getShell(), mFilePath); 215 216 createFrameSelectionControls(c); 217 createOptionsBar(c); 218 createFrameTraceView(c); 219 220 getSite().setSelectionProvider(mFrameTreeViewer); 221 222 IActionBars actionBars = getEditorSite().getActionBars(); 223 actionBars.setGlobalActionHandler(ActionFactory.COPY.getId(), 224 new Action("Copy") { 225 @Override 226 public void run() { 227 copySelectionToClipboard(); 228 } 229 }); 230 231 actionBars.setGlobalActionHandler(ActionFactory.SELECT_ALL.getId(), 232 new Action("Select All") { 233 @Override 234 public void run() { 235 selectAll(); 236 } 237 }); 238 239 actionBars.setGlobalActionHandler(ActionFactory.FIND.getId(), 240 new Action("Find") { 241 @Override 242 public void run() { 243 showFindDialog(); 244 } 245 }); 246 } 247 setInput(Shell shell, String tracePath)248 public void setInput(Shell shell, String tracePath) { 249 ProgressMonitorDialog dlg = new ProgressMonitorDialog(shell); 250 TraceFileParserTask parser = new TraceFileParserTask(mFilePath); 251 try { 252 dlg.run(true, true, parser); 253 } catch (InvocationTargetException e) { 254 // exception while parsing, display error to user 255 MessageDialog.openError(shell, 256 "Error parsing OpenGL Trace File", 257 e.getCause().getMessage()); 258 return; 259 } catch (InterruptedException e) { 260 // operation canceled by user, just return 261 return; 262 } 263 264 mTrace = parser.getTrace(); 265 mShowContextSwitcher = (mTrace == null) ? false : mTrace.getContexts().size() > 1; 266 if (mStateViewPage != null) { 267 mStateViewPage.setInput(mTrace); 268 } 269 if (mFrameSummaryViewPage != null) { 270 mFrameSummaryViewPage.setInput(mTrace); 271 } 272 if (mDetailsPage != null) { 273 mDetailsPage.setInput(mTrace); 274 } 275 if (mDurationMinimap != null) { 276 mDurationMinimap.setInput(mTrace); 277 } 278 279 Display.getDefault().asyncExec(new Runnable() { 280 @Override 281 public void run() { 282 refreshUI(); 283 } 284 }); 285 } 286 refreshUI()287 private void refreshUI() { 288 if (mTrace == null || mTrace.getGLCalls().size() == 0) { 289 setFrameCount(0); 290 return; 291 } 292 293 setFrameCount(mTrace.getFrames().size()); 294 selectFrame(1); 295 } 296 createFrameSelectionControls(Composite parent)297 private void createFrameSelectionControls(Composite parent) { 298 Composite c = new Composite(parent, SWT.NONE); 299 c.setLayout(new GridLayout(3, false)); 300 GridData gd = new GridData(GridData.FILL_HORIZONTAL); 301 c.setLayoutData(gd); 302 303 Label l = new Label(c, SWT.NONE); 304 l.setText("Select Frame:"); 305 306 mFrameSelectionScale = new Scale(c, SWT.HORIZONTAL); 307 mFrameSelectionScale.setMinimum(1); 308 mFrameSelectionScale.setMaximum(1); 309 mFrameSelectionScale.setSelection(0); 310 gd = new GridData(GridData.FILL_HORIZONTAL); 311 mFrameSelectionScale.setLayoutData(gd); 312 313 mFrameSelectionScale.addSelectionListener(new SelectionAdapter() { 314 @Override 315 public void widgetSelected(SelectionEvent e) { 316 int selectedFrame = mFrameSelectionScale.getSelection(); 317 mFrameSelectionSpinner.setSelection(selectedFrame); 318 selectFrame(selectedFrame); 319 } 320 }); 321 322 mFrameSelectionSpinner = new Spinner(c, SWT.BORDER); 323 gd = new GridData(); 324 // width to hold atleast 6 digits 325 gd.widthHint = SwtUtils.getApproximateFontWidth(mFrameSelectionSpinner) * 6; 326 mFrameSelectionSpinner.setLayoutData(gd); 327 328 mFrameSelectionSpinner.setMinimum(1); 329 mFrameSelectionSpinner.setMaximum(1); 330 mFrameSelectionSpinner.setSelection(0); 331 mFrameSelectionSpinner.addSelectionListener(new SelectionAdapter() { 332 @Override 333 public void widgetSelected(SelectionEvent e) { 334 int selectedFrame = mFrameSelectionSpinner.getSelection(); 335 mFrameSelectionScale.setSelection(selectedFrame); 336 selectFrame(selectedFrame); 337 } 338 }); 339 } 340 setFrameCount(int nFrames)341 private void setFrameCount(int nFrames) { 342 boolean en = nFrames > 0; 343 mFrameSelectionScale.setEnabled(en); 344 mFrameSelectionSpinner.setEnabled(en); 345 346 mFrameSelectionScale.setMaximum(nFrames); 347 mFrameSelectionSpinner.setMaximum(nFrames); 348 } 349 selectFrame(int selectedFrame)350 private void selectFrame(int selectedFrame) { 351 mFrameSelectionScale.setSelection(selectedFrame); 352 mFrameSelectionSpinner.setSelection(selectedFrame); 353 354 synchronized (mTreeRefresherLock) { 355 if (mTrace != null) { 356 GLFrame f = mTrace.getFrame(selectedFrame - 1); 357 mCallStartIndex = f.getStartIndex(); 358 mCallEndIndex = f.getEndIndex(); 359 } else { 360 mCallStartIndex = mCallEndIndex = 0; 361 } 362 363 mCurrentFrame = selectedFrame - 1; 364 365 scheduleNewRefreshJob(); 366 } 367 368 // update minimap view 369 mDurationMinimap.setCallRangeForCurrentFrame(mCallStartIndex, mCallEndIndex); 370 } 371 372 /** 373 * Show only calls from the given context 374 * @param context context id whose calls should be displayed. Illegal values will result in 375 * calls from all contexts being displayed. 376 */ selectContext(int context)377 private void selectContext(int context) { 378 if (mCurrentlyDisplayedContext == context) { 379 return; 380 } 381 382 synchronized (mTreeRefresherLock) { 383 mCurrentlyDisplayedContext = context; 384 scheduleNewRefreshJob(); 385 } 386 } 387 scheduleNewRefreshJob()388 private void scheduleNewRefreshJob() { 389 if (mTreeRefresherJob != null) { 390 return; 391 } 392 393 mTreeRefresherJob = new Job("Refresh GL Trace View Tree") { 394 @Override 395 protected IStatus run(IProgressMonitor monitor) { 396 final int start, end, context; 397 398 synchronized (mTreeRefresherLock) { 399 start = mCallStartIndex; 400 end = mCallEndIndex; 401 context = mCurrentlyDisplayedContext; 402 403 mTreeRefresherJob = null; 404 } 405 406 // update tree view in the editor 407 Display.getDefault().syncExec(new Runnable() { 408 @Override 409 public void run() { 410 refreshTree(start, end, context); 411 412 // update the frame summary view 413 if (mFrameSummaryViewPage != null) { 414 mFrameSummaryViewPage.setSelectedFrame(mCurrentFrame); 415 } 416 } 417 }); 418 return Status.OK_STATUS; 419 } 420 }; 421 mTreeRefresherJob.setPriority(Job.SHORT); 422 mTreeRefresherJob.schedule(TREE_REFRESH_INTERVAL_MS); 423 } 424 refreshTree(int startCallIndex, int endCallIndex, int contextToDisplay)425 private void refreshTree(int startCallIndex, int endCallIndex, int contextToDisplay) { 426 mTreeViewerNodes = GLCallGroups.constructCallHierarchy(mTrace, 427 startCallIndex, endCallIndex, 428 contextToDisplay); 429 mFrameTreeViewer.setInput(mTreeViewerNodes); 430 mFrameTreeViewer.refresh(); 431 mFrameTreeViewer.expandAll(); 432 } 433 createOptionsBar(Composite parent)434 private void createOptionsBar(Composite parent) { 435 int numColumns = mShowContextSwitcher ? 4 : 3; 436 437 Composite c = new Composite(parent, SWT.NONE); 438 c.setLayout(new GridLayout(numColumns, false)); 439 GridData gd = new GridData(GridData.FILL_HORIZONTAL); 440 c.setLayoutData(gd); 441 442 Label l = new Label(c, SWT.NONE); 443 l.setText("Filter:"); 444 445 mFilterText = new Text(c, SWT.BORDER | SWT.ICON_SEARCH | SWT.SEARCH | SWT.ICON_CANCEL); 446 mFilterText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); 447 mFilterText.setMessage(DEFAULT_FILTER_MESSAGE); 448 mFilterText.addModifyListener(new ModifyListener() { 449 @Override 450 public void modifyText(ModifyEvent e) { 451 updateAppliedFilters(); 452 } 453 }); 454 455 if (mShowContextSwitcher) { 456 mContextSwitchCombo = new Combo(c, SWT.BORDER | SWT.READ_ONLY); 457 458 // Setup the combo such that "All Contexts" is the first item, 459 // and then we have an item for each context. 460 mContextSwitchCombo.add("All Contexts"); 461 mContextSwitchCombo.select(0); 462 mCurrentlyDisplayedContext = -1; // showing all contexts 463 for (int i = 0; i < mTrace.getContexts().size(); i++) { 464 mContextSwitchCombo.add("Context " + i); 465 } 466 467 mContextSwitchCombo.addSelectionListener(new SelectionAdapter() { 468 @Override 469 public void widgetSelected(SelectionEvent e) { 470 selectContext(mContextSwitchCombo.getSelectionIndex() - 1); 471 } 472 }); 473 } else { 474 mCurrentlyDisplayedContext = 0; 475 } 476 477 ToolBar toolBar = new ToolBar(c, SWT.FLAT | SWT.BORDER); 478 479 mExpandAllToolItem = new ToolItem(toolBar, SWT.PUSH); 480 mExpandAllToolItem.setToolTipText("Expand All"); 481 if (sExpandAllIcon == null) { 482 ImageDescriptor id = GlTracePlugin.getImageDescriptor("/icons/expandall.png"); 483 sExpandAllIcon = id.createImage(); 484 } 485 if (sExpandAllIcon != null) { 486 mExpandAllToolItem.setImage(sExpandAllIcon); 487 } 488 489 mCollapseAllToolItem = new ToolItem(toolBar, SWT.PUSH); 490 mCollapseAllToolItem.setToolTipText("Collapse All"); 491 mCollapseAllToolItem.setImage( 492 PlatformUI.getWorkbench().getSharedImages().getImage( 493 ISharedImages.IMG_ELCL_COLLAPSEALL)); 494 495 mSaveAsToolItem = new ToolItem(toolBar, SWT.PUSH); 496 mSaveAsToolItem.setToolTipText("Export Trace"); 497 mSaveAsToolItem.setImage( 498 PlatformUI.getWorkbench().getSharedImages().getImage( 499 ISharedImages.IMG_ETOOL_SAVEAS_EDIT)); 500 501 SelectionListener toolbarSelectionListener = new SelectionAdapter() { 502 @Override 503 public void widgetSelected(SelectionEvent e) { 504 if (e.getSource() == mCollapseAllToolItem) { 505 setTreeItemsExpanded(false); 506 } else if (e.getSource() == mExpandAllToolItem) { 507 setTreeItemsExpanded(true); 508 } else if (e.getSource() == mSaveAsToolItem) { 509 exportTrace(); 510 } 511 } 512 }; 513 mExpandAllToolItem.addSelectionListener(toolbarSelectionListener); 514 mCollapseAllToolItem.addSelectionListener(toolbarSelectionListener); 515 mSaveAsToolItem.addSelectionListener(toolbarSelectionListener); 516 } 517 updateAppliedFilters()518 private void updateAppliedFilters() { 519 mGLCallFilter.setFilters(mFilterText.getText().trim()); 520 mFrameTreeViewer.refresh(); 521 } 522 createFrameTraceView(Composite parent)523 private void createFrameTraceView(Composite parent) { 524 Composite c = new Composite(parent, SWT.NONE); 525 c.setLayout(new GridLayout(2, false)); 526 GridData gd = new GridData(GridData.FILL_BOTH); 527 c.setLayoutData(gd); 528 529 final Tree tree = new Tree(c, SWT.BORDER | SWT.FULL_SELECTION | SWT.MULTI); 530 gd = new GridData(GridData.FILL_BOTH); 531 tree.setLayoutData(gd); 532 tree.setLinesVisible(true); 533 tree.setHeaderVisible(true); 534 535 mFrameTreeViewer = new TreeViewer(tree); 536 CellLabelProvider labelProvider = new GLFrameLabelProvider(); 537 538 // column showing the GL context id 539 TreeViewerColumn tvc = new TreeViewerColumn(mFrameTreeViewer, SWT.NONE); 540 tvc.setLabelProvider(labelProvider); 541 TreeColumn column = tvc.getColumn(); 542 column.setText("Function"); 543 column.setWidth(500); 544 545 // column showing the GL function duration (wall clock time) 546 tvc = new TreeViewerColumn(mFrameTreeViewer, SWT.NONE); 547 tvc.setLabelProvider(labelProvider); 548 column = tvc.getColumn(); 549 column.setText("Wall Time (ns)"); 550 column.setWidth(150); 551 column.setAlignment(SWT.RIGHT); 552 553 // column showing the GL function duration (thread time) 554 tvc = new TreeViewerColumn(mFrameTreeViewer, SWT.NONE); 555 tvc.setLabelProvider(labelProvider); 556 column = tvc.getColumn(); 557 column.setText("Thread Time (ns)"); 558 column.setWidth(150); 559 column.setAlignment(SWT.RIGHT); 560 561 mFrameTreeViewer.setContentProvider(new GLFrameContentProvider()); 562 563 mGLCallFilter = new GLCallFilter(); 564 mFrameTreeViewer.addFilter(mGLCallFilter); 565 566 // when the control is resized, give all the additional space 567 // to the function name column. 568 tree.addControlListener(new ControlAdapter() { 569 @Override 570 public void controlResized(ControlEvent e) { 571 int w = mFrameTreeViewer.getTree().getClientArea().width; 572 if (w > 200) { 573 mFrameTreeViewer.getTree().getColumn(2).setWidth(100); 574 mFrameTreeViewer.getTree().getColumn(1).setWidth(100); 575 mFrameTreeViewer.getTree().getColumn(0).setWidth(w - 200); 576 } 577 } 578 }); 579 580 mDurationMinimap = new DurationMinimap(c, mTrace); 581 gd = new GridData(GridData.FILL_VERTICAL); 582 gd.widthHint = gd.minimumWidth = mDurationMinimap.getMinimumWidth(); 583 mDurationMinimap.setLayoutData(gd); 584 mDurationMinimap.addCallSelectionListener(new ICallSelectionListener() { 585 @Override 586 public void callSelected(int selectedCallIndex) { 587 if (selectedCallIndex > 0 && selectedCallIndex < mTreeViewerNodes.size()) { 588 TreeItem item = tree.getItem(selectedCallIndex); 589 tree.select(item); 590 tree.setTopItem(item); 591 } 592 } 593 }); 594 595 mVerticalScrollBar = tree.getVerticalBar(); 596 mVerticalScrollBar.addSelectionListener(new SelectionAdapter() { 597 @Override 598 public void widgetSelected(SelectionEvent e) { 599 updateVisibleRange(); 600 } 601 }); 602 } 603 updateVisibleRange()604 private void updateVisibleRange() { 605 int visibleCallTopIndex = mCallStartIndex; 606 int visibleCallBottomIndex = mCallEndIndex; 607 608 if (mVerticalScrollBar.isEnabled()) { 609 int selection = mVerticalScrollBar.getSelection(); 610 int thumb = mVerticalScrollBar.getThumb(); 611 int max = mVerticalScrollBar.getMaximum(); 612 613 // from the scrollbar values, compute the visible fraction 614 double top = (double) selection / max; 615 double bottom = (double) (selection + thumb) / max; 616 617 // map the fraction to the call indices 618 int range = mCallEndIndex - mCallStartIndex; 619 visibleCallTopIndex = mCallStartIndex + (int) Math.floor(range * top); 620 visibleCallBottomIndex = mCallStartIndex + (int) Math.ceil(range * bottom); 621 } 622 623 mDurationMinimap.setVisibleCallRange(visibleCallTopIndex, visibleCallBottomIndex); 624 } 625 626 @Override setFocus()627 public void setFocus() { 628 mFrameTreeViewer.getTree().setFocus(); 629 } 630 631 private static class GLFrameContentProvider implements ITreeContentProvider { 632 @Override dispose()633 public void dispose() { 634 } 635 636 @Override inputChanged(Viewer viewer, Object oldInput, Object newInput)637 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { 638 } 639 640 @Override getElements(Object inputElement)641 public Object[] getElements(Object inputElement) { 642 return getChildren(inputElement); 643 } 644 645 @Override getChildren(Object parentElement)646 public Object[] getChildren(Object parentElement) { 647 if (parentElement instanceof List<?>) { 648 return ((List<?>) parentElement).toArray(); 649 } 650 651 if (!(parentElement instanceof GLCallNode)) { 652 return null; 653 } 654 655 GLCallNode parent = (GLCallNode) parentElement; 656 if (parent.hasChildren()) { 657 return parent.getChildren().toArray(); 658 } else { 659 return new Object[0]; 660 } 661 } 662 663 @Override getParent(Object element)664 public Object getParent(Object element) { 665 if (!(element instanceof GLCallNode)) { 666 return null; 667 } 668 669 return ((GLCallNode) element).getParent(); 670 } 671 672 @Override hasChildren(Object element)673 public boolean hasChildren(Object element) { 674 if (!(element instanceof GLCallNode)) { 675 return false; 676 } 677 678 return ((GLCallNode) element).hasChildren(); 679 } 680 } 681 682 private class GLFrameLabelProvider extends ColumnLabelProvider { 683 @Override update(ViewerCell cell)684 public void update(ViewerCell cell) { 685 Object element = cell.getElement(); 686 if (!(element instanceof GLCallNode)) { 687 return; 688 } 689 690 GLCall c = ((GLCallNode) element).getCall(); 691 692 if (c.getFunction() == Function.glDrawArrays 693 || c.getFunction() == Function.glDrawElements) { 694 cell.setForeground(mGldrawTextColor); 695 } 696 697 if (c.hasErrors()) { 698 cell.setForeground(mGlCallErrorColor); 699 } 700 701 cell.setText(getColumnText(c, cell.getColumnIndex())); 702 } 703 getColumnText(GLCall c, int columnIndex)704 private String getColumnText(GLCall c, int columnIndex) { 705 switch (columnIndex) { 706 case 0: 707 if (c.getFunction() == Function.glPushGroupMarkerEXT) { 708 Object marker = c.getProperty(GLCall.PROPERTY_MARKERNAME); 709 if (marker instanceof String) { 710 return ((String) marker); 711 } 712 } 713 return c.toString(); 714 case 1: 715 return formatDuration(c.getWallDuration()); 716 case 2: 717 return formatDuration(c.getThreadDuration()); 718 default: 719 return Integer.toString(c.getContextId()); 720 } 721 } 722 formatDuration(int time)723 private String formatDuration(int time) { 724 // Max duration is in the 10s of milliseconds, so xx,xxx,xxx ns 725 // So we require a format specifier that is 10 characters wide 726 return String.format("%,10d", time); //$NON-NLS-1$ 727 } 728 } 729 730 private static class GLCallFilter extends ViewerFilter { 731 private final List<Pattern> mPatterns = new ArrayList<Pattern>(); 732 setFilters(String filter)733 public void setFilters(String filter) { 734 mPatterns.clear(); 735 736 // split the user input into multiple regexes 737 // we assume that the regexes are OR'ed together i.e., all text that matches 738 // any one of the regexes will be displayed 739 for (String regex : filter.split(" ")) { 740 mPatterns.add(Pattern.compile(regex, Pattern.CASE_INSENSITIVE)); 741 } 742 } 743 744 @Override select(Viewer viewer, Object parentElement, Object element)745 public boolean select(Viewer viewer, Object parentElement, Object element) { 746 if (!(element instanceof GLCallNode)) { 747 return true; 748 } 749 750 String text = getTextUnderNode((GLCallNode) element); 751 752 if (mPatterns.size() == 0) { 753 // match if there are no regex filters 754 return true; 755 } 756 757 for (Pattern p : mPatterns) { 758 Matcher matcher = p.matcher(text); 759 if (matcher.find()) { 760 // match if atleast one of the regexes matches this text 761 return true; 762 } 763 } 764 765 return false; 766 } 767 768 /** Obtain a string representation of all functions under a given tree node. */ getTextUnderNode(GLCallNode element)769 private String getTextUnderNode(GLCallNode element) { 770 String func = element.getCall().getFunction().toString(); 771 if (!element.hasChildren()) { 772 return func; 773 } 774 775 StringBuilder sb = new StringBuilder(100); 776 sb.append(func); 777 778 for (GLCallNode child : element.getChildren()) { 779 sb.append(getTextUnderNode(child)); 780 } 781 782 return sb.toString(); 783 } 784 } 785 786 @Override addSelectionChangedListener(ISelectionChangedListener listener)787 public void addSelectionChangedListener(ISelectionChangedListener listener) { 788 if (mFrameTreeViewer != null) { 789 mFrameTreeViewer.addSelectionChangedListener(listener); 790 } 791 } 792 793 @Override getSelection()794 public ISelection getSelection() { 795 if (mFrameTreeViewer != null) { 796 return mFrameTreeViewer.getSelection(); 797 } else { 798 return null; 799 } 800 } 801 802 @Override removeSelectionChangedListener(ISelectionChangedListener listener)803 public void removeSelectionChangedListener(ISelectionChangedListener listener) { 804 if (mFrameTreeViewer != null) { 805 mFrameTreeViewer.removeSelectionChangedListener(listener); 806 } 807 } 808 809 @Override setSelection(ISelection selection)810 public void setSelection(ISelection selection) { 811 if (mFrameTreeViewer != null) { 812 mFrameTreeViewer.setSelection(selection); 813 } 814 } 815 getTrace()816 public GLTrace getTrace() { 817 return mTrace; 818 } 819 getStateViewPage()820 public StateViewPage getStateViewPage() { 821 if (mStateViewPage == null) { 822 mStateViewPage = new StateViewPage(mTrace); 823 } 824 825 return mStateViewPage; 826 } 827 getFrameSummaryViewPage()828 public FrameSummaryViewPage getFrameSummaryViewPage() { 829 if (mFrameSummaryViewPage == null) { 830 mFrameSummaryViewPage = new FrameSummaryViewPage(mTrace); 831 } 832 833 return mFrameSummaryViewPage; 834 } 835 getDetailsPage()836 public DetailsPage getDetailsPage() { 837 if (mDetailsPage == null) { 838 mDetailsPage = new DetailsPage(mTrace); 839 } 840 841 return mDetailsPage; 842 } 843 copySelectionToClipboard()844 private void copySelectionToClipboard() { 845 if (mFrameTreeViewer == null || mFrameTreeViewer.getTree().isDisposed()) { 846 return; 847 } 848 849 StringBuilder sb = new StringBuilder(); 850 851 for (TreeItem it: mFrameTreeViewer.getTree().getSelection()) { 852 Object data = it.getData(); 853 if (data instanceof GLCallNode) { 854 sb.append(((GLCallNode) data).getCall()); 855 sb.append(NEWLINE); 856 } 857 } 858 859 if (sb.length() > 0) { 860 Clipboard cb = new Clipboard(Display.getDefault()); 861 cb.setContents( 862 new Object[] { sb.toString() }, 863 new Transfer[] { TextTransfer.getInstance() }); 864 cb.dispose(); 865 } 866 } 867 selectAll()868 private void selectAll() { 869 if (mFrameTreeViewer == null || mFrameTreeViewer.getTree().isDisposed()) { 870 return; 871 } 872 873 mFrameTreeViewer.getTree().selectAll(); 874 } 875 exportTrace()876 private void exportTrace() { 877 if (mFrameTreeViewer == null || mFrameTreeViewer.getTree().isDisposed()) { 878 return; 879 } 880 881 if (mCallEndIndex == 0) { 882 return; 883 } 884 885 FileDialog fd = new FileDialog(mFrameTreeViewer.getTree().getShell(), SWT.SAVE); 886 fd.setFilterExtensions(new String[] { "*.txt" }); 887 if (sLastExportedToFolder != null) { 888 fd.setFilterPath(sLastExportedToFolder); 889 } 890 891 String path = fd.open(); 892 if (path == null) { 893 return; 894 } 895 896 File f = new File(path); 897 sLastExportedToFolder = f.getParent(); 898 try { 899 exportFrameTo(f); 900 } catch (IOException e) { 901 ErrorDialog.openError(mFrameTreeViewer.getTree().getShell(), 902 "Export trace file.", 903 "Unexpected error exporting trace file.", 904 new Status(Status.ERROR, GlTracePlugin.PLUGIN_ID, e.toString())); 905 } 906 } 907 exportFrameTo(File f)908 private void exportFrameTo(File f) throws IOException { 909 String glCalls = serializeGlCalls(mTrace.getGLCalls(), mCallStartIndex, mCallEndIndex); 910 Files.write(glCalls, f, Charsets.UTF_8); 911 } 912 serializeGlCalls(List<GLCall> glCalls, int start, int end)913 private String serializeGlCalls(List<GLCall> glCalls, int start, int end) { 914 StringBuilder sb = new StringBuilder(); 915 while (start < end) { 916 sb.append(glCalls.get(start).toString()); 917 sb.append("\n"); //$NON-NLS-1$ 918 start++; 919 } 920 921 return sb.toString(); 922 } 923 setTreeItemsExpanded(boolean expand)924 private void setTreeItemsExpanded(boolean expand) { 925 if (mFrameTreeViewer == null || mFrameTreeViewer.getTree().isDisposed()) { 926 return; 927 } 928 929 if (expand) { 930 mFrameTreeViewer.expandAll(); 931 } else { 932 mFrameTreeViewer.collapseAll(); 933 } 934 } 935 936 private class TraceViewerFindTarget extends AbstractBufferFindTarget { 937 @Override getItemCount()938 public int getItemCount() { 939 return mFrameTreeViewer.getTree().getItemCount(); 940 } 941 942 @Override getItem(int index)943 public String getItem(int index) { 944 Object data = mFrameTreeViewer.getTree().getItem(index).getData(); 945 if (data instanceof GLCallNode) { 946 return ((GLCallNode) data).getCall().toString(); 947 } 948 return null; 949 } 950 951 @Override selectAndReveal(int index)952 public void selectAndReveal(int index) { 953 Tree t = mFrameTreeViewer.getTree(); 954 t.deselectAll(); 955 t.select(t.getItem(index)); 956 t.showSelection(); 957 } 958 959 @Override getStartingIndex()960 public int getStartingIndex() { 961 return 0; 962 } 963 }; 964 965 private FindDialog mFindDialog; 966 private TraceViewerFindTarget mFindTarget = new TraceViewerFindTarget(); 967 showFindDialog()968 private void showFindDialog() { 969 if (mFindDialog != null) { 970 // the dialog is already displayed 971 return; 972 } 973 974 mFindDialog = new FindDialog(Display.getDefault().getActiveShell(), 975 mFindTarget, 976 FindDialog.FIND_NEXT_ID); 977 mFindDialog.open(); // blocks until find dialog is closed 978 mFindDialog = null; 979 } 980 getInputPath()981 public String getInputPath() { 982 return mFilePath; 983 } 984 } 985