• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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