auto import from //depot/cupcake/@135843
This commit is contained in:
19
tools/traceview/src/Android.mk
Normal file
19
tools/traceview/src/Android.mk
Normal file
@@ -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)
|
||||
141
tools/traceview/src/com/android/traceview/Call.java
Normal file
141
tools/traceview/src/com/android/traceview/Call.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
113
tools/traceview/src/com/android/traceview/ColorController.java
Normal file
113
tools/traceview/src/com/android/traceview/ColorController.java
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
602
tools/traceview/src/com/android/traceview/DmTraceReader.java
Normal file
602
tools/traceview/src/com/android/traceview/DmTraceReader.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
192
tools/traceview/src/com/android/traceview/MainWindow.java
Normal file
192
tools/traceview/src/com/android/traceview/MainWindow.java
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
458
tools/traceview/src/com/android/traceview/MethodData.java
Normal file
458
tools/traceview/src/com/android/traceview/MethodData.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
81
tools/traceview/src/com/android/traceview/ProfileData.java
Normal file
81
tools/traceview/src/com/android/traceview/ProfileData.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
51
tools/traceview/src/com/android/traceview/ProfileNode.java
Normal file
51
tools/traceview/src/com/android/traceview/ProfileNode.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
361
tools/traceview/src/com/android/traceview/ProfileProvider.java
Normal file
361
tools/traceview/src/com/android/traceview/ProfileProvider.java
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
34
tools/traceview/src/com/android/traceview/ProfileSelf.java
Normal file
34
tools/traceview/src/com/android/traceview/ProfileSelf.java
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
308
tools/traceview/src/com/android/traceview/ProfileView.java
Normal file
308
tools/traceview/src/com/android/traceview/ProfileView.java
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
45
tools/traceview/src/com/android/traceview/QtraceReader.java
Normal file
45
tools/traceview/src/com/android/traceview/QtraceReader.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
70
tools/traceview/src/com/android/traceview/Selection.java
Normal file
70
tools/traceview/src/com/android/traceview/Selection.java
Normal file
@@ -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
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
228
tools/traceview/src/com/android/traceview/ThreadData.java
Normal file
228
tools/traceview/src/com/android/traceview/ThreadData.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
148
tools/traceview/src/com/android/traceview/TickScaler.java
Normal file
148
tools/traceview/src/com/android/traceview/TickScaler.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
1961
tools/traceview/src/com/android/traceview/TimeLineView.java
Normal file
1961
tools/traceview/src/com/android/traceview/TimeLineView.java
Normal file
File diff suppressed because it is too large
Load Diff
55
tools/traceview/src/com/android/traceview/TraceReader.java
Normal file
55
tools/traceview/src/com/android/traceview/TraceReader.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
93
tools/traceview/src/com/android/traceview/TraceUnits.java
Normal file
93
tools/traceview/src/com/android/traceview/TraceUnits.java
Normal file
@@ -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
|
||||
};
|
||||
}
|
||||
BIN
tools/traceview/src/resources/icons/sort_down.png
Normal file
BIN
tools/traceview/src/resources/icons/sort_down.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 299 B |
BIN
tools/traceview/src/resources/icons/sort_up.png
Normal file
BIN
tools/traceview/src/resources/icons/sort_up.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 292 B |
Reference in New Issue
Block a user