aboutsummaryrefslogtreecommitdiffstats
path: root/traceview/src
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2009-03-03 19:29:09 -0800
committerThe Android Open Source Project <initial-contribution@android.com>2009-03-03 19:29:09 -0800
commit55a2c71f27d3e0b8344597c7f281e687cb7aeb1b (patch)
treeecd18b995aea8eeeb8b3823266280d41245bf0f7 /traceview/src
parent82ea7a177797b844b252effea5c7c7c5d63ea4ac (diff)
downloadsdk-55a2c71f27d3e0b8344597c7f281e687cb7aeb1b.zip
sdk-55a2c71f27d3e0b8344597c7f281e687cb7aeb1b.tar.gz
sdk-55a2c71f27d3e0b8344597c7f281e687cb7aeb1b.tar.bz2
auto import from //depot/cupcake/@135843
Diffstat (limited to 'traceview/src')
-rw-r--r--traceview/src/Android.mk19
-rw-r--r--traceview/src/com/android/traceview/Call.java141
-rw-r--r--traceview/src/com/android/traceview/ColorController.java113
-rw-r--r--traceview/src/com/android/traceview/DmTraceReader.java602
-rw-r--r--traceview/src/com/android/traceview/MainWindow.java192
-rw-r--r--traceview/src/com/android/traceview/MethodData.java458
-rw-r--r--traceview/src/com/android/traceview/ProfileData.java81
-rw-r--r--traceview/src/com/android/traceview/ProfileNode.java51
-rw-r--r--traceview/src/com/android/traceview/ProfileProvider.java361
-rw-r--r--traceview/src/com/android/traceview/ProfileSelf.java34
-rw-r--r--traceview/src/com/android/traceview/ProfileView.java308
-rw-r--r--traceview/src/com/android/traceview/QtraceReader.java45
-rw-r--r--traceview/src/com/android/traceview/Selection.java70
-rw-r--r--traceview/src/com/android/traceview/SelectionController.java35
-rw-r--r--traceview/src/com/android/traceview/ThreadData.java228
-rw-r--r--traceview/src/com/android/traceview/TickScaler.java148
-rw-r--r--traceview/src/com/android/traceview/TimeLineView.java1961
-rw-r--r--traceview/src/com/android/traceview/TraceReader.java55
-rw-r--r--traceview/src/com/android/traceview/TraceUnits.java93
-rw-r--r--traceview/src/resources/icons/sort_down.pngbin0 -> 299 bytes
-rw-r--r--traceview/src/resources/icons/sort_up.pngbin0 -> 292 bytes
21 files changed, 4995 insertions, 0 deletions
diff --git a/traceview/src/Android.mk b/traceview/src/Android.mk
new file mode 100644
index 0000000..7a006de
--- /dev/null
+++ b/traceview/src/Android.mk
@@ -0,0 +1,19 @@
+# Copyright 2007 The Android Open Source Project
+#
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_JAVA_RESOURCE_DIRS := resources
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+LOCAL_JAR_MANIFEST := ../etc/manifest.txt
+LOCAL_JAVA_LIBRARIES := \
+ androidprefs \
+ sdkstats \
+ swt \
+ org.eclipse.jface_3.2.0.I20060605-1400 \
+ org.eclipse.equinox.common_3.2.0.v20060603 \
+ org.eclipse.core.commands_3.2.0.I20060605-1400
+LOCAL_MODULE := traceview
+
+include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/traceview/src/com/android/traceview/Call.java b/traceview/src/com/android/traceview/Call.java
new file mode 100644
index 0000000..40ac244
--- /dev/null
+++ b/traceview/src/com/android/traceview/Call.java
@@ -0,0 +1,141 @@
+/*
+ * 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.swt.graphics.Color;
+
+class Call implements TimeLineView.Block {
+
+ // Values for bits within the mFlags field.
+ private static final int METHOD_ACTION_MASK = 0x3;
+ private static final int IS_RECURSIVE = 0x10;
+
+ private int mThreadId;
+ private int mFlags;
+ MethodData mMethodData;
+
+ /** 0-based thread-local start time */
+ long mThreadStartTime;
+
+ /** global start time */
+ long mGlobalStartTime;
+
+ /** global end time */
+ long mGlobalEndTime;
+
+ private String mName;
+
+ /**
+ * This constructor is used for the root of a Call tree. The name is
+ * the name of the corresponding thread.
+ */
+ Call(String name, MethodData methodData) {
+ mName = name;
+ mMethodData = methodData;
+ }
+
+ Call() {
+ }
+
+ Call(int threadId, MethodData methodData, long time, int methodAction) {
+ mThreadId = threadId;
+ mMethodData = methodData;
+ mThreadStartTime = time;
+ mFlags = methodAction & METHOD_ACTION_MASK;
+ mName = methodData.getProfileName();
+ }
+
+ public void set(int threadId, MethodData methodData, long time, int methodAction) {
+ mThreadId = threadId;
+ mMethodData = methodData;
+ mThreadStartTime = time;
+ mFlags = methodAction & METHOD_ACTION_MASK;
+ mName = methodData.getProfileName();
+ }
+
+ public void updateName() {
+ mName = mMethodData.getProfileName();
+ }
+
+ public double addWeight(int x, int y, double weight) {
+ return mMethodData.addWeight(x, y, weight);
+ }
+
+ public void clearWeight() {
+ mMethodData.clearWeight();
+ }
+
+ public long getStartTime() {
+ return mGlobalStartTime;
+ }
+
+ public long getEndTime() {
+ return mGlobalEndTime;
+ }
+
+ public Color getColor() {
+ return mMethodData.getColor();
+ }
+
+ public void addExclusiveTime(long elapsed) {
+ mMethodData.addElapsedExclusive(elapsed);
+ if ((mFlags & IS_RECURSIVE) == 0) {
+ mMethodData.addTopExclusive(elapsed);
+ }
+ }
+
+ public void addInclusiveTime(long elapsed, Call parent) {
+ boolean isRecursive = (mFlags & IS_RECURSIVE) != 0;
+ mMethodData.addElapsedInclusive(elapsed, isRecursive, parent);
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public void setName(String name) {
+ mName = name;
+ }
+
+ int getThreadId() {
+ return mThreadId;
+ }
+
+ public MethodData getMethodData() {
+ return mMethodData;
+ }
+
+ int getMethodAction() {
+ return mFlags & METHOD_ACTION_MASK;
+ }
+
+ public void dump() {
+ System.out.printf("%s [%d, %d]\n", mName, mGlobalStartTime, mGlobalEndTime);
+ }
+
+ public void setRecursive(boolean isRecursive) {
+ if (isRecursive) {
+ mFlags |= IS_RECURSIVE;
+ } else {
+ mFlags &= ~IS_RECURSIVE;
+ }
+ }
+
+ public boolean isRecursive() {
+ return (mFlags & IS_RECURSIVE) != 0;
+ }
+}
diff --git a/traceview/src/com/android/traceview/ColorController.java b/traceview/src/com/android/traceview/ColorController.java
new file mode 100644
index 0000000..f5e4c0d
--- /dev/null
+++ b/traceview/src/com/android/traceview/ColorController.java
@@ -0,0 +1,113 @@
+/*
+ * 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 java.util.HashMap;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.RGB;
+import org.eclipse.swt.widgets.Display;
+
+public class ColorController {
+ private static final int[] systemColors = { SWT.COLOR_BLUE, SWT.COLOR_RED,
+ SWT.COLOR_GREEN, SWT.COLOR_CYAN, SWT.COLOR_MAGENTA, SWT.COLOR_DARK_BLUE,
+ SWT.COLOR_DARK_RED, SWT.COLOR_DARK_GREEN, SWT.COLOR_DARK_YELLOW,
+ SWT.COLOR_DARK_CYAN, SWT.COLOR_DARK_MAGENTA, SWT.COLOR_BLACK };
+
+ private static RGB[] rgbColors = { new RGB(90, 90, 255), // blue
+ new RGB(0, 240, 0), // green
+ new RGB(255, 0, 0), // red
+ new RGB(0, 255, 255), // cyan
+ new RGB(255, 80, 255), // magenta
+ new RGB(200, 200, 0), // yellow
+ new RGB(40, 0, 200), // dark blue
+ new RGB(150, 255, 150), // light green
+ new RGB(150, 0, 0), // dark red
+ new RGB(30, 150, 150), // dark cyan
+ new RGB(200, 200, 255), // light blue
+ new RGB(0, 120, 0), // dark green
+ new RGB(255, 150, 150), // light red
+ new RGB(140, 80, 140), // dark magenta
+ new RGB(150, 100, 50), // brown
+ new RGB(70, 70, 70), // dark grey
+ };
+
+ private static HashMap<Integer, Color> colorCache = new HashMap<Integer, Color>();
+ private static HashMap<Integer, Image> imageCache = new HashMap<Integer, Image>();
+
+ public ColorController() {
+ }
+
+ public static Color requestColor(Display display, RGB rgb) {
+ return requestColor(display, rgb.red, rgb.green, rgb.blue);
+ }
+
+ public static Image requestColorSquare(Display display, RGB rgb) {
+ return requestColorSquare(display, rgb.red, rgb.green, rgb.blue);
+ }
+
+ public static Color requestColor(Display display, int red, int green, int blue) {
+ int key = (red << 16) | (green << 8) | blue;
+ Color color = colorCache.get(key);
+ if (color == null) {
+ color = new Color(display, red, green, blue);
+ colorCache.put(key, color);
+ }
+ return color;
+ }
+
+ public static Image requestColorSquare(Display display, int red, int green, int blue) {
+ int key = (red << 16) | (green << 8) | blue;
+ Image image = imageCache.get(key);
+ if (image == null) {
+ image = new Image(display, 8, 14);
+ GC gc = new GC(image);
+ Color color = requestColor(display, red, green, blue);
+ gc.setBackground(color);
+ gc.fillRectangle(image.getBounds());
+ gc.dispose();
+ imageCache.put(key, image);
+ }
+ return image;
+ }
+
+ public static void assignMethodColors(Display display, MethodData[] methods) {
+ int nextColorIndex = 0;
+ for (MethodData md : methods) {
+ RGB rgb = rgbColors[nextColorIndex];
+ if (++nextColorIndex == rgbColors.length)
+ nextColorIndex = 0;
+ Color color = requestColor(display, rgb);
+ Image image = requestColorSquare(display, rgb);
+ md.setColor(color);
+ md.setImage(image);
+
+ // Compute and set a faded color
+ int fadedRed = 150 + rgb.red / 4;
+ int fadedGreen = 150 + rgb.green / 4;
+ int fadedBlue = 150 + rgb.blue / 4;
+ RGB faded = new RGB(fadedRed, fadedGreen, fadedBlue);
+ color = requestColor(display, faded);
+ image = requestColorSquare(display, faded);
+ md.setFadedColor(color);
+ md.setFadedImage(image);
+ }
+ }
+}
diff --git a/traceview/src/com/android/traceview/DmTraceReader.java b/traceview/src/com/android/traceview/DmTraceReader.java
new file mode 100644
index 0000000..5a19c19
--- /dev/null
+++ b/traceview/src/com/android/traceview/DmTraceReader.java
@@ -0,0 +1,602 @@
+/*
+ * 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 java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteOrder;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class DmTraceReader extends TraceReader {
+
+ private int mVersionNumber = 0;
+ private boolean mDebug = false;
+ private static final int TRACE_MAGIC = 0x574f4c53;
+ private boolean mRegression;
+ private ProfileProvider mProfileProvider;
+ private String mTraceFileName;
+ private MethodData mTopLevel;
+ private ArrayList<Call> mCallList;
+ private ArrayList<Call> mSwitchList;
+ private HashMap<Integer, MethodData> mMethodMap;
+ private HashMap<Integer, ThreadData> mThreadMap;
+ private ThreadData[] mSortedThreads;
+ private MethodData[] mSortedMethods;
+ private long mGlobalEndTime;
+ private MethodData mContextSwitch;
+ private int mOffsetToData;
+ private byte[] mBytes = new byte[8];
+
+ // A regex for matching the thread "id name" lines in the .key file
+ private static final Pattern mIdNamePattern = Pattern.compile("(\\d+)\t(.*)"); // $NON-NLS-1$
+
+ DmTraceReader(String traceFileName, boolean regression) {
+ mTraceFileName = traceFileName;
+ mRegression = regression;
+ mMethodMap = new HashMap<Integer, MethodData>();
+ mThreadMap = new HashMap<Integer, ThreadData>();
+
+ // Create a single top-level MethodData object to hold the profile data
+ // for time spent in the unknown caller.
+ mTopLevel = new MethodData(0, "(toplevel)");
+ mContextSwitch = new MethodData(-1, "(context switch)");
+ mMethodMap.put(0, mTopLevel);
+ generateTrees();
+ // dumpTrees();
+ }
+
+ void generateTrees() {
+ try {
+ long offset = parseKeys();
+ parseData(offset);
+ analyzeData();
+ } catch (IOException e) {
+ System.err.println(e.getMessage());
+ System.exit(1);
+ }
+ }
+
+ @Override
+ public ProfileProvider getProfileProvider() {
+ if (mProfileProvider == null)
+ mProfileProvider = new ProfileProvider(this);
+ return mProfileProvider;
+ }
+
+ Call readCall(MappedByteBuffer buffer, Call call) {
+ int threadId;
+ int methodId;
+ long time;
+
+ try {
+ if (mVersionNumber == 1)
+ threadId = buffer.get();
+ else
+ threadId = buffer.getShort();
+ methodId = buffer.getInt();
+ time = buffer.getInt();
+ } catch (BufferUnderflowException ex) {
+ return null;
+ }
+
+ int methodAction = methodId & 0x03;
+ methodId = methodId & ~0x03;
+ MethodData methodData = mMethodMap.get(methodId);
+ if (methodData == null) {
+ String name = String.format("(0x%1$x)", methodId); // $NON-NLS-1$
+ methodData = new MethodData(methodId, name);
+ }
+
+ if (call != null) {
+ call.set(threadId, methodData, time, methodAction);
+ } else {
+ call = new Call(threadId, methodData, time, methodAction);
+ }
+ return call;
+ }
+
+ private MappedByteBuffer mapFile(String filename, long offset) {
+ MappedByteBuffer buffer = null;
+ try {
+ FileInputStream dataFile = new FileInputStream(filename);
+ File file = new File(filename);
+ FileChannel fc = dataFile.getChannel();
+ buffer = fc.map(FileChannel.MapMode.READ_ONLY, offset, file.length() - offset);
+ buffer.order(ByteOrder.LITTLE_ENDIAN);
+ } catch (FileNotFoundException ex) {
+ System.err.println(ex.getMessage());
+ System.exit(1);
+ } catch (IOException ex) {
+ System.err.println(ex.getMessage());
+ System.exit(1);
+ }
+
+ return buffer;
+ }
+
+ private void readDataFileHeader(MappedByteBuffer buffer) {
+ int magic = buffer.getInt();
+ if (magic != TRACE_MAGIC) {
+ System.err.printf(
+ "Error: magic number mismatch; got 0x%x, expected 0x%x\n",
+ magic, TRACE_MAGIC);
+ throw new RuntimeException();
+ }
+ // read version
+ int version = buffer.getShort();
+
+ // read offset
+ mOffsetToData = buffer.getShort() - 16;
+
+ // read startWhen
+ buffer.getLong();
+
+ // Skip over "mOffsetToData" bytes
+ for (int ii = 0; ii < mOffsetToData; ii++) {
+ buffer.get();
+ }
+
+ // Save this position so that we can re-read the data later
+ buffer.mark();
+ }
+
+ private void parseData(long offset) {
+ MappedByteBuffer buffer = mapFile(mTraceFileName, offset);
+ readDataFileHeader(buffer);
+ parseDataPass1(buffer);
+
+ buffer.reset();
+ parseDataPass2(buffer);
+ }
+
+ private void parseDataPass1(MappedByteBuffer buffer) {
+ mSwitchList = new ArrayList<Call>();
+
+ // Read the first call so that we can set "prevThreadData"
+ Call call = new Call();
+ call = readCall(buffer, call);
+ if (call == null)
+ return;
+ long callTime = call.mThreadStartTime;
+ long prevCallTime = 0;
+ ThreadData threadData = mThreadMap.get(call.getThreadId());
+ if (threadData == null) {
+ String name = String.format("[%1$d]", call.getThreadId()); // $NON-NLS-1$
+ threadData = new ThreadData(call.getThreadId(), name, mTopLevel);
+ mThreadMap.put(call.getThreadId(), threadData);
+ }
+ ThreadData prevThreadData = threadData;
+ while (true) {
+ // If a context switch occurred, then insert a placeholder "call"
+ // record so that we can do something reasonable with the global
+ // timestamps.
+ if (prevThreadData != threadData) {
+ Call switchEnter = new Call(prevThreadData.getId(),
+ mContextSwitch, prevCallTime, 0);
+ prevThreadData.setLastContextSwitch(switchEnter);
+ mSwitchList.add(switchEnter);
+ Call contextSwitch = threadData.getLastContextSwitch();
+ if (contextSwitch != null) {
+ long prevStartTime = contextSwitch.mThreadStartTime;
+ long elapsed = callTime - prevStartTime;
+ long beforeSwitch = elapsed / 2;
+ long afterSwitch = elapsed - beforeSwitch;
+ long exitTime = callTime - afterSwitch;
+ contextSwitch.mThreadStartTime = prevStartTime + beforeSwitch;
+ Call switchExit = new Call(threadData.getId(),
+ mContextSwitch, exitTime, 1);
+
+ mSwitchList.add(switchExit);
+ }
+ prevThreadData = threadData;
+ }
+
+ // Read the next call
+ call = readCall(buffer, call);
+ if (call == null) {
+ break;
+ }
+ prevCallTime = callTime;
+ callTime = call.mThreadStartTime;
+
+ threadData = mThreadMap.get(call.getThreadId());
+ if (threadData == null) {
+ String name = String.format("[%d]", call.getThreadId());
+ threadData = new ThreadData(call.getThreadId(), name, mTopLevel);
+ mThreadMap.put(call.getThreadId(), threadData);
+ }
+ }
+ }
+
+ void parseDataPass2(MappedByteBuffer buffer) {
+ mCallList = new ArrayList<Call>();
+
+ // Read the first call so that we can set "prevThreadData"
+ Call call = readCall(buffer, null);
+ long callTime = call.mThreadStartTime;
+ long prevCallTime = callTime;
+ ThreadData threadData = mThreadMap.get(call.getThreadId());
+ ThreadData prevThreadData = threadData;
+ threadData.setGlobalStartTime(0);
+
+ int nthContextSwitch = 0;
+
+ // Assign a global timestamp to each event.
+ long globalTime = 0;
+ while (true) {
+ long elapsed = callTime - prevCallTime;
+ if (threadData != prevThreadData) {
+ // Get the next context switch. This one is entered
+ // by the previous thread.
+ Call contextSwitch = mSwitchList.get(nthContextSwitch++);
+ mCallList.add(contextSwitch);
+ elapsed = contextSwitch.mThreadStartTime - prevCallTime;
+ globalTime += elapsed;
+ elapsed = 0;
+ contextSwitch.mGlobalStartTime = globalTime;
+ prevThreadData.handleCall(contextSwitch, globalTime);
+
+ if (!threadData.isEmpty()) {
+ // This context switch is exited by the current thread.
+ contextSwitch = mSwitchList.get(nthContextSwitch++);
+ mCallList.add(contextSwitch);
+ contextSwitch.mGlobalStartTime = globalTime;
+ elapsed = callTime - contextSwitch.mThreadStartTime;
+ threadData.handleCall(contextSwitch, globalTime);
+ }
+
+ // If the thread's global start time has not been set yet,
+ // then set it.
+ if (threadData.getGlobalStartTime() == -1)
+ threadData.setGlobalStartTime(globalTime);
+ prevThreadData = threadData;
+ }
+
+ globalTime += elapsed;
+ call.mGlobalStartTime = globalTime;
+
+ threadData.handleCall(call, globalTime);
+ mCallList.add(call);
+
+ // Read the next call
+ call = readCall(buffer, null);
+ if (call == null) {
+ break;
+ }
+ prevCallTime = callTime;
+ callTime = call.mThreadStartTime;
+ threadData = mThreadMap.get(call.getThreadId());
+ }
+
+ // Allow each thread to do any cleanup of the call stack.
+ // Also add the elapsed time for each thread to the toplevel
+ // method's inclusive time.
+ for (int id : mThreadMap.keySet()) {
+ threadData = mThreadMap.get(id);
+ long endTime = threadData.endTrace();
+ if (endTime > 0)
+ mTopLevel.addElapsedInclusive(endTime, false, null);
+ }
+
+ mGlobalEndTime = globalTime;
+
+ if (mRegression) {
+ dumpCallTimes();
+ }
+ }
+
+ static final int PARSE_VERSION = 0;
+ static final int PARSE_THREADS = 1;
+ static final int PARSE_METHODS = 2;
+ static final int PARSE_OPTIONS = 4;
+
+ long parseKeys() throws IOException {
+ BufferedReader in = null;
+ try {
+ in = new BufferedReader(new FileReader(mTraceFileName));
+ } catch (FileNotFoundException ex) {
+ System.err.println(ex.getMessage());
+ }
+
+ long offset = 0;
+ int mode = PARSE_VERSION;
+ String line = null;
+ while (true) {
+ line = in.readLine();
+ if (line == null) {
+ throw new IOException("Key section does not have an *end marker");
+ }
+
+ // Calculate how much we have read from the file so far. The
+ // extra byte is for the line ending not included by readLine().
+ offset += line.length() + 1;
+ if (line.startsWith("*")) {
+ if (line.equals("*version")) {
+ mode = PARSE_VERSION;
+ continue;
+ }
+ if (line.equals("*threads")) {
+ mode = PARSE_THREADS;
+ continue;
+ }
+ if (line.equals("*methods")) {
+ mode = PARSE_METHODS;
+ continue;
+ }
+ if (line.equals("*end")) {
+ return offset;
+ }
+ }
+ switch (mode) {
+ case PARSE_VERSION:
+ mVersionNumber = Integer.decode(line);
+ mode = PARSE_OPTIONS;
+ break;
+ case PARSE_THREADS:
+ parseThread(line);
+ break;
+ case PARSE_METHODS:
+ parseMethod(line);
+ break;
+ case PARSE_OPTIONS:
+ break;
+ }
+ }
+ }
+
+ void parseThread(String line) {
+ String idStr = null;
+ String name = null;
+ Matcher matcher = mIdNamePattern.matcher(line);
+ if (matcher.find()) {
+ idStr = matcher.group(1);
+ name = matcher.group(2);
+ }
+ if (idStr == null) return;
+ if (name == null) name = "(unknown)";
+
+ int id = Integer.decode(idStr);
+ mThreadMap.put(id, new ThreadData(id, name, mTopLevel));
+ }
+
+ void parseMethod(String line) {
+ String[] tokens = line.split("\t");
+ int id = Long.decode(tokens[0]).intValue();
+ String className = tokens[1];
+ String methodName = null;
+ String signature = null;
+ String pathname = null;
+ int lineNumber = -1;
+ if (tokens.length == 6) {
+ methodName = tokens[2];
+ signature = tokens[3];
+ pathname = tokens[4];
+ lineNumber = Integer.decode(tokens[5]);
+ pathname = constructPathname(className, pathname);
+ } else if (tokens.length > 2) {
+ if (tokens[3].startsWith("(")) {
+ methodName = tokens[2];
+ signature = tokens[3];
+ } else {
+ pathname = tokens[2];
+ lineNumber = Integer.decode(tokens[3]);
+ }
+ }
+
+ mMethodMap.put(id, new MethodData(id, className, methodName, signature,
+ pathname, lineNumber));
+ }
+
+ private String constructPathname(String className, String pathname) {
+ int index = className.lastIndexOf('/');
+ if (index > 0 && index < className.length() - 1
+ && pathname.endsWith(".java"))
+ pathname = className.substring(0, index + 1) + pathname;
+ return pathname;
+ }
+
+ private void analyzeData() {
+ // Sort the threads into decreasing cpu time
+ Collection<ThreadData> tv = mThreadMap.values();
+ mSortedThreads = tv.toArray(new ThreadData[tv.size()]);
+ Arrays.sort(mSortedThreads, new Comparator<ThreadData>() {
+ public int compare(ThreadData td1, ThreadData td2) {
+ if (td2.getCpuTime() > td1.getCpuTime())
+ return 1;
+ if (td2.getCpuTime() < td1.getCpuTime())
+ return -1;
+ return td2.getName().compareTo(td1.getName());
+ }
+ });
+
+ // Analyze the call tree so that we can label the "worst" children.
+ // Also set all the root pointers in each node in the call tree.
+ long sum = 0;
+ for (ThreadData t : mSortedThreads) {
+ if (t.isEmpty() == false) {
+ Call root = t.getCalltreeRoot();
+ root.mGlobalStartTime = t.getGlobalStartTime();
+ }
+ }
+
+ // Sort the methods into decreasing inclusive time
+ Collection<MethodData> mv = mMethodMap.values();
+ MethodData[] methods;
+ methods = mv.toArray(new MethodData[mv.size()]);
+ Arrays.sort(methods, new Comparator<MethodData>() {
+ public int compare(MethodData md1, MethodData md2) {
+ if (md2.getElapsedInclusive() > md1.getElapsedInclusive())
+ return 1;
+ if (md2.getElapsedInclusive() < md1.getElapsedInclusive())
+ return -1;
+ return md1.getName().compareTo(md2.getName());
+ }
+ });
+
+ // Count the number of methods with non-zero inclusive time
+ int nonZero = 0;
+ for (MethodData md : methods) {
+ if (md.getElapsedInclusive() == 0)
+ break;
+ nonZero += 1;
+ }
+
+ // Copy the methods with non-zero time
+ mSortedMethods = new MethodData[nonZero];
+ int ii = 0;
+ for (MethodData md : methods) {
+ if (md.getElapsedInclusive() == 0)
+ break;
+ md.setRank(ii);
+ mSortedMethods[ii++] = md;
+ }
+
+ // Let each method analyze its profile data
+ for (MethodData md : mSortedMethods) {
+ md.analyzeData();
+ }
+
+ // Update all the calls to include the method rank in
+ // their name.
+ for (Call call : mCallList) {
+ call.updateName();
+ }
+
+ if (mRegression) {
+ dumpMethodStats();
+ }
+ }
+
+ /*
+ * This method computes a list of records that describe the the execution
+ * timeline for each thread. Each record is a pair: (row, block) where: row:
+ * is the ThreadData object block: is the call (containing the start and end
+ * times)
+ */
+ @Override
+ public ArrayList<TimeLineView.Record> getThreadTimeRecords() {
+ TimeLineView.Record record;
+ ArrayList<TimeLineView.Record> timeRecs;
+ timeRecs = new ArrayList<TimeLineView.Record>();
+
+ // For each thread, push a "toplevel" call that encompasses the
+ // entire execution of the thread.
+ for (ThreadData threadData : mSortedThreads) {
+ if (!threadData.isEmpty() && threadData.getId() != 0) {
+ Call call = new Call(threadData.getId(), mTopLevel,
+ threadData.getGlobalStartTime(), 0);
+ call.mGlobalStartTime = threadData.getGlobalStartTime();
+ call.mGlobalEndTime = threadData.getGlobalEndTime();
+ record = new TimeLineView.Record(threadData, call);
+ timeRecs.add(record);
+ }
+ }
+
+ for (Call call : mCallList) {
+ if (call.getMethodAction() != 0 || call.getThreadId() == 0)
+ continue;
+ ThreadData threadData = mThreadMap.get(call.getThreadId());
+ record = new TimeLineView.Record(threadData, call);
+ timeRecs.add(record);
+ }
+
+ if (mRegression) {
+ dumpTimeRecs(timeRecs);
+ System.exit(0);
+ }
+ return timeRecs;
+ }
+
+ private void dumpCallTimes() {
+ String action;
+
+ System.out.format("id thread global start,end method\n");
+ for (Call call : mCallList) {
+ if (call.getMethodAction() == 0) {
+ action = "+";
+ } else {
+ action = " ";
+ }
+ long callTime = call.mThreadStartTime;
+ System.out.format("%2d %6d %8d %8d %s %s\n",
+ call.getThreadId(), callTime, call.mGlobalStartTime,
+ call.mGlobalEndTime, action, call.getMethodData().getName());
+// if (call.getMethodAction() == 0 && call.getGlobalEndTime() < call.getGlobalStartTime()) {
+// System.out.printf("endtime %d < startTime %d\n",
+// call.getGlobalEndTime(), call.getGlobalStartTime());
+// }
+ }
+ }
+
+ private void dumpMethodStats() {
+ System.out.format("\nExclusive Inclusive Calls Method\n");
+ for (MethodData md : mSortedMethods) {
+ System.out.format("%9d %9d %9s %s\n",
+ md.getElapsedExclusive(), md.getElapsedInclusive(),
+ md.getCalls(), md.getProfileName());
+ }
+ }
+
+ private void dumpTimeRecs(ArrayList<TimeLineView.Record> timeRecs) {
+ System.out.format("\nid thread global start,end method\n");
+ for (TimeLineView.Record record : timeRecs) {
+ Call call = (Call) record.block;
+ long callTime = call.mThreadStartTime;
+ System.out.format("%2d %6d %8d %8d %s\n",
+ call.getThreadId(), callTime,
+ call.mGlobalStartTime, call.mGlobalEndTime,
+ call.getMethodData().getName());
+ }
+ }
+
+ @Override
+ public HashMap<Integer, String> getThreadLabels() {
+ HashMap<Integer, String> labels = new HashMap<Integer, String>();
+ for (ThreadData t : mThreadMap.values()) {
+ labels.put(t.getId(), t.getName());
+ }
+ return labels;
+ }
+
+ @Override
+ public MethodData[] getMethods() {
+ return mSortedMethods;
+ }
+
+ @Override
+ public ThreadData[] getThreads() {
+ return mSortedThreads;
+ }
+
+ @Override
+ public long getEndTime() {
+ return mGlobalEndTime;
+ }
+}
diff --git a/traceview/src/com/android/traceview/MainWindow.java b/traceview/src/com/android/traceview/MainWindow.java
new file mode 100644
index 0000000..b0c24e9
--- /dev/null
+++ b/traceview/src/com/android/traceview/MainWindow.java
@@ -0,0 +1,192 @@
+/*
+ * 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 com.android.sdkstats.SdkStatsService;
+
+import org.eclipse.jface.window.ApplicationWindow;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.SashForm;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.nio.channels.FileChannel;
+import java.util.HashMap;
+
+public class MainWindow extends ApplicationWindow {
+
+ private final static String PING_NAME = "Traceview";
+ private final static String PING_VERSION = "1.0";
+
+ private TraceReader mReader;
+ private String mTraceName;
+
+ // A global cache of string names.
+ public static HashMap<String, String> sStringCache = new HashMap<String, String>();
+
+ public MainWindow(String traceName, TraceReader reader) {
+ super(null);
+ mReader = reader;
+ mTraceName = traceName;
+ }
+
+ public void run() {
+ setBlockOnOpen(true);
+ open();
+ Display.getCurrent().dispose();
+ }
+
+ @Override
+ protected void configureShell(Shell shell) {
+ super.configureShell(shell);
+ shell.setText("Traceview: " + mTraceName);
+ shell.setBounds(100, 10, 1282, 900);
+ }
+
+ @Override
+ protected Control createContents(Composite parent) {
+ ColorController.assignMethodColors(parent.getDisplay(), mReader.getMethods());
+ SelectionController selectionController = new SelectionController();
+
+ GridLayout gridLayout = new GridLayout(1, false);
+ gridLayout.marginWidth = 0;
+ gridLayout.marginHeight = 0;
+ gridLayout.horizontalSpacing = 0;
+ gridLayout.verticalSpacing = 0;
+ parent.setLayout(gridLayout);
+
+ Display display = parent.getDisplay();
+ Color darkGray = display.getSystemColor(SWT.COLOR_DARK_GRAY);
+
+ // Create a sash form to separate the timeline view (on top)
+ // and the profile view (on bottom)
+ SashForm sashForm1 = new SashForm(parent, SWT.VERTICAL);
+ sashForm1.setBackground(darkGray);
+ sashForm1.SASH_WIDTH = 3;
+ GridData data = new GridData(GridData.FILL_BOTH);
+ sashForm1.setLayoutData(data);
+
+ // Create the timeline view
+ new TimeLineView(sashForm1, mReader, selectionController);
+
+ // Create the profile view
+ new ProfileView(sashForm1, mReader, selectionController);
+ return sashForm1;
+ }
+
+ /**
+ * Convert the old two-file format into the current concatenated one.
+ *
+ * @param base Base path of the two files, i.e. base.key and base.data
+ * @return Path to a temporary file that will be deleted on exit.
+ * @throws IOException
+ */
+ private static String makeTempTraceFile(String base) throws IOException {
+ // Make a temporary file that will go away on exit and prepare to
+ // write into it.
+ File temp = File.createTempFile(base, ".trace");
+ temp.deleteOnExit();
+ FileChannel dstChannel = new FileOutputStream(temp).getChannel();
+
+ // First copy the contents of the key file into our temp file.
+ FileChannel srcChannel = new FileInputStream(base + ".key").getChannel();
+ long size = dstChannel.transferFrom(srcChannel, 0, srcChannel.size());
+ srcChannel.close();
+
+ // Then concatenate the data file.
+ srcChannel = new FileInputStream(base + ".data").getChannel();
+ dstChannel.transferFrom(srcChannel, size, srcChannel.size());
+
+ // Clean up.
+ srcChannel.close();
+ dstChannel.close();
+
+ // Return the path of the temp file.
+ return temp.getPath();
+ }
+
+ public static void main(String[] args) {
+ TraceReader reader = null;
+ boolean regression = false;
+
+ // ping the usage server
+ SdkStatsService.ping(PING_NAME, PING_VERSION);
+
+ // Process command line arguments
+ int argc = 0;
+ int len = args.length;
+ while (argc < len) {
+ String arg = args[argc];
+ if (arg.charAt(0) != '-') {
+ break;
+ }
+ if (arg.equals("-r")) {
+ regression = true;
+ } else {
+ break;
+ }
+ argc++;
+ }
+ if (argc != len - 1) {
+ System.out.printf("Usage: java %s [-r] trace%n", MainWindow.class.getName());
+ System.out.printf(" -r regression only%n");
+ return;
+ }
+
+ String traceName = args[len - 1];
+ File file = new File(traceName);
+ if (file.exists() && file.isDirectory()) {
+ System.out.printf("Qemu trace files not supported yet.\n");
+ System.exit(1);
+ // reader = new QtraceReader(traceName);
+ } else {
+ // If the filename as given doesn't exist...
+ if (!file.exists()) {
+ // Try appending .trace.
+ if (new File(traceName + ".trace").exists()) {
+ traceName = traceName + ".trace";
+ // Next, see if it is the old two-file trace.
+ } else if (new File(traceName + ".data").exists()
+ && new File(traceName + ".key").exists()) {
+ try {
+ traceName = makeTempTraceFile(traceName);
+ } catch (IOException e) {
+ System.err.printf("cannot convert old trace file '%s'\n", traceName);
+ System.exit(1);
+ }
+ // Otherwise, give up.
+ } else {
+ System.err.printf("trace file '%s' not found\n", traceName);
+ System.exit(1);
+ }
+ }
+
+ reader = new DmTraceReader(traceName, regression);
+ }
+ reader.getTraceUnits().setTimeScale(TraceUnits.TimeScale.MilliSeconds);
+ new MainWindow(traceName, reader).run();
+ }
+}
diff --git a/traceview/src/com/android/traceview/MethodData.java b/traceview/src/com/android/traceview/MethodData.java
new file mode 100644
index 0000000..0bc9853
--- /dev/null
+++ b/traceview/src/com/android/traceview/MethodData.java
@@ -0,0 +1,458 @@
+/*
+ * 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.swt.graphics.Color;
+import org.eclipse.swt.graphics.Image;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashMap;
+
+public class MethodData {
+
+ private int mId;
+ private int mRank = -1;
+ private String mClassName;
+ private String mMethodName;
+ private String mSignature;
+ private String mName;
+ private String mProfileName;
+ private String mPathname;
+ private int mLineNumber;
+ private long mElapsedExclusive;
+ private long mElapsedInclusive;
+ private long mTopExclusive;
+ private int[] mNumCalls = new int[2]; // index 0=normal, 1=recursive
+ private Color mColor;
+ private Color mFadedColor;
+ private Image mImage;
+ private Image mFadedImage;
+ private HashMap<Integer, ProfileData> mParents;
+ private HashMap<Integer, ProfileData> mChildren;
+
+ // The parents of this method when this method was in a recursive call
+ private HashMap<Integer, ProfileData> mRecursiveParents;
+
+ // The children of this method when this method was in a recursive call
+ private HashMap<Integer, ProfileData> mRecursiveChildren;
+
+ private ProfileNode[] mProfileNodes;
+ private int mX;
+ private int mY;
+ private double mWeight;
+
+ public MethodData(int id, String className) {
+ mId = id;
+ mClassName = className;
+ mMethodName = null;
+ mSignature = null;
+ mPathname = null;
+ mLineNumber = -1;
+ computeName();
+ computeProfileName();
+ }
+
+ public MethodData(int id, String className, String methodName,
+ String signature, String pathname, int lineNumber) {
+ mId = id;
+ mClassName = className;
+ mMethodName = methodName;
+ mSignature = signature;
+ mPathname = pathname;
+ mLineNumber = lineNumber;
+ computeName();
+ computeProfileName();
+ }
+
+ private Comparator<ProfileData> mByElapsedInclusive = new Comparator<ProfileData>() {
+ public int compare(ProfileData pd1, ProfileData pd2) {
+ if (pd2.getElapsedInclusive() > pd1.getElapsedInclusive())
+ return 1;
+ if (pd2.getElapsedInclusive() < pd1.getElapsedInclusive())
+ return -1;
+ return 0;
+ }
+ };
+
+ public double addWeight(int x, int y, double weight) {
+ if (mX == x && mY == y)
+ mWeight += weight;
+ else {
+ mX = x;
+ mY = y;
+ mWeight = weight;
+ }
+ return mWeight;
+ }
+
+ public void clearWeight() {
+ mWeight = 0;
+ }
+
+ public int getRank() {
+ return mRank;
+ }
+
+ public void setRank(int rank) {
+ mRank = rank;
+ computeProfileName();
+ }
+
+ public void addElapsedExclusive(long time) {
+ mElapsedExclusive += time;
+ }
+
+ public void addElapsedInclusive(long time, boolean isRecursive, Call parent) {
+ if (isRecursive == false) {
+ mElapsedInclusive += time;
+ mNumCalls[0] += 1;
+ } else {
+ mNumCalls[1] += 1;
+ }
+
+ if (parent == null)
+ return;
+
+ // Find the child method in the parent
+ MethodData parentMethod = parent.mMethodData;
+ if (parent.isRecursive()) {
+ parentMethod.mRecursiveChildren = updateInclusive(time,
+ parentMethod, this, false,
+ parentMethod.mRecursiveChildren);
+ } else {
+ parentMethod.mChildren = updateInclusive(time,
+ parentMethod, this, false, parentMethod.mChildren);
+ }
+
+ // Find the parent method in the child
+ if (isRecursive) {
+ mRecursiveParents = updateInclusive(time, this, parentMethod, true,
+ mRecursiveParents);
+ } else {
+ mParents = updateInclusive(time, this, parentMethod, true,
+ mParents);
+ }
+ }
+
+ private HashMap<Integer, ProfileData> updateInclusive(long time,
+ MethodData contextMethod, MethodData elementMethod,
+ boolean elementIsParent, HashMap<Integer, ProfileData> map) {
+ if (map == null) {
+ map = new HashMap<Integer, ProfileData>(4);
+ } else {
+ ProfileData profileData = map.get(elementMethod.mId);
+ if (profileData != null) {
+ profileData.addElapsedInclusive(time);
+ return map;
+ }
+ }
+
+ ProfileData elementData = new ProfileData(contextMethod,
+ elementMethod, elementIsParent);
+ elementData.setElapsedInclusive(time);
+ elementData.setNumCalls(1);
+ map.put(elementMethod.mId, elementData);
+ return map;
+ }
+
+ public void analyzeData() {
+ // Sort the parents and children into decreasing inclusive time
+ ProfileData[] sortedParents;
+ ProfileData[] sortedChildren;
+ ProfileData[] sortedRecursiveParents;
+ ProfileData[] sortedRecursiveChildren;
+
+ sortedParents = sortProfileData(mParents);
+ sortedChildren = sortProfileData(mChildren);
+ sortedRecursiveParents = sortProfileData(mRecursiveParents);
+ sortedRecursiveChildren = sortProfileData(mRecursiveChildren);
+
+ // Add "self" time to the top of the sorted children
+ sortedChildren = addSelf(sortedChildren);
+
+ // Create the ProfileNode objects that we need
+ ArrayList<ProfileNode> nodes = new ArrayList<ProfileNode>();
+ ProfileNode profileNode;
+ if (mParents != null) {
+ profileNode = new ProfileNode("Parents", this, sortedParents,
+ true, false);
+ nodes.add(profileNode);
+ }
+ if (mChildren != null) {
+ profileNode = new ProfileNode("Children", this, sortedChildren,
+ false, false);
+ nodes.add(profileNode);
+ }
+ if (mRecursiveParents!= null) {
+ profileNode = new ProfileNode("Parents while recursive", this,
+ sortedRecursiveParents, true, true);
+ nodes.add(profileNode);
+ }
+ if (mRecursiveChildren != null) {
+ profileNode = new ProfileNode("Children while recursive", this,
+ sortedRecursiveChildren, false, true);
+ nodes.add(profileNode);
+ }
+ mProfileNodes = nodes.toArray(new ProfileNode[nodes.size()]);
+ }
+
+ // Create and return a ProfileData[] array that is a sorted copy
+ // of the given HashMap values.
+ private ProfileData[] sortProfileData(HashMap<Integer, ProfileData> map) {
+ if (map == null)
+ return null;
+
+ // Convert the hash values to an array of ProfileData
+ Collection<ProfileData> values = map.values();
+ ProfileData[] sorted = values.toArray(new ProfileData[values.size()]);
+
+ // Sort the array by elapsed inclusive time
+ Arrays.sort(sorted, mByElapsedInclusive);
+ return sorted;
+ }
+
+ private ProfileData[] addSelf(ProfileData[] children) {
+ ProfileData[] pdata;
+ if (children == null) {
+ pdata = new ProfileData[1];
+ } else {
+ pdata = new ProfileData[children.length + 1];
+ System.arraycopy(children, 0, pdata, 1, children.length);
+ }
+ pdata[0] = new ProfileSelf(this);
+ return pdata;
+ }
+
+ public void addTopExclusive(long time) {
+ mTopExclusive += time;
+ }
+
+ public long getTopExclusive() {
+ return mTopExclusive;
+ }
+
+ public int getId() {
+ return mId;
+ }
+
+ private void computeName() {
+ if (mMethodName == null) {
+ mName = mClassName;
+ return;
+ }
+
+ StringBuilder sb = new StringBuilder();
+ sb.append(mClassName);
+ sb.append("."); //$NON-NLS-1$
+ sb.append(mMethodName);
+ sb.append(" "); //$NON-NLS-1$
+ sb.append(mSignature);
+ mName = sb.toString();
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public String getClassName() {
+ return mClassName;
+ }
+
+ public String getMethodName() {
+ return mMethodName;
+ }
+
+ public String getProfileName() {
+ return mProfileName;
+ }
+
+ public void computeProfileName() {
+ if (mRank == -1) {
+ mProfileName = mName;
+ return;
+ }
+
+ StringBuilder sb = new StringBuilder();
+ sb.append(mRank);
+ sb.append(" "); //$NON-NLS-1$
+ sb.append(getName());
+ mProfileName = sb.toString();
+ }
+
+ public String getCalls() {
+ return String.format("%d+%d", mNumCalls[0], mNumCalls[1]);
+ }
+
+ public int getTotalCalls() {
+ return mNumCalls[0] + mNumCalls[1];
+ }
+
+ public Color getColor() {
+ return mColor;
+ }
+
+ public void setColor(Color color) {
+ mColor = color;
+ }
+
+ public void setImage(Image image) {
+ mImage = image;
+ }
+
+ public Image getImage() {
+ return mImage;
+ }
+
+ @Override
+ public String toString() {
+ return getName();
+ }
+
+ public long getElapsedExclusive() {
+ return mElapsedExclusive;
+ }
+
+ public long getElapsedInclusive() {
+ return mElapsedInclusive;
+ }
+
+ public void setFadedColor(Color fadedColor) {
+ mFadedColor = fadedColor;
+ }
+
+ public Color getFadedColor() {
+ return mFadedColor;
+ }
+
+ public void setFadedImage(Image fadedImage) {
+ mFadedImage = fadedImage;
+ }
+
+ public Image getFadedImage() {
+ return mFadedImage;
+ }
+
+ public void setPathname(String pathname) {
+ mPathname = pathname;
+ }
+
+ public String getPathname() {
+ return mPathname;
+ }
+
+ public void setLineNumber(int lineNumber) {
+ mLineNumber = lineNumber;
+ }
+
+ public int getLineNumber() {
+ return mLineNumber;
+ }
+
+ public ProfileNode[] getProfileNodes() {
+ return mProfileNodes;
+ }
+
+ public static class Sorter implements Comparator<MethodData> {
+ public int compare(MethodData md1, MethodData md2) {
+ if (mColumn == Column.BY_NAME) {
+ int result = md1.getName().compareTo(md2.getName());
+ return (mDirection == Direction.INCREASING) ? result : -result;
+ }
+ if (mColumn == Column.BY_INCLUSIVE) {
+ if (md2.getElapsedInclusive() > md1.getElapsedInclusive())
+ return (mDirection == Direction.INCREASING) ? -1 : 1;
+ if (md2.getElapsedInclusive() < md1.getElapsedInclusive())
+ return (mDirection == Direction.INCREASING) ? 1 : -1;
+ return md1.getName().compareTo(md2.getName());
+ }
+ if (mColumn == Column.BY_EXCLUSIVE) {
+ if (md2.getElapsedExclusive() > md1.getElapsedExclusive())
+ return (mDirection == Direction.INCREASING) ? -1 : 1;
+ if (md2.getElapsedExclusive() < md1.getElapsedExclusive())
+ return (mDirection == Direction.INCREASING) ? 1 : -1;
+ return md1.getName().compareTo(md2.getName());
+ }
+ if (mColumn == Column.BY_CALLS) {
+ int result = md1.getTotalCalls() - md2.getTotalCalls();
+ if (result == 0)
+ return md1.getName().compareTo(md2.getName());
+ return (mDirection == Direction.INCREASING) ? result : -result;
+ }
+ if (mColumn == Column.BY_TIME_PER_CALL) {
+ double time1 = md1.getElapsedInclusive();
+ time1 = time1 / md1.getTotalCalls();
+ double time2 = md2.getElapsedInclusive();
+ time2 = time2 / md2.getTotalCalls();
+ double diff = time1 - time2;
+ int result = 0;
+ if (diff < 0)
+ result = -1;
+ else if (diff > 0)
+ result = 1;
+ if (result == 0)
+ return md1.getName().compareTo(md2.getName());
+ return (mDirection == Direction.INCREASING) ? result : -result;
+ }
+ return 0;
+ }
+
+ public void setColumn(Column column) {
+ // If the sort column specified is the same as last time,
+ // then reverse the sort order.
+ if (mColumn == column) {
+ // Reverse the sort order
+ if (mDirection == Direction.INCREASING)
+ mDirection = Direction.DECREASING;
+ else
+ mDirection = Direction.INCREASING;
+ } else {
+ // Sort names into increasing order, data into decreasing order.
+ if (column == Column.BY_NAME)
+ mDirection = Direction.INCREASING;
+ else
+ mDirection = Direction.DECREASING;
+ }
+ mColumn = column;
+ }
+
+ public Column getColumn() {
+ return mColumn;
+ }
+
+ public void setDirection(Direction direction) {
+ mDirection = direction;
+ }
+
+ public Direction getDirection() {
+ return mDirection;
+ }
+
+ public static enum Column {
+ BY_NAME, BY_EXCLUSIVE, BY_INCLUSIVE, BY_CALLS, BY_TIME_PER_CALL
+ };
+
+ public static enum Direction {
+ INCREASING, DECREASING
+ };
+
+ private Column mColumn;
+ private Direction mDirection;
+ }
+}
diff --git a/traceview/src/com/android/traceview/ProfileData.java b/traceview/src/com/android/traceview/ProfileData.java
new file mode 100644
index 0000000..f0c1d61
--- /dev/null
+++ b/traceview/src/com/android/traceview/ProfileData.java
@@ -0,0 +1,81 @@
+/*
+ * 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;
+
+
+public class ProfileData {
+
+ protected MethodData mElement;
+
+ /** mContext is either the parent or child of mElement */
+ protected MethodData mContext;
+ protected boolean mElementIsParent;
+ protected long mElapsedInclusive;
+ protected int mNumCalls;
+
+ public ProfileData() {
+ }
+
+ public ProfileData(MethodData context, MethodData element,
+ boolean elementIsParent) {
+ mContext = context;
+ mElement = element;
+ mElementIsParent = elementIsParent;
+ }
+
+ public String getProfileName() {
+ return mElement.getProfileName();
+ }
+
+ public MethodData getMethodData() {
+ return mElement;
+ }
+
+ public void addElapsedInclusive(long elapsedInclusive) {
+ mElapsedInclusive += elapsedInclusive;
+ mNumCalls += 1;
+ }
+
+ public void setElapsedInclusive(long elapsedInclusive) {
+ mElapsedInclusive = elapsedInclusive;
+ }
+
+ public long getElapsedInclusive() {
+ return mElapsedInclusive;
+ }
+
+ public void setNumCalls(int numCalls) {
+ mNumCalls = numCalls;
+ }
+
+ public String getNumCalls() {
+ int totalCalls;
+ if (mElementIsParent)
+ totalCalls = mContext.getTotalCalls();
+ else
+ totalCalls = mElement.getTotalCalls();
+ return String.format("%d/%d", mNumCalls, totalCalls);
+ }
+
+ public boolean isParent() {
+ return mElementIsParent;
+ }
+
+ public MethodData getContext() {
+ return mContext;
+ }
+}
diff --git a/traceview/src/com/android/traceview/ProfileNode.java b/traceview/src/com/android/traceview/ProfileNode.java
new file mode 100644
index 0000000..7cb0b5d
--- /dev/null
+++ b/traceview/src/com/android/traceview/ProfileNode.java
@@ -0,0 +1,51 @@
+/*
+ * 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;
+
+public class ProfileNode {
+
+ private String mLabel;
+ private MethodData mMethodData;
+ private ProfileData[] mChildren;
+ private boolean mIsParent;
+ private boolean mIsRecursive;
+
+ public ProfileNode(String label, MethodData methodData,
+ ProfileData[] children, boolean isParent, boolean isRecursive) {
+ mLabel = label;
+ mMethodData = methodData;
+ mChildren = children;
+ mIsParent = isParent;
+ mIsRecursive = isRecursive;
+ }
+
+ public String getLabel() {
+ return mLabel;
+ }
+
+ public ProfileData[] getChildren() {
+ return mChildren;
+ }
+
+ public boolean isParent() {
+ return mIsParent;
+ }
+
+ public boolean isRecursive() {
+ return mIsRecursive;
+ }
+}
diff --git a/traceview/src/com/android/traceview/ProfileProvider.java b/traceview/src/com/android/traceview/ProfileProvider.java
new file mode 100644
index 0000000..fe5c832
--- /dev/null
+++ b/traceview/src/com/android/traceview/ProfileProvider.java
@@ -0,0 +1,361 @@
+/*
+ * 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 java.io.InputStream;
+import java.util.Arrays;
+import java.util.regex.Pattern;
+
+import org.eclipse.jface.viewers.IColorProvider;
+import org.eclipse.jface.viewers.ITableLabelProvider;
+import org.eclipse.jface.viewers.ITreeContentProvider;
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.jface.viewers.TreeViewer;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Tree;
+import org.eclipse.swt.widgets.TreeColumn;
+import org.eclipse.swt.widgets.TreeItem;
+
+class ProfileProvider implements ITreeContentProvider {
+
+ private MethodData[] mRoots;
+ private SelectionAdapter mListener;
+ private TreeViewer mTreeViewer;
+ private TraceReader mReader;
+ private Image mSortUp;
+ private Image mSortDown;
+ private String mColumnNames[] = { "Name", "Incl %", "Inclusive", "Excl %",
+ "Exclusive", "Calls+Recur\nCalls/Total", "Time/Call" };
+ private int mColumnWidths[] = { 370, 70, 70, 70, 70, 90, 70 };
+ private int mColumnAlignments[] = { SWT.LEFT, SWT.RIGHT, SWT.RIGHT,
+ SWT.RIGHT, SWT.RIGHT, SWT.CENTER, SWT.RIGHT };
+ private static final int COL_NAME = 0;
+ private static final int COL_INCLUSIVE_PER = 1;
+ private static final int COL_INCLUSIVE = 2;
+ private static final int COL_EXCLUSIVE_PER = 3;
+ private static final int COL_EXCLUSIVE = 4;
+ private static final int COL_CALLS = 5;
+ private static final int COL_TIME_PER_CALL = 6;
+ private long mTotalTime;
+ private Pattern mUppercase;
+ private int mPrevMatchIndex = -1;
+
+ public ProfileProvider(TraceReader reader) {
+ mRoots = reader.getMethods();
+ mReader = reader;
+ mTotalTime = reader.getEndTime();
+ Display display = Display.getCurrent();
+ InputStream in = getClass().getClassLoader().getResourceAsStream(
+ "icons/sort_up.png");
+ mSortUp = new Image(display, in);
+ in = getClass().getClassLoader().getResourceAsStream(
+ "icons/sort_down.png");
+ mSortDown = new Image(display, in);
+ mUppercase = Pattern.compile("[A-Z]");
+ }
+
+ private MethodData doMatchName(String name, int startIndex) {
+ // Check if the given "name" has any uppercase letters
+ boolean hasUpper = mUppercase.matcher(name).matches();
+ for (int ii = startIndex; ii < mRoots.length; ++ii) {
+ MethodData md = mRoots[ii];
+ String fullName = md.getName();
+ // If there were no upper case letters in the given name,
+ // then ignore case when matching.
+ if (!hasUpper)
+ fullName = fullName.toLowerCase();
+ if (fullName.indexOf(name) != -1) {
+ mPrevMatchIndex = ii;
+ return md;
+ }
+ }
+ mPrevMatchIndex = -1;
+ return null;
+ }
+
+ public MethodData findMatchingName(String name) {
+ return doMatchName(name, 0);
+ }
+
+ public MethodData findNextMatchingName(String name) {
+ return doMatchName(name, mPrevMatchIndex + 1);
+ }
+
+ public MethodData findMatchingTreeItem(TreeItem item) {
+ if (item == null)
+ return null;
+ String text = item.getText();
+ if (Character.isDigit(text.charAt(0)) == false)
+ return null;
+ int spaceIndex = text.indexOf(' ');
+ String numstr = text.substring(0, spaceIndex);
+ int rank = Integer.valueOf(numstr);
+ for (MethodData md : mRoots) {
+ if (md.getRank() == rank)
+ return md;
+ }
+ return null;
+ }
+
+ public void setTreeViewer(TreeViewer treeViewer) {
+ mTreeViewer = treeViewer;
+ }
+
+ public String[] getColumnNames() {
+ return mColumnNames;
+ }
+
+ public int[] getColumnWidths() {
+ return mColumnWidths;
+ }
+
+ public int[] getColumnAlignments() {
+ return mColumnAlignments;
+ }
+
+ public Object[] getChildren(Object element) {
+ if (element instanceof MethodData) {
+ MethodData md = (MethodData) element;
+ return md.getProfileNodes();
+ }
+ if (element instanceof ProfileNode) {
+ ProfileNode pn = (ProfileNode) element;
+ return pn.getChildren();
+ }
+ return new Object[0];
+ }
+
+ public Object getParent(Object element) {
+ return null;
+ }
+
+ public boolean hasChildren(Object element) {
+ if (element instanceof MethodData)
+ return true;
+ if (element instanceof ProfileNode)
+ return true;
+ return false;
+ }
+
+ public Object[] getElements(Object element) {
+ return mRoots;
+ }
+
+ public void dispose() {
+ }
+
+ public void inputChanged(Viewer arg0, Object arg1, Object arg2) {
+ }
+
+ public Object getRoot() {
+ return "root";
+ }
+
+ public SelectionAdapter getColumnListener() {
+ if (mListener == null)
+ mListener = new ColumnListener();
+ return mListener;
+ }
+
+ public LabelProvider getLabelProvider() {
+ return new ProfileLabelProvider();
+ }
+
+ class ProfileLabelProvider extends LabelProvider implements
+ ITableLabelProvider, IColorProvider {
+ Color colorRed;
+ Color colorParentsBack;
+ Color colorChildrenBack;
+ TraceUnits traceUnits;
+
+ public ProfileLabelProvider() {
+ Display display = Display.getCurrent();
+ colorRed = display.getSystemColor(SWT.COLOR_RED);
+ colorParentsBack = new Color(display, 230, 230, 255); // blue
+ colorChildrenBack = new Color(display, 255, 255, 210); // yellow
+ traceUnits = mReader.getTraceUnits();
+ }
+
+ public String getColumnText(Object element, int col) {
+ if (element instanceof MethodData) {
+ MethodData md = (MethodData) element;
+ if (col == COL_NAME)
+ return md.getProfileName();
+ if (col == COL_EXCLUSIVE) {
+ double val = md.getElapsedExclusive();
+ val = traceUnits.getScaledValue(val);
+ return String.format("%.3f", val);
+ }
+ if (col == COL_EXCLUSIVE_PER) {
+ double val = md.getElapsedExclusive();
+ double per = val * 100.0 / mTotalTime;
+ return String.format("%.1f%%", per);
+ }
+ if (col == COL_INCLUSIVE) {
+ double val = md.getElapsedInclusive();
+ val = traceUnits.getScaledValue(val);
+ return String.format("%.3f", val);
+ }
+ if (col == COL_INCLUSIVE_PER) {
+ double val = md.getElapsedInclusive();
+ double per = val * 100.0 / mTotalTime;
+ return String.format("%.1f%%", per);
+ }
+ if (col == COL_CALLS)
+ return md.getCalls();
+ if (col == COL_TIME_PER_CALL) {
+ int numCalls = md.getTotalCalls();
+ double val = md.getElapsedInclusive();
+ val = val / numCalls;
+ val = traceUnits.getScaledValue(val);
+ return String.format("%.3f", val);
+ }
+ } else if (element instanceof ProfileSelf) {
+ ProfileSelf ps = (ProfileSelf) element;
+ if (col == COL_NAME)
+ return ps.getProfileName();
+ if (col == COL_INCLUSIVE) {
+ double val = ps.getElapsedInclusive();
+ val = traceUnits.getScaledValue(val);
+ return String.format("%.3f", val);
+ }
+ if (col == COL_INCLUSIVE_PER) {
+ double total;
+ double val = ps.getElapsedInclusive();
+ MethodData context = ps.getContext();
+ total = context.getElapsedInclusive();
+ double per = val * 100.0 / total;
+ return String.format("%.1f%%", per);
+ }
+ return "";
+ } else if (element instanceof ProfileData) {
+ ProfileData pd = (ProfileData) element;
+ if (col == COL_NAME)
+ return pd.getProfileName();
+ if (col == COL_INCLUSIVE) {
+ double val = pd.getElapsedInclusive();
+ val = traceUnits.getScaledValue(val);
+ return String.format("%.3f", val);
+ }
+ if (col == COL_INCLUSIVE_PER) {
+ double total;
+ double val = pd.getElapsedInclusive();
+ MethodData context = pd.getContext();
+ total = context.getElapsedInclusive();
+ double per = val * 100.0 / total;
+ return String.format("%.1f%%", per);
+ }
+ if (col == COL_CALLS)
+ return pd.getNumCalls();
+ return "";
+ } else if (element instanceof ProfileNode) {
+ ProfileNode pn = (ProfileNode) element;
+ if (col == COL_NAME)
+ return pn.getLabel();
+ return "";
+ }
+ return "col" + col;
+ }
+
+ public Image getColumnImage(Object element, int col) {
+ if (col != COL_NAME)
+ return null;
+ if (element instanceof MethodData) {
+ MethodData md = (MethodData) element;
+ return md.getImage();
+ }
+ if (element instanceof ProfileData) {
+ ProfileData pd = (ProfileData) element;
+ MethodData md = pd.getMethodData();
+ return md.getImage();
+ }
+ return null;
+ }
+
+ public Color getForeground(Object element) {
+ return null;
+ }
+
+ public Color getBackground(Object element) {
+ if (element instanceof ProfileData) {
+ ProfileData pd = (ProfileData) element;
+ if (pd.isParent())
+ return colorParentsBack;
+ return colorChildrenBack;
+ }
+ if (element instanceof ProfileNode) {
+ ProfileNode pn = (ProfileNode) element;
+ if (pn.isParent())
+ return colorParentsBack;
+ return colorChildrenBack;
+ }
+ return null;
+ }
+ }
+
+ class ColumnListener extends SelectionAdapter {
+ MethodData.Sorter sorter = new MethodData.Sorter();
+
+ @Override
+ public void widgetSelected(SelectionEvent event) {
+ TreeColumn column = (TreeColumn) event.widget;
+ String name = column.getText();
+ Tree tree = column.getParent();
+ tree.setRedraw(false);
+ TreeColumn[] columns = tree.getColumns();
+ for (TreeColumn col : columns) {
+ col.setImage(null);
+ }
+ if (name == mColumnNames[COL_NAME]) {
+ // Sort names alphabetically
+ sorter.setColumn(MethodData.Sorter.Column.BY_NAME);
+ Arrays.sort(mRoots, sorter);
+ } else if (name == mColumnNames[COL_EXCLUSIVE]) {
+ sorter.setColumn(MethodData.Sorter.Column.BY_EXCLUSIVE);
+ Arrays.sort(mRoots, sorter);
+ } else if (name == mColumnNames[COL_EXCLUSIVE_PER]) {
+ sorter.setColumn(MethodData.Sorter.Column.BY_EXCLUSIVE);
+ Arrays.sort(mRoots, sorter);
+ } else if (name == mColumnNames[COL_INCLUSIVE]) {
+ sorter.setColumn(MethodData.Sorter.Column.BY_INCLUSIVE);
+ Arrays.sort(mRoots, sorter);
+ } else if (name == mColumnNames[COL_INCLUSIVE_PER]) {
+ sorter.setColumn(MethodData.Sorter.Column.BY_INCLUSIVE);
+ Arrays.sort(mRoots, sorter);
+ } else if (name == mColumnNames[COL_CALLS]) {
+ sorter.setColumn(MethodData.Sorter.Column.BY_CALLS);
+ Arrays.sort(mRoots, sorter);
+ } else if (name == mColumnNames[COL_TIME_PER_CALL]) {
+ sorter.setColumn(MethodData.Sorter.Column.BY_TIME_PER_CALL);
+ Arrays.sort(mRoots, sorter);
+ }
+ MethodData.Sorter.Direction direction = sorter.getDirection();
+ if (direction == MethodData.Sorter.Direction.INCREASING)
+ column.setImage(mSortDown);
+ else
+ column.setImage(mSortUp);
+ tree.setRedraw(true);
+ mTreeViewer.refresh();
+ }
+ }
+}
diff --git a/traceview/src/com/android/traceview/ProfileSelf.java b/traceview/src/com/android/traceview/ProfileSelf.java
new file mode 100644
index 0000000..3a4f3d9
--- /dev/null
+++ b/traceview/src/com/android/traceview/ProfileSelf.java
@@ -0,0 +1,34 @@
+/*
+ * 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;
+
+public class ProfileSelf extends ProfileData {
+ public ProfileSelf(MethodData methodData) {
+ mElement = methodData;
+ mContext = methodData;
+ }
+
+ @Override
+ public String getProfileName() {
+ return "self";
+ }
+
+ @Override
+ public long getElapsedInclusive() {
+ return mElement.getTopExclusive();
+ }
+}
diff --git a/traceview/src/com/android/traceview/ProfileView.java b/traceview/src/com/android/traceview/ProfileView.java
new file mode 100644
index 0000000..e48cb56
--- /dev/null
+++ b/traceview/src/com/android/traceview/ProfileView.java
@@ -0,0 +1,308 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.Observable;
+import java.util.Observer;
+
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.ITreeViewerListener;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.jface.viewers.TreeExpansionEvent;
+import org.eclipse.jface.viewers.TreeViewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.KeyAdapter;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.swt.widgets.Tree;
+import org.eclipse.swt.widgets.TreeColumn;
+import org.eclipse.swt.widgets.TreeItem;
+
+public class ProfileView extends Composite implements Observer {
+
+ private TreeViewer mTreeViewer;
+ private Text mSearchBox;
+ private SelectionController mSelectionController;
+ private ProfileProvider mProfileProvider;
+ private Color mColorNoMatch;
+ private Color mColorMatch;
+ private MethodData mCurrentHighlightedMethod;
+
+ public ProfileView(Composite parent, TraceReader reader,
+ SelectionController selectController) {
+ super(parent, SWT.NONE);
+ setLayout(new GridLayout(1, false));
+ this.mSelectionController = selectController;
+ mSelectionController.addObserver(this);
+
+ // Add a tree viewer at the top
+ mTreeViewer = new TreeViewer(this, SWT.MULTI | SWT.NONE);
+ mTreeViewer.setUseHashlookup(true);
+ mProfileProvider = reader.getProfileProvider();
+ mProfileProvider.setTreeViewer(mTreeViewer);
+ SelectionAdapter listener = mProfileProvider.getColumnListener();
+ final Tree tree = mTreeViewer.getTree();
+ tree.setHeaderVisible(true);
+ tree.setLayoutData(new GridData(GridData.FILL_BOTH));
+
+ // Get the column names from the ProfileProvider
+ String[] columnNames = mProfileProvider.getColumnNames();
+ int[] columnWidths = mProfileProvider.getColumnWidths();
+ int[] columnAlignments = mProfileProvider.getColumnAlignments();
+ for (int ii = 0; ii < columnWidths.length; ++ii) {
+ TreeColumn column = new TreeColumn(tree, SWT.LEFT);
+ column.setText(columnNames[ii]);
+ column.setWidth(columnWidths[ii]);
+ column.setMoveable(true);
+ column.addSelectionListener(listener);
+ column.setAlignment(columnAlignments[ii]);
+ }
+
+ // Add a listener to the tree so that we can make the row
+ // height smaller.
+ tree.addListener(SWT.MeasureItem, new Listener() {
+ public void handleEvent(Event event) {
+ int fontHeight = event.gc.getFontMetrics().getHeight();
+ event.height = fontHeight;
+ }
+ });
+
+ mTreeViewer.setContentProvider(mProfileProvider);
+ mTreeViewer.setLabelProvider(mProfileProvider.getLabelProvider());
+ mTreeViewer.setInput(mProfileProvider.getRoot());
+
+ // Create another composite to hold the label and text box
+ Composite composite = new Composite(this, SWT.NONE);
+ composite.setLayout(new GridLayout(2, false));
+ composite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+
+ // Add a label for the search box
+ Label label = new Label(composite, SWT.NONE);
+ label.setText("Find:");
+
+ // Add a text box for searching for method names
+ mSearchBox = new Text(composite, SWT.BORDER);
+ mSearchBox.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+
+ Display display = getDisplay();
+ mColorNoMatch = new Color(display, 255, 200, 200);
+ mColorMatch = mSearchBox.getBackground();
+
+ mSearchBox.addModifyListener(new ModifyListener() {
+ public void modifyText(ModifyEvent ev) {
+ String query = mSearchBox.getText();
+ if (query.length() == 0)
+ return;
+ findName(query);
+ }
+ });
+
+ // Add a key listener to the text box so that we can clear
+ // the text box if the user presses <ESC>.
+ mSearchBox.addKeyListener(new KeyAdapter() {
+ @Override
+ public void keyPressed(KeyEvent event) {
+ if (event.keyCode == SWT.ESC) {
+ mSearchBox.setText("");
+ } else if (event.keyCode == SWT.CR) {
+ String query = mSearchBox.getText();
+ if (query.length() == 0)
+ return;
+ findNextName(query);
+ }
+ }
+ });
+
+ // Also add a key listener to the tree viewer so that the
+ // user can just start typing anywhere in the tree view.
+ tree.addKeyListener(new KeyAdapter() {
+ @Override
+ public void keyPressed(KeyEvent event) {
+ if (event.keyCode == SWT.ESC) {
+ mSearchBox.setText("");
+ } else if (event.keyCode == SWT.BS) {
+ // Erase the last character from the search box
+ String text = mSearchBox.getText();
+ int len = text.length();
+ String chopped;
+ if (len <= 1)
+ chopped = "";
+ else
+ chopped = text.substring(0, len - 1);
+ mSearchBox.setText(chopped);
+ } else if (event.keyCode == SWT.CR) {
+ String query = mSearchBox.getText();
+ if (query.length() == 0)
+ return;
+ findNextName(query);
+ } else {
+ // Append the given character to the search box
+ String str = String.valueOf(event.character);
+ mSearchBox.append(str);
+ }
+ event.doit = false;
+ }
+ });
+
+ // Add a selection listener to the tree so that the user can click
+ // on a method that is a child or parent and jump to that method.
+ mTreeViewer
+ .addSelectionChangedListener(new ISelectionChangedListener() {
+ public void selectionChanged(SelectionChangedEvent ev) {
+ ISelection sel = ev.getSelection();
+ if (sel.isEmpty())
+ return;
+ if (sel instanceof IStructuredSelection) {
+ IStructuredSelection selection = (IStructuredSelection) sel;
+ Object element = selection.getFirstElement();
+ if (element == null)
+ return;
+ if (element instanceof MethodData) {
+ MethodData md = (MethodData) element;
+ highlightMethod(md, true);
+ }
+ if (element instanceof ProfileData) {
+ MethodData md = ((ProfileData) element)
+ .getMethodData();
+ highlightMethod(md, true);
+ }
+ }
+ }
+ });
+
+ // Add a tree listener so that we can expand the parents and children
+ // of a method when a method is expanded.
+ mTreeViewer.addTreeListener(new ITreeViewerListener() {
+ public void treeExpanded(TreeExpansionEvent event) {
+ Object element = event.getElement();
+ if (element instanceof MethodData) {
+ MethodData md = (MethodData) element;
+ expandNode(md);
+ }
+ }
+ public void treeCollapsed(TreeExpansionEvent event) {
+ }
+ });
+
+ tree.addListener(SWT.MouseDown, new Listener() {
+ public void handleEvent(Event event) {
+ Point point = new Point(event.x, event.y);
+ TreeItem treeItem = tree.getItem(point);
+ MethodData md = mProfileProvider.findMatchingTreeItem(treeItem);
+ if (md == null)
+ return;
+ ArrayList<Selection> selections = new ArrayList<Selection>();
+ selections.add(Selection.highlight("MethodData", md));
+ mSelectionController.change(selections, "ProfileView");
+ }
+ });
+ }
+
+ private void findName(String query) {
+ MethodData md = mProfileProvider.findMatchingName(query);
+ selectMethod(md);
+ }
+
+ private void findNextName(String query) {
+ MethodData md = mProfileProvider.findNextMatchingName(query);
+ selectMethod(md);
+ }
+
+ private void selectMethod(MethodData md) {
+ if (md == null) {
+ mSearchBox.setBackground(mColorNoMatch);
+ return;
+ }
+ mSearchBox.setBackground(mColorMatch);
+ highlightMethod(md, false);
+ }
+
+ public void update(Observable objservable, Object arg) {
+ // Ignore updates from myself
+ if (arg == "ProfileView")
+ return;
+ // System.out.printf("profileview update from %s\n", arg);
+ 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();
+ if (name == "MethodData") {
+ MethodData md = (MethodData) selection.getValue();
+ highlightMethod(md, true);
+ return;
+ }
+ if (name == "Call") {
+ Call call = (Call) selection.getValue();
+ MethodData md = call.mMethodData;
+ highlightMethod(md, true);
+ return;
+ }
+ }
+ }
+
+ private void highlightMethod(MethodData md, boolean clearSearch) {
+ if (md == null)
+ return;
+ // Avoid an infinite recursion
+ if (md == mCurrentHighlightedMethod)
+ return;
+ if (clearSearch) {
+ mSearchBox.setText("");
+ mSearchBox.setBackground(mColorMatch);
+ }
+ mCurrentHighlightedMethod = md;
+ mTreeViewer.collapseAll();
+ // Expand this node and its children
+ expandNode(md);
+ StructuredSelection sel = new StructuredSelection(md);
+ mTreeViewer.setSelection(sel, true);
+ Tree tree = mTreeViewer.getTree();
+ TreeItem[] items = tree.getSelection();
+ tree.setTopItem(items[0]);
+ // workaround a Mac bug by adding showItem().
+ tree.showItem(items[0]);
+ }
+
+ private void expandNode(MethodData md) {
+ ProfileNode[] nodes = md.getProfileNodes();
+ mTreeViewer.setExpandedState(md, true);
+ // Also expand the "Parents" and "Children" nodes.
+ for (ProfileNode node : nodes) {
+ if (node.isRecursive() == false)
+ mTreeViewer.setExpandedState(node, true);
+ }
+ }
+}
diff --git a/traceview/src/com/android/traceview/QtraceReader.java b/traceview/src/com/android/traceview/QtraceReader.java
new file mode 100644
index 0000000..c4db4a2
--- /dev/null
+++ b/traceview/src/com/android/traceview/QtraceReader.java
@@ -0,0 +1,45 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.HashMap;
+
+public class QtraceReader extends TraceReader {
+ QtraceReader(String traceName) {
+ }
+
+ @Override
+ public MethodData[] getMethods() {
+ return null;
+ }
+
+ @Override
+ public HashMap<Integer, String> getThreadLabels() {
+ return null;
+ }
+
+ @Override
+ public ArrayList<TimeLineView.Record> getThreadTimeRecords() {
+ return null;
+ }
+
+ @Override
+ public ProfileProvider getProfileProvider() {
+ return null;
+ }
+}
diff --git a/traceview/src/com/android/traceview/Selection.java b/traceview/src/com/android/traceview/Selection.java
new file mode 100644
index 0000000..3764619
--- /dev/null
+++ b/traceview/src/com/android/traceview/Selection.java
@@ -0,0 +1,70 @@
+/*
+ * 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;
+
+public class Selection {
+
+ private Action mAction;
+ private String mName;
+ private Object mValue;
+
+ public Selection(Action action, String name, Object value) {
+ mAction = action;
+ mName = name;
+ mValue = value;
+ }
+
+ public static Selection highlight(String name, Object value) {
+ return new Selection(Action.Highlight, name, value);
+ }
+
+ public static Selection include(String name, Object value) {
+ return new Selection(Action.Include, name, value);
+ }
+
+ public static Selection exclude(String name, Object value) {
+ return new Selection(Action.Exclude, name, value);
+ }
+
+ public void setName(String name) {
+ mName = name;
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public void setValue(Object value) {
+ mValue = value;
+ }
+
+ public Object getValue() {
+ return mValue;
+ }
+
+ public void setAction(Action action) {
+ mAction = action;
+ }
+
+ public Action getAction() {
+ return mAction;
+ }
+
+ public static enum Action {
+ Highlight, Include, Exclude, Aggregate
+ };
+}
diff --git a/traceview/src/com/android/traceview/SelectionController.java b/traceview/src/com/android/traceview/SelectionController.java
new file mode 100644
index 0000000..4c930ea
--- /dev/null
+++ b/traceview/src/com/android/traceview/SelectionController.java
@@ -0,0 +1,35 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.Observable;
+
+public class SelectionController extends Observable {
+
+ private ArrayList<Selection> mSelections;
+
+ public void change(ArrayList<Selection> selections, Object arg) {
+ this.mSelections = selections;
+ setChanged();
+ notifyObservers(arg);
+ }
+
+ public ArrayList<Selection> getSelections() {
+ return mSelections;
+ }
+}
diff --git a/traceview/src/com/android/traceview/ThreadData.java b/traceview/src/com/android/traceview/ThreadData.java
new file mode 100644
index 0000000..54ea891
--- /dev/null
+++ b/traceview/src/com/android/traceview/ThreadData.java
@@ -0,0 +1,228 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.HashMap;
+
+class ThreadData implements TimeLineView.Row {
+
+ private int mId;
+ private String mName;
+ private long mGlobalStartTime = -1;
+ private long mGlobalEndTime = -1;
+ private long mLastEventTime;
+ private long mCpuTime;
+ private Call mRoot;
+ private Call mCurrent;
+ private Call mLastContextSwitch;
+ private ArrayList<Call> mStack = new ArrayList<Call>();
+
+ // This is a hash of all the methods that are currently on the stack.
+ private HashMap<MethodData, Integer> mStackMethods = new HashMap<MethodData, Integer>();
+
+ // True if no calls have ever been added to this thread
+ private boolean mIsEmpty;
+
+ ThreadData(int id, String name, MethodData topLevel) {
+ mId = id;
+ mName = String.format("[%d] %s", id, name);
+ mRoot = new Call(mName, topLevel);
+ mCurrent = mRoot;
+ mIsEmpty = true;
+ }
+
+ public boolean isEmpty() {
+ return mIsEmpty;
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public Call getCalltreeRoot() {
+ return mRoot;
+ }
+
+ void handleCall(Call call, long globalTime) {
+ mIsEmpty = false;
+ long currentTime = call.mThreadStartTime;
+ if (currentTime < mLastEventTime) {
+ System.err
+ .printf(
+ "ThreadData: '%1$s' call time (%2$d) is less than previous time (%3$d) for thread '%4$s'\n",
+ call.getName(), currentTime, mLastEventTime, mName);
+ System.exit(1);
+ }
+ long elapsed = currentTime - mLastEventTime;
+ mCpuTime += elapsed;
+ if (call.getMethodAction() == 0) {
+ // This is a method entry.
+ enter(call, elapsed);
+ } else {
+ // This is a method exit.
+ exit(call, elapsed, globalTime);
+ }
+ mLastEventTime = currentTime;
+ mGlobalEndTime = globalTime;
+ }
+
+ private void enter(Call c, long elapsed) {
+ Call caller = mCurrent;
+ push(c);
+
+ // Check the stack for a matching method to determine if this call
+ // is recursive.
+ MethodData md = c.mMethodData;
+ Integer num = mStackMethods.get(md);
+ if (num == null) {
+ num = 0;
+ } else if (num > 0) {
+ c.setRecursive(true);
+ }
+ num += 1;
+ mStackMethods.put(md, num);
+ mCurrent = c;
+
+ // Add the elapsed time to the caller's exclusive time
+ caller.addExclusiveTime(elapsed);
+ }
+
+ private void exit(Call c, long elapsed, long globalTime) {
+ mCurrent.mGlobalEndTime = globalTime;
+ Call top = pop();
+ if (top == null) {
+ return;
+ }
+
+ if (mCurrent.mMethodData != c.mMethodData) {
+ String error = "Method exit (" + c.getName()
+ + ") does not match current method (" + mCurrent.getName()
+ + ")";
+ throw new RuntimeException(error);
+ } else {
+ long duration = c.mThreadStartTime - mCurrent.mThreadStartTime;
+ Call caller = top();
+ mCurrent.addExclusiveTime(elapsed);
+ mCurrent.addInclusiveTime(duration, caller);
+ if (caller == null) {
+ caller = mRoot;
+ }
+ mCurrent = caller;
+ }
+ }
+
+ public void push(Call c) {
+ mStack.add(c);
+ }
+
+ public Call pop() {
+ ArrayList<Call> stack = mStack;
+ if (stack.size() == 0)
+ return null;
+ Call top = stack.get(stack.size() - 1);
+ stack.remove(stack.size() - 1);
+
+ // Decrement the count on the method in the hash table and remove
+ // the entry when it goes to zero.
+ MethodData md = top.mMethodData;
+ Integer num = mStackMethods.get(md);
+ if (num != null) {
+ num -= 1;
+ if (num <= 0) {
+ mStackMethods.remove(md);
+ } else {
+ mStackMethods.put(md, num);
+ }
+ }
+ return top;
+ }
+
+ public Call top() {
+ ArrayList<Call> stack = mStack;
+ if (stack.size() == 0)
+ return null;
+ return stack.get(stack.size() - 1);
+ }
+
+ public long endTrace() {
+ // If we have calls on the stack when the trace ends, then clean up
+ // the stack and compute the inclusive time of the methods by pretending
+ // that we are exiting from their methods now.
+ while (mCurrent != mRoot) {
+ long duration = mLastEventTime - mCurrent.mThreadStartTime;
+ pop();
+ Call caller = top();
+ mCurrent.addInclusiveTime(duration, caller);
+ mCurrent.mGlobalEndTime = mGlobalEndTime;
+ if (caller == null) {
+ caller = mRoot;
+ }
+ mCurrent = caller;
+ }
+ return mLastEventTime;
+ }
+
+ @Override
+ public String toString() {
+ return mName;
+ }
+
+ public int getId() {
+ return mId;
+ }
+
+ public void setCpuTime(long cpuTime) {
+ mCpuTime = cpuTime;
+ }
+
+ public long getCpuTime() {
+ return mCpuTime;
+ }
+
+ public void setGlobalStartTime(long globalStartTime) {
+ mGlobalStartTime = globalStartTime;
+ }
+
+ public long getGlobalStartTime() {
+ return mGlobalStartTime;
+ }
+
+ public void setLastEventTime(long lastEventTime) {
+ mLastEventTime = lastEventTime;
+ }
+
+ public long getLastEventTime() {
+ return mLastEventTime;
+ }
+
+ public void setGlobalEndTime(long globalEndTime) {
+ mGlobalEndTime = globalEndTime;
+ }
+
+ public long getGlobalEndTime() {
+ return mGlobalEndTime;
+ }
+
+ public void setLastContextSwitch(Call lastContextSwitch) {
+ mLastContextSwitch = lastContextSwitch;
+ }
+
+ public Call getLastContextSwitch() {
+ return mLastContextSwitch;
+ }
+}
diff --git a/traceview/src/com/android/traceview/TickScaler.java b/traceview/src/com/android/traceview/TickScaler.java
new file mode 100644
index 0000000..79fa160
--- /dev/null
+++ b/traceview/src/com/android/traceview/TickScaler.java
@@ -0,0 +1,148 @@
+/*
+ * 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;
+
+class TickScaler {
+
+ private double mMinVal; // required input
+ private double mMaxVal; // required input
+ private double mRangeVal;
+ private int mNumPixels; // required input
+ private int mPixelsPerTick; // required input
+ private double mPixelsPerRange;
+ private double mTickIncrement;
+ private double mMinMajorTick;
+
+ TickScaler(double minVal, double maxVal, int numPixels, int pixelsPerTick) {
+ mMinVal = minVal;
+ mMaxVal = maxVal;
+ mNumPixels = numPixels;
+ mPixelsPerTick = pixelsPerTick;
+ }
+
+ public void setMinVal(double minVal) {
+ mMinVal = minVal;
+ }
+
+ public double getMinVal() {
+ return mMinVal;
+ }
+
+ public void setMaxVal(double maxVal) {
+ mMaxVal = maxVal;
+ }
+
+ public double getMaxVal() {
+ return mMaxVal;
+ }
+
+ public void setNumPixels(int numPixels) {
+ mNumPixels = numPixels;
+ }
+
+ public int getNumPixels() {
+ return mNumPixels;
+ }
+
+ public void setPixelsPerTick(int pixelsPerTick) {
+ mPixelsPerTick = pixelsPerTick;
+ }
+
+ public int getPixelsPerTick() {
+ return mPixelsPerTick;
+ }
+
+ public void setPixelsPerRange(double pixelsPerRange) {
+ mPixelsPerRange = pixelsPerRange;
+ }
+
+ public double getPixelsPerRange() {
+ return mPixelsPerRange;
+ }
+
+ public void setTickIncrement(double tickIncrement) {
+ mTickIncrement = tickIncrement;
+ }
+
+ public double getTickIncrement() {
+ return mTickIncrement;
+ }
+
+ public void setMinMajorTick(double minMajorTick) {
+ mMinMajorTick = minMajorTick;
+ }
+
+ public double getMinMajorTick() {
+ return mMinMajorTick;
+ }
+
+ // Convert a time value to a 0-based pixel value
+ public int valueToPixel(double value) {
+ return (int) Math.ceil(mPixelsPerRange * (value - mMinVal) - 0.5);
+ }
+
+ // Convert a time value to a 0-based fractional pixel
+ public double valueToPixelFraction(double value) {
+ return mPixelsPerRange * (value - mMinVal);
+ }
+
+ // Convert a 0-based pixel value to a time value
+ public double pixelToValue(int pixel) {
+ return mMinVal + (pixel / mPixelsPerRange);
+ }
+
+ public void computeTicks(boolean useGivenEndPoints) {
+ int numTicks = mNumPixels / mPixelsPerTick;
+ mRangeVal = mMaxVal - mMinVal;
+ mTickIncrement = mRangeVal / numTicks;
+ double dlogTickIncrement = Math.log10(mTickIncrement);
+ int logTickIncrement = (int) Math.floor(dlogTickIncrement);
+ double scale = Math.pow(10, logTickIncrement);
+ double scaledTickIncr = mTickIncrement / scale;
+ if (scaledTickIncr > 5.0)
+ scaledTickIncr = 10;
+ else if (scaledTickIncr > 2)
+ scaledTickIncr = 5;
+ else if (scaledTickIncr > 1)
+ scaledTickIncr = 2;
+ else
+ scaledTickIncr = 1;
+ mTickIncrement = scaledTickIncr * scale;
+
+ if (!useGivenEndPoints) {
+ // Round up the max val to the next minor tick
+ double minorTickIncrement = mTickIncrement / 5;
+ double dval = mMaxVal / minorTickIncrement;
+ int ival = (int) dval;
+ if (ival != dval)
+ mMaxVal = (ival + 1) * minorTickIncrement;
+
+ // Round down the min val to a multiple of tickIncrement
+ ival = (int) (mMinVal / mTickIncrement);
+ mMinVal = ival * mTickIncrement;
+ mMinMajorTick = mMinVal;
+ } else {
+ int ival = (int) (mMinVal / mTickIncrement);
+ mMinMajorTick = ival * mTickIncrement;
+ if (mMinMajorTick < mMinVal)
+ mMinMajorTick = mMinMajorTick + mTickIncrement;
+ }
+
+ mRangeVal = mMaxVal - mMinVal;
+ mPixelsPerRange = (double) mNumPixels / mRangeVal;
+ }
+}
diff --git a/traceview/src/com/android/traceview/TimeLineView.java b/traceview/src/com/android/traceview/TimeLineView.java
new file mode 100644
index 0000000..67dc97b
--- /dev/null
+++ b/traceview/src/com/android/traceview/TimeLineView.java
@@ -0,0 +1,1961 @@
+/*
+ * 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.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 double mTotalElapsed;
+ private RowData[] mRows;
+ private Segment[] mSegments;
+ private ArrayList<Segment> mSegmentList = new ArrayList<Segment>();
+ 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 = 38;
+ private static final int tickToFontSpacing = 2;
+
+ /** start of first row */
+ private static final int topMargin = 70;
+ private int mMouseRow = -1;
+ private int mNumRows;
+ private int mStartRow;
+ private int mEndRow;
+ private TraceUnits mUnits;
+ private int mSmallFontWidth;
+ private int mSmallFontHeight;
+ private int mMediumFontWidth;
+ 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 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();
+ 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();
+
+ if (mSetFonts) {
+ gc.setFont(mFontRegistry.get("medium")); // $NON-NLS-1$
+ }
+ mMediumFontWidth = gc.getFontMetrics().getAverageCharWidth();
+
+ 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() {
+ 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();
+ }
+ });
+
+ mSurface.addListener(SWT.Resize, new Listener() {
+ 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() {
+ public void mouseMove(MouseEvent me) {
+ mSurface.mouseMove(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() {
+ public void mouseMove(MouseEvent me) {
+ mTimescale.mouseMove(me);
+ }
+ });
+
+ mLabels.addMouseMoveListener(new MouseMoveListener() {
+ public void mouseMove(MouseEvent me) {
+ mLabels.mouseMove(me);
+ }
+ });
+
+ setData(reader.getThreadTimeRecords());
+ }
+
+ 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>() {
+ 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;
+ }
+ });
+
+ // 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;
+ 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;
+ mTotalElapsed += 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);
+ mSegmentList.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);
+ 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);
+ }
+
+ 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>() {
+ 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 = mSegmentList.toArray(new Segment[mSegmentList.size()]);
+ Arrays.sort(mSegments, new Comparator<Segment>() {
+ 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 void popFrames(RowData rd, Block top, long startTime) {
+ long topEndTime = top.getEndTime();
+ long lastEndTime = top.getStartTime();
+ while (topEndTime <= startTime) {
+ if (topEndTime > lastEndTime) {
+ Segment segment = new Segment(rd, top, lastEndTime, topEndTime);
+ mSegmentList.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);
+ mSegmentList.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() {
+ 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() {
+ 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 int mMethodStartY;
+ 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;
+ addPaintListener(new PaintListener() {
+ 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;
+ }
+
+ 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 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);
+ 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 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
+ };
+
+ private class Surface extends Canvas {
+
+ public Surface(Composite parent) {
+ super(parent, SWT.NO_BACKGROUND | SWT.V_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() {
+ public void paintControl(PaintEvent pe) {
+ draw(pe.display, pe.gc);
+ }
+ });
+
+ mZoomAnimator = new Runnable() {
+ public void run() {
+ animateZoom();
+ }
+ };
+
+ mHighlightAnimator = new Runnable() {
+ 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 * (double) ii / (double) 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);
+ }
+
+ 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);
+ 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();
+ }
+
+ 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;
+
+ 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) {
+ blockName = strip.mSegment.mBlock.getName();
+ blockColor = strip.mColor;
+ }
+ 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);
+ 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());
+ }
+ } else {
+ mTimescale.setMethodName(null);
+ mTimescale.setMethodColor(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 = highlightHeight;
+ 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 prevCallStart = -1;
+ if (mHighlightCall != null) {
+ int callPixelStart = -1;
+ int callPixelEnd = -1;
+ callStart = mHighlightCall.mGlobalStartTime;
+ callEnd = mHighlightCall.mGlobalEndTime;
+ callMethod = mHighlightCall.mMethodData;
+ 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;
+ 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;
+
+ 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) {
+ prevMethodStart = pixelStart;
+ int rangeWidth = width;
+ if (rangeWidth == 0)
+ rangeWidth = 1;
+ mHighlightExclusive.add(new Range(pixelStart
+ + LeftMargin, rangeWidth, y1, color));
+ Call call = (Call) block;
+ callStart = call.mGlobalStartTime;
+ int callPixelStart = -1;
+ if (callStart >= minVal)
+ callPixelStart = mScaleInfo.valueToPixel(callStart);
+ if (prevCallStart != callPixelStart) {
+ prevCallStart = callPixelStart;
+ int callPixelEnd = -1;
+ callEnd = call.mGlobalEndTime;
+ if (callEnd <= maxVal)
+ callPixelEnd = mScaleInfo.valueToPixel(callEnd);
+ 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) {
+ prevMethodStart = pixelStart;
+ 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,
+ 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, y1, width, rowHeight, rd,
+ segment, color);
+ mStripList.add(strip);
+ }
+ } else {
+ double weight = computeWeight(recordStart, recordEnd,
+ 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,
+ 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, y1, width, 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, int pixel) {
+ 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();
+ }
+
+ // 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;
+ highlightHeight = highlightHeights[mHighlightStep];
+ getDisplay().timerExec(HIGHLIGHT_TIMER_INTERVAL, mHighlightAnimator);
+ }
+ redraw();
+ }
+
+ private void clearHighlights() {
+ // System.out.printf("clearHighlights()\n");
+ mShowHighlightName = false;
+ highlightHeight = 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 highlightHeight = 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;
+ mBlock = block;
+ mStartTime = startTime;
+ mEndTime = endTime;
+ }
+
+ private RowData mRowData;
+ private Block mBlock;
+ private long mStartTime;
+ private long mEndTime;
+ }
+
+ 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;
+ }
+}
diff --git a/traceview/src/com/android/traceview/TraceReader.java b/traceview/src/com/android/traceview/TraceReader.java
new file mode 100644
index 0000000..ae75876
--- /dev/null
+++ b/traceview/src/com/android/traceview/TraceReader.java
@@ -0,0 +1,55 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.HashMap;
+
+public abstract class TraceReader {
+
+ private TraceUnits mTraceUnits;
+
+ public TraceUnits getTraceUnits() {
+ if (mTraceUnits == null)
+ mTraceUnits = new TraceUnits();
+ return mTraceUnits;
+ }
+
+ public ArrayList<TimeLineView.Record> getThreadTimeRecords() {
+ return null;
+ }
+
+ public HashMap<Integer, String> getThreadLabels() {
+ return null;
+ }
+
+ public MethodData[] getMethods() {
+ return null;
+ }
+
+ public ThreadData[] getThreads() {
+ return null;
+ }
+
+ public long getEndTime() {
+ return 0;
+ }
+
+ public ProfileProvider getProfileProvider() {
+ return null;
+ }
+}
diff --git a/traceview/src/com/android/traceview/TraceUnits.java b/traceview/src/com/android/traceview/TraceUnits.java
new file mode 100644
index 0000000..20938f5
--- /dev/null
+++ b/traceview/src/com/android/traceview/TraceUnits.java
@@ -0,0 +1,93 @@
+/*
+ * 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 java.text.DecimalFormat;
+
+// This should be a singleton.
+public class TraceUnits {
+
+ private TimeScale mTimeScale = TimeScale.MicroSeconds;
+ private double mScale = 1.0;
+ DecimalFormat mFormatter = new DecimalFormat();
+
+ public double getScaledValue(long value) {
+ return value * mScale;
+ }
+
+ public double getScaledValue(double value) {
+ return value * mScale;
+ }
+
+ public String valueOf(long value) {
+ return valueOf((double) value);
+ }
+
+ public String valueOf(double value) {
+ String pattern;
+ double scaled = value * mScale;
+ if ((int) scaled == scaled)
+ pattern = "###,###";
+ else
+ pattern = "###,###.###";
+ mFormatter.applyPattern(pattern);
+ return mFormatter.format(scaled);
+ }
+
+ public String labelledString(double value) {
+ String units = label();
+ String num = valueOf(value);
+ return String.format("%s: %s", units, num);
+ }
+
+ public String labelledString(long value) {
+ return labelledString((double) value);
+ }
+
+ public String label() {
+ if (mScale == 1.0)
+ return "usec";
+ if (mScale == 0.001)
+ return "msec";
+ if (mScale == 0.000001)
+ return "sec";
+ return null;
+ }
+
+ public void setTimeScale(TimeScale val) {
+ mTimeScale = val;
+ switch (val) {
+ case Seconds:
+ mScale = 0.000001;
+ break;
+ case MilliSeconds:
+ mScale = 0.001;
+ break;
+ case MicroSeconds:
+ mScale = 1.0;
+ break;
+ }
+ }
+
+ public TimeScale getTimeScale() {
+ return mTimeScale;
+ }
+
+ public enum TimeScale {
+ Seconds, MilliSeconds, MicroSeconds
+ };
+}
diff --git a/traceview/src/resources/icons/sort_down.png b/traceview/src/resources/icons/sort_down.png
new file mode 100644
index 0000000..de6a2a0
--- /dev/null
+++ b/traceview/src/resources/icons/sort_down.png
Binary files differ
diff --git a/traceview/src/resources/icons/sort_up.png b/traceview/src/resources/icons/sort_up.png
new file mode 100644
index 0000000..8578a87
--- /dev/null
+++ b/traceview/src/resources/icons/sort_up.png
Binary files differ