com.android.ide.eclipse.gltrace.views.FrameSummaryViewPage.java Source code

Java tutorial

Introduction

Here is the source code for com.android.ide.eclipse.gltrace.views.FrameSummaryViewPage.java

Source

/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.ide.eclipse.gltrace.views;

import com.android.ide.eclipse.gltrace.GLProtoBuf.GLMessage.Function;
import com.android.ide.eclipse.gltrace.model.GLCall;
import com.android.ide.eclipse.gltrace.model.GLTrace;
import com.android.ide.eclipse.gltrace.widgets.ImageCanvas;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.viewers.ColumnLabelProvider;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerCell;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.ui.part.Page;

import java.util.EnumMap;
import java.util.List;
import java.util.Map;

/**
 * A {@link FrameSummaryViewPage} displays summary information regarding a frame. This includes
 * the contents of the frame buffer at the end of the frame, and statistics regarding the
 * OpenGL Calls present in the frame.
 */
public class FrameSummaryViewPage extends Page {
    private GLTrace mTrace;

    private final Object mLock = new Object();
    private Job mRefresherJob;
    private int mCurrentFrame;

    private SashForm mSash;
    private ImageCanvas mImageCanvas;

    private Label mWallClockTimeLabel;
    private Label mThreadTimeLabel;

    private TableViewer mStatsTableViewer;
    private StatsLabelProvider mStatsLabelProvider;
    private StatsTableComparator mStatsTableComparator;

    private FitToCanvasAction mFitToCanvasAction;
    private SaveImageAction mSaveImageAction;

    private static final String[] STATS_TABLE_PROPERTIES = { "Function", "Count", "Wall Time (ns)",
            "Thread Time (ns)", };
    private static final float[] STATS_TABLE_COLWIDTH_RATIOS = { 0.4f, 0.1f, 0.25f, 0.25f, };
    private static final int[] STATS_TABLE_COL_ALIGNMENT = { SWT.LEFT, SWT.LEFT, SWT.RIGHT, SWT.RIGHT, };

    public FrameSummaryViewPage(GLTrace trace) {
        mTrace = trace;
    }

    public void setInput(GLTrace trace) {
        mTrace = trace;
    }

    @Override
    public void createControl(Composite parent) {
        mSash = new SashForm(parent, SWT.VERTICAL);

        // create image canvas where the framebuffer is displayed
        mImageCanvas = new ImageCanvas(mSash);

        // create a composite where the frame statistics are displayed
        createFrameStatisticsPart(mSash);

        mSash.setWeights(new int[] { 70, 30 });

        mFitToCanvasAction = new FitToCanvasAction(true, mImageCanvas);
        mSaveImageAction = new SaveImageAction(mImageCanvas);

        IToolBarManager toolbarManager = getSite().getActionBars().getToolBarManager();
        toolbarManager.add(mFitToCanvasAction);
        toolbarManager.add(mSaveImageAction);
    }

    private void createFrameStatisticsPart(Composite parent) {
        Composite c = new Composite(parent, SWT.NONE);
        c.setLayout(new GridLayout(2, false));
        GridDataFactory.fillDefaults().grab(true, true).applyTo(c);

        Label l = new Label(c, SWT.NONE);
        l.setText("Cumulative call duration of all OpenGL Calls in this frame:");
        l.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_DARK_GRAY));
        GridDataFactory.fillDefaults().span(2, 1).applyTo(l);

        l = new Label(c, SWT.NONE);
        l.setText("Wall Clock Time: ");
        GridDataFactory.fillDefaults().align(SWT.RIGHT, SWT.CENTER).applyTo(l);

        mWallClockTimeLabel = new Label(c, SWT.NONE);
        GridDataFactory.defaultsFor(mWallClockTimeLabel).grab(true, false).applyTo(mWallClockTimeLabel);

        l = new Label(c, SWT.NONE);
        l.setText("Thread Time: ");
        GridDataFactory.fillDefaults().align(SWT.RIGHT, SWT.CENTER).applyTo(l);

        mThreadTimeLabel = new Label(c, SWT.NONE);
        GridDataFactory.defaultsFor(mThreadTimeLabel).grab(true, false).applyTo(mThreadTimeLabel);

        l = new Label(c, SWT.HORIZONTAL | SWT.SEPARATOR);
        GridDataFactory.fillDefaults().span(2, 1).applyTo(l);

        l = new Label(c, SWT.NONE);
        l.setText("Per OpenGL Function Statistics:");
        l.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_DARK_GRAY));
        GridDataFactory.fillDefaults().span(2, 1).applyTo(l);

        final Table table = new Table(c, SWT.BORDER | SWT.FULL_SELECTION);
        GridDataFactory.fillDefaults().grab(true, true).span(2, 1).applyTo(table);

        table.setLinesVisible(true);
        table.setHeaderVisible(true);

        mStatsTableViewer = new TableViewer(table);
        mStatsLabelProvider = new StatsLabelProvider();
        mStatsTableComparator = new StatsTableComparator(1);

        // when a column is selected, sort the table based on that column
        SelectionListener columnSelectionListener = new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                TableColumn tc = (TableColumn) e.widget;
                String colText = tc.getText();
                for (int i = 0; i < STATS_TABLE_PROPERTIES.length; i++) {
                    if (STATS_TABLE_PROPERTIES[i].equals(colText)) {
                        mStatsTableComparator.setSortColumn(i);
                        table.setSortColumn(tc);
                        table.setSortDirection(mStatsTableComparator.getDirection());
                        mStatsTableViewer.refresh();
                        break;
                    }
                }
            }
        };

        for (int i = 0; i < STATS_TABLE_PROPERTIES.length; i++) {
            TableViewerColumn tvc = new TableViewerColumn(mStatsTableViewer, SWT.NONE);
            tvc.getColumn().setText(STATS_TABLE_PROPERTIES[i]);
            tvc.setLabelProvider(mStatsLabelProvider);
            tvc.getColumn().setAlignment(STATS_TABLE_COL_ALIGNMENT[i]);
            tvc.getColumn().addSelectionListener(columnSelectionListener);
        }
        mStatsTableViewer.setContentProvider(new StatsContentProvider());
        mStatsTableViewer.setInput(null);
        mStatsTableViewer.setComparator(mStatsTableComparator);

        // resize columns appropriately when the size of the widget changes
        table.addControlListener(new ControlAdapter() {
            @Override
            public void controlResized(ControlEvent e) {
                int w = table.getClientArea().width;

                for (int i = 0; i < STATS_TABLE_COLWIDTH_RATIOS.length; i++) {
                    table.getColumn(i).setWidth((int) (w * STATS_TABLE_COLWIDTH_RATIOS[i]));
                }
            }
        });
    }

    @Override
    public Control getControl() {
        return mSash;
    }

    @Override
    public void setFocus() {
    }

    public void setSelectedFrame(int frame) {
        if (mTrace == null) {
            return;
        }

        synchronized (mLock) {
            mCurrentFrame = frame;

            if (mRefresherJob != null) {
                return;
            }

            mRefresherJob = new Job("Update Frame Summary Task") {
                @Override
                protected IStatus run(IProgressMonitor monitor) {
                    final int currentFrame;
                    synchronized (mLock) {
                        currentFrame = mCurrentFrame;
                        mRefresherJob = null;
                    }
                    ;

                    updateImageCanvas(currentFrame);
                    updateFrameStats(currentFrame);

                    return Status.OK_STATUS;
                }
            };
            mRefresherJob.setPriority(Job.SHORT);
            mRefresherJob.schedule(500);
        }
        ;
    }

    private void updateFrameStats(int frame) {
        final List<GLCall> calls = mTrace.getGLCallsForFrame(frame);

        Job job = new Job("Update Frame Statistics") {
            @Override
            protected IStatus run(IProgressMonitor monitor) {
                long wallClockDuration = 0;
                long threadDuration = 0;

                final Map<Function, PerCallStats> cumulativeStats = new EnumMap<Function, PerCallStats>(
                        Function.class);

                for (GLCall c : calls) {
                    wallClockDuration += c.getWallDuration();
                    threadDuration += c.getThreadDuration();

                    PerCallStats stats = cumulativeStats.get(c.getFunction());
                    if (stats == null) {
                        stats = new PerCallStats();
                    }

                    stats.count++;
                    stats.threadDuration += c.getThreadDuration();
                    stats.wallDuration += c.getWallDuration();

                    cumulativeStats.put(c.getFunction(), stats);
                }

                final String wallTime = formatMilliSeconds(wallClockDuration);
                final String threadTime = formatMilliSeconds(threadDuration);

                Display.getDefault().syncExec(new Runnable() {
                    @Override
                    public void run() {
                        mWallClockTimeLabel.setText(wallTime);
                        mThreadTimeLabel.setText(threadTime);
                        mStatsTableViewer.setInput(cumulativeStats);
                    }
                });

                return Status.OK_STATUS;
            }
        };
        job.setUser(true);
        job.schedule();
    }

    private String formatMilliSeconds(long nanoSeconds) {
        double milliSeconds = (double) nanoSeconds / 1000000;
        return String.format("%.2f ms", milliSeconds); //$NON-NLS-1$
    }

    private void updateImageCanvas(int frame) {
        int lastCallIndex = mTrace.getFrame(frame).getEndIndex() - 1;
        if (lastCallIndex >= 0 && lastCallIndex < mTrace.getGLCalls().size()) {
            GLCall call = mTrace.getGLCalls().get(lastCallIndex);
            final Image image = mTrace.getImage(call);
            Display.getDefault().asyncExec(new Runnable() {
                @Override
                public void run() {
                    mImageCanvas.setImage(image);

                    mFitToCanvasAction.setEnabled(image != null);
                    mSaveImageAction.setEnabled(image != null);
                }
            });
        }
    }

    /** Cumulative stats maintained for each type of OpenGL Function in a particular frame. */
    private static class PerCallStats {
        public int count;
        public long wallDuration;
        public long threadDuration;
    }

    private static class StatsContentProvider implements IStructuredContentProvider {
        @Override
        public void dispose() {
        }

        @Override
        public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
        }

        @Override
        public Object[] getElements(Object inputElement) {
            if (inputElement instanceof Map<?, ?>) {
                return ((Map<?, ?>) inputElement).entrySet().toArray();
            }

            return null;
        }
    }

    private static class StatsLabelProvider extends ColumnLabelProvider {
        @Override
        public void update(ViewerCell cell) {
            Object element = cell.getElement();
            if (!(element instanceof Map.Entry<?, ?>)) {
                return;
            }

            Function f = (Function) ((Map.Entry<?, ?>) element).getKey();
            PerCallStats stats = (PerCallStats) ((Map.Entry<?, ?>) element).getValue();

            switch (cell.getColumnIndex()) {
            case 0:
                cell.setText(f.toString());
                break;
            case 1:
                cell.setText(Integer.toString(stats.count));
                break;
            case 2:
                cell.setText(formatDuration(stats.wallDuration));
                break;
            case 3:
                cell.setText(formatDuration(stats.threadDuration));
                break;
            default:
                // should not happen
                cell.setText("??"); //$NON-NLS-1$
                break;
            }
        }

        private String formatDuration(long time) {
            // Max duration is in the 10s of milliseconds = xx,xxx,xxx ns
            // So we require a format specifier that is 10 characters wide
            return String.format("%,10d", time); //$NON-NLS-1$
        }
    }

    private static class StatsTableComparator extends ViewerComparator {
        private int mSortColumn;
        private boolean mDescending = true;

        private StatsTableComparator(int defaultSortColIndex) {
            mSortColumn = defaultSortColIndex;
        }

        public void setSortColumn(int index) {
            if (index == mSortColumn) {
                // if same column as what we are currently sorting on,
                // then toggle the direction
                mDescending = !mDescending;
            } else {
                mSortColumn = index;
                mDescending = true;
            }
        }

        public int getDirection() {
            return mDescending ? SWT.UP : SWT.DOWN;
        }

        @Override
        public int compare(Viewer viewer, Object e1, Object e2) {
            Map.Entry<?, ?> entry1;
            Map.Entry<?, ?> entry2;

            if (mDescending) {
                entry1 = (Map.Entry<?, ?>) e1;
                entry2 = (Map.Entry<?, ?>) e2;
            } else {
                entry1 = (Map.Entry<?, ?>) e2;
                entry2 = (Map.Entry<?, ?>) e1;
            }

            String k1 = entry1.getKey().toString();
            String k2 = entry2.getKey().toString();

            PerCallStats stats1 = (PerCallStats) entry1.getValue();
            PerCallStats stats2 = (PerCallStats) entry2.getValue();

            switch (mSortColumn) {
            case 0: // function name
                return String.CASE_INSENSITIVE_ORDER.compare(k1, k2);
            case 1:
                return stats1.count - stats2.count;
            case 2:
                return (int) (stats1.wallDuration - stats2.wallDuration);
            case 3:
                return (int) (stats1.threadDuration - stats2.threadDuration);
            default:
                return super.compare(viewer, e1, e2);
            }
        }
    }
}