blob: f87c081fd04fef59421283fdea482017703449af [file] [log] [blame]
Fyodor Kupolovca348512018-01-10 18:05:53 -08001/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
17package com.android.internal.os;
18
Olivier Gaillard103aae22018-06-07 18:20:10 +010019import android.annotation.Nullable;
Fyodor Kupolovca348512018-01-10 18:05:53 -080020import android.os.Binder;
21import android.os.SystemClock;
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -070022import android.os.UserHandle;
Fyodor Kupolov8aa51242018-04-23 12:44:51 -070023import android.text.format.DateFormat;
Fyodor Kupolovca348512018-01-10 18:05:53 -080024import android.util.ArrayMap;
Olivier Gaillardf82d2e732018-06-07 11:45:35 +010025import android.util.Log;
26import android.util.Pair;
Olivier Gaillard00bfb1b2018-07-10 11:25:09 +010027import android.util.Slog;
Fyodor Kupolovca348512018-01-10 18:05:53 -080028import android.util.SparseArray;
29
Fyodor Kupolov3f3af612018-04-18 17:26:43 -070030import com.android.internal.annotations.GuardedBy;
Fyodor Kupolovca348512018-01-10 18:05:53 -080031import com.android.internal.annotations.VisibleForTesting;
32import com.android.internal.util.Preconditions;
33
34import java.io.PrintWriter;
35import java.util.ArrayList;
Olivier Gaillardf82d2e732018-06-07 11:45:35 +010036import java.util.Collections;
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -070037import java.util.Comparator;
Fyodor Kupolovca348512018-01-10 18:05:53 -080038import java.util.List;
39import java.util.Map;
40import java.util.Queue;
Olivier Gaillardf82d2e732018-06-07 11:45:35 +010041import java.util.Set;
Fyodor Kupolovca348512018-01-10 18:05:53 -080042import java.util.concurrent.ConcurrentLinkedQueue;
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -070043import java.util.function.ToDoubleFunction;
Fyodor Kupolovca348512018-01-10 18:05:53 -080044
45/**
46 * Collects statistics about CPU time spent per binder call across multiple dimensions, e.g.
47 * per thread, uid or call description.
48 */
49public class BinderCallsStats {
Olivier Gaillard1d7f6152018-07-03 13:57:58 +010050 public static final boolean ENABLED_DEFAULT = true;
51 public static final boolean DETAILED_TRACKING_DEFAULT = true;
52 public static final int PERIODIC_SAMPLING_INTERVAL_DEFAULT = 10;
53
Olivier Gaillardf82d2e732018-06-07 11:45:35 +010054 private static final String TAG = "BinderCallsStats";
Fyodor Kupolovca348512018-01-10 18:05:53 -080055 private static final int CALL_SESSIONS_POOL_SIZE = 100;
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -070056 private static final int PERIODIC_SAMPLING_INTERVAL = 10;
Olivier Gaillardf82d2e732018-06-07 11:45:35 +010057 private static final int MAX_EXCEPTION_COUNT_SIZE = 50;
58 private static final String EXCEPTION_COUNT_OVERFLOW_NAME = "overflow";
Olivier Gaillard1d7f6152018-07-03 13:57:58 +010059 private static final CallSession NOT_ENABLED = new CallSession();
Fyodor Kupolovca348512018-01-10 18:05:53 -080060 private static final BinderCallsStats sInstance = new BinderCallsStats();
61
Olivier Gaillard1d7f6152018-07-03 13:57:58 +010062 private volatile boolean mEnabled = ENABLED_DEFAULT;
63 private volatile boolean mDetailedTracking = DETAILED_TRACKING_DEFAULT;
64 private volatile int mPeriodicSamplingInterval = PERIODIC_SAMPLING_INTERVAL_DEFAULT;
Fyodor Kupolov3f3af612018-04-18 17:26:43 -070065 @GuardedBy("mLock")
Fyodor Kupolovca348512018-01-10 18:05:53 -080066 private final SparseArray<UidEntry> mUidEntries = new SparseArray<>();
Olivier Gaillardf82d2e732018-06-07 11:45:35 +010067 @GuardedBy("mLock")
68 private final ArrayMap<String, Integer> mExceptionCounts = new ArrayMap<>();
Fyodor Kupolovca348512018-01-10 18:05:53 -080069 private final Queue<CallSession> mCallSessionsPool = new ConcurrentLinkedQueue<>();
Fyodor Kupolov3f3af612018-04-18 17:26:43 -070070 private final Object mLock = new Object();
Fyodor Kupolov8aa51242018-04-23 12:44:51 -070071 private long mStartTime = System.currentTimeMillis();
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -070072 @GuardedBy("mLock")
73 private UidEntry mSampledEntries = new UidEntry(-1);
Fyodor Kupolovca348512018-01-10 18:05:53 -080074
Olivier Gaillard1d7f6152018-07-03 13:57:58 +010075 @VisibleForTesting // Use getInstance() instead.
76 public BinderCallsStats() {
Fyodor Kupolovca348512018-01-10 18:05:53 -080077 }
78
79 public CallSession callStarted(Binder binder, int code) {
Olivier Gaillard103aae22018-06-07 18:20:10 +010080 return callStarted(binder.getClass().getName(), code, binder.getTransactionName(code));
Fyodor Kupolovca348512018-01-10 18:05:53 -080081 }
82
Olivier Gaillard103aae22018-06-07 18:20:10 +010083 private CallSession callStarted(String className, int code, @Nullable String methodName) {
Olivier Gaillard1d7f6152018-07-03 13:57:58 +010084 if (!mEnabled) {
85 return NOT_ENABLED;
86 }
87
Fyodor Kupolovca348512018-01-10 18:05:53 -080088 CallSession s = mCallSessionsPool.poll();
89 if (s == null) {
90 s = new CallSession();
91 }
Olivier Gaillard1d7f6152018-07-03 13:57:58 +010092
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -070093 s.callStat.className = className;
94 s.callStat.msg = code;
Olivier Gaillard103aae22018-06-07 18:20:10 +010095 s.callStat.methodName = methodName;
Olivier Gaillard9429bf52018-05-15 23:25:03 +010096 s.exceptionThrown = false;
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -070097 s.cpuTimeStarted = -1;
98 s.timeStarted = -1;
99
100 synchronized (mLock) {
101 if (mDetailedTracking) {
102 s.cpuTimeStarted = getThreadTimeMicro();
103 s.timeStarted = getElapsedRealtimeMicro();
104 } else {
105 s.sampledCallStat = mSampledEntries.getOrCreate(s.callStat);
Olivier Gaillard1d7f6152018-07-03 13:57:58 +0100106 if (s.sampledCallStat.callCount % mPeriodicSamplingInterval == 0) {
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700107 s.cpuTimeStarted = getThreadTimeMicro();
108 s.timeStarted = getElapsedRealtimeMicro();
109 }
110 }
111 }
Fyodor Kupolovca348512018-01-10 18:05:53 -0800112 return s;
113 }
114
Olivier Gaillard58b56e32018-06-01 16:18:43 +0100115 public void callEnded(CallSession s, int parcelRequestSize, int parcelReplySize) {
Fyodor Kupolovca348512018-01-10 18:05:53 -0800116 Preconditions.checkNotNull(s);
Olivier Gaillard1d7f6152018-07-03 13:57:58 +0100117 if (s == NOT_ENABLED) {
118 return;
119 }
120
121 processCallEnded(s, parcelRequestSize, parcelReplySize);
122
123 if (mCallSessionsPool.size() < CALL_SESSIONS_POOL_SIZE) {
124 mCallSessionsPool.add(s);
125 }
126 }
127
128 private void processCallEnded(CallSession s, int parcelRequestSize, int parcelReplySize) {
Fyodor Kupolov3f3af612018-04-18 17:26:43 -0700129 synchronized (mLock) {
Olivier Gaillard1d7f6152018-07-03 13:57:58 +0100130 if (!mEnabled) {
131 return;
132 }
133
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700134 long duration;
135 long latencyDuration;
Fyodor Kupolov3f3af612018-04-18 17:26:43 -0700136 if (mDetailedTracking) {
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700137 duration = getThreadTimeMicro() - s.cpuTimeStarted;
138 latencyDuration = getElapsedRealtimeMicro() - s.timeStarted;
139 } else {
140 CallStat cs = s.sampledCallStat;
141 // Non-negative time signals beginning of the new sampling interval
142 if (s.cpuTimeStarted >= 0) {
143 duration = getThreadTimeMicro() - s.cpuTimeStarted;
144 latencyDuration = getElapsedRealtimeMicro() - s.timeStarted;
145 } else {
146 // callCount is always incremented, but time only once per sampling interval
Olivier Gaillard1d7f6152018-07-03 13:57:58 +0100147 long samplesCount = cs.callCount / mPeriodicSamplingInterval + 1;
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700148 duration = cs.cpuTimeMicros / samplesCount;
Olivier Gaillard58b56e32018-06-01 16:18:43 +0100149 latencyDuration = cs.latencyMicros / samplesCount;
Fyodor Kupolov3f3af612018-04-18 17:26:43 -0700150 }
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700151 }
152
153 int callingUid = getCallingUid();
154
155 UidEntry uidEntry = mUidEntries.get(callingUid);
156 if (uidEntry == null) {
157 uidEntry = new UidEntry(callingUid);
158 mUidEntries.put(callingUid, uidEntry);
159 }
160
Olivier Gaillard11965ed2018-06-04 14:14:04 +0100161 CallStat callStat;
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700162 if (mDetailedTracking) {
163 // Find CallStat entry and update its total time
Olivier Gaillard11965ed2018-06-04 14:14:04 +0100164 callStat = uidEntry.getOrCreate(s.callStat);
Olivier Gaillard9429bf52018-05-15 23:25:03 +0100165 callStat.exceptionCount += s.exceptionThrown ? 1 : 0;
Olivier Gaillard58b56e32018-06-01 16:18:43 +0100166 callStat.maxRequestSizeBytes =
167 Math.max(callStat.maxRequestSizeBytes, parcelRequestSize);
168 callStat.maxReplySizeBytes =
169 Math.max(callStat.maxReplySizeBytes, parcelReplySize);
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700170 } else {
171 // update sampled timings in the beginning of each interval
Olivier Gaillard11965ed2018-06-04 14:14:04 +0100172 callStat = s.sampledCallStat;
173 }
174 callStat.callCount++;
Olivier Gaillard103aae22018-06-07 18:20:10 +0100175 callStat.methodName = s.callStat.methodName;
Olivier Gaillard11965ed2018-06-04 14:14:04 +0100176 if (s.cpuTimeStarted >= 0) {
177 callStat.cpuTimeMicros += duration;
178 callStat.maxCpuTimeMicros = Math.max(callStat.maxCpuTimeMicros, duration);
179 callStat.latencyMicros += latencyDuration;
180 callStat.maxLatencyMicros = Math.max(callStat.maxLatencyMicros, latencyDuration);
Fyodor Kupolovca348512018-01-10 18:05:53 -0800181 }
Fyodor Kupolov3f3af612018-04-18 17:26:43 -0700182
Olivier Gaillard121988e2018-05-15 20:49:47 +0100183 uidEntry.cpuTimeMicros += duration;
Fyodor Kupolovca348512018-01-10 18:05:53 -0800184 uidEntry.callCount++;
Fyodor Kupolovca348512018-01-10 18:05:53 -0800185 }
Fyodor Kupolovca348512018-01-10 18:05:53 -0800186 }
187
Olivier Gaillard9429bf52018-05-15 23:25:03 +0100188 /**
189 * Called if an exception is thrown while executing the binder transaction.
190 *
191 * <li>BinderCallsStats#callEnded will be called afterwards.
192 * <li>Do not throw an exception in this method, it will swallow the original exception thrown
193 * by the binder transaction.
194 */
Olivier Gaillardf82d2e732018-06-07 11:45:35 +0100195 public void callThrewException(CallSession s, Exception exception) {
Olivier Gaillard9429bf52018-05-15 23:25:03 +0100196 Preconditions.checkNotNull(s);
Olivier Gaillard1d7f6152018-07-03 13:57:58 +0100197 if (!mEnabled) {
198 return;
199 }
Olivier Gaillard9429bf52018-05-15 23:25:03 +0100200 s.exceptionThrown = true;
Olivier Gaillardf82d2e732018-06-07 11:45:35 +0100201 try {
202 String className = exception.getClass().getName();
203 synchronized (mLock) {
204 if (mExceptionCounts.size() >= MAX_EXCEPTION_COUNT_SIZE) {
205 className = EXCEPTION_COUNT_OVERFLOW_NAME;
206 }
207 Integer count = mExceptionCounts.get(className);
208 mExceptionCounts.put(className, count == null ? 1 : count + 1);
209 }
210 } catch (RuntimeException e) {
211 // Do not propagate the exception. We do not want to swallow original exception.
212 Log.wtf(TAG, "Unexpected exception while updating mExceptionCounts", e);
213 }
Olivier Gaillard9429bf52018-05-15 23:25:03 +0100214 }
215
Olivier Gaillard00bfb1b2018-07-10 11:25:09 +0100216 public ArrayList<ExportedCallStat> getExportedCallStats() {
217 // We do not collect all the data if detailed tracking is off.
218 if (!mDetailedTracking) {
219 return new ArrayList<ExportedCallStat>();
220 }
221
222 ArrayList<ExportedCallStat> resultCallStats = new ArrayList<>();
223 synchronized (mLock) {
224 int uidEntriesSize = mUidEntries.size();
225 for (int entryIdx = 0; entryIdx < uidEntriesSize; entryIdx++){
226 UidEntry entry = mUidEntries.valueAt(entryIdx);
227 for (CallStat stat : entry.getCallStatsList()) {
228 ExportedCallStat exported = new ExportedCallStat();
229 exported.uid = entry.uid;
230 exported.className = stat.className;
231 exported.methodName = stat.methodName == null
232 ? String.valueOf(stat.msg) : stat.methodName;
233 exported.cpuTimeMicros = stat.cpuTimeMicros;
234 exported.maxCpuTimeMicros = stat.maxCpuTimeMicros;
235 exported.latencyMicros = stat.latencyMicros;
236 exported.maxLatencyMicros = stat.maxLatencyMicros;
237 exported.callCount = stat.callCount;
238 exported.maxRequestSizeBytes = stat.maxRequestSizeBytes;
239 exported.maxReplySizeBytes = stat.maxReplySizeBytes;
240 exported.exceptionCount = stat.exceptionCount;
241 resultCallStats.add(exported);
242 }
243 }
244 }
245
246 return resultCallStats;
247 }
248
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700249 public void dump(PrintWriter pw, Map<Integer,String> appIdToPkgNameMap, boolean verbose) {
250 synchronized (mLock) {
251 dumpLocked(pw, appIdToPkgNameMap, verbose);
252 }
253 }
254
255 private void dumpLocked(PrintWriter pw, Map<Integer,String> appIdToPkgNameMap, boolean verbose) {
Olivier Gaillard1d7f6152018-07-03 13:57:58 +0100256 if (!mEnabled) {
257 pw.println("Binder calls stats disabled.");
258 return;
259 }
260
Fyodor Kupolovca348512018-01-10 18:05:53 -0800261 long totalCallsCount = 0;
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700262 long totalCpuTime = 0;
Fyodor Kupolov8aa51242018-04-23 12:44:51 -0700263 pw.print("Start time: ");
264 pw.println(DateFormat.format("yyyy-MM-dd HH:mm:ss", mStartTime));
Fyodor Kupolovca348512018-01-10 18:05:53 -0800265 List<UidEntry> entries = new ArrayList<>();
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700266
267 int uidEntriesSize = mUidEntries.size();
268 for (int i = 0; i < uidEntriesSize; i++) {
269 UidEntry e = mUidEntries.valueAt(i);
270 entries.add(e);
271 totalCpuTime += e.cpuTimeMicros;
272 totalCallsCount += e.callCount;
Fyodor Kupolovca348512018-01-10 18:05:53 -0800273 }
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700274
275 entries.sort(Comparator.<UidEntry>comparingDouble(value -> value.cpuTimeMicros).reversed());
276 String datasetSizeDesc = verbose ? "" : "(top 90% by cpu time) ";
277 StringBuilder sb = new StringBuilder();
Fyodor Kupolov3f3af612018-04-18 17:26:43 -0700278 if (mDetailedTracking) {
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700279 pw.println("Per-UID raw data " + datasetSizeDesc
Olivier Gaillardd2020112018-07-06 15:21:13 +0100280 + "(package/uid, call_desc, cpu_time_micros, max_cpu_time_micros, "
281 + "latency_time_micros, max_latency_time_micros, exception_count, "
282 + "max_request_size_bytes, max_reply_size_bytes, call_count):");
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700283 List<UidEntry> topEntries = verbose ? entries
284 : getHighestValues(entries, value -> value.cpuTimeMicros, 0.9);
285 for (UidEntry uidEntry : topEntries) {
286 for (CallStat e : uidEntry.getCallStatsList()) {
Fyodor Kupolov3f3af612018-04-18 17:26:43 -0700287 sb.setLength(0);
288 sb.append(" ")
Olivier Gaillardd2020112018-07-06 15:21:13 +0100289 .append(uidToString(uidEntry.uid, appIdToPkgNameMap))
Olivier Gaillard103aae22018-06-07 18:20:10 +0100290 .append(",").append(e)
Olivier Gaillard9429bf52018-05-15 23:25:03 +0100291 .append(',').append(e.cpuTimeMicros)
Olivier Gaillard11965ed2018-06-04 14:14:04 +0100292 .append(',').append(e.maxCpuTimeMicros)
Olivier Gaillard121988e2018-05-15 20:49:47 +0100293 .append(',').append(e.latencyMicros)
Olivier Gaillard58b56e32018-06-01 16:18:43 +0100294 .append(',').append(e.maxLatencyMicros)
Olivier Gaillard9429bf52018-05-15 23:25:03 +0100295 .append(',').append(e.exceptionCount)
Olivier Gaillard58b56e32018-06-01 16:18:43 +0100296 .append(',').append(e.maxRequestSizeBytes)
297 .append(',').append(e.maxReplySizeBytes)
Olivier Gaillard121988e2018-05-15 20:49:47 +0100298 .append(',').append(e.callCount);
Fyodor Kupolov3f3af612018-04-18 17:26:43 -0700299 pw.println(sb);
300 }
301 }
302 pw.println();
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700303 } else {
304 pw.println("Sampled stats " + datasetSizeDesc
305 + "(call_desc, cpu_time, call_count, exception_count):");
306 List<CallStat> sampledStatsList = mSampledEntries.getCallStatsList();
307 // Show all if verbose, otherwise 90th percentile
308 if (!verbose) {
309 sampledStatsList = getHighestValues(sampledStatsList,
310 value -> value.cpuTimeMicros, 0.9);
311 }
312 for (CallStat e : sampledStatsList) {
313 sb.setLength(0);
314 sb.append(" ").append(e)
Olivier Gaillard1d7f6152018-07-03 13:57:58 +0100315 .append(',').append(e.cpuTimeMicros * mPeriodicSamplingInterval)
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700316 .append(',').append(e.callCount)
317 .append(',').append(e.exceptionCount);
318 pw.println(sb);
Fyodor Kupolov3f3af612018-04-18 17:26:43 -0700319 }
320 pw.println();
Fyodor Kupolovca348512018-01-10 18:05:53 -0800321 }
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700322 pw.println("Per-UID Summary " + datasetSizeDesc
323 + "(cpu_time, % of total cpu_time, call_count, exception_count, package/uid):");
324 List<UidEntry> summaryEntries = verbose ? entries
325 : getHighestValues(entries, value -> value.cpuTimeMicros, 0.9);
326 for (UidEntry entry : summaryEntries) {
327 String uidStr = uidToString(entry.uid, appIdToPkgNameMap);
328 pw.println(String.format(" %10d %3.0f%% %8d %3d %s",
329 entry.cpuTimeMicros, 100d * entry.cpuTimeMicros / totalCpuTime, entry.callCount,
330 entry.exceptionCount, uidStr));
331 }
332 pw.println();
333 pw.println(String.format(" Summary: total_cpu_time=%d, "
334 + "calls_count=%d, avg_call_cpu_time=%.0f",
335 totalCpuTime, totalCallsCount, (double)totalCpuTime / totalCallsCount));
Olivier Gaillardf82d2e732018-06-07 11:45:35 +0100336 pw.println();
337
338 pw.println("Exceptions thrown (exception_count, class_name):");
339 List<Pair<String, Integer>> exceptionEntries = new ArrayList<>();
340 // We cannot use new ArrayList(Collection) constructor because MapCollections does not
341 // implement toArray method.
342 mExceptionCounts.entrySet().iterator().forEachRemaining(
343 (e) -> exceptionEntries.add(Pair.create(e.getKey(), e.getValue())));
344 exceptionEntries.sort((e1, e2) -> Integer.compare(e2.second, e1.second));
345 for (Pair<String, Integer> entry : exceptionEntries) {
346 pw.println(String.format(" %6d %s", entry.second, entry.first));
347 }
Fyodor Kupolovca348512018-01-10 18:05:53 -0800348 }
349
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700350 private static String uidToString(int uid, Map<Integer, String> pkgNameMap) {
351 int appId = UserHandle.getAppId(uid);
352 String pkgName = pkgNameMap == null ? null : pkgNameMap.get(appId);
353 String uidStr = UserHandle.formatUid(uid);
354 return pkgName == null ? uidStr : pkgName + '/' + uidStr;
Fyodor Kupolovca348512018-01-10 18:05:53 -0800355 }
356
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700357 protected long getThreadTimeMicro() {
358 return SystemClock.currentThreadTimeMicro();
359 }
360
361 protected int getCallingUid() {
362 return Binder.getCallingUid();
363 }
364
Olivier Gaillard11965ed2018-06-04 14:14:04 +0100365 protected long getElapsedRealtimeMicro() {
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700366 return SystemClock.elapsedRealtimeNanos() / 1000;
Olivier Gaillard121988e2018-05-15 20:49:47 +0100367 }
368
Fyodor Kupolovca348512018-01-10 18:05:53 -0800369 public static BinderCallsStats getInstance() {
370 return sInstance;
371 }
372
Fyodor Kupolov3f3af612018-04-18 17:26:43 -0700373 public void setDetailedTracking(boolean enabled) {
Olivier Gaillard1d7f6152018-07-03 13:57:58 +0100374 synchronized (mLock) {
375 if (enabled != mDetailedTracking) {
376 mDetailedTracking = enabled;
377 reset();
378 }
379 }
380 }
381
382 public void setEnabled(boolean enabled) {
383 synchronized (mLock) {
384 if (enabled != mEnabled) {
385 mEnabled = enabled;
386 reset();
387 }
388 }
389 }
390
391 public void setSamplingInterval(int samplingInterval) {
392 synchronized (mLock) {
393 if (samplingInterval != mPeriodicSamplingInterval) {
394 mPeriodicSamplingInterval = samplingInterval;
395 reset();
396 }
Fyodor Kupolov3f3af612018-04-18 17:26:43 -0700397 }
Fyodor Kupolovca348512018-01-10 18:05:53 -0800398 }
399
Fyodor Kupolov3f3af612018-04-18 17:26:43 -0700400 public void reset() {
401 synchronized (mLock) {
402 mUidEntries.clear();
Olivier Gaillardf82d2e732018-06-07 11:45:35 +0100403 mExceptionCounts.clear();
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700404 mSampledEntries.mCallStats.clear();
Fyodor Kupolov8aa51242018-04-23 12:44:51 -0700405 mStartTime = System.currentTimeMillis();
Fyodor Kupolov3f3af612018-04-18 17:26:43 -0700406 }
Fyodor Kupolovca348512018-01-10 18:05:53 -0800407 }
408
Olivier Gaillard00bfb1b2018-07-10 11:25:09 +0100409 /**
410 * Aggregated data by uid/class/method to be sent through WestWorld.
411 */
412 public static class ExportedCallStat {
413 public int uid;
414 public String className;
415 public String methodName;
416 public long cpuTimeMicros;
417 public long maxCpuTimeMicros;
418 public long latencyMicros;
419 public long maxLatencyMicros;
420 public long callCount;
421 public long maxRequestSizeBytes;
422 public long maxReplySizeBytes;
423 public long exceptionCount;
424 }
425
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700426 @VisibleForTesting
427 public static class CallStat {
428 public String className;
429 public int msg;
Olivier Gaillard103aae22018-06-07 18:20:10 +0100430 // Method name might be null when we cannot resolve the transaction code. For instance, if
431 // the binder was not generated by AIDL.
432 public @Nullable String methodName;
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700433 public long cpuTimeMicros;
Olivier Gaillard11965ed2018-06-04 14:14:04 +0100434 public long maxCpuTimeMicros;
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700435 public long latencyMicros;
Olivier Gaillard58b56e32018-06-01 16:18:43 +0100436 public long maxLatencyMicros;
Olivier Gaillard11965ed2018-06-04 14:14:04 +0100437 public long callCount;
438 // The following fields are only computed if mDetailedTracking is set.
Olivier Gaillard58b56e32018-06-01 16:18:43 +0100439 public long maxRequestSizeBytes;
440 public long maxReplySizeBytes;
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700441 public long exceptionCount;
Fyodor Kupolovca348512018-01-10 18:05:53 -0800442
443 CallStat() {
444 }
445
446 CallStat(String className, int msg) {
447 this.className = className;
448 this.msg = msg;
449 }
450
451 @Override
452 public boolean equals(Object o) {
453 if (this == o) {
454 return true;
455 }
456
457 CallStat callStat = (CallStat) o;
458
Fyodor Kupolov3f3af612018-04-18 17:26:43 -0700459 return msg == callStat.msg && (className.equals(callStat.className));
Fyodor Kupolovca348512018-01-10 18:05:53 -0800460 }
461
462 @Override
463 public int hashCode() {
464 int result = className.hashCode();
465 result = 31 * result + msg;
466 return result;
467 }
468
469 @Override
470 public String toString() {
Olivier Gaillard103aae22018-06-07 18:20:10 +0100471 return className + "#" + (methodName == null ? msg : methodName);
Fyodor Kupolovca348512018-01-10 18:05:53 -0800472 }
473 }
474
475 public static class CallSession {
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700476 long cpuTimeStarted;
477 long timeStarted;
Olivier Gaillard9429bf52018-05-15 23:25:03 +0100478 boolean exceptionThrown;
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700479 final CallStat callStat = new CallStat();
480 CallStat sampledCallStat;
Fyodor Kupolovca348512018-01-10 18:05:53 -0800481 }
482
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700483 @VisibleForTesting
484 public static class UidEntry {
Fyodor Kupolovca348512018-01-10 18:05:53 -0800485 int uid;
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700486 public long cpuTimeMicros;
487 public long callCount;
488 public int exceptionCount;
Fyodor Kupolovca348512018-01-10 18:05:53 -0800489
490 UidEntry(int uid) {
491 this.uid = uid;
492 }
493
494 // Aggregate time spent per each call name: call_desc -> cpu_time_micros
495 Map<CallStat, CallStat> mCallStats = new ArrayMap<>();
496
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700497 CallStat getOrCreate(CallStat callStat) {
498 CallStat mapCallStat = mCallStats.get(callStat);
499 // Only create CallStat if it's a new entry, otherwise update existing instance
500 if (mapCallStat == null) {
501 mapCallStat = new CallStat(callStat.className, callStat.msg);
502 mCallStats.put(mapCallStat, mapCallStat);
503 }
504 return mapCallStat;
505 }
506
507 /**
508 * Returns list of calls sorted by CPU time
509 */
510 public List<CallStat> getCallStatsList() {
511 List<CallStat> callStats = new ArrayList<>(mCallStats.keySet());
512 callStats.sort((o1, o2) -> {
513 if (o1.cpuTimeMicros < o2.cpuTimeMicros) {
514 return 1;
515 } else if (o1.cpuTimeMicros > o2.cpuTimeMicros) {
516 return -1;
517 }
518 return 0;
519 });
520 return callStats;
521 }
522
Fyodor Kupolovca348512018-01-10 18:05:53 -0800523 @Override
524 public String toString() {
525 return "UidEntry{" +
Olivier Gaillard121988e2018-05-15 20:49:47 +0100526 "cpuTimeMicros=" + cpuTimeMicros +
Fyodor Kupolovca348512018-01-10 18:05:53 -0800527 ", callCount=" + callCount +
528 ", mCallStats=" + mCallStats +
529 '}';
530 }
531
532 @Override
533 public boolean equals(Object o) {
534 if (this == o) {
535 return true;
536 }
537
538 UidEntry uidEntry = (UidEntry) o;
539 return uid == uidEntry.uid;
540 }
541
542 @Override
543 public int hashCode() {
544 return uid;
545 }
546 }
547
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700548 @VisibleForTesting
549 public SparseArray<UidEntry> getUidEntries() {
550 return mUidEntries;
551 }
552
553 @VisibleForTesting
554 public UidEntry getSampledEntries() {
555 return mSampledEntries;
556 }
557
558 @VisibleForTesting
Olivier Gaillardf82d2e732018-06-07 11:45:35 +0100559 public ArrayMap<String, Integer> getExceptionCounts() {
560 return mExceptionCounts;
561 }
562
563 @VisibleForTesting
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700564 public static <T> List<T> getHighestValues(List<T> list, ToDoubleFunction<T> toDouble,
565 double percentile) {
566 List<T> sortedList = new ArrayList<>(list);
567 sortedList.sort(Comparator.comparingDouble(toDouble).reversed());
568 double total = 0;
569 for (T item : list) {
570 total += toDouble.applyAsDouble(item);
571 }
572 List<T> result = new ArrayList<>();
573 double runningSum = 0;
574 for (T item : sortedList) {
575 if (runningSum > percentile * total) {
576 break;
577 }
578 result.add(item);
579 runningSum += toDouble.applyAsDouble(item);
580 }
581 return result;
582 }
583
Fyodor Kupolovca348512018-01-10 18:05:53 -0800584}