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