Home | History | Annotate | Download | only in views
      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 
     96     public FrameSummaryViewPage(GLTrace trace) {
     97         mTrace = trace;
     98     }
     99 
    100     public void setInput(GLTrace trace) {
    101         mTrace = trace;
    102     }
    103 
    104     @Override
    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 
    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
    213     public Control getControl() {
    214         return mSash;
    215     }
    216 
    217     @Override
    218     public void setFocus() {
    219     }
    220 
    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 
    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 
    300     private String formatMilliSeconds(long nanoSeconds) {
    301         double milliSeconds = (double) nanoSeconds / 1000000;
    302         return String.format("%.2f ms", milliSeconds);          //$NON-NLS-1$
    303     }
    304 
    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
    331         public void dispose() {
    332         }
    333 
    334         @Override
    335         public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
    336         }
    337 
    338         @Override
    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
    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 
    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 
    390         private StatsTableComparator(int defaultSortColIndex) {
    391             mSortColumn = defaultSortColIndex;
    392         }
    393 
    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 
    405         public int getDirection() {
    406             return mDescending ? SWT.UP : SWT.DOWN;
    407         }
    408 
    409         @Override
    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