1 /* 2 * Copyright (C) 2012 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.views; 18 19 import com.android.ide.eclipse.gltrace.GLProtoBuf.GLMessage.Function; 20 import com.android.ide.eclipse.gltrace.model.GLCall; 21 import com.android.ide.eclipse.gltrace.model.GLTrace; 22 import com.android.ide.eclipse.gltrace.widgets.ImageCanvas; 23 24 import org.eclipse.core.runtime.IProgressMonitor; 25 import org.eclipse.core.runtime.IStatus; 26 import org.eclipse.core.runtime.Status; 27 import org.eclipse.core.runtime.jobs.Job; 28 import org.eclipse.jface.action.IToolBarManager; 29 import org.eclipse.jface.layout.GridDataFactory; 30 import org.eclipse.jface.viewers.ColumnLabelProvider; 31 import org.eclipse.jface.viewers.IStructuredContentProvider; 32 import org.eclipse.jface.viewers.TableViewer; 33 import org.eclipse.jface.viewers.TableViewerColumn; 34 import org.eclipse.jface.viewers.Viewer; 35 import org.eclipse.jface.viewers.ViewerCell; 36 import org.eclipse.jface.viewers.ViewerComparator; 37 import org.eclipse.swt.SWT; 38 import org.eclipse.swt.custom.SashForm; 39 import org.eclipse.swt.events.ControlAdapter; 40 import org.eclipse.swt.events.ControlEvent; 41 import org.eclipse.swt.events.SelectionAdapter; 42 import org.eclipse.swt.events.SelectionEvent; 43 import org.eclipse.swt.events.SelectionListener; 44 import org.eclipse.swt.graphics.Image; 45 import org.eclipse.swt.layout.GridLayout; 46 import org.eclipse.swt.widgets.Composite; 47 import org.eclipse.swt.widgets.Control; 48 import org.eclipse.swt.widgets.Display; 49 import org.eclipse.swt.widgets.Label; 50 import org.eclipse.swt.widgets.Table; 51 import org.eclipse.swt.widgets.TableColumn; 52 import org.eclipse.ui.part.Page; 53 54 import java.util.EnumMap; 55 import java.util.List; 56 import java.util.Map; 57 58 /** 59 * A {@link FrameSummaryViewPage} displays summary information regarding a frame. This includes 60 * the contents of the frame buffer at the end of the frame, and statistics regarding the 61 * OpenGL Calls present in the frame. 62 */ 63 public class FrameSummaryViewPage extends Page { 64 private final GLTrace mTrace; 65 66 private int mCurrentFrame; 67 68 private SashForm mSash; 69 private ImageCanvas mImageCanvas; 70 71 private Label mWallClockTimeLabel; 72 private Label mThreadTimeLabel; 73 74 private TableViewer mStatsTableViewer; 75 private StatsLabelProvider mStatsLabelProvider; 76 private StatsTableComparator mStatsTableComparator; 77 78 private static final String[] STATS_TABLE_PROPERTIES = { 79 "Function", 80 "Count", 81 "Wall Time (ns)", 82 "Thread Time (ns)", 83 }; 84 private static final float[] STATS_TABLE_COLWIDTH_RATIOS = { 85 0.4f, 0.1f, 0.25f, 0.25f, 86 }; 87 private static final int[] STATS_TABLE_COL_ALIGNMENT = { 88 SWT.LEFT, SWT.LEFT, SWT.RIGHT, SWT.RIGHT, 89 }; 90 FrameSummaryViewPage(GLTrace trace)91 public FrameSummaryViewPage(GLTrace trace) { 92 mTrace = trace; 93 } 94 95 @Override createControl(Composite parent)96 public void createControl(Composite parent) { 97 mSash = new SashForm(parent, SWT.VERTICAL); 98 99 // create image canvas where the framebuffer is displayed 100 mImageCanvas = new ImageCanvas(mSash); 101 102 // create a composite where the frame statistics are displayed 103 createFrameStatisticsPart(mSash); 104 105 mSash.setWeights(new int[] {70, 30}); 106 107 IToolBarManager toolbarManager = getSite().getActionBars().getToolBarManager(); 108 toolbarManager.add(new FitToCanvasAction(true, mImageCanvas)); 109 } 110 createFrameStatisticsPart(Composite parent)111 private void createFrameStatisticsPart(Composite parent) { 112 Composite c = new Composite(parent, SWT.NONE); 113 c.setLayout(new GridLayout(2, false)); 114 GridDataFactory.fillDefaults().grab(true, true).applyTo(c); 115 116 Label l = new Label(c, SWT.NONE); 117 l.setText("Cumulative call duration of all OpenGL Calls in this frame:"); 118 l.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_DARK_GRAY)); 119 GridDataFactory.fillDefaults().span(2, 1).applyTo(l); 120 121 l = new Label(c, SWT.NONE); 122 l.setText("Wall Clock Time: "); 123 GridDataFactory.fillDefaults().align(SWT.RIGHT, SWT.CENTER).applyTo(l); 124 125 mWallClockTimeLabel = new Label(c, SWT.NONE); 126 GridDataFactory.defaultsFor(mWallClockTimeLabel) 127 .grab(true, false) 128 .applyTo(mWallClockTimeLabel); 129 130 l = new Label(c, SWT.NONE); 131 l.setText("Thread Time: "); 132 GridDataFactory.fillDefaults().align(SWT.RIGHT, SWT.CENTER).applyTo(l); 133 134 mThreadTimeLabel = new Label(c, SWT.NONE); 135 GridDataFactory.defaultsFor(mThreadTimeLabel) 136 .grab(true, false) 137 .applyTo(mThreadTimeLabel); 138 139 l = new Label(c, SWT.HORIZONTAL | SWT.SEPARATOR); 140 GridDataFactory.fillDefaults().span(2, 1).applyTo(l); 141 142 l = new Label(c, SWT.NONE); 143 l.setText("Per OpenGL Function Statistics:"); 144 l.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_DARK_GRAY)); 145 GridDataFactory.fillDefaults().span(2, 1).applyTo(l); 146 147 final Table table = new Table(c, SWT.BORDER | SWT.FULL_SELECTION); 148 GridDataFactory.fillDefaults().grab(true, true).span(2, 1).applyTo(table); 149 150 table.setLinesVisible(true); 151 table.setHeaderVisible(true); 152 153 mStatsTableViewer = new TableViewer(table); 154 mStatsLabelProvider = new StatsLabelProvider(); 155 mStatsTableComparator = new StatsTableComparator(1); 156 157 // when a column is selected, sort the table based on that column 158 SelectionListener columnSelectionListener = new SelectionAdapter() { 159 @Override 160 public void widgetSelected(SelectionEvent e) { 161 TableColumn tc = (TableColumn) e.widget; 162 String colText = tc.getText(); 163 for (int i = 0; i < STATS_TABLE_PROPERTIES.length; i++) { 164 if (STATS_TABLE_PROPERTIES[i].equals(colText)) { 165 mStatsTableComparator.setSortColumn(i); 166 table.setSortColumn(tc); 167 table.setSortDirection(mStatsTableComparator.getDirection()); 168 mStatsTableViewer.refresh(); 169 break; 170 } 171 } 172 } 173 }; 174 175 for (int i = 0; i < STATS_TABLE_PROPERTIES.length; i++) { 176 TableViewerColumn tvc = new TableViewerColumn(mStatsTableViewer, SWT.NONE); 177 tvc.getColumn().setText(STATS_TABLE_PROPERTIES[i]); 178 tvc.setLabelProvider(mStatsLabelProvider); 179 tvc.getColumn().setAlignment(STATS_TABLE_COL_ALIGNMENT[i]); 180 tvc.getColumn().addSelectionListener(columnSelectionListener); 181 } 182 mStatsTableViewer.setContentProvider(new StatsContentProvider()); 183 mStatsTableViewer.setInput(null); 184 mStatsTableViewer.setComparator(mStatsTableComparator); 185 186 // resize columns appropriately when the size of the widget changes 187 table.addControlListener(new ControlAdapter() { 188 @Override 189 public void controlResized(ControlEvent e) { 190 int w = table.getClientArea().width; 191 192 for (int i = 0; i < STATS_TABLE_COLWIDTH_RATIOS.length; i++) { 193 table.getColumn(i).setWidth((int) (w * STATS_TABLE_COLWIDTH_RATIOS[i])); 194 } 195 } 196 }); 197 } 198 199 @Override getControl()200 public Control getControl() { 201 return mSash; 202 } 203 204 @Override setFocus()205 public void setFocus() { 206 } 207 setSelectedFrame(int frame)208 public void setSelectedFrame(int frame) { 209 mCurrentFrame = frame; 210 211 updateImageCanvas(); 212 updateFrameStats(); 213 } 214 updateFrameStats()215 private void updateFrameStats() { 216 final List<GLCall> calls = mTrace.getGLCallsForFrame(mCurrentFrame); 217 218 Job job = new Job("Update Frame Statistics") { 219 @Override 220 protected IStatus run(IProgressMonitor monitor) { 221 long wallClockDuration = 0; 222 long threadDuration = 0; 223 224 final Map<Function, PerCallStats> cumulativeStats = 225 new EnumMap<Function, PerCallStats>(Function.class); 226 227 for (GLCall c: calls) { 228 wallClockDuration += c.getWallDuration(); 229 threadDuration += c.getThreadDuration(); 230 231 PerCallStats stats = cumulativeStats.get(c.getFunction()); 232 if (stats == null) { 233 stats = new PerCallStats(); 234 } 235 236 stats.count++; 237 stats.threadDuration += c.getThreadDuration(); 238 stats.wallDuration += c.getWallDuration(); 239 240 cumulativeStats.put(c.getFunction(), stats); 241 } 242 243 final String wallTime = formatMilliSeconds(wallClockDuration); 244 final String threadTime = formatMilliSeconds(threadDuration); 245 246 Display.getDefault().syncExec(new Runnable() { 247 @Override 248 public void run() { 249 mWallClockTimeLabel.setText(wallTime); 250 mThreadTimeLabel.setText(threadTime); 251 mStatsTableViewer.setInput(cumulativeStats); 252 } 253 }); 254 255 return Status.OK_STATUS; 256 } 257 }; 258 job.setUser(true); 259 job.schedule(); 260 } 261 formatMilliSeconds(long nanoSeconds)262 private String formatMilliSeconds(long nanoSeconds) { 263 double milliSeconds = (double) nanoSeconds / 1000000; 264 return String.format("%.2f ms", milliSeconds); //$NON-NLS-1$ 265 } 266 updateImageCanvas()267 private void updateImageCanvas() { 268 int lastCallIndex = mTrace.getFrame(mCurrentFrame).getEndIndex() - 1; 269 if (lastCallIndex >= 0 && lastCallIndex < mTrace.getGLCalls().size()) { 270 GLCall call = mTrace.getGLCalls().get(lastCallIndex); 271 final Image image = mTrace.getImage(call); 272 Display.getDefault().asyncExec(new Runnable() { 273 @Override 274 public void run() { 275 mImageCanvas.setImage(image); 276 } 277 }); 278 } 279 } 280 281 /** Cumulative stats maintained for each type of OpenGL Function in a particular frame. */ 282 private static class PerCallStats { 283 public int count; 284 public long wallDuration; 285 public long threadDuration; 286 } 287 288 private static class StatsContentProvider implements IStructuredContentProvider { 289 @Override dispose()290 public void dispose() { 291 } 292 293 @Override inputChanged(Viewer viewer, Object oldInput, Object newInput)294 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { 295 } 296 297 @Override getElements(Object inputElement)298 public Object[] getElements(Object inputElement) { 299 if (inputElement instanceof Map<?, ?>) { 300 return ((Map<?, ?>) inputElement).entrySet().toArray(); 301 } 302 303 return null; 304 } 305 } 306 307 private static class StatsLabelProvider extends ColumnLabelProvider { 308 @Override update(ViewerCell cell)309 public void update(ViewerCell cell) { 310 Object element = cell.getElement(); 311 if (!(element instanceof Map.Entry<?, ?>)) { 312 return; 313 } 314 315 Function f = (Function) ((Map.Entry<?, ?>) element).getKey(); 316 PerCallStats stats = (PerCallStats) ((Map.Entry<?, ?>) element).getValue(); 317 318 switch (cell.getColumnIndex()) { 319 case 0: 320 cell.setText(f.toString()); 321 break; 322 case 1: 323 cell.setText(Integer.toString(stats.count)); 324 break; 325 case 2: 326 cell.setText(formatDuration(stats.wallDuration)); 327 break; 328 case 3: 329 cell.setText(formatDuration(stats.threadDuration)); 330 break; 331 default: 332 // should not happen 333 cell.setText("??"); //$NON-NLS-1$ 334 break; 335 } 336 } 337 formatDuration(long time)338 private String formatDuration(long time) { 339 // Max duration is in the 10s of milliseconds = xx,xxx,xxx ns 340 // So we require a format specifier that is 10 characters wide 341 return String.format("%,10d", time); //$NON-NLS-1$ 342 } 343 } 344 345 private static class StatsTableComparator extends ViewerComparator { 346 private int mSortColumn; 347 private boolean mDescending = true; 348 StatsTableComparator(int defaultSortColIndex)349 private StatsTableComparator(int defaultSortColIndex) { 350 mSortColumn = defaultSortColIndex; 351 } 352 setSortColumn(int index)353 public void setSortColumn(int index) { 354 if (index == mSortColumn) { 355 // if same column as what we are currently sorting on, 356 // then toggle the direction 357 mDescending = !mDescending; 358 } else { 359 mSortColumn = index; 360 mDescending = true; 361 } 362 } 363 getDirection()364 public int getDirection() { 365 return mDescending ? SWT.UP : SWT.DOWN; 366 } 367 368 @Override compare(Viewer viewer, Object e1, Object e2)369 public int compare(Viewer viewer, Object e1, Object e2) { 370 Map.Entry<?, ?> entry1; 371 Map.Entry<?, ?> entry2; 372 373 if (mDescending) { 374 entry1 = (Map.Entry<?, ?>) e1; 375 entry2 = (Map.Entry<?, ?>) e2; 376 } else { 377 entry1 = (Map.Entry<?, ?>) e2; 378 entry2 = (Map.Entry<?, ?>) e1; 379 } 380 381 String k1 = entry1.getKey().toString(); 382 String k2 = entry2.getKey().toString(); 383 384 PerCallStats stats1 = (PerCallStats) entry1.getValue(); 385 PerCallStats stats2 = (PerCallStats) entry2.getValue(); 386 387 switch (mSortColumn) { 388 case 0: // function name 389 return String.CASE_INSENSITIVE_ORDER.compare(k1, k2); 390 case 1: 391 return stats1.count - stats2.count; 392 case 2: 393 return (int) (stats1.wallDuration - stats2.wallDuration); 394 case 3: 395 return (int) (stats1.threadDuration - stats2.threadDuration); 396 default: 397 return super.compare(viewer, e1, e2); 398 } 399 } 400 } 401 } 402