sysperfana/analyzetoolext/com.nokia.s60tools.analyzetool/src/com/nokia/s60tools/analyzetool/internal/ui/graph/AnalyzeToolGraph.java
/*
* Copyright (c) 2008-2010 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description: Definitions for the class AnalyzeToolGraph
*
*/
package com.nokia.s60tools.analyzetool.internal.ui.graph;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.draw2d.FigureCanvas;
import org.eclipse.draw2d.Graphics;
import org.eclipse.draw2d.KeyEvent;
import org.eclipse.draw2d.KeyListener;
import org.eclipse.draw2d.MouseEvent;
import org.eclipse.draw2d.MouseListener;
import org.eclipse.draw2d.MouseMotionListener;
import org.eclipse.draw2d.Panel;
import org.eclipse.draw2d.XYLayout;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.PointList;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.jface.dialogs.MessageDialogWithToggle;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IPartListener;
import org.eclipse.ui.IPartService;
import org.eclipse.ui.IWindowListener;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchPartSite;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.views.properties.PropertySheet;
import com.nokia.s60tools.analyzetool.Activator;
import com.nokia.s60tools.analyzetool.engine.IMemoryActivityModel;
import com.nokia.s60tools.analyzetool.engine.IMemoryActivityModelChangeListener;
import com.nokia.s60tools.analyzetool.engine.statistic.AllocInfo;
import com.nokia.s60tools.analyzetool.engine.statistic.BaseInfo;
import com.nokia.s60tools.analyzetool.engine.statistic.FreeInfo;
import com.nokia.s60tools.analyzetool.engine.statistic.ProcessInfo;
import com.nokia.s60tools.analyzetool.engine.statistic.SymReader;
import com.nokia.s60tools.analyzetool.global.Util;
import com.nokia.s60tools.analyzetool.internal.ui.util.ColorUtil;
import com.nokia.s60tools.analyzetool.internal.ui.util.GraphUtils;
import com.nokia.s60tools.analyzetool.ui.MainView;
import com.nokia.s60tools.analyzetool.ui.ResourceVisitor;
/**
* A FigureCanvas containing the graph and X-axis area of the AnalyzeTool chart.
*/
public class AnalyzeToolGraph extends FigureCanvas implements
IMemoryActivityModelChangeListener {
private static final int BOUNDARY_OFFSET = 3;
/** used for range model listener */
private static final String PROP_MAXIMUM = "maximum"; //$NON-NLS-1$
private static final int X_AXIS_HEIGHT = 50;
/** used for "Don't ask again" dialog */
private static final String PROMPT_KEY = "GraphOptimisationPrompt";//$NON-NLS-1$
/** the scaling factor used for scaling the x-axis */
private double scale = 1.0;
/** the model */
private IMemoryActivityModel model;
/** for synchronization with the PropertySheet */
private DotSelectionProvider iDotsSelecProv = new DotSelectionProvider();
private IWorkbenchPartSite site;
/** used when user selects a dot on the graph, and moves with arrow keys */
private ISelection iCurrentSelectedDot = null;
/** controls mouse and arrow key movements */
private MouseAndKeyController mc;
private SymReader iSymReader = null;
private IProject iCurrentProject = null;
/** Contains c++ files info for the current project. */
private final AbstractList<String> cppFileNames;
/** "time ->" on axis */
private Image timeImage;
private GraphPartServiceListener iGraphPartServiceListener;
private boolean optimisedDrawing;
private boolean userInformed;
/**
* The threshold value for filtered drawing of dots. Only draw the dot if
* the memory operation size is above or equals / below or equals the
* threshold. Zero for no filtering.
*/
private long threshold;
/** indicates whether filtering is above or below the threshold */
private boolean aboveThreshold;
/**
* Constructor
*
* @param parent
* The parent composite
*/
public AnalyzeToolGraph(Composite parent) {
super(parent);
IPartService partService = PlatformUI.getWorkbench()
.getActiveWorkbenchWindow().getPartService();
iGraphPartServiceListener = new GraphPartServiceListener();
partService.addPartListener(iGraphPartServiceListener);
PlatformUI.getWorkbench().addWindowListener(iGraphPartServiceListener);
cppFileNames = new ArrayList<String>();
}
/**
* Draws the graph on the canvas. Intended to be called on a paint event.
*
* @param graphics
*/
public void paint(final Graphics graphics) {
if (optimisedDrawing && !userInformed) {
userInformed = true;
IPreferenceStore preferenceStore = Activator.getPreferences();
if (!preferenceStore.getString(PROMPT_KEY).equals(
MessageDialogWithToggle.ALWAYS)) {
String dilaogTitle = "Optimised Graph";
String message = "The process contains too many memory operations to display efficiently. To optimise, only leaks will be indicated on the graph.";
String toggleMessage = "Don't show this again";
MessageDialogWithToggle.openInformation(getShell(),
dilaogTitle, message, toggleMessage, false,
preferenceStore, PROMPT_KEY);
}
}
Rectangle visibleRect = graphics.getClip(new Rectangle());
YConverter yConverter = new YConverter(getClientArea().height, model
.getHighestCumulatedMemoryAlloc());
if (model.getSelectedProcess() != null
&& model.getSelectedProcess().getAllocsFrees().size() > 0) {
PointList pts = new PointList((model.getSelectedProcess()
.getAllocsFrees().size() * 2) - 1);
Point prevPt = null;
List<Point> dotLocations = new ArrayList<Point>();
List<Integer> colorDotLocations = new ArrayList<Integer>();
for (BaseInfo info : model.getSelectedProcess().getAllocsFrees()) {
int x_point = (int) ((info.getTime() - model
.getFirstProcessTime()) / getScale());
int y_point = yConverter.bytesToY(info.getTotalMem());
if (y_point < 0) {
y_point = 0;
}
if (prevPt != null) {
pts.addPoint(x_point, prevPt.y);
}
Point nextPt = new Point(x_point, y_point);
if (visibleRect.contains(nextPt)
&& (isLeak(info) || (!optimisedDrawing
&& !dotLocations.contains(nextPt) && validInThreshold(info)))) {
// for improved performance, only draw dots that are in
// visible clip area
// and don't draw a dot if there is one already, unless it's
// a leak
dotLocations.add(nextPt);
colorDotLocations.add(getColorForAllocType(info));
}
pts.addPoint(nextPt);
prevPt = nextPt;
}
if (pts.size() > 0) {
graphics.pushState();
graphics.setForegroundColor(Display.getDefault()
.getSystemColor(SWT.COLOR_DARK_YELLOW));
graphics.setLineWidthFloat(optimisedDrawing ? 0.5f : 2.0f);
graphics.drawPolyline(pts);
graphics.setLineWidthFloat(optimisedDrawing ? 0.5f : 1.0f);
graphics.setAntialias(SWT.ON);
graphics.setForegroundColor(Display.getDefault()
.getSystemColor(SWT.COLOR_RED));
graphics.setBackgroundColor(Display.getDefault()
.getSystemColor(SWT.COLOR_RED));
int colourCode = SWT.COLOR_RED;
for (int j = 0; j < dotLocations.size(); j++) {
Point dotLocation = dotLocations.get(j);
if (!optimisedDrawing
&& colorDotLocations.get(j) != colourCode) {
colourCode = colorDotLocations.get(j);
graphics.setBackgroundColor(Display.getDefault()
.getSystemColor(colourCode));
}
// paint the dot
graphics.fillOval(dotLocation.x - 2, dotLocation.y - 2, 5,
5);
if (!optimisedDrawing && colourCode == SWT.COLOR_RED) {
// draw a red line
graphics.drawLine(dotLocation.x, dotLocation.y,
dotLocation.x, yConverter.bytesToY(0));
}
}
graphics.popState();
}
}
}
/**
* Returns true of the size of the alloc or free is greater or equals / less
* or equals the threshold. Returns true if no threshold is set.
*
* @param info
* the memory operation to check
* @return
*/
private boolean validInThreshold(BaseInfo info) {
if (threshold <= 0
|| info instanceof AllocInfo
&& (aboveThreshold ? (((AllocInfo) info).getSizeInt() >= threshold)
: (((AllocInfo) info).getSizeInt() <= threshold))) {
return true;
}
if (info instanceof FreeInfo
&& ((aboveThreshold && ((FreeInfo) info).getSizeInt() >= threshold) || (!aboveThreshold && ((FreeInfo) info)
.getSizeInt() <= threshold))) {
// check at least one of its alloc qualifies
for (AllocInfo allocInfo : ((FreeInfo) info).getFreedAllocs()) {
if ((aboveThreshold && allocInfo.getSizeInt() >= threshold)
|| (!aboveThreshold && allocInfo.getSizeInt() <= threshold)) {
return true;
}
}
}
return false;
}
/**
* Draws the background grid on the canvas. Intended to be called on a paint
* event.
*
* @param graphics
*/
public void drawBackGroundLines(Graphics graphics) {
Rectangle canvasRect = graphics
.getClip(new org.eclipse.draw2d.geometry.Rectangle());
graphics.setForegroundColor(ColorUtil.COLOR_100); // middle grey
graphics.setBackgroundColor(ColorUtil.COLOR_170); // lighter grey
int height = getClientArea().height;
int width = getClientArea().width;
graphics.fillRectangle(new Rectangle(canvasRect.x, 0, width, height
- X_AXIS_HEIGHT));
double visY = height - X_AXIS_HEIGHT;
int k = 0;
// horizontal lines, height is divided into 10 sections, line is dotted
// (5 pixel length)
for (float y = 0; k <= 10; y += visY * 10000 / 100001, k++) {
for (int x = canvasRect.x; x <= canvasRect.x + canvasRect.width; x += 5) {
if ((x / 5) % 2 == 0)
graphics.drawLine(x, ((int) y) + 1, x + 5, ((int) y) + 1);
}
}
graphics.setForegroundColor(ColorUtil.COLOR_100);
graphics.setBackgroundColor(ColorUtil.WHITE);
int alignedLeftEdge = (canvasRect.x / 50) * 50;
// vertical lines (one darker, one lighter vertical line in turns every
// 50 points in width)
if (width > 0) {
for (int x = alignedLeftEdge; x <= canvasRect.x + canvasRect.width; x += 50) {
if (x % 100 == 0)
graphics.setForegroundColor(ColorUtil.COLOR_100);
else
graphics.setForegroundColor(ColorUtil.COLOR_200);
for (int y = 0; y < height; y += 5) {
if ((y / 5) % 2 == 0)
graphics.drawLine(x, y, x, y + 5);
}
}
}
graphics.setForegroundColor(ColorUtil.COLOR_100);
graphics.setBackgroundColor(ColorUtil.WHITE);
for (int x = alignedLeftEdge; x <= canvasRect.x + canvasRect.width; x += 50) {
String timeStringWithUnits = GraphUtils.getTimeStringWithUnits(x
* scale);
graphics.drawString(timeStringWithUnits, x + 5, height - 13);
}
if (timeImage == null) {
timeImage = GraphUtils.getVerticalLabel("Time");
}
graphics.drawImage(timeImage, width / 2, height - 30);
}
/**
* Returns the content of the tooltip appropriate for the given coordinate
*
* @param x
* the X-coordinate
* @param y
* the Y-coordinate
* @return String containing the content of the tooltip
*/
public String getToolTipText(int x, int y) {
StringBuilder text = new StringBuilder();
if (model.getSelectedProcess() == null) {
return "";
}
if (y > getClientArea().height - X_AXIS_HEIGHT) {
return null;
}
double timeInMs = (x * getScale());// x value in milliseconds
YConverter yConverter = new YConverter(getClientArea().height, model
.getHighestCumulatedMemoryAlloc());
double bytes = yConverter.yToBytes(y); // y value in bytes
text.append(GraphUtils.renderTime(timeInMs));
BaseInfo allocInfo = findClosestAlloc((int) timeInMs,
(int) ((x - BOUNDARY_OFFSET) * getScale()),
(int) ((x + BOUNDARY_OFFSET) * getScale()), (int) bytes,
(int) yConverter.yToBytes(y + BOUNDARY_OFFSET),
(int) yConverter.yToBytes(y - BOUNDARY_OFFSET));
if (allocInfo != null) {
text.append(String.format("%n%s: %,d B%nTotal: %,d B",
getMemOpString(allocInfo),
Math.abs(allocInfo.getSizeInt()), allocInfo.getTotalMem()));
} else {
text.append(", " + GraphUtils.formatBytes(bytes));
}
return text.toString();
}
/**
* Returns a string representation of the type of the given memory operation
*
* @param allocInfo
* the memory operation to use
* @return
*/
private String getMemOpString(BaseInfo allocInfo) {
return allocInfo instanceof AllocInfo ? (((AllocInfo) allocInfo)
.isFreed() ? "Alloc" : "Leak") : "Free";
}
/**
* Convenience method for
* {@link #findClosestAlloc(int, int, int, int, int, int)} wrapping
* conversion from x,y coordinates to time and byte values.
*
* @param x
* X-coordinate
* @param y
* Y-coordinate
* @return closest BaseInfo within bounds, or null if none found
*/
private BaseInfo findClosest(int x, int y) {
if (y > getClientArea().height - X_AXIS_HEIGHT) {
return null;
}
YConverter yConverter = new YConverter(getClientArea().height, model
.getHighestCumulatedMemoryAlloc());
return findClosestAlloc((int) (x * getScale()),
(int) ((x - BOUNDARY_OFFSET) * getScale()),
(int) ((x + BOUNDARY_OFFSET) * getScale()), (int) (yConverter
.yToBytes(y)), (int) yConverter.yToBytes(y
+ BOUNDARY_OFFSET), (int) yConverter.yToBytes(y
- BOUNDARY_OFFSET));
}
/**
* Finds the closest matching BaseInfo in the model. BaseInfo has to fit
* into the given bounds and be a better match than other BaseInfo in the
* same bounds.
*
* @param timeInMsMidPoint
* time in milliseconds for the exact point of interest
* @param timeInMsBoundLeft
* Left boundary for time in milliseconds
* @param timeInMsBoundRight
* Right boundary for time in milliseconds
* @param bytesMidPoint
* Cumulative memory in bytes for the exact point of interest
* @param bytesBoundLeft
* Left boundary for cumulative memory in bytes
* @param bytesBoundRight
* Right boundary for cumulative memory in bytes
* @return
*/
private BaseInfo findClosestAlloc(int timeInMsMidPoint,
int timeInMsBoundLeft, int timeInMsBoundRight, int bytesMidPoint,
int bytesBoundLeft, int bytesBoundRight) {
BaseInfo ret = null;
if (model.getSelectedProcess() == null) {
return ret;
}
int marginEnd = timeInMsBoundRight;
ProcessInfo process = model.getSelectedProcess();
AbstractList<BaseInfo> allocsFrees = process.getAllocsFrees();
Long firstTime = model.getFirstProcessTime();
for (BaseInfo info : allocsFrees) {
Long infoRelativeTime = info.getTime() - firstTime;
// check current alloc info is within given bounds
if (infoRelativeTime >= timeInMsBoundLeft
&& infoRelativeTime <= marginEnd
&& info.getTotalMem() >= bytesBoundLeft
&& info.getTotalMem() <= bytesBoundRight) {
// check whether current alloc info is a better match than
// previously found
if (ret == null
|| (Math.abs(infoRelativeTime - timeInMsMidPoint) < Math
.abs((ret.getTime() - firstTime)
- timeInMsMidPoint))
|| ((ret.getTime() - firstTime) == infoRelativeTime && Math
.abs(info.getTotalMem() - bytesMidPoint) < Math
.abs(ret.getTotalMem() - bytesMidPoint))) {
ret = info;
}
} else if ((info.getTime() - firstTime) > timeInMsBoundRight) {
break;
}
}
return ret;
}
/**
* Finds the next AllocInfo in the model.
*
* @param allocInfo
* AllocInfo prior to the one to return
* @return The next AllocInfo in the model. This may return null if the
* passed object was the last in the model.
*/
private BaseInfo findNextAlloc(BaseInfo allocInfo, boolean forward) {
BaseInfo ret = null;
ProcessInfo processInfo = model.getSelectedProcess();
if (processInfo == null) {
return ret;
}
AbstractList<BaseInfo> allocsFrees = processInfo.getAllocsFrees();
int i = allocsFrees.indexOf(allocInfo);
if (forward) {
if (i < allocsFrees.size() - 1) {
ret = allocsFrees.get(i + 1);
}
} else {
if (i > 0) {
ret = allocsFrees.get(i - 1);
}
}
return ret;
}
/**
* This method first zooms in graph to the maximum possible scale and zooms
* out so that it fits in the canvas area.
*
*/
public void zoomGraph() {
int width = getClientArea().width;
if (width <= 0 || model == null)
return;
double new_scale = getMaxTimeValueInMilliSeconds() / width;
setScale(new_scale);
setZoomedSize(0);
}
/**
* Returns the current scaling factor for the graph's width
*
* @return Scale
*/
public double getScale() {
return scale;
}
/**
* Sets the scaling factor for the graph's width
*
* @param newScale
*/
public void setScale(double newScale) {
this.scale = newScale;
}
/**
* Returns the highest time value of the current graph in milliseconds
*
* @return Last time
*/
public long getLastTimeValueInMilliSeconds() {
return model.getLastProcessTime() - model.getFirstProcessTime();
}
/**
* Returns the last time value in the model plus 1 per cent.
*
* @return
*/
private long getMaxTimeValueInMilliSeconds() {
return getLastTimeValueInMilliSeconds()
+ (int) (getLastTimeValueInMilliSeconds() * 0.01);
}
/**
* Adds a new model to this class
*
* @param newModel
* the IMemoryActivityModel to use
*/
public void setInput(IMemoryActivityModel newModel) {
if (this.model != null) {
this.model.removeListener(this);
}
threshold = 0; // reset threshold
this.model = newModel;
this.model.addListener(this);
}
/**
* Creates the content of the FigureCanvas. Intended to be called once in
* creating the ViewPart content.
*/
public void createContent() {
mc = new MouseAndKeyController();
Panel panel = new Panel() {
@Override
public void paint(Graphics graphics) {
if (model != null) {
drawBackGroundLines(graphics);
AnalyzeToolGraph.this.paint(graphics);
mc.render(graphics);
} else {
erase();
}
}
};
panel.setLayoutManager(new XYLayout());
panel.addMouseMotionListener(mc);
panel.addMouseListener(mc);
panel.addKeyListener(mc);
setContents(panel);
panel.setFocusTraversable(true);
final org.eclipse.swt.widgets.ScrollBar horizontalBar = getHorizontalBar();
horizontalBar.addSelectionListener(new SelectionListener() {
public void widgetDefaultSelected(SelectionEvent arg0) {
// do nothing by design
}
public void widgetSelected(SelectionEvent event) {
AnalyzeToolGraph.this.redraw();
}
});
addControlListener(new ControlAdapter() {
@Override
public void controlResized(ControlEvent e) {
horizontalBar.setPageIncrement(getBounds().width);
redraw();
}
});
}
/**
* Class containing code to deal with mouse and key events
*
*/
private class MouseAndKeyController implements MouseMotionListener,
MouseListener, KeyListener {
protected int mouseButton;
protected Point start;
protected boolean beingDragged;
private BaseInfo lastShownAlloc;
protected Point lastMouse;
/*
* (non-Javadoc)
*
* @see
* org.eclipse.draw2d.MouseMotionListener#mouseDragged(org.eclipse.draw2d
* .MouseEvent)
*/
public void mouseDragged(MouseEvent me) {
if (mouseButton == 1) {// for some reason me.button doesn't work
// here and always returns 0 so we need to
// track it from mouse pressed
beingDragged = true;
lastMouse = me.getLocation();
} else {
beingDragged = false;
}
}
public void render(Graphics graphics) {
graphics.pushState();
if (beingDragged) {
graphics.setForegroundColor(Display.getDefault()
.getSystemColor(SWT.COLOR_BLACK));
graphics.setLineWidth(1);
int dragEnd = lastMouse.x;
int dragLeftX = start.x <= dragEnd ? start.x : dragEnd;// drag
// area
// left
// edge
int dragRightX = start.x <= dragEnd ? dragEnd : start.x;// drag
// area
// right
// edge
graphics.drawRectangle(new Rectangle(dragLeftX, 0,
(dragRightX - dragLeftX), getClientArea().height));
}
if (lastShownAlloc != null) {
// for the selected dot, try to mark related allocs and free
FreeInfo freeInfo = null;
if (lastShownAlloc instanceof FreeInfo) {
freeInfo = (FreeInfo) lastShownAlloc;
} else {
freeInfo = ((AllocInfo) lastShownAlloc).getFreedBy();
}
if (freeInfo != null) {
YConverter yConverter = new YConverter(
getClientArea().height, model
.getHighestCumulatedMemoryAlloc());
Rectangle visibleRect = graphics
.getClip(new org.eclipse.draw2d.geometry.Rectangle());
Point pfree = getLocationOnGraph(yConverter, freeInfo);
if (lastShownAlloc != freeInfo
&& visibleRect.contains(pfree)) {
graphics.setForegroundColor(Display.getDefault()
.getSystemColor(SWT.COLOR_DARK_GREEN));
graphics.drawLine(pfree.x + 1, pfree.y + 3,
pfree.x + 1, yConverter.bytesToY(0));
if (optimisedDrawing) { // draw circle as well since we
// don't have dots
graphics.drawOval(pfree.x - 2, pfree.y - 2, 5, 5);
}
}
for (AllocInfo freedAlloc : freeInfo.getFreedAllocs()) {
Point pAlloc = getLocationOnGraph(yConverter,
freedAlloc);
if (lastShownAlloc != freedAlloc
&& visibleRect.contains(pAlloc)) {
graphics.setForegroundColor(Display.getDefault()
.getSystemColor(SWT.COLOR_DARK_BLUE));
graphics.drawLine(pAlloc.x + 1, pAlloc.y + 3,
pAlloc.x + 1, yConverter.bytesToY(0));
if (optimisedDrawing) { // draw circle as well since
// we don't have dots
graphics.drawOval(pAlloc.x - 2, pAlloc.y - 2,
5, 5);
}
}
}
}
// this should be the alloc that has its details displayed
// mark it in the graph
// draw a small circle around the alloc / dealloc, and a
// vertical line towards the X-axis
Point p = getLocationOnGraph(lastShownAlloc);
graphics.setForegroundColor(Display.getDefault()
.getSystemColor(SWT.COLOR_CYAN));
graphics.setLineWidth(1);
graphics.drawOval(p.x - 2, p.y - 2, 5, 5);
if (optimisedDrawing || !isLeak(lastShownAlloc)) {
graphics.drawLine(p.x + 1, p.y + 3, p.x + 1,
getClientArea().height - X_AXIS_HEIGHT);
}
}
graphics.popState();
}
private Point getLocationOnGraph(BaseInfo alloc) {
YConverter yConverter = new YConverter(getClientArea().height,
model.getHighestCumulatedMemoryAlloc());
return getLocationOnGraph(yConverter, alloc);
}
private Point getLocationOnGraph(YConverter yConverter, BaseInfo alloc) {
int x = (int) ((alloc.getTime() - model.getFirstProcessTime()) / getScale());
int y = yConverter.bytesToY(alloc.getTotalMem());
if (y < 0) {
y = 0;
}
return new Point(x, y);
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.draw2d.MouseMotionListener#mouseEntered(org.eclipse.draw2d
* .MouseEvent)
*/
public void mouseEntered(MouseEvent e) {
// do nothing by design
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.draw2d.MouseMotionListener#mouseExited(org.eclipse.draw2d
* .MouseEvent)
*/
public void mouseExited(MouseEvent e) {
// do nothing by design
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.draw2d.MouseMotionListener#mouseHover(org.eclipse.draw2d
* .MouseEvent)
*/
public void mouseHover(MouseEvent e) {
if (model != null) {
setToolTipText(getToolTipText(e.x, e.y));
redraw();
}
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.draw2d.MouseMotionListener#mouseMoved(org.eclipse.draw2d
* .MouseEvent)
*/
public void mouseMoved(MouseEvent e) {
// do nothing by design
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.draw2d.MouseListener#mouseDoubleClicked(org.eclipse.draw2d
* .MouseEvent)
*/
public void mouseDoubleClicked(MouseEvent e) {
beingDragged = false;
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.draw2d.MouseListener#mousePressed(org.eclipse.draw2d.
* MouseEvent)
*/
public void mousePressed(MouseEvent e) {
beingDragged = false;
start = e.getLocation();
mouseButton = e.button;
e.consume(); // don't pass on to other listeners
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.draw2d.MouseListener#mouseReleased(org.eclipse.draw2d
* .MouseEvent)
*/
public void mouseReleased(MouseEvent me) {
if (beingDragged) {
beingDragged = false;
// finished drag - perform zoom in now
// set new scale
int dragEnd = me.getLocation().x;
int dragLeftX = start.x <= dragEnd ? start.x : dragEnd;// drag
// area
// left
// edge
int dragRightX = start.x <= dragEnd ? dragEnd : start.x;// drag
// area
// right
// edge
int dragWindowStartTime = (int) (dragLeftX * getScale());
int dragWidth = dragRightX - dragLeftX;// drag area width
int oldVisibleWidth = getViewport().getSize().width;// visible
// window
// width
double newScale = getScale() * dragWidth / oldVisibleWidth;
if (newScale < 1.0) {
// don't allow a scale smaller than 1 millisecond per pixel
// TODO: indicate a failed zoom
return;
}
int dragLeftXScaled = (int) (dragWindowStartTime / newScale);
// set new window location
setScale(newScale);
setZoomedSize(dragLeftXScaled);
// make sure the drag window disappears
AnalyzeToolGraph.this.redraw();
} else if (me.button == 3) {
// mouse right click - zoom out
double maxScale = getMaxTimeValueInMilliSeconds()
/ getClientArea().width;
if (getScale() < maxScale) {
double newScale = GraphUtils.nextScale(getScale(), true);
if (getScale() != newScale) {
if (newScale > maxScale) {
newScale = maxScale;
}
// get left window edge is ms to be set to after zooming
double leftEdgeInMs = getViewport().getViewLocation().x
* getScale();
setScale(newScale);
setZoomedSize((int) (leftEdgeInMs / newScale));
}
}
} else if (me.button == 1) {
BaseInfo info = findClosest(me.x, me.y);
if (info != null) {
iDotsSelecProv
.setSelection(new StructuredSelection(
new MemOpDescriptor(model
.getFirstProcessTime(), info,
iCurrentProject, iSymReader,
cppFileNames, model
.getCallstackManager())));
lastShownAlloc = info;
}
}
}
public void keyPressed(KeyEvent ke) {
// do nothing by design
}
public void keyReleased(KeyEvent ke) {
if (ke.keycode == SWT.ARROW_RIGHT || ke.keycode == SWT.ARROW_LEFT) {
if (lastShownAlloc != null) {
BaseInfo info = findNextAlloc(lastShownAlloc,
ke.keycode == SWT.ARROW_RIGHT);
if (info != null) {
iDotsSelecProv.setSelection(new StructuredSelection(
new MemOpDescriptor(
model.getFirstProcessTime(), info,
iCurrentProject, iSymReader,
cppFileNames, model
.getCallstackManager())));
lastShownAlloc = info;
// if info is hidden from the visible graph area, scroll
// to reveal
int x = getLocationOnGraph(info).x;
int leftEdge = getViewport().getHorizontalRangeModel()
.getValue();
int width = getViewport().getHorizontalRangeModel()
.getExtent();
if (x < leftEdge) {
if (x > 10) {
x -= 10;
}
getViewport().getHorizontalRangeModel().setValue(x);
} else if (x > (leftEdge + width)) {
x -= (width - 10);
getViewport().getHorizontalRangeModel().setValue(x);
}
AnalyzeToolGraph.this.redraw();
}
}
}
}
/**
* Resets any state.
*/
public void clearState() {
mouseButton = 0;
start = null;
beingDragged = false;
lastShownAlloc = null;
lastMouse = null;
if (iDotsSelecProv != null) {
iDotsSelecProv.setSelection(StructuredSelection.EMPTY);
}
}
}
/**
* Returns an SWT system color code depending on the type of the paramter
* passed. </br> SWT.COLOR_DARK_BLUE for a alloc (that is not a leak) </br>
* SWT.COLOR_RED for a leak </br> SWT.COLOR_DARK_GREEN for a free
*
* @param info
* the allocation to evaluate
* @return the appropriate SWT system color code
*/
private int getColorForAllocType(BaseInfo info) {
int color;
if (info instanceof AllocInfo) {
if (((AllocInfo) info).isFreed()) {
color = SWT.COLOR_DARK_BLUE;
} else {
color = SWT.COLOR_RED;
}
} else {
color = SWT.COLOR_DARK_GREEN;
}
return color;
}
/**
* Returns true if the passed info is a leak, false otherwise. AllocInfo
* that haven't been freed are considered leaks.
*
* @param info
* the BaseInfo to test
* @return true if leak, false otherwise
*/
private static boolean isLeak(BaseInfo info) {
return info instanceof AllocInfo && !(((AllocInfo) info).isFreed()) ? true
: false;
}
/**
*
* Converts bytes from/to Y values
*
*/
class YConverter {
private final double visY;
private final double multiplier;
public YConverter(int clientAreaHeight, int maxAllocValueInBytes) {
visY = clientAreaHeight - X_AXIS_HEIGHT;
multiplier = GraphUtils.prettyMaxBytes(maxAllocValueInBytes) / visY;
}
public int bytesToY(int bytes) {
return (int) (visY - (bytes / multiplier));
}
public double yToBytes(int y) {
return (visY - y) * multiplier;
}
}
/*
* (non-Javadoc)
*
* @see
* com.nokia.s60tools.analyzetool.engine.IMemoryActivityModelChangeListener
* #onProcessesAdded()
*/
public void onProcessesAdded() {
// the model is now ready to use - call a redraw on the graph
PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() {
public void run() {
mc.clearState();
zoomGraph();
redraw();
}
});
}
/*
* (non-Javadoc)
*
* @see
* com.nokia.s60tools.analyzetool.engine.IMemoryActivityModelChangeListener
* #onProcessSelected
* (com.nokia.s60tools.analyzetool.engine.statistic.ProcessInfo)
*/
public void onProcessSelected(ProcessInfo p) {
PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() {
public void run() {
mc.clearState();
optimisedDrawing = model.getSelectedProcess().getAllocsFrees()
.size() > ChartContainer.OPT_DRAWING_LIMIT ? true
: false;
userInformed = false;
zoomGraph();
redraw();
}
});
}
@Override
public void dispose() {
// there is no dispose() entry point in this class; we may have to call
// this via MainView.dispose()
site.setSelectionProvider(null);
iDotsSelecProv = null;
if (timeImage != null) {
timeImage.dispose();
}
IPartService partService = PlatformUI.getWorkbench()
.getActiveWorkbenchWindow().getPartService();
partService.removePartListener(iGraphPartServiceListener);
iGraphPartServiceListener = null;
if (iSymReader != null) {
iSymReader.dispose();
iSymReader = null;
}
}
private void setZoomedSize(final int newXLocation) {
int prefSize = (int) (getMaxTimeValueInMilliSeconds() / getScale());
// new width has to propagate to viewport first before we can set
// the new viewport selection
// set newXLocation in listener
Panel panel = (Panel) (getContents());
panel.setPreferredSize(prefSize, 0);
if (prefSize > getClientArea().width) {
getViewport().getHorizontalRangeModel().addPropertyChangeListener(
new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent e) {
if (e.getPropertyName().equals(PROP_MAXIMUM)) {
getViewport().getHorizontalRangeModel()
.removePropertyChangeListener(this);
getViewport().getHorizontalRangeModel()
.setValue(newXLocation);
}
}
});
panel.setSize(prefSize, 0);
} else {
// this only works if the canvas is large enough, otherwise use
// property listener
getViewport().getHorizontalRangeModel().setValue(newXLocation);
}
}
/**
* DotSelectionProvider : when a user selects a Dot on the graph, this class
* delivers the associated descriptor MemOpDescriptor to the views
* interested in it mainly the Properties View.
*
*/
private class DotSelectionProvider implements ISelectionProvider,
SelectionListener {
private ListenerList iSelectionChangedListeners = new ListenerList();
/*
* (non-Javadoc)
*
* @see
* org.eclipse.jface.viewers.ISelectionProvider#addSelectionChangedListener
* (org.eclipse.jface.viewers.ISelectionChangedListener)
*/
public void addSelectionChangedListener(
ISelectionChangedListener listener) {
// TODO is there a way to allow only properties view to register.
iSelectionChangedListeners.add(listener);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.viewers.ISelectionProvider#getSelection()
*/
public ISelection getSelection() {
return iCurrentSelectedDot;
}
/*
* (non-Javadoc)
*
* @seeorg.eclipse.jface.viewers.ISelectionProvider#
* removeSelectionChangedListener
* (org.eclipse.jface.viewers.ISelectionChangedListener)
*/
public void removeSelectionChangedListener(
ISelectionChangedListener listener) {
iSelectionChangedListeners.remove(listener);
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.jface.viewers.ISelectionProvider#setSelection(org.eclipse
* .jface.viewers.ISelection)
*/
public void setSelection(ISelection selection) {
iCurrentSelectedDot = selection;
// notify the listeners mainly the property view
for (final Object listenerObj : iSelectionChangedListeners
.getListeners()) {
((ISelectionChangedListener) listenerObj)
.selectionChanged(new SelectionChangedEvent(this,
getSelection()));
}
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.swt.events.SelectionListener#widgetDefaultSelected(org
* .eclipse.swt.events.SelectionEvent)
*/
public void widgetDefaultSelected(SelectionEvent event) {
widgetSelected(event);
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.swt.events.SelectionListener#widgetSelected(org.eclipse
* .swt.events.SelectionEvent)
*/
public void widgetSelected(SelectionEvent arg0) {
for (final Object listenerObj : iSelectionChangedListeners
.getListeners()) {
((ISelectionChangedListener) listenerObj)
.selectionChanged(new SelectionChangedEvent(this,
getSelection()));
}
}
}
/**
* GraphPartServiceListener is for convenience only. It serves to find an
* appropriate time to add DotSelectionProvider to the part site. It also
* covers that case where the properties View is already open before opening
* AnalyzeTool View.
*
*/
private class GraphPartServiceListener implements IPartListener,
IWindowListener {
public void partActivated(IWorkbenchPart part) {
if (part instanceof MainView || part instanceof PropertySheet) {
site = part.getSite();
// set property sheet selection provider.
site.setSelectionProvider(iDotsSelecProv);
if (part instanceof MainView && iSymReader != null) {
iSymReader.reOpenCachedSymbolFiles();
}
}
}
public void partBroughtToTop(IWorkbenchPart part) {
// do nothing
}
public void partClosed(IWorkbenchPart part) {
// TODO this is not working. why?
if (part instanceof MainView || part instanceof PropertySheet) {
site = part.getSite();
iDotsSelecProv.setSelection(StructuredSelection.EMPTY);
// set property sheet selection provider.
site.setSelectionProvider(null);
}
}
public void partDeactivated(IWorkbenchPart part) {
// the user might want to rebuild the project, so close the sumbol
// files
if (part instanceof MainView && iSymReader != null) {
iSymReader.closeCachedSymbolFiles();
}
}
public void partOpened(IWorkbenchPart part) {
if (part instanceof MainView) {
try {
part.getSite().getPage().showView(
"org.eclipse.ui.views.PropertySheet");//$NON-NLS-1$
} catch (PartInitException e) {
// just log the exception
Activator.getDefault()
.log(IStatus.ERROR, e.getMessage(), e);
}
}
}
public void windowActivated(IWorkbenchWindow window) {
if (iSymReader != null
&& window.getActivePage().getActivePart() instanceof MainView) {
iSymReader.reOpenCachedSymbolFiles();
}
}
public void windowClosed(IWorkbenchWindow window) {
// do nothing
}
public void windowDeactivated(IWorkbenchWindow window) {
// the user might do a re-build from the command line
if (iSymReader != null) {
iSymReader.closeCachedSymbolFiles();
}
}
public void windowOpened(IWorkbenchWindow window) {
// do nothing
}
}
/**
* set a new project
*
* @param aProject
*/
public void setProject(IProject aProject) {
if (iCurrentProject != aProject) {
iCurrentProject = aProject;
iSymReader = new SymReader(aProject);
iSymReader.loadProjectTargetsInfo();
ResourceVisitor visitor = new ResourceVisitor(this);
try {
iCurrentProject.accept(visitor);
} catch (CoreException ce) {
// just log the exception
Activator.getDefault().log(IStatus.ERROR, ce.getMessage(), ce);
}
}
}
/**
* Load all cpp files from the project. This is callback to
* ResourcceVisitor.
*
* @param resource
*/
public final void loadFileInfo(IResource resource) {
// get all the cpp file info which are belongs to current project
String cppFileName = Util.getCPPFileNameAndPath(resource);
// if cpp file found, save it
if (cppFileName != null && !cppFileNames.contains(cppFileName)) {
this.cppFileNames.add(cppFileName);
}
}
/**
* Sets the threshold. Only memory operations of a size greater or equals /
* lower or equals the threshold will be drawn on the graph.
*
* @param value
* the threshold value in bytes
* @param above
* true if filtering "above", false if "below" the threshold
*/
public void setThreshold(long value, boolean above) {
if (value < 0) {
throw new IllegalArgumentException(
"The threshold cannot be less than 0");
}
threshold = value;
aboveThreshold = above;
}
}