-
Notifications
You must be signed in to change notification settings - Fork 0
/
VmTraceData.java
312 lines (307 loc) · 13.2 KB
/
VmTraceData.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
package com.mmt.travel.app.NFR.Latency;
import android.support.annotation.NonNull;
import android.util.SparseArray;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* Created by mmt6054 on 09/10/18.
*/
public class VmTraceData {
private final int mVersion;
private final boolean mDataFileOverflow;
private final VmClockType mVmClockType;
private final String mVm;
private final Map<String, String> mTraceProperties;
private final long mStartTimeUs;
private final long mElapsedTimeUs;
/** Map from method id to method info. */
private final Map<Long, MethodInfo> mMethods;
/** Map from thread name to thread info. */
private final Map<String, ThreadInfo> mThreadInfo;
private VmTraceData(Builder b) {
mVersion = b.mVersion;
mDataFileOverflow = b.mDataFileOverflow;
mVmClockType = b.mVmClockType;
mVm = b.mVm;
mTraceProperties = b.mProperties;
mMethods = b.mMethods;
mStartTimeUs = b.mStartTimeUs;
mElapsedTimeUs = b.mElapsedTimeUs;
mThreadInfo = Maps.newHashMapWithExpectedSize(b.mThreads.size());
for (int i = 0; i < b.mThreads.size(); i++) {
int id = b.mThreads.keyAt(i);
String name = b.mThreads.valueAt(i);
ThreadInfo info = mThreadInfo.get(name);
if (info != null) {
// there is alread a thread with the same name
name = String.format("%1$s-%2$d", name, id);
}
info = new ThreadInfo(id, name, b.mTopLevelCalls.get(id));
mThreadInfo.put(name, info);
}
}
public int getVersion() {
return mVersion;
}
public boolean isDataFileOverflow() {
return mDataFileOverflow;
}
public VmClockType getVmClockType() {
return mVmClockType;
}
public String getVm() {
return mVm;
}
public Map<String, String> getTraceProperties() {
return mTraceProperties;
}
public static TimeUnit getDefaultTimeUnits() {
// The traces from the VM currently use microseconds.
// TODO: figure out if this can be obtained/inferred from the trace itself
return TimeUnit.MICROSECONDS;
}
public Collection<ThreadInfo> getThreads() {
return mThreadInfo.values();
}
public List<ThreadInfo> getThreads(boolean excludeThreadsWithNoActivity) {
Collection<ThreadInfo> allThreads = getThreads();
if (!excludeThreadsWithNoActivity) {
return ImmutableList.copyOf(allThreads);
}
return Lists.newArrayList(
Iterables.filter(allThreads, input -> input.getTopLevelCall() != null));
}
public ThreadInfo getThread(String name) {
return mThreadInfo.get(name);
}
public Map<Long,MethodInfo> getMethods() {
return mMethods;
}
public MethodInfo getMethod(long methodId) {
return mMethods.get(methodId);
}
public long getStartTimeUs() {
return mStartTimeUs;
}
public long getElapsedTimeUs() {
return mElapsedTimeUs;
}
/** Returns the duration of this call as a percentage of the duration of the top level call. */
public double getDurationPercentage(Call call, ThreadInfo thread, ClockType clockType,
boolean inclusiveTime) {
MethodInfo methodInfo = getMethod(call.getMethodId());
TimeSelector selector = TimeSelector.create(clockType, inclusiveTime);
long methodTime = selector.get(methodInfo, thread, TimeUnit.NANOSECONDS);
return getDurationPercentage(methodTime, thread, clockType);
}
/**
* Returns the given duration as a percentage of the duration of the top level call
* in given thread.
*/
public double getDurationPercentage(long methodTime, ThreadInfo thread, ClockType clockType) {
Call topCall = getThread(thread.getName()).getTopLevelCall();
if (topCall == null) {
return 100.;
}
MethodInfo topInfo = getMethod(topCall.getMethodId());
// always use inclusive time to obtain the top level's time when computing percentages
TimeSelector selector = TimeSelector.create(clockType, true);
long topLevelTime = selector.get(topInfo, thread, TimeUnit.NANOSECONDS);
return (double) methodTime/topLevelTime * 100;
}
public SearchResult searchFor(String pattern, ThreadInfo thread) {
pattern = pattern.toLowerCase(Locale.US);
Set<MethodInfo> methods = new HashSet<MethodInfo>();
Set<Call> calls = new HashSet<Call>();
Call topLevelCall = getThread(thread.getName()).getTopLevelCall();
if (topLevelCall == null) {
// no matches
return new SearchResult(methods, calls);
}
// Find all methods matching given pattern called on given thread
for (MethodInfo method: getMethods().values()) {
String fullName = method.getFullName().toLowerCase(Locale.US);
if (fullName.contains(pattern)) { // method name matches
long inclusiveTime = method.getProfileData()
.getInclusiveTime(thread, ClockType.GLOBAL, TimeUnit.NANOSECONDS);
if (inclusiveTime > 0) {
// method was called in this thread
methods.add(method);
}
}
}
// Find all invocations of the matched methods
Iterator<Call> iterator = topLevelCall.getCallHierarchyIterator();
while (iterator.hasNext()) {
Call c = iterator.next();
MethodInfo method = getMethod(c.getMethodId());
if (methods.contains(method)) {
calls.add(c);
}
}
return new SearchResult(methods, calls);
}
public static class Builder implements VmTraceHandler {
private static final String KEY_CLOCK = "clock";
private static final String KEY_DATA_OVERFLOW = "data-file-overflow";
private static final String KEY_VM = "vm";
private static final String KEY_ELAPSED_TIME_US = "elapsed-time-usec";
private static final boolean DEBUG = false;
private int mVersion;
private long mStartTimeUs;
private long mElapsedTimeUs;
private boolean mDataFileOverflow;
private VmClockType mVmClockType = VmClockType.THREAD_CPU;
private String mVm = "";
private final Map<String, String> mProperties = new HashMap<String, String>(10);
/** Map from thread ids to thread names. */
public final SparseArray<String> mThreads = new SparseArray<String>(10);
/** Map from method id to method info. */
public final Map<Long,MethodInfo> mMethods = new HashMap<Long, MethodInfo>(100);
/** Map from thread id to per thread stack call reconstructor. */
public final SparseArray<CallStackReconstructor> mStackReconstructors
= new SparseArray<CallStackReconstructor>(10);
/** Map from thread id to the top level call for that thread. */
private final SparseArray<Call> mTopLevelCalls = new SparseArray<Call>(10);
@Override
public void setVersion(int version) {
mVersion = version;
}
@Override
public void setProperty(String key, String value) {
if (key.equals(KEY_CLOCK)) {
if (value.equals("thread-cpu")) {
mVmClockType = VmClockType.THREAD_CPU;
} else if (value.equals("wall")) {
mVmClockType = VmClockType.WALL;
} else if (value.equals("dual")) {
mVmClockType = VmClockType.DUAL;
}
} else if (key.equals(KEY_DATA_OVERFLOW)) {
mDataFileOverflow = Boolean.parseBoolean(value);
} else if (key.equals(KEY_VM)) {
mVm = value;
} else if (key.equals(KEY_ELAPSED_TIME_US)) {
mElapsedTimeUs = Long.parseLong(value);
} else {
mProperties.put(key, value);
}
}
@Override
public void addThread(int id, String name) {
mThreads.put(id, name);
}
@Override
public void addMethod(long id, MethodInfo info) {
mMethods.put(id, info);
}
@Override
public void addMethodAction(
int threadId,
long methodId,
TraceAction methodAction,
int threadTime,
int globalTime) {
// create thread info if it doesn't exist
if (mThreads.get(threadId) == null) {
mThreads.put(threadId, String.format("Thread id: %1$d", threadId));
}
// create method info if it doesn't exist
if (mMethods.get(methodId) == null) {
MethodInfo info = new MethodInfo(methodId, "unknown", "unknown", "unknown",
"unknown", -1);
mMethods.put(methodId, info);
}
if (DEBUG) {
MethodInfo methodInfo = mMethods.get(methodId);
System.out.printf("Thread %1$30s: (%2$8x) %3$-40s %4$20s\n",
mThreads.get(threadId), methodId, methodInfo.getShortName(), methodAction);
}
CallStackReconstructor reconstructor = mStackReconstructors.get(threadId);
if (reconstructor == null) {
long topLevelCallId = createUniqueMethodIdForThread(threadId);
reconstructor = new CallStackReconstructor(topLevelCallId);
mStackReconstructors.put(threadId, reconstructor);
}
reconstructor.addTraceAction(methodId, methodAction, threadTime, globalTime);
}
private long createUniqueMethodIdForThread(int threadId) {
long id = Long.MAX_VALUE - threadId;
assert mMethods.get(id) == null :
"Unexpected error while attempting to create a unique key - key already exists";
MethodInfo info = new MethodInfo(id, mThreads.get(threadId), "", "", "", 0);
mMethods.put(id, info);
return id;
}
public VmTraceData build() {
for (int i = 0; i < mStackReconstructors.size(); i++) {
int threadId = mStackReconstructors.keyAt(i);
CallStackReconstructor reconstructor = mStackReconstructors.valueAt(i);
mTopLevelCalls.put(threadId, reconstructor.getTopLevel());
}
VmTraceData data = new VmTraceData(this);
computeTimingStatistics(data);
return data;
}
@Override
public void setStartTimeUs(long startTimeUs) {
mStartTimeUs = startTimeUs;
}
private void computeTimingStatistics(VmTraceData data) {
ProfileDataBuilder builder = new ProfileDataBuilder();
for (ThreadInfo thread : data.getThreads()) {
Call c = thread.getTopLevelCall();
if (c == null) {
continue;
}
builder.computeCallStats(c, null, thread);
}
for (Long methodId : builder.getMethodsWithProfileData()) {
MethodInfo method = data.getMethod(methodId);
method.setProfileData(builder.getProfileData(methodId));
}
}
}
private static class ProfileDataBuilder {
/** Maps method ids to their corresponding method data builders */
private final Map<Long, MethodProfileData.Builder> mBuilderMap = Maps.newHashMap();
public void computeCallStats(Call c, Call parent, ThreadInfo thread) {
long methodId = c.getMethodId();
MethodProfileData.Builder builder = getProfileDataBuilder(methodId);
builder.addCallTime(c, parent, thread);
builder.incrementInvocationCount(c, parent, thread);
if (c.isRecursive()) {
builder.setRecursive();
}
for (Call callee : c.getCallees()) {
computeCallStats(callee, c, thread);
}
}
@NonNull
private MethodProfileData.Builder getProfileDataBuilder(long methodId) {
MethodProfileData.Builder builder = mBuilderMap.get(methodId);
if (builder == null) {
builder = new MethodProfileData.Builder();
mBuilderMap.put(methodId, builder);
}
return builder;
}
public Set<Long> getMethodsWithProfileData() {
return mBuilderMap.keySet();
}
public MethodProfileData getProfileData(Long methodId) {
return mBuilderMap.get(methodId).build();
}
}
}