diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:29:09 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:29:09 -0800 |
commit | 55a2c71f27d3e0b8344597c7f281e687cb7aeb1b (patch) | |
tree | ecd18b995aea8eeeb8b3823266280d41245bf0f7 /traceview/src | |
parent | 82ea7a177797b844b252effea5c7c7c5d63ea4ac (diff) | |
download | sdk-55a2c71f27d3e0b8344597c7f281e687cb7aeb1b.zip sdk-55a2c71f27d3e0b8344597c7f281e687cb7aeb1b.tar.gz sdk-55a2c71f27d3e0b8344597c7f281e687cb7aeb1b.tar.bz2 |
auto import from //depot/cupcake/@135843
Diffstat (limited to 'traceview/src')
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 Binary files differnew file mode 100644 index 0000000..de6a2a0 --- /dev/null +++ b/traceview/src/resources/icons/sort_down.png diff --git a/traceview/src/resources/icons/sort_up.png b/traceview/src/resources/icons/sort_up.png Binary files differnew file mode 100644 index 0000000..8578a87 --- /dev/null +++ b/traceview/src/resources/icons/sort_up.png |