blob: 25de48f20ce1bcb6dee8c2248171b49d8765d23f [file] [log] [blame]
/*
* 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);
}
}
}
}