blob: cc9613a3a8ed282442c7f7aa1bd30bcf7a71afc0 [file] [log] [blame]
/*
* Copyright (C) 2006 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.traceview;
import org.eclipse.jface.resource.FontRegistry;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.MouseWheelListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.ScrollBar;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Observable;
import java.util.Observer;
public class TimeLineView extends Composite implements Observer {
private HashMap<String, RowData> mRowByName;
private RowData[] mRows;
private Segment[] mSegments;
private HashMap<Integer, String> mThreadLabels;
private Timescale mTimescale;
private Surface mSurface;
private RowLabels mLabels;
private SashForm mSashForm;
private int mScrollOffsetY;
public static final int PixelsPerTick = 50;
private TickScaler mScaleInfo = new TickScaler(0, 0, 0, PixelsPerTick);
private static final int LeftMargin = 10; // blank space on left
private static final int RightMargin = 60; // blank space on right
private Color mColorBlack;
private Color mColorGray;
private Color mColorDarkGray;
private Color mColorForeground;
private Color mColorRowBack;
private Color mColorZoomSelection;
private FontRegistry mFontRegistry;
/** vertical height of drawn blocks in each row */
private static final int rowHeight = 20;
/** the blank space between rows */
private static final int rowYMargin = 12;
private static final int rowYMarginHalf = rowYMargin / 2;
/** total vertical space for row */
private static final int rowYSpace = rowHeight + rowYMargin;
private static final int majorTickLength = 8;
private static final int minorTickLength = 4;
private static final int timeLineOffsetY = 58;
private static final int tickToFontSpacing = 2;
/** start of first row */
private static final int topMargin = 90;
private int mMouseRow = -1;
private int mNumRows;
private int mStartRow;
private int mEndRow;
private TraceUnits mUnits;
private String mClockSource;
private boolean mHaveCpuTime;
private boolean mHaveRealTime;
private int mSmallFontWidth;
private int mSmallFontHeight;
private SelectionController mSelectionController;
private MethodData mHighlightMethodData;
private Call mHighlightCall;
private static final int MinInclusiveRange = 3;
/** Setting the fonts looks good on Linux but bad on Macs */
private boolean mSetFonts = false;
public static interface Block {
public String getName();
public MethodData getMethodData();
public long getStartTime();
public long getEndTime();
public Color getColor();
public double addWeight(int x, int y, double weight);
public void clearWeight();
public long getExclusiveCpuTime();
public long getInclusiveCpuTime();
public long getExclusiveRealTime();
public long getInclusiveRealTime();
public boolean isContextSwitch();
public boolean isIgnoredBlock();
public Block getParentBlock();
}
public static interface Row {
public int getId();
public String getName();
}
public static class Record {
Row row;
Block block;
public Record(Row row, Block block) {
this.row = row;
this.block = block;
}
}
public TimeLineView(Composite parent, TraceReader reader,
SelectionController selectionController) {
super(parent, SWT.NONE);
mRowByName = new HashMap<String, RowData>();
this.mSelectionController = selectionController;
selectionController.addObserver(this);
mUnits = reader.getTraceUnits();
mClockSource = reader.getClockSource();
mHaveCpuTime = reader.haveCpuTime();
mHaveRealTime = reader.haveRealTime();
mThreadLabels = reader.getThreadLabels();
Display display = getDisplay();
mColorGray = display.getSystemColor(SWT.COLOR_GRAY);
mColorDarkGray = display.getSystemColor(SWT.COLOR_DARK_GRAY);
mColorBlack = display.getSystemColor(SWT.COLOR_BLACK);
// mColorBackground = display.getSystemColor(SWT.COLOR_WHITE);
mColorForeground = display.getSystemColor(SWT.COLOR_BLACK);
mColorRowBack = new Color(display, 240, 240, 255);
mColorZoomSelection = new Color(display, 230, 230, 230);
mFontRegistry = new FontRegistry(display);
mFontRegistry.put("small", //$NON-NLS-1$
new FontData[] { new FontData("Arial", 8, SWT.NORMAL) }); //$NON-NLS-1$
mFontRegistry.put("courier8", //$NON-NLS-1$
new FontData[] { new FontData("Courier New", 8, SWT.BOLD) }); //$NON-NLS-1$
mFontRegistry.put("medium", //$NON-NLS-1$
new FontData[] { new FontData("Courier New", 10, SWT.NORMAL) }); //$NON-NLS-1$
Image image = new Image(display, new Rectangle(100, 100, 100, 100));
GC gc = new GC(image);
if (mSetFonts) {
gc.setFont(mFontRegistry.get("small")); //$NON-NLS-1$
}
mSmallFontWidth = gc.getFontMetrics().getAverageCharWidth();
mSmallFontHeight = gc.getFontMetrics().getHeight();
image.dispose();
gc.dispose();
setLayout(new FillLayout());
// Create a sash form for holding two canvas views, one for the
// thread labels and one for the thread timeline.
mSashForm = new SashForm(this, SWT.HORIZONTAL);
mSashForm.setBackground(mColorGray);
mSashForm.SASH_WIDTH = 3;
// Create a composite for the left side of the sash
Composite composite = new Composite(mSashForm, SWT.NONE);
GridLayout layout = new GridLayout(1, true /* make columns equal width */);
layout.marginHeight = 0;
layout.marginWidth = 0;
layout.verticalSpacing = 1;
composite.setLayout(layout);
// Create a blank corner space in the upper left corner
BlankCorner corner = new BlankCorner(composite);
GridData gridData = new GridData(GridData.FILL_HORIZONTAL);
gridData.heightHint = topMargin;
corner.setLayoutData(gridData);
// Add the thread labels below the blank corner.
mLabels = new RowLabels(composite);
gridData = new GridData(GridData.FILL_BOTH);
mLabels.setLayoutData(gridData);
// Create another composite for the right side of the sash
composite = new Composite(mSashForm, SWT.NONE);
layout = new GridLayout(1, true /* make columns equal width */);
layout.marginHeight = 0;
layout.marginWidth = 0;
layout.verticalSpacing = 1;
composite.setLayout(layout);
mTimescale = new Timescale(composite);
gridData = new GridData(GridData.FILL_HORIZONTAL);
gridData.heightHint = topMargin;
mTimescale.setLayoutData(gridData);
mSurface = new Surface(composite);
gridData = new GridData(GridData.FILL_BOTH);
mSurface.setLayoutData(gridData);
mSashForm.setWeights(new int[] { 1, 5 });
final ScrollBar vBar = mSurface.getVerticalBar();
vBar.addListener(SWT.Selection, new Listener() {
@Override
public void handleEvent(Event e) {
mScrollOffsetY = vBar.getSelection();
Point dim = mSurface.getSize();
int newScrollOffsetY = computeVisibleRows(dim.y);
if (newScrollOffsetY != mScrollOffsetY) {
mScrollOffsetY = newScrollOffsetY;
vBar.setSelection(newScrollOffsetY);
}
mLabels.redraw();
mSurface.redraw();
}
});
final ScrollBar hBar = mSurface.getHorizontalBar();
hBar.addListener(SWT.Selection, new Listener() {
@Override
public void handleEvent(Event e) {
mSurface.setScaleFromHorizontalScrollBar(hBar.getSelection());
mSurface.redraw();
}
});
mSurface.addListener(SWT.Resize, new Listener() {
@Override
public void handleEvent(Event e) {
Point dim = mSurface.getSize();
// If we don't need the scroll bar then don't display it.
if (dim.y >= mNumRows * rowYSpace) {
vBar.setVisible(false);
} else {
vBar.setVisible(true);
}
int newScrollOffsetY = computeVisibleRows(dim.y);
if (newScrollOffsetY != mScrollOffsetY) {
mScrollOffsetY = newScrollOffsetY;
vBar.setSelection(newScrollOffsetY);
}
int spaceNeeded = mNumRows * rowYSpace;
vBar.setMaximum(spaceNeeded);
vBar.setThumb(dim.y);
mLabels.redraw();
mSurface.redraw();
}
});
mSurface.addMouseListener(new MouseAdapter() {
@Override
public void mouseUp(MouseEvent me) {
mSurface.mouseUp(me);
}
@Override
public void mouseDown(MouseEvent me) {
mSurface.mouseDown(me);
}
@Override
public void mouseDoubleClick(MouseEvent me) {
mSurface.mouseDoubleClick(me);
}
});
mSurface.addMouseMoveListener(new MouseMoveListener() {
@Override
public void mouseMove(MouseEvent me) {
mSurface.mouseMove(me);
}
});
mSurface.addMouseWheelListener(new MouseWheelListener() {
@Override
public void mouseScrolled(MouseEvent me) {
mSurface.mouseScrolled(me);
}
});
mTimescale.addMouseListener(new MouseAdapter() {
@Override
public void mouseUp(MouseEvent me) {
mTimescale.mouseUp(me);
}
@Override
public void mouseDown(MouseEvent me) {
mTimescale.mouseDown(me);
}
@Override
public void mouseDoubleClick(MouseEvent me) {
mTimescale.mouseDoubleClick(me);
}
});
mTimescale.addMouseMoveListener(new MouseMoveListener() {
@Override
public void mouseMove(MouseEvent me) {
mTimescale.mouseMove(me);
}
});
mLabels.addMouseMoveListener(new MouseMoveListener() {
@Override
public void mouseMove(MouseEvent me) {
mLabels.mouseMove(me);
}
});
setData(reader.getThreadTimeRecords());
}
@Override
public void update(Observable objservable, Object arg) {
// Ignore updates from myself
if (arg == "TimeLineView") //$NON-NLS-1$
return;
// System.out.printf("timeline update from %s\n", arg);
boolean foundHighlight = false;
ArrayList<Selection> selections;
selections = mSelectionController.getSelections();
for (Selection selection : selections) {
Selection.Action action = selection.getAction();
if (action != Selection.Action.Highlight)
continue;
String name = selection.getName();
// System.out.printf(" timeline highlight %s from %s\n", name, arg);
if (name == "MethodData") { //$NON-NLS-1$
foundHighlight = true;
mHighlightMethodData = (MethodData) selection.getValue();
// System.out.printf(" method %s\n",
// highlightMethodData.getName());
mHighlightCall = null;
startHighlighting();
} else if (name == "Call") { //$NON-NLS-1$
foundHighlight = true;
mHighlightCall = (Call) selection.getValue();
// System.out.printf(" call %s\n", highlightCall.getName());
mHighlightMethodData = null;
startHighlighting();
}
}
if (foundHighlight == false)
mSurface.clearHighlights();
}
public void setData(ArrayList<Record> records) {
if (records == null)
records = new ArrayList<Record>();
if (false) {
System.out.println("TimelineView() list of records:"); //$NON-NLS-1$
for (Record r : records) {
System.out.printf("row '%s' block '%s' [%d, %d]\n", r.row //$NON-NLS-1$
.getName(), r.block.getName(), r.block.getStartTime(),
r.block.getEndTime());
if (r.block.getStartTime() > r.block.getEndTime()) {
System.err.printf("Error: block startTime > endTime\n"); //$NON-NLS-1$
System.exit(1);
}
}
}
// Sort the records into increasing start time, and decreasing end time
Collections.sort(records, new Comparator<Record>() {
@Override
public int compare(Record r1, Record r2) {
long start1 = r1.block.getStartTime();
long start2 = r2.block.getStartTime();
if (start1 > start2)
return 1;
if (start1 < start2)
return -1;
// The start times are the same, so compare the end times
long end1 = r1.block.getEndTime();
long end2 = r2.block.getEndTime();
if (end1 > end2)
return -1;
if (end1 < end2)
return 1;
return 0;
}
});
ArrayList<Segment> segmentList = new ArrayList<Segment>();
// The records are sorted into increasing start time,
// so the minimum start time is the start time of the first record.
double minVal = 0;
if (records.size() > 0)
minVal = records.get(0).block.getStartTime();
// Sum the time spent in each row and block, and
// keep track of the maximum end time.
double maxVal = 0;
for (Record rec : records) {
Row row = rec.row;
Block block = rec.block;
if (block.isIgnoredBlock()) {
continue;
}
String rowName = row.getName();
RowData rd = mRowByName.get(rowName);
if (rd == null) {
rd = new RowData(row);
mRowByName.put(rowName, rd);
}
long blockStartTime = block.getStartTime();
long blockEndTime = block.getEndTime();
if (blockEndTime > rd.mEndTime) {
long start = Math.max(blockStartTime, rd.mEndTime);
rd.mElapsed += blockEndTime - start;
rd.mEndTime = blockEndTime;
}
if (blockEndTime > maxVal)
maxVal = blockEndTime;
// Keep track of nested blocks by using a stack (for each row).
// Create a Segment object for each visible part of a block.
Block top = rd.top();
if (top == null) {
rd.push(block);
continue;
}
long topStartTime = top.getStartTime();
long topEndTime = top.getEndTime();
if (topEndTime >= blockStartTime) {
// Add this segment if it has a non-zero elapsed time.
if (topStartTime < blockStartTime) {
Segment segment = new Segment(rd, top, topStartTime,
blockStartTime);
segmentList.add(segment);
}
// If this block starts where the previous (top) block ends,
// then pop off the top block.
if (topEndTime == blockStartTime)
rd.pop();
rd.push(block);
} else {
// We may have to pop several frames here.
popFrames(rd, top, blockStartTime, segmentList);
rd.push(block);
}
}
// Clean up the stack of each row
for (RowData rd : mRowByName.values()) {
Block top = rd.top();
popFrames(rd, top, Integer.MAX_VALUE, segmentList);
}
mSurface.setRange(minVal, maxVal);
mSurface.setLimitRange(minVal, maxVal);
// Sort the rows into decreasing elapsed time
Collection<RowData> rv = mRowByName.values();
mRows = rv.toArray(new RowData[rv.size()]);
Arrays.sort(mRows, new Comparator<RowData>() {
@Override
public int compare(RowData rd1, RowData rd2) {
return (int) (rd2.mElapsed - rd1.mElapsed);
}
});
// Assign ranks to the sorted rows
for (int ii = 0; ii < mRows.length; ++ii) {
mRows[ii].mRank = ii;
}
// Compute the number of rows with data
mNumRows = 0;
for (int ii = 0; ii < mRows.length; ++ii) {
if (mRows[ii].mElapsed == 0)
break;
mNumRows += 1;
}
// Sort the blocks into increasing rows, and within rows into
// increasing start values.
mSegments = segmentList.toArray(new Segment[segmentList.size()]);
Arrays.sort(mSegments, new Comparator<Segment>() {
@Override
public int compare(Segment bd1, Segment bd2) {
RowData rd1 = bd1.mRowData;
RowData rd2 = bd2.mRowData;
int diff = rd1.mRank - rd2.mRank;
if (diff == 0) {
long timeDiff = bd1.mStartTime - bd2.mStartTime;
if (timeDiff == 0)
timeDiff = bd1.mEndTime - bd2.mEndTime;
return (int) timeDiff;
}
return diff;
}
});
if (false) {
for (Segment segment : mSegments) {
System.out.printf("seg '%s' [%6d, %6d] %s\n",
segment.mRowData.mName, segment.mStartTime,
segment.mEndTime, segment.mBlock.getName());
if (segment.mStartTime > segment.mEndTime) {
System.err.printf("Error: segment startTime > endTime\n");
System.exit(1);
}
}
}
}
private static void popFrames(RowData rd, Block top, long startTime,
ArrayList<Segment> segmentList) {
long topEndTime = top.getEndTime();
long lastEndTime = top.getStartTime();
while (topEndTime <= startTime) {
if (topEndTime > lastEndTime) {
Segment segment = new Segment(rd, top, lastEndTime, topEndTime);
segmentList.add(segment);
lastEndTime = topEndTime;
}
rd.pop();
top = rd.top();
if (top == null)
return;
topEndTime = top.getEndTime();
}
// If we get here, then topEndTime > startTime
if (lastEndTime < startTime) {
Segment bd = new Segment(rd, top, lastEndTime, startTime);
segmentList.add(bd);
}
}
private class RowLabels extends Canvas {
/** The space between the row label and the sash line */
private static final int labelMarginX = 2;
public RowLabels(Composite parent) {
super(parent, SWT.NO_BACKGROUND);
addPaintListener(new PaintListener() {
@Override
public void paintControl(PaintEvent pe) {
draw(pe.display, pe.gc);
}
});
}
private void mouseMove(MouseEvent me) {
int rownum = (me.y + mScrollOffsetY) / rowYSpace;
if (mMouseRow != rownum) {
mMouseRow = rownum;
redraw();
mSurface.redraw();
}
}
private void draw(Display display, GC gc) {
if (mSegments.length == 0) {
// gc.setBackground(colorBackground);
// gc.fillRectangle(getBounds());
return;
}
Point dim = getSize();
// Create an image for double-buffering
Image image = new Image(display, getBounds());
// Set up the off-screen gc
GC gcImage = new GC(image);
if (mSetFonts)
gcImage.setFont(mFontRegistry.get("medium")); //$NON-NLS-1$
if (mNumRows > 2) {
// Draw the row background stripes
gcImage.setBackground(mColorRowBack);
for (int ii = 1; ii < mNumRows; ii += 2) {
RowData rd = mRows[ii];
int y1 = rd.mRank * rowYSpace - mScrollOffsetY;
gcImage.fillRectangle(0, y1, dim.x, rowYSpace);
}
}
// Draw the row labels
int offsetY = rowYMarginHalf - mScrollOffsetY;
for (int ii = mStartRow; ii <= mEndRow; ++ii) {
RowData rd = mRows[ii];
int y1 = rd.mRank * rowYSpace + offsetY;
Point extent = gcImage.stringExtent(rd.mName);
int x1 = dim.x - extent.x - labelMarginX;
gcImage.drawString(rd.mName, x1, y1, true);
}
// Draw a highlight box on the row where the mouse is.
if (mMouseRow >= mStartRow && mMouseRow <= mEndRow) {
gcImage.setForeground(mColorGray);
int y1 = mMouseRow * rowYSpace - mScrollOffsetY;
gcImage.drawRectangle(0, y1, dim.x, rowYSpace);
}
// Draw the off-screen buffer to the screen
gc.drawImage(image, 0, 0);
// Clean up
image.dispose();
gcImage.dispose();
}
}
private class BlankCorner extends Canvas {
public BlankCorner(Composite parent) {
//super(parent, SWT.NO_BACKGROUND);
super(parent, SWT.NONE);
addPaintListener(new PaintListener() {
@Override
public void paintControl(PaintEvent pe) {
draw(pe.display, pe.gc);
}
});
}
private void draw(Display display, GC gc) {
// Create a blank image and draw it to the canvas
Image image = new Image(display, getBounds());
gc.drawImage(image, 0, 0);
// Clean up
image.dispose();
}
}
private class Timescale extends Canvas {
private Point mMouse = new Point(LeftMargin, 0);
private Cursor mZoomCursor;
private String mMethodName = null;
private Color mMethodColor = null;
private String mDetails;
private int mMethodStartY;
private int mDetailsStartY;
private int mMarkStartX;
private int mMarkEndX;
/** The space between the colored block and the method name */
private static final int METHOD_BLOCK_MARGIN = 10;
public Timescale(Composite parent) {
//super(parent, SWT.NO_BACKGROUND);
super(parent, SWT.NONE);
Display display = getDisplay();
mZoomCursor = new Cursor(display, SWT.CURSOR_SIZEWE);
setCursor(mZoomCursor);
mMethodStartY = mSmallFontHeight + 1;
mDetailsStartY = mMethodStartY + mSmallFontHeight + 1;
addPaintListener(new PaintListener() {
@Override
public void paintControl(PaintEvent pe) {
draw(pe.display, pe.gc);
}
});
}
public void setVbarPosition(int x) {
mMouse.x = x;
}
public void setMarkStart(int x) {
mMarkStartX = x;
}
public void setMarkEnd(int x) {
mMarkEndX = x;
}
public void setMethodName(String name) {
mMethodName = name;
}
public void setMethodColor(Color color) {
mMethodColor = color;
}
public void setDetails(String details) {
mDetails = details;
}
private void mouseMove(MouseEvent me) {
me.y = -1;
mSurface.mouseMove(me);
}
private void mouseDown(MouseEvent me) {
mSurface.startScaling(me.x);
mSurface.redraw();
}
private void mouseUp(MouseEvent me) {
mSurface.stopScaling(me.x);
}
private void mouseDoubleClick(MouseEvent me) {
mSurface.resetScale();
mSurface.redraw();
}
private void draw(Display display, GC gc) {
Point dim = getSize();
// Create an image for double-buffering
Image image = new Image(display, getBounds());
// Set up the off-screen gc
GC gcImage = new GC(image);
if (mSetFonts)
gcImage.setFont(mFontRegistry.get("medium")); //$NON-NLS-1$
if (mSurface.drawingSelection()) {
drawSelection(display, gcImage);
}
drawTicks(display, gcImage);
// Draw the vertical bar where the mouse is
gcImage.setForeground(mColorDarkGray);
gcImage.drawLine(mMouse.x, timeLineOffsetY, mMouse.x, dim.y);
// Draw the current millseconds
drawTickLegend(display, gcImage);
// Draw the method name and color, if needed
drawMethod(display, gcImage);
// Draw the details, if needed
drawDetails(display, gcImage);
// Draw the off-screen buffer to the screen
gc.drawImage(image, 0, 0);
// Clean up
image.dispose();
gcImage.dispose();
}
private void drawSelection(Display display, GC gc) {
Point dim = getSize();
gc.setForeground(mColorGray);
gc.drawLine(mMarkStartX, timeLineOffsetY, mMarkStartX, dim.y);
gc.setBackground(mColorZoomSelection);
int x, width;
if (mMarkStartX < mMarkEndX) {
x = mMarkStartX;
width = mMarkEndX - mMarkStartX;
} else {
x = mMarkEndX;
width = mMarkStartX - mMarkEndX;
}
if (width > 1) {
gc.fillRectangle(x, timeLineOffsetY, width, dim.y);
}
}
private void drawTickLegend(Display display, GC gc) {
int mouseX = mMouse.x - LeftMargin;
double mouseXval = mScaleInfo.pixelToValue(mouseX);
String info = mUnits.labelledString(mouseXval);
gc.setForeground(mColorForeground);
gc.drawString(info, LeftMargin + 2, 1, true);
// Display the maximum data value
double maxVal = mScaleInfo.getMaxVal();
info = mUnits.labelledString(maxVal);
if (mClockSource != null) {
info = String.format(" max %s (%s)", info, mClockSource); //$NON-NLS-1$
} else {
info = String.format(" max %s ", info); //$NON-NLS-1$
}
Point extent = gc.stringExtent(info);
Point dim = getSize();
int x1 = dim.x - RightMargin - extent.x;
gc.drawString(info, x1, 1, true);
}
private void drawMethod(Display display, GC gc) {
if (mMethodName == null) {
return;
}
int x1 = LeftMargin;
int y1 = mMethodStartY;
gc.setBackground(mMethodColor);
int width = 2 * mSmallFontWidth;
gc.fillRectangle(x1, y1, width, mSmallFontHeight);
x1 += width + METHOD_BLOCK_MARGIN;
gc.drawString(mMethodName, x1, y1, true);
}
private void drawDetails(Display display, GC gc) {
if (mDetails == null) {
return;
}
int x1 = LeftMargin + 2 * mSmallFontWidth + METHOD_BLOCK_MARGIN;
int y1 = mDetailsStartY;
gc.drawString(mDetails, x1, y1, true);
}
private void drawTicks(Display display, GC gc) {
Point dim = getSize();
int y2 = majorTickLength + timeLineOffsetY;
int y3 = minorTickLength + timeLineOffsetY;
int y4 = y2 + tickToFontSpacing;
gc.setForeground(mColorForeground);
gc.drawLine(LeftMargin, timeLineOffsetY, dim.x - RightMargin,
timeLineOffsetY);
double minVal = mScaleInfo.getMinVal();
double maxVal = mScaleInfo.getMaxVal();
double minMajorTick = mScaleInfo.getMinMajorTick();
double tickIncrement = mScaleInfo.getTickIncrement();
double minorTickIncrement = tickIncrement / 5;
double pixelsPerRange = mScaleInfo.getPixelsPerRange();
// Draw the initial minor ticks, if any
if (minVal < minMajorTick) {
gc.setForeground(mColorGray);
double xMinor = minMajorTick;
for (int ii = 1; ii <= 4; ++ii) {
xMinor -= minorTickIncrement;
if (xMinor < minVal)
break;
int x1 = LeftMargin
+ (int) (0.5 + (xMinor - minVal) * pixelsPerRange);
gc.drawLine(x1, timeLineOffsetY, x1, y3);
}
}
if (tickIncrement <= 10) {
// TODO avoid rendering the loop when tickIncrement is invalid. It can be zero
// or too small.
// System.out.println(String.format("Timescale.drawTicks error: tickIncrement=%1f", tickIncrement));
return;
}
for (double x = minMajorTick; x <= maxVal; x += tickIncrement) {
int x1 = LeftMargin
+ (int) (0.5 + (x - minVal) * pixelsPerRange);
// Draw a major tick
gc.setForeground(mColorForeground);
gc.drawLine(x1, timeLineOffsetY, x1, y2);
if (x > maxVal)
break;
// Draw the tick text
String tickString = mUnits.valueOf(x);
gc.drawString(tickString, x1, y4, true);
// Draw 4 minor ticks between major ticks
gc.setForeground(mColorGray);
double xMinor = x;
for (int ii = 1; ii <= 4; ii++) {
xMinor += minorTickIncrement;
if (xMinor > maxVal)
break;
x1 = LeftMargin
+ (int) (0.5 + (xMinor - minVal) * pixelsPerRange);
gc.drawLine(x1, timeLineOffsetY, x1, y3);
}
}
}
}
private static enum GraphicsState {
Normal, Marking, Scaling, Animating, Scrolling
};
private class Surface extends Canvas {
public Surface(Composite parent) {
super(parent, SWT.NO_BACKGROUND | SWT.V_SCROLL | SWT.H_SCROLL);
Display display = getDisplay();
mNormalCursor = new Cursor(display, SWT.CURSOR_CROSS);
mIncreasingCursor = new Cursor(display, SWT.CURSOR_SIZEE);
mDecreasingCursor = new Cursor(display, SWT.CURSOR_SIZEW);
initZoomFractionsWithExp();
addPaintListener(new PaintListener() {
@Override
public void paintControl(PaintEvent pe) {
draw(pe.display, pe.gc);
}
});
mZoomAnimator = new Runnable() {
@Override
public void run() {
animateZoom();
}
};
mHighlightAnimator = new Runnable() {
@Override
public void run() {
animateHighlight();
}
};
}
private void initZoomFractionsWithExp() {
mZoomFractions = new double[ZOOM_STEPS];
int next = 0;
for (int ii = 0; ii < ZOOM_STEPS / 2; ++ii, ++next) {
mZoomFractions[next] = (double) (1 << ii)
/ (double) (1 << (ZOOM_STEPS / 2));
// System.out.printf("%d %f\n", next, zoomFractions[next]);
}
for (int ii = 2; ii < 2 + ZOOM_STEPS / 2; ++ii, ++next) {
mZoomFractions[next] = (double) ((1 << ii) - 1)
/ (double) (1 << ii);
// System.out.printf("%d %f\n", next, zoomFractions[next]);
}
}
@SuppressWarnings("unused")
private void initZoomFractionsWithSinWave() {
mZoomFractions = new double[ZOOM_STEPS];
for (int ii = 0; ii < ZOOM_STEPS; ++ii) {
double offset = Math.PI * ii / ZOOM_STEPS;
mZoomFractions[ii] = (Math.sin((1.5 * Math.PI + offset)) + 1.0) / 2.0;
// System.out.printf("%d %f\n", ii, zoomFractions[ii]);
}
}
public void setRange(double minVal, double maxVal) {
mMinDataVal = minVal;
mMaxDataVal = maxVal;
mScaleInfo.setMinVal(minVal);
mScaleInfo.setMaxVal(maxVal);
}
public void setLimitRange(double minVal, double maxVal) {
mLimitMinVal = minVal;
mLimitMaxVal = maxVal;
}
public void resetScale() {
mScaleInfo.setMinVal(mLimitMinVal);
mScaleInfo.setMaxVal(mLimitMaxVal);
}
public void setScaleFromHorizontalScrollBar(int selection) {
double minVal = mScaleInfo.getMinVal();
double maxVal = mScaleInfo.getMaxVal();
double visibleRange = maxVal - minVal;
minVal = mLimitMinVal + selection;
maxVal = minVal + visibleRange;
if (maxVal > mLimitMaxVal) {
maxVal = mLimitMaxVal;
minVal = maxVal - visibleRange;
}
mScaleInfo.setMinVal(minVal);
mScaleInfo.setMaxVal(maxVal);
mGraphicsState = GraphicsState.Scrolling;
}
private void updateHorizontalScrollBar() {
double minVal = mScaleInfo.getMinVal();
double maxVal = mScaleInfo.getMaxVal();
double visibleRange = maxVal - minVal;
double fullRange = mLimitMaxVal - mLimitMinVal;
ScrollBar hBar = getHorizontalBar();
if (fullRange > visibleRange) {
hBar.setVisible(true);
hBar.setMinimum(0);
hBar.setMaximum((int)Math.ceil(fullRange));
hBar.setThumb((int)Math.ceil(visibleRange));
hBar.setSelection((int)Math.floor(minVal - mLimitMinVal));
} else {
hBar.setVisible(false);
}
}
private void draw(Display display, GC gc) {
if (mSegments.length == 0) {
// gc.setBackground(colorBackground);
// gc.fillRectangle(getBounds());
return;
}
// Create an image for double-buffering
Image image = new Image(display, getBounds());
// Set up the off-screen gc
GC gcImage = new GC(image);
if (mSetFonts)
gcImage.setFont(mFontRegistry.get("small")); //$NON-NLS-1$
// Draw the background
// gcImage.setBackground(colorBackground);
// gcImage.fillRectangle(image.getBounds());
if (mGraphicsState == GraphicsState.Scaling) {
double diff = mMouse.x - mMouseMarkStartX;
if (diff > 0) {
double newMinVal = mScaleMinVal - diff / mScalePixelsPerRange;
if (newMinVal < mLimitMinVal)
newMinVal = mLimitMinVal;
mScaleInfo.setMinVal(newMinVal);
// System.out.printf("diff %f scaleMin %f newMin %f\n",
// diff, scaleMinVal, newMinVal);
} else if (diff < 0) {
double newMaxVal = mScaleMaxVal - diff / mScalePixelsPerRange;
if (newMaxVal > mLimitMaxVal)
newMaxVal = mLimitMaxVal;
mScaleInfo.setMaxVal(newMaxVal);
// System.out.printf("diff %f scaleMax %f newMax %f\n",
// diff, scaleMaxVal, newMaxVal);
}
}
// Recompute the ticks and strips only if the size has changed,
// or we scrolled so that a new row is visible.
Point dim = getSize();
if (mStartRow != mCachedStartRow || mEndRow != mCachedEndRow
|| mScaleInfo.getMinVal() != mCachedMinVal
|| mScaleInfo.getMaxVal() != mCachedMaxVal) {
mCachedStartRow = mStartRow;
mCachedEndRow = mEndRow;
int xdim = dim.x - TotalXMargin;
mScaleInfo.setNumPixels(xdim);
boolean forceEndPoints = (mGraphicsState == GraphicsState.Scaling
|| mGraphicsState == GraphicsState.Animating
|| mGraphicsState == GraphicsState.Scrolling);
mScaleInfo.computeTicks(forceEndPoints);
mCachedMinVal = mScaleInfo.getMinVal();
mCachedMaxVal = mScaleInfo.getMaxVal();
if (mLimitMinVal > mScaleInfo.getMinVal())
mLimitMinVal = mScaleInfo.getMinVal();
if (mLimitMaxVal < mScaleInfo.getMaxVal())
mLimitMaxVal = mScaleInfo.getMaxVal();
// Compute the strips
computeStrips();
// Update the horizontal scrollbar.
updateHorizontalScrollBar();
}
if (mNumRows > 2) {
// Draw the row background stripes
gcImage.setBackground(mColorRowBack);
for (int ii = 1; ii < mNumRows; ii += 2) {
RowData rd = mRows[ii];
int y1 = rd.mRank * rowYSpace - mScrollOffsetY;
gcImage.fillRectangle(0, y1, dim.x, rowYSpace);
}
}
if (drawingSelection()) {
drawSelection(display, gcImage);
}
String blockName = null;
Color blockColor = null;
String blockDetails = null;
if (mDebug) {
double pixelsPerRange = mScaleInfo.getPixelsPerRange();
System.out
.printf(
"dim.x %d pixels %d minVal %f, maxVal %f ppr %f rpp %f\n",
dim.x, dim.x - TotalXMargin, mScaleInfo
.getMinVal(), mScaleInfo.getMaxVal(),
pixelsPerRange, 1.0 / pixelsPerRange);
}
// Draw the strips
Block selectBlock = null;
for (Strip strip : mStripList) {
if (strip.mColor == null) {
// System.out.printf("strip.color is null\n");
continue;
}
gcImage.setBackground(strip.mColor);
gcImage.fillRectangle(strip.mX, strip.mY - mScrollOffsetY, strip.mWidth,
strip.mHeight);
if (mMouseRow == strip.mRowData.mRank) {
if (mMouse.x >= strip.mX
&& mMouse.x < strip.mX + strip.mWidth) {
Block block = strip.mSegment.mBlock;
blockName = block.getName();
blockColor = strip.mColor;
if (mHaveCpuTime) {
if (mHaveRealTime) {
blockDetails = String.format(
"excl cpu %s, incl cpu %s, "
+ "excl real %s, incl real %s",
mUnits.labelledString(block.getExclusiveCpuTime()),
mUnits.labelledString(block.getInclusiveCpuTime()),
mUnits.labelledString(block.getExclusiveRealTime()),
mUnits.labelledString(block.getInclusiveRealTime()));
} else {
blockDetails = String.format(
"excl cpu %s, incl cpu %s",
mUnits.labelledString(block.getExclusiveCpuTime()),
mUnits.labelledString(block.getInclusiveCpuTime()));
}
} else {
blockDetails = String.format(
"excl real %s, incl real %s",
mUnits.labelledString(block.getExclusiveRealTime()),
mUnits.labelledString(block.getInclusiveRealTime()));
}
}
if (mMouseSelect.x >= strip.mX
&& mMouseSelect.x < strip.mX + strip.mWidth) {
selectBlock = strip.mSegment.mBlock;
}
}
}
mMouseSelect.x = 0;
mMouseSelect.y = 0;
if (selectBlock != null) {
ArrayList<Selection> selections = new ArrayList<Selection>();
// Get the row label
RowData rd = mRows[mMouseRow];
selections.add(Selection.highlight("Thread", rd.mName)); //$NON-NLS-1$
selections.add(Selection.highlight("Call", selectBlock)); //$NON-NLS-1$
int mouseX = mMouse.x - LeftMargin;
double mouseXval = mScaleInfo.pixelToValue(mouseX);
selections.add(Selection.highlight("Time", mouseXval)); //$NON-NLS-1$
mSelectionController.change(selections, "TimeLineView"); //$NON-NLS-1$
mHighlightMethodData = null;
mHighlightCall = (Call) selectBlock;
startHighlighting();
}
// Draw a highlight box on the row where the mouse is.
// Except don't draw the box if we are animating the
// highlighing of a call or method because the inclusive
// highlight bar passes through the highlight box and
// causes an annoying flashing artifact.
if (mMouseRow >= 0 && mMouseRow < mNumRows && mHighlightStep == 0) {
gcImage.setForeground(mColorGray);
int y1 = mMouseRow * rowYSpace - mScrollOffsetY;
gcImage.drawLine(0, y1, dim.x, y1);
gcImage.drawLine(0, y1 + rowYSpace, dim.x, y1 + rowYSpace);
}
// Highlight a selected method, if any
drawHighlights(gcImage, dim);
// Draw a vertical line where the mouse is.
gcImage.setForeground(mColorDarkGray);
int lineEnd = Math.min(dim.y, mNumRows * rowYSpace);
gcImage.drawLine(mMouse.x, 0, mMouse.x, lineEnd);
if (blockName != null) {
mTimescale.setMethodName(blockName);
mTimescale.setMethodColor(blockColor);
mTimescale.setDetails(blockDetails);
mShowHighlightName = false;
} else if (mShowHighlightName) {
// Draw the highlighted method name
MethodData md = mHighlightMethodData;
if (md == null && mHighlightCall != null)
md = mHighlightCall.getMethodData();
if (md == null)
System.out.printf("null highlight?\n"); //$NON-NLS-1$
if (md != null) {
mTimescale.setMethodName(md.getProfileName());
mTimescale.setMethodColor(md.getColor());
mTimescale.setDetails(null);
}
} else {
mTimescale.setMethodName(null);
mTimescale.setMethodColor(null);
mTimescale.setDetails(null);
}
mTimescale.redraw();
// Draw the off-screen buffer to the screen
gc.drawImage(image, 0, 0);
// Clean up
image.dispose();
gcImage.dispose();
}
private void drawHighlights(GC gc, Point dim) {
int height = mHighlightHeight;
if (height <= 0)
return;
for (Range range : mHighlightExclusive) {
gc.setBackground(range.mColor);
int xStart = range.mXdim.x;
int width = range.mXdim.y;
gc.fillRectangle(xStart, range.mY - height - mScrollOffsetY, width, height);
}
// Draw the inclusive lines a bit shorter
height -= 1;
if (height <= 0)
height = 1;
// Highlight the inclusive ranges
gc.setForeground(mColorDarkGray);
gc.setBackground(mColorDarkGray);
for (Range range : mHighlightInclusive) {
int x1 = range.mXdim.x;
int x2 = range.mXdim.y;
boolean drawLeftEnd = false;
boolean drawRightEnd = false;
if (x1 >= LeftMargin)
drawLeftEnd = true;
else
x1 = LeftMargin;
if (x2 >= LeftMargin)
drawRightEnd = true;
else
x2 = dim.x - RightMargin;
int y1 = range.mY + rowHeight + 2 - mScrollOffsetY;
// If the range is very narrow, then just draw a small
// rectangle.
if (x2 - x1 < MinInclusiveRange) {
int width = x2 - x1;
if (width < 2)
width = 2;
gc.fillRectangle(x1, y1, width, height);
continue;
}
if (drawLeftEnd) {
if (drawRightEnd) {
// Draw both ends
int[] points = { x1, y1, x1, y1 + height, x2,
y1 + height, x2, y1 };
gc.drawPolyline(points);
} else {
// Draw the left end
int[] points = { x1, y1, x1, y1 + height, x2,
y1 + height };
gc.drawPolyline(points);
}
} else {
if (drawRightEnd) {
// Draw the right end
int[] points = { x1, y1 + height, x2, y1 + height, x2,
y1 };
gc.drawPolyline(points);
} else {
// Draw neither end, just the line
int[] points = { x1, y1 + height, x2, y1 + height };
gc.drawPolyline(points);
}
}
// Draw the arrowheads, if necessary
if (drawLeftEnd == false) {
int[] points = { x1 + 7, y1 + height - 4, x1, y1 + height,
x1 + 7, y1 + height + 4 };
gc.fillPolygon(points);
}
if (drawRightEnd == false) {
int[] points = { x2 - 7, y1 + height - 4, x2, y1 + height,
x2 - 7, y1 + height + 4 };
gc.fillPolygon(points);
}
}
}
private boolean drawingSelection() {
return mGraphicsState == GraphicsState.Marking
|| mGraphicsState == GraphicsState.Animating;
}
private void drawSelection(Display display, GC gc) {
Point dim = getSize();
gc.setForeground(mColorGray);
gc.drawLine(mMouseMarkStartX, 0, mMouseMarkStartX, dim.y);
gc.setBackground(mColorZoomSelection);
int width;
int mouseX = (mGraphicsState == GraphicsState.Animating) ? mMouseMarkEndX : mMouse.x;
int x;
if (mMouseMarkStartX < mouseX) {
x = mMouseMarkStartX;
width = mouseX - mMouseMarkStartX;
} else {
x = mouseX;
width = mMouseMarkStartX - mouseX;
}
gc.fillRectangle(x, 0, width, dim.y);
}
private void computeStrips() {
double minVal = mScaleInfo.getMinVal();
double maxVal = mScaleInfo.getMaxVal();
// Allocate space for the pixel data
Pixel[] pixels = new Pixel[mNumRows];
for (int ii = 0; ii < mNumRows; ++ii)
pixels[ii] = new Pixel();
// Clear the per-block pixel data
for (int ii = 0; ii < mSegments.length; ++ii) {
mSegments[ii].mBlock.clearWeight();
}
mStripList.clear();
mHighlightExclusive.clear();
mHighlightInclusive.clear();
MethodData callMethod = null;
long callStart = 0;
long callEnd = -1;
RowData callRowData = null;
int prevMethodStart = -1;
int prevMethodEnd = -1;
int prevCallStart = -1;
int prevCallEnd = -1;
if (mHighlightCall != null) {
int callPixelStart = -1;
int callPixelEnd = -1;
callStart = mHighlightCall.getStartTime();
callEnd = mHighlightCall.getEndTime();
callMethod = mHighlightCall.getMethodData();
if (callStart >= minVal)
callPixelStart = mScaleInfo.valueToPixel(callStart);
if (callEnd <= maxVal)
callPixelEnd = mScaleInfo.valueToPixel(callEnd);
// System.out.printf("callStart,End %d,%d minVal,maxVal %f,%f
// callPixelStart,End %d,%d\n",
// callStart, callEnd, minVal, maxVal, callPixelStart,
// callPixelEnd);
int threadId = mHighlightCall.getThreadId();
String threadName = mThreadLabels.get(threadId);
callRowData = mRowByName.get(threadName);
int y1 = callRowData.mRank * rowYSpace + rowYMarginHalf;
Color color = callMethod.getColor();
mHighlightInclusive.add(new Range(callPixelStart + LeftMargin,
callPixelEnd + LeftMargin, y1, color));
}
for (Segment segment : mSegments) {
if (segment.mEndTime <= minVal)
continue;
if (segment.mStartTime >= maxVal)
continue;
Block block = segment.mBlock;
// Skip over blocks that were not assigned a color, including the
// top level block and others that have zero inclusive time.
Color color = block.getColor();
if (color == null)
continue;
double recordStart = Math.max(segment.mStartTime, minVal);
double recordEnd = Math.min(segment.mEndTime, maxVal);
if (recordStart == recordEnd)
continue;
int pixelStart = mScaleInfo.valueToPixel(recordStart);
int pixelEnd = mScaleInfo.valueToPixel(recordEnd);
int width = pixelEnd - pixelStart;
boolean isContextSwitch = segment.mIsContextSwitch;
RowData rd = segment.mRowData;
MethodData md = block.getMethodData();
// We will add the scroll offset later when we draw the strips
int y1 = rd.mRank * rowYSpace + rowYMarginHalf;
// If we can't display any more rows, then quit
if (rd.mRank > mEndRow)
break;
// System.out.printf("segment %s val: [%.1f, %.1f] frac [%f, %f]
// pixel: [%d, %d] pix.start %d weight %.2f %s\n",
// block.getName(), recordStart, recordEnd,
// scaleInfo.valueToPixelFraction(recordStart),
// scaleInfo.valueToPixelFraction(recordEnd),
// pixelStart, pixelEnd, pixels[rd.rank].start,
// pixels[rd.rank].maxWeight,
// pixels[rd.rank].segment != null
// ? pixels[rd.rank].segment.block.getName()
// : "null");
if (mHighlightMethodData != null) {
if (mHighlightMethodData == md) {
if (prevMethodStart != pixelStart || prevMethodEnd != pixelEnd) {
prevMethodStart = pixelStart;
prevMethodEnd = pixelEnd;
int rangeWidth = width;
if (rangeWidth == 0)
rangeWidth = 1;
mHighlightExclusive.add(new Range(pixelStart
+ LeftMargin, rangeWidth, y1, color));
callStart = block.getStartTime();
int callPixelStart = -1;
if (callStart >= minVal)
callPixelStart = mScaleInfo.valueToPixel(callStart);
int callPixelEnd = -1;
callEnd = block.getEndTime();
if (callEnd <= maxVal)
callPixelEnd = mScaleInfo.valueToPixel(callEnd);
if (prevCallStart != callPixelStart || prevCallEnd != callPixelEnd) {
prevCallStart = callPixelStart;
prevCallEnd = callPixelEnd;
mHighlightInclusive.add(new Range(
callPixelStart + LeftMargin,
callPixelEnd + LeftMargin, y1, color));
}
}
} else if (mFadeColors) {
color = md.getFadedColor();
}
} else if (mHighlightCall != null) {
if (segment.mStartTime >= callStart
&& segment.mEndTime <= callEnd && callMethod == md
&& callRowData == rd) {
if (prevMethodStart != pixelStart || prevMethodEnd != pixelEnd) {
prevMethodStart = pixelStart;
prevMethodEnd = pixelEnd;
int rangeWidth = width;
if (rangeWidth == 0)
rangeWidth = 1;
mHighlightExclusive.add(new Range(pixelStart
+ LeftMargin, rangeWidth, y1, color));
}
} else if (mFadeColors) {
color = md.getFadedColor();
}
}
// Cases:
// 1. This segment starts on a different pixel than the
// previous segment started on. In this case, emit
// the pixel strip, if any, and:
// A. If the width is 0, then add this segment's
// weight to the Pixel.
// B. If the width > 0, then emit a strip for this
// segment (no partial Pixel data).
//
// 2. Otherwise (the new segment starts on the same
// pixel as the previous segment): add its "weight"
// to the current pixel, and:
// A. If the new segment has width 1,
// then emit the pixel strip and then
// add the segment's weight to the pixel.
// B. If the new segment has width > 1,
// then emit the pixel strip, and emit the rest
// of the strip for this segment (no partial Pixel
// data).
Pixel pix = pixels[rd.mRank];
if (pix.mStart != pixelStart) {
if (pix.mSegment != null) {
// Emit the pixel strip. This also clears the pixel.
emitPixelStrip(rd, y1, pix);
}
if (width == 0) {
// Compute the "weight" of this segment for the first
// pixel. For a pixel N, the "weight" of a segment is
// how much of the region [N - 0.5, N + 0.5] is covered
// by the segment.
double weight = computeWeight(recordStart, recordEnd,
isContextSwitch, pixelStart);
weight = block.addWeight(pixelStart, rd.mRank, weight);
if (weight > pix.mMaxWeight) {
pix.setFields(pixelStart, weight, segment, color,
rd);
}
} else {
int x1 = pixelStart + LeftMargin;
Strip strip = new Strip(
x1, isContextSwitch ? y1 + rowHeight - 1 : y1,
width, isContextSwitch ? 1 : rowHeight,
rd, segment, color);
mStripList.add(strip);
}
} else {
double weight = computeWeight(recordStart, recordEnd,
isContextSwitch, pixelStart);
weight = block.addWeight(pixelStart, rd.mRank, weight);
if (weight > pix.mMaxWeight) {
pix.setFields(pixelStart, weight, segment, color, rd);
}
if (width == 1) {
// Emit the pixel strip. This also clears the pixel.
emitPixelStrip(rd, y1, pix);
// Compute the weight for the next pixel
pixelStart += 1;
weight = computeWeight(recordStart, recordEnd,
isContextSwitch, pixelStart);
weight = block.addWeight(pixelStart, rd.mRank, weight);
pix.setFields(pixelStart, weight, segment, color, rd);
} else if (width > 1) {
// Emit the pixel strip. This also clears the pixel.
emitPixelStrip(rd, y1, pix);
// Emit a strip for the rest of the segment.
pixelStart += 1;
width -= 1;
int x1 = pixelStart + LeftMargin;
Strip strip = new Strip(
x1, isContextSwitch ? y1 + rowHeight - 1 : y1,
width, isContextSwitch ? 1 : rowHeight,
rd,segment, color);
mStripList.add(strip);
}
}
}
// Emit the last pixels of each row, if any
for (int ii = 0; ii < mNumRows; ++ii) {
Pixel pix = pixels[ii];
if (pix.mSegment != null) {
RowData rd = pix.mRowData;
int y1 = rd.mRank * rowYSpace + rowYMarginHalf;
// Emit the pixel strip. This also clears the pixel.
emitPixelStrip(rd, y1, pix);
}
}
if (false) {
System.out.printf("computeStrips()\n");
for (Strip strip : mStripList) {
System.out.printf("%3d, %3d width %3d height %d %s\n",
strip.mX, strip.mY, strip.mWidth, strip.mHeight,
strip.mSegment.mBlock.getName());
}
}
}
private double computeWeight(double start, double end,
boolean isContextSwitch, int pixel) {
if (isContextSwitch) {
return 0;
}
double pixelStartFraction = mScaleInfo.valueToPixelFraction(start);
double pixelEndFraction = mScaleInfo.valueToPixelFraction(end);
double leftEndPoint = Math.max(pixelStartFraction, pixel - 0.5);
double rightEndPoint = Math.min(pixelEndFraction, pixel + 0.5);
double weight = rightEndPoint - leftEndPoint;
return weight;
}
private void emitPixelStrip(RowData rd, int y, Pixel pixel) {
Strip strip;
if (pixel.mSegment == null)
return;
int x = pixel.mStart + LeftMargin;
// Compute the percentage of the row height proportional to
// the weight of this pixel. But don't let the proportion
// exceed 3/4 of the row height so that we can easily see
// if a given time range includes more than one method.
int height = (int) (pixel.mMaxWeight * rowHeight * 0.75);
if (height < mMinStripHeight)
height = mMinStripHeight;
int remainder = rowHeight - height;
if (remainder > 0) {
strip = new Strip(x, y, 1, remainder, rd, pixel.mSegment,
mFadeColors ? mColorGray : mColorBlack);
mStripList.add(strip);
// System.out.printf("emitPixel (%d, %d) height %d black\n",
// x, y, remainder);
}
strip = new Strip(x, y + remainder, 1, height, rd, pixel.mSegment,
pixel.mColor);
mStripList.add(strip);
// System.out.printf("emitPixel (%d, %d) height %d %s\n",
// x, y + remainder, height, pixel.segment.block.getName());
pixel.mSegment = null;
pixel.mMaxWeight = 0.0;
}
private void mouseMove(MouseEvent me) {
if (false) {
if (mHighlightMethodData != null) {
mHighlightMethodData = null;
// Force a recomputation of the strip colors
mCachedEndRow = -1;
}
}
Point dim = mSurface.getSize();
int x = me.x;
if (x < LeftMargin)
x = LeftMargin;
if (x > dim.x - RightMargin)
x = dim.x - RightMargin;
mMouse.x = x;
mMouse.y = me.y;
mTimescale.setVbarPosition(x);
if (mGraphicsState == GraphicsState.Marking) {
mTimescale.setMarkEnd(x);
}
if (mGraphicsState == GraphicsState.Normal) {
// Set the cursor to the normal state.
mSurface.setCursor(mNormalCursor);
} else if (mGraphicsState == GraphicsState.Marking) {
// Make the cursor point in the direction of the sweep
if (mMouse.x >= mMouseMarkStartX)
mSurface.setCursor(mIncreasingCursor);
else
mSurface.setCursor(mDecreasingCursor);
}
int rownum = (mMouse.y + mScrollOffsetY) / rowYSpace;
if (me.y < 0 || me.y >= dim.y) {
rownum = -1;
}
if (mMouseRow != rownum) {
mMouseRow = rownum;
mLabels.redraw();
}
redraw();
}
private void mouseDown(MouseEvent me) {
Point dim = mSurface.getSize();
int x = me.x;
if (x < LeftMargin)
x = LeftMargin;
if (x > dim.x - RightMargin)
x = dim.x - RightMargin;
mMouseMarkStartX = x;
mGraphicsState = GraphicsState.Marking;
mSurface.setCursor(mIncreasingCursor);
mTimescale.setMarkStart(mMouseMarkStartX);
mTimescale.setMarkEnd(mMouseMarkStartX);
redraw();
}
private void mouseUp(MouseEvent me) {
mSurface.setCursor(mNormalCursor);
if (mGraphicsState != GraphicsState.Marking) {
mGraphicsState = GraphicsState.Normal;
return;
}
mGraphicsState = GraphicsState.Animating;
Point dim = mSurface.getSize();
// If the user released the mouse outside the drawing area then
// cancel the zoom.
if (me.y <= 0 || me.y >= dim.y) {
mGraphicsState = GraphicsState.Normal;
redraw();
return;
}
int x = me.x;
if (x < LeftMargin)
x = LeftMargin;
if (x > dim.x - RightMargin)
x = dim.x - RightMargin;
mMouseMarkEndX = x;
// If the user clicked and released the mouse at the same point
// (+/- a pixel or two) then cancel the zoom (but select the
// method).
int dist = mMouseMarkEndX - mMouseMarkStartX;
if (dist < 0)
dist = -dist;
if (dist <= 2) {
mGraphicsState = GraphicsState.Normal;
// Select the method underneath the mouse
mMouseSelect.x = mMouseMarkStartX;
mMouseSelect.y = me.y;
redraw();
return;
}
// Make mouseEndX be the higher end point
if (mMouseMarkEndX < mMouseMarkStartX) {
int temp = mMouseMarkEndX;
mMouseMarkEndX = mMouseMarkStartX;
mMouseMarkStartX = temp;
}
// If the zoom area is the whole window (or nearly the whole
// window) then cancel the zoom.
if (mMouseMarkStartX <= LeftMargin + MinZoomPixelMargin
&& mMouseMarkEndX >= dim.x - RightMargin - MinZoomPixelMargin) {
mGraphicsState = GraphicsState.Normal;
redraw();
return;
}
// Compute some variables needed for zooming.
// It's probably easiest to explain by an example. There
// are two scales (or dimensions) involved: one for the pixels
// and one for the values (microseconds). To keep the example
// simple, suppose we have pixels in the range [0,16] and
// values in the range [100, 260], and suppose the user
// selects a zoom window from pixel 4 to pixel 8.
//
// usec: 100 140 180 260
// |-------|ZZZZZZZ|---------------|
// pixel: 0 4 8 16
//
// I've drawn the pixels starting at zero for simplicity, but
// in fact the drawable area is offset from the left margin
// by the value of "LeftMargin".
//
// The "pixels-per-range" (ppr) in this case is 0.1 (a tenth of
// a pixel per usec). What we want is to redraw the screen in
// several steps, each time increasing the zoom window until the
// zoom window fills the screen. For simplicity, assume that
// we want to zoom in four equal steps. Then the snapshots
// of the screen at each step would look something like this:
//
// usec: 100 140 180 260
// |-------|ZZZZZZZ|---------------|
// pixel: 0 4 8 16
//
// usec: ? 140 180 ?
// |-----|ZZZZZZZZZZZZZ|-----------|
// pixel: 0 3 10 16
//
// usec: ? 140 180 ?
// |---|ZZZZZZZZZZZZZZZZZZZ|-------|
// pixel: 0 2 12 16
//
// usec: ?140 180 ?
// |-|ZZZZZZZZZZZZZZZZZZZZZZZZZ|---|
// pixel: 0 1 14 16
//
// usec: 140 180
// |ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ|
// pixel: 0 16
//
// The problem is how to compute the endpoints (denoted by ?)
// for each step. This is a little tricky. We first need to
// compute the "fixed point": this is the point in the selection
// that doesn't move left or right. Then we can recompute the
// "ppr" (pixels per range) at each step and then find the
// endpoints. The computation of the end points is done
// in animateZoom(). This method computes the fixed point
// and some other variables needed in animateZoom().
double minVal = mScaleInfo.getMinVal();
double maxVal = mScaleInfo.getMaxVal();
double ppr = mScaleInfo.getPixelsPerRange();
mZoomMin = minVal + ((mMouseMarkStartX - LeftMargin) / ppr);
mZoomMax = minVal + ((mMouseMarkEndX - LeftMargin) / ppr);
// Clamp the min and max values to the actual data min and max
if (mZoomMin < mMinDataVal)
mZoomMin = mMinDataVal;
if (mZoomMax > mMaxDataVal)
mZoomMax = mMaxDataVal;
// Snap the min and max points to the grid determined by the
// TickScaler
// before we zoom.
int xdim = dim.x - TotalXMargin;
TickScaler scaler = new TickScaler(mZoomMin, mZoomMax, xdim,
PixelsPerTick);
scaler.computeTicks(false);
mZoomMin = scaler.getMinVal();
mZoomMax = scaler.getMaxVal();
// Also snap the mouse points (in pixel space) to be consistent with
// zoomMin and zoomMax (in value space).
mMouseMarkStartX = (int) ((mZoomMin - minVal) * ppr + LeftMargin);
mMouseMarkEndX = (int) ((mZoomMax - minVal) * ppr + LeftMargin);
mTimescale.setMarkStart(mMouseMarkStartX);
mTimescale.setMarkEnd(mMouseMarkEndX);
// Compute the mouse selection end point distances
mMouseEndDistance = dim.x - RightMargin - mMouseMarkEndX;
mMouseStartDistance = mMouseMarkStartX - LeftMargin;
mZoomMouseStart = mMouseMarkStartX;
mZoomMouseEnd = mMouseMarkEndX;
mZoomStep = 0;
// Compute the fixed point in both value space and pixel space.
mMin2ZoomMin = mZoomMin - minVal;
mZoomMax2Max = maxVal - mZoomMax;
mZoomFixed = mZoomMin + (mZoomMax - mZoomMin) * mMin2ZoomMin
/ (mMin2ZoomMin + mZoomMax2Max);
mZoomFixedPixel = (mZoomFixed - minVal) * ppr + LeftMargin;
mFixedPixelStartDistance = mZoomFixedPixel - LeftMargin;
mFixedPixelEndDistance = dim.x - RightMargin - mZoomFixedPixel;
mZoomMin2Fixed = mZoomFixed - mZoomMin;
mFixed2ZoomMax = mZoomMax - mZoomFixed;
getDisplay().timerExec(ZOOM_TIMER_INTERVAL, mZoomAnimator);
redraw();
update();
}
private void mouseScrolled(MouseEvent me) {
mGraphicsState = GraphicsState.Scrolling;
double tMin = mScaleInfo.getMinVal();
double tMax = mScaleInfo.getMaxVal();
double zoomFactor = 2;
double tMinRef = mLimitMinVal;
double tMaxRef = mLimitMaxVal;
double t; // the fixed point
double tMinNew;
double tMaxNew;
if (me.count > 0) {
// we zoom in
Point dim = mSurface.getSize();
int x = me.x;
if (x < LeftMargin)
x = LeftMargin;
if (x > dim.x - RightMargin)
x = dim.x - RightMargin;
double ppr = mScaleInfo.getPixelsPerRange();
t = tMin + ((x - LeftMargin) / ppr);
tMinNew = Math.max(tMinRef, t - (t - tMin) / zoomFactor);
tMaxNew = Math.min(tMaxRef, t + (tMax - t) / zoomFactor);
} else {
// we zoom out
double factor = (tMax - tMin) / (tMaxRef - tMinRef);
if (factor < 1) {
t = (factor * tMinRef - tMin) / (factor - 1);
tMinNew = Math.max(tMinRef, t - zoomFactor * (t - tMin));
tMaxNew = Math.min(tMaxRef, t + zoomFactor * (tMax - t));
} else {
return;
}
}
mScaleInfo.setMinVal(tMinNew);
mScaleInfo.setMaxVal(tMaxNew);
mSurface.redraw();
}
// No defined behavior yet for double-click.
private void mouseDoubleClick(MouseEvent me) {
}
public void startScaling(int mouseX) {
Point dim = mSurface.getSize();
int x = mouseX;
if (x < LeftMargin)
x = LeftMargin;
if (x > dim.x - RightMargin)
x = dim.x - RightMargin;
mMouseMarkStartX = x;
mGraphicsState = GraphicsState.Scaling;
mScalePixelsPerRange = mScaleInfo.getPixelsPerRange();
mScaleMinVal = mScaleInfo.getMinVal();
mScaleMaxVal = mScaleInfo.getMaxVal();
}
public void stopScaling(int mouseX) {
mGraphicsState = GraphicsState.Normal;
}
private void animateHighlight() {
mHighlightStep += 1;
if (mHighlightStep >= HIGHLIGHT_STEPS) {
mFadeColors = false;
mHighlightStep = 0;
// Force a recomputation of the strip colors
mCachedEndRow = -1;
} else {
mFadeColors = true;
mShowHighlightName = true;
mHighlightHeight = highlightHeights[mHighlightStep];
getDisplay().timerExec(HIGHLIGHT_TIMER_INTERVAL, mHighlightAnimator);
}
redraw();
}
private void clearHighlights() {
// System.out.printf("clearHighlights()\n");
mShowHighlightName = false;
mHighlightHeight = 0;
mHighlightMethodData = null;
mHighlightCall = null;
mFadeColors = false;
mHighlightStep = 0;
// Force a recomputation of the strip colors
mCachedEndRow = -1;
redraw();
}
private void animateZoom() {
mZoomStep += 1;
if (mZoomStep > ZOOM_STEPS) {
mGraphicsState = GraphicsState.Normal;
// Force a normal recomputation
mCachedMinVal = mScaleInfo.getMinVal() + 1;
} else if (mZoomStep == ZOOM_STEPS) {
mScaleInfo.setMinVal(mZoomMin);
mScaleInfo.setMaxVal(mZoomMax);
mMouseMarkStartX = LeftMargin;
Point dim = getSize();
mMouseMarkEndX = dim.x - RightMargin;
mTimescale.setMarkStart(mMouseMarkStartX);
mTimescale.setMarkEnd(mMouseMarkEndX);
getDisplay().timerExec(ZOOM_TIMER_INTERVAL, mZoomAnimator);
} else {
// Zoom in slowly at first, then speed up, then slow down.
// The zoom fractions are precomputed to save time.
double fraction = mZoomFractions[mZoomStep];
mMouseMarkStartX = (int) (mZoomMouseStart - fraction * mMouseStartDistance);
mMouseMarkEndX = (int) (mZoomMouseEnd + fraction * mMouseEndDistance);
mTimescale.setMarkStart(mMouseMarkStartX);
mTimescale.setMarkEnd(mMouseMarkEndX);
// Compute the new pixels-per-range. Avoid division by zero.
double ppr;
if (mZoomMin2Fixed >= mFixed2ZoomMax)
ppr = (mZoomFixedPixel - mMouseMarkStartX) / mZoomMin2Fixed;
else
ppr = (mMouseMarkEndX - mZoomFixedPixel) / mFixed2ZoomMax;
double newMin = mZoomFixed - mFixedPixelStartDistance / ppr;
double newMax = mZoomFixed + mFixedPixelEndDistance / ppr;
mScaleInfo.setMinVal(newMin);
mScaleInfo.setMaxVal(newMax);
getDisplay().timerExec(ZOOM_TIMER_INTERVAL, mZoomAnimator);
}
redraw();
}
private static final int TotalXMargin = LeftMargin + RightMargin;
private static final int yMargin = 1; // blank space on top
// The minimum margin on each side of the zoom window, in pixels.
private static final int MinZoomPixelMargin = 10;
private GraphicsState mGraphicsState = GraphicsState.Normal;
private Point mMouse = new Point(LeftMargin, 0);
private int mMouseMarkStartX;
private int mMouseMarkEndX;
private boolean mDebug = false;
private ArrayList<Strip> mStripList = new ArrayList<Strip>();
private ArrayList<Range> mHighlightExclusive = new ArrayList<Range>();
private ArrayList<Range> mHighlightInclusive = new ArrayList<Range>();
private int mMinStripHeight = 2;
private double mCachedMinVal;
private double mCachedMaxVal;
private int mCachedStartRow;
private int mCachedEndRow;
private double mScalePixelsPerRange;
private double mScaleMinVal;
private double mScaleMaxVal;
private double mLimitMinVal;
private double mLimitMaxVal;
private double mMinDataVal;
private double mMaxDataVal;
private Cursor mNormalCursor;
private Cursor mIncreasingCursor;
private Cursor mDecreasingCursor;
private static final int ZOOM_TIMER_INTERVAL = 10;
private static final int HIGHLIGHT_TIMER_INTERVAL = 50;
private static final int ZOOM_STEPS = 8; // must be even
private int mHighlightHeight = 4;
private final int[] highlightHeights = { 0, 2, 4, 5, 6, 5, 4, 2, 4, 5,
6 };
private final int HIGHLIGHT_STEPS = highlightHeights.length;
private boolean mFadeColors;
private boolean mShowHighlightName;
private double[] mZoomFractions;
private int mZoomStep;
private int mZoomMouseStart;
private int mZoomMouseEnd;
private int mMouseStartDistance;
private int mMouseEndDistance;
private Point mMouseSelect = new Point(0, 0);
private double mZoomFixed;
private double mZoomFixedPixel;
private double mFixedPixelStartDistance;
private double mFixedPixelEndDistance;
private double mZoomMin2Fixed;
private double mMin2ZoomMin;
private double mFixed2ZoomMax;
private double mZoomMax2Max;
private double mZoomMin;
private double mZoomMax;
private Runnable mZoomAnimator;
private Runnable mHighlightAnimator;
private int mHighlightStep;
}
private int computeVisibleRows(int ydim) {
// If we resize, then move the bottom row down. Don't allow the scroll
// to waste space at the bottom.
int offsetY = mScrollOffsetY;
int spaceNeeded = mNumRows * rowYSpace;
if (offsetY + ydim > spaceNeeded) {
offsetY = spaceNeeded - ydim;
if (offsetY < 0) {
offsetY = 0;
}
}
mStartRow = offsetY / rowYSpace;
mEndRow = (offsetY + ydim) / rowYSpace;
if (mEndRow >= mNumRows) {
mEndRow = mNumRows - 1;
}
return offsetY;
}
private void startHighlighting() {
// System.out.printf("startHighlighting()\n");
mSurface.mHighlightStep = 0;
mSurface.mFadeColors = true;
// Force a recomputation of the color strips
mSurface.mCachedEndRow = -1;
getDisplay().timerExec(0, mSurface.mHighlightAnimator);
}
private static class RowData {
RowData(Row row) {
mName = row.getName();
mStack = new ArrayList<Block>();
}
public void push(Block block) {
mStack.add(block);
}
public Block top() {
if (mStack.size() == 0)
return null;
return mStack.get(mStack.size() - 1);
}
public void pop() {
if (mStack.size() == 0)
return;
mStack.remove(mStack.size() - 1);
}
private String mName;
private int mRank;
private long mElapsed;
private long mEndTime;
private ArrayList<Block> mStack;
}
private static class Segment {
Segment(RowData rowData, Block block, long startTime, long endTime) {
mRowData = rowData;
if (block.isContextSwitch()) {
mBlock = block.getParentBlock();
mIsContextSwitch = true;
} else {
mBlock = block;
}
mStartTime = startTime;
mEndTime = endTime;
}
private RowData mRowData;
private Block mBlock;
private long mStartTime;
private long mEndTime;
private boolean mIsContextSwitch;
}
private static class Strip {
Strip(int x, int y, int width, int height, RowData rowData,
Segment segment, Color color) {
mX = x;
mY = y;
mWidth = width;
mHeight = height;
mRowData = rowData;
mSegment = segment;
mColor = color;
}
int mX;
int mY;
int mWidth;
int mHeight;
RowData mRowData;
Segment mSegment;
Color mColor;
}
private static class Pixel {
public void setFields(int start, double weight, Segment segment,
Color color, RowData rowData) {
mStart = start;
mMaxWeight = weight;
mSegment = segment;
mColor = color;
mRowData = rowData;
}
int mStart = -2; // some value that won't match another pixel
double mMaxWeight;
Segment mSegment;
Color mColor; // we need the color here because it may be faded
RowData mRowData;
}
private static class Range {
Range(int xStart, int width, int y, Color color) {
mXdim.x = xStart;
mXdim.y = width;
mY = y;
mColor = color;
}
Point mXdim = new Point(0, 0);
int mY;
Color mColor;
}
}