blob: 63c7dd0e0cb3cc9800da834b64634f9d13b4f0ad [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 Gaillard2c13c6f2018-07-16 16:55:58 +010041import java.util.Random;
Olivier Gaillardf82d2e732018-06-07 11:45:35 +010042import java.util.Set;
Fyodor Kupolovca348512018-01-10 18:05:53 -080043import java.util.concurrent.ConcurrentLinkedQueue;
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -070044import java.util.function.ToDoubleFunction;
Fyodor Kupolovca348512018-01-10 18:05:53 -080045
46/**
47 * Collects statistics about CPU time spent per binder call across multiple dimensions, e.g.
48 * per thread, uid or call description.
49 */
50public class BinderCallsStats {
Olivier Gaillard1d7f6152018-07-03 13:57:58 +010051 public static final boolean ENABLED_DEFAULT = true;
52 public static final boolean DETAILED_TRACKING_DEFAULT = true;
53 public static final int PERIODIC_SAMPLING_INTERVAL_DEFAULT = 10;
54
Olivier Gaillardf82d2e732018-06-07 11:45:35 +010055 private static final String TAG = "BinderCallsStats";
Fyodor Kupolovca348512018-01-10 18:05:53 -080056 private static final int CALL_SESSIONS_POOL_SIZE = 100;
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -070057 private static final int PERIODIC_SAMPLING_INTERVAL = 10;
Olivier Gaillardf82d2e732018-06-07 11:45:35 +010058 private static final int MAX_EXCEPTION_COUNT_SIZE = 50;
59 private static final String EXCEPTION_COUNT_OVERFLOW_NAME = "overflow";
Olivier Gaillard1d7f6152018-07-03 13:57:58 +010060 private static final CallSession NOT_ENABLED = new CallSession();
Olivier Gaillard2c13c6f2018-07-16 16:55:58 +010061 private static final BinderCallsStats sInstance = new BinderCallsStats(new Random());
Fyodor Kupolovca348512018-01-10 18:05:53 -080062
Olivier Gaillard1d7f6152018-07-03 13:57:58 +010063 private volatile boolean mEnabled = ENABLED_DEFAULT;
64 private volatile boolean mDetailedTracking = DETAILED_TRACKING_DEFAULT;
65 private volatile int mPeriodicSamplingInterval = PERIODIC_SAMPLING_INTERVAL_DEFAULT;
Fyodor Kupolov3f3af612018-04-18 17:26:43 -070066 @GuardedBy("mLock")
Fyodor Kupolovca348512018-01-10 18:05:53 -080067 private final SparseArray<UidEntry> mUidEntries = new SparseArray<>();
Olivier Gaillardf82d2e732018-06-07 11:45:35 +010068 @GuardedBy("mLock")
69 private final ArrayMap<String, Integer> mExceptionCounts = new ArrayMap<>();
Fyodor Kupolovca348512018-01-10 18:05:53 -080070 private final Queue<CallSession> mCallSessionsPool = new ConcurrentLinkedQueue<>();
Fyodor Kupolov3f3af612018-04-18 17:26:43 -070071 private final Object mLock = new Object();
Olivier Gaillard2c13c6f2018-07-16 16:55:58 +010072 private final Random mRandom;
Fyodor Kupolov8aa51242018-04-23 12:44:51 -070073 private long mStartTime = System.currentTimeMillis();
Fyodor Kupolovca348512018-01-10 18:05:53 -080074
Olivier Gaillard1d7f6152018-07-03 13:57:58 +010075 @VisibleForTesting // Use getInstance() instead.
Olivier Gaillard2c13c6f2018-07-16 16:55:58 +010076 public BinderCallsStats(Random random) {
77 this.mRandom = random;
Fyodor Kupolovca348512018-01-10 18:05:53 -080078 }
79
80 public CallSession callStarted(Binder binder, int code) {
Olivier Gaillard103aae22018-06-07 18:20:10 +010081 return callStarted(binder.getClass().getName(), code, binder.getTransactionName(code));
Fyodor Kupolovca348512018-01-10 18:05:53 -080082 }
83
Olivier Gaillard103aae22018-06-07 18:20:10 +010084 private CallSession callStarted(String className, int code, @Nullable String methodName) {
Olivier Gaillard1d7f6152018-07-03 13:57:58 +010085 if (!mEnabled) {
Olivier Gaillard2c13c6f2018-07-16 16:55:58 +010086 return NOT_ENABLED;
Olivier Gaillard1d7f6152018-07-03 13:57:58 +010087 }
88
Fyodor Kupolovca348512018-01-10 18:05:53 -080089 CallSession s = mCallSessionsPool.poll();
90 if (s == null) {
91 s = new CallSession();
92 }
Olivier Gaillard1d7f6152018-07-03 13:57:58 +010093
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -070094 s.callStat.className = className;
95 s.callStat.msg = code;
Olivier Gaillard103aae22018-06-07 18:20:10 +010096 s.callStat.methodName = methodName;
Olivier Gaillard9429bf52018-05-15 23:25:03 +010097 s.exceptionThrown = false;
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -070098 s.cpuTimeStarted = -1;
99 s.timeStarted = -1;
100
101 synchronized (mLock) {
Olivier Gaillard2c13c6f2018-07-16 16:55:58 +0100102 if (!mDetailedTracking && !shouldTrackCall()) {
103 return s;
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700104 }
Olivier Gaillard2c13c6f2018-07-16 16:55:58 +0100105
106 s.cpuTimeStarted = getThreadTimeMicro();
107 s.timeStarted = getElapsedRealtimeMicro();
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700108 }
Fyodor Kupolovca348512018-01-10 18:05:53 -0800109 return s;
110 }
111
Olivier Gaillard58b56e32018-06-01 16:18:43 +0100112 public void callEnded(CallSession s, int parcelRequestSize, int parcelReplySize) {
Fyodor Kupolovca348512018-01-10 18:05:53 -0800113 Preconditions.checkNotNull(s);
Olivier Gaillard1d7f6152018-07-03 13:57:58 +0100114 if (s == NOT_ENABLED) {
Olivier Gaillard2c13c6f2018-07-16 16:55:58 +0100115 return;
Olivier Gaillard1d7f6152018-07-03 13:57:58 +0100116 }
117
118 processCallEnded(s, parcelRequestSize, parcelReplySize);
119
120 if (mCallSessionsPool.size() < CALL_SESSIONS_POOL_SIZE) {
121 mCallSessionsPool.add(s);
122 }
123 }
124
125 private void processCallEnded(CallSession s, int parcelRequestSize, int parcelReplySize) {
Fyodor Kupolov3f3af612018-04-18 17:26:43 -0700126 synchronized (mLock) {
Olivier Gaillard1d7f6152018-07-03 13:57:58 +0100127 if (!mEnabled) {
Olivier Gaillard2c13c6f2018-07-16 16:55:58 +0100128 return;
Olivier Gaillard1d7f6152018-07-03 13:57:58 +0100129 }
130
Olivier Gaillard2c13c6f2018-07-16 16:55:58 +0100131 final int callingUid = getCallingUid();
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700132 UidEntry uidEntry = mUidEntries.get(callingUid);
133 if (uidEntry == null) {
134 uidEntry = new UidEntry(callingUid);
135 mUidEntries.put(callingUid, uidEntry);
136 }
Olivier Gaillard2c13c6f2018-07-16 16:55:58 +0100137 uidEntry.callCount++;
138 CallStat callStat = uidEntry.getOrCreate(s.callStat);
Olivier Gaillard11965ed2018-06-04 14:14:04 +0100139 callStat.callCount++;
Olivier Gaillard2c13c6f2018-07-16 16:55:58 +0100140
141 // Non-negative time signals we need to record data for this call.
142 final boolean recordCall = s.cpuTimeStarted >= 0;
143 if (recordCall) {
144 final long duration = getThreadTimeMicro() - s.cpuTimeStarted;
145 final long latencyDuration = getElapsedRealtimeMicro() - s.timeStarted;
146 uidEntry.cpuTimeMicros += duration;
147 uidEntry.recordedCallCount++;
148
149 callStat.recordedCallCount++;
150 callStat.methodName = s.callStat.methodName;
Olivier Gaillard11965ed2018-06-04 14:14:04 +0100151 callStat.cpuTimeMicros += duration;
152 callStat.maxCpuTimeMicros = Math.max(callStat.maxCpuTimeMicros, duration);
153 callStat.latencyMicros += latencyDuration;
Olivier Gaillard2c13c6f2018-07-16 16:55:58 +0100154 callStat.maxLatencyMicros =
155 Math.max(callStat.maxLatencyMicros, latencyDuration);
156 if (mDetailedTracking) {
157 callStat.exceptionCount += s.exceptionThrown ? 1 : 0;
158 callStat.maxRequestSizeBytes =
159 Math.max(callStat.maxRequestSizeBytes, parcelRequestSize);
160 callStat.maxReplySizeBytes =
161 Math.max(callStat.maxReplySizeBytes, parcelReplySize);
162 }
Fyodor Kupolovca348512018-01-10 18:05:53 -0800163 }
Fyodor Kupolovca348512018-01-10 18:05:53 -0800164 }
Fyodor Kupolovca348512018-01-10 18:05:53 -0800165 }
166
Olivier Gaillard9429bf52018-05-15 23:25:03 +0100167 /**
168 * Called if an exception is thrown while executing the binder transaction.
169 *
170 * <li>BinderCallsStats#callEnded will be called afterwards.
171 * <li>Do not throw an exception in this method, it will swallow the original exception thrown
172 * by the binder transaction.
173 */
Olivier Gaillardf82d2e732018-06-07 11:45:35 +0100174 public void callThrewException(CallSession s, Exception exception) {
Olivier Gaillard9429bf52018-05-15 23:25:03 +0100175 Preconditions.checkNotNull(s);
Olivier Gaillard1d7f6152018-07-03 13:57:58 +0100176 if (!mEnabled) {
Olivier Gaillard2c13c6f2018-07-16 16:55:58 +0100177 return;
Olivier Gaillard1d7f6152018-07-03 13:57:58 +0100178 }
Olivier Gaillard9429bf52018-05-15 23:25:03 +0100179 s.exceptionThrown = true;
Olivier Gaillardf82d2e732018-06-07 11:45:35 +0100180 try {
181 String className = exception.getClass().getName();
182 synchronized (mLock) {
183 if (mExceptionCounts.size() >= MAX_EXCEPTION_COUNT_SIZE) {
Olivier Gaillard2c13c6f2018-07-16 16:55:58 +0100184 className = EXCEPTION_COUNT_OVERFLOW_NAME;
Olivier Gaillardf82d2e732018-06-07 11:45:35 +0100185 }
186 Integer count = mExceptionCounts.get(className);
187 mExceptionCounts.put(className, count == null ? 1 : count + 1);
188 }
189 } catch (RuntimeException e) {
Olivier Gaillard2c13c6f2018-07-16 16:55:58 +0100190 // Do not propagate the exception. We do not want to swallow original exception.
191 Log.wtf(TAG, "Unexpected exception while updating mExceptionCounts", e);
Olivier Gaillardf82d2e732018-06-07 11:45:35 +0100192 }
Olivier Gaillard9429bf52018-05-15 23:25:03 +0100193 }
194
Olivier Gaillard00bfb1b2018-07-10 11:25:09 +0100195 public ArrayList<ExportedCallStat> getExportedCallStats() {
196 // We do not collect all the data if detailed tracking is off.
197 if (!mDetailedTracking) {
Olivier Gaillard2c13c6f2018-07-16 16:55:58 +0100198 return new ArrayList<ExportedCallStat>();
Olivier Gaillard00bfb1b2018-07-10 11:25:09 +0100199 }
200
201 ArrayList<ExportedCallStat> resultCallStats = new ArrayList<>();
202 synchronized (mLock) {
203 int uidEntriesSize = mUidEntries.size();
204 for (int entryIdx = 0; entryIdx < uidEntriesSize; entryIdx++){
205 UidEntry entry = mUidEntries.valueAt(entryIdx);
206 for (CallStat stat : entry.getCallStatsList()) {
207 ExportedCallStat exported = new ExportedCallStat();
208 exported.uid = entry.uid;
209 exported.className = stat.className;
210 exported.methodName = stat.methodName == null
Olivier Gaillard2c13c6f2018-07-16 16:55:58 +0100211 ? String.valueOf(stat.msg) : stat.methodName;
Olivier Gaillard00bfb1b2018-07-10 11:25:09 +0100212 exported.cpuTimeMicros = stat.cpuTimeMicros;
213 exported.maxCpuTimeMicros = stat.maxCpuTimeMicros;
214 exported.latencyMicros = stat.latencyMicros;
215 exported.maxLatencyMicros = stat.maxLatencyMicros;
Olivier Gaillard2c13c6f2018-07-16 16:55:58 +0100216 exported.recordedCallCount = stat.recordedCallCount;
Olivier Gaillard00bfb1b2018-07-10 11:25:09 +0100217 exported.callCount = stat.callCount;
218 exported.maxRequestSizeBytes = stat.maxRequestSizeBytes;
219 exported.maxReplySizeBytes = stat.maxReplySizeBytes;
220 exported.exceptionCount = stat.exceptionCount;
221 resultCallStats.add(exported);
222 }
223 }
224 }
225
226 return resultCallStats;
227 }
228
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700229 public void dump(PrintWriter pw, Map<Integer,String> appIdToPkgNameMap, boolean verbose) {
230 synchronized (mLock) {
231 dumpLocked(pw, appIdToPkgNameMap, verbose);
232 }
233 }
234
235 private void dumpLocked(PrintWriter pw, Map<Integer,String> appIdToPkgNameMap, boolean verbose) {
Olivier Gaillard1d7f6152018-07-03 13:57:58 +0100236 if (!mEnabled) {
Olivier Gaillard2c13c6f2018-07-16 16:55:58 +0100237 pw.println("Binder calls stats disabled.");
238 return;
Olivier Gaillard1d7f6152018-07-03 13:57:58 +0100239 }
240
Fyodor Kupolovca348512018-01-10 18:05:53 -0800241 long totalCallsCount = 0;
Olivier Gaillard2c13c6f2018-07-16 16:55:58 +0100242 long totalRecordedCallsCount = 0;
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700243 long totalCpuTime = 0;
Fyodor Kupolov8aa51242018-04-23 12:44:51 -0700244 pw.print("Start time: ");
245 pw.println(DateFormat.format("yyyy-MM-dd HH:mm:ss", mStartTime));
Olivier Gaillard2c13c6f2018-07-16 16:55:58 +0100246 pw.println("Sampling interval period: " + mPeriodicSamplingInterval);
Fyodor Kupolovca348512018-01-10 18:05:53 -0800247 List<UidEntry> entries = new ArrayList<>();
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700248
249 int uidEntriesSize = mUidEntries.size();
250 for (int i = 0; i < uidEntriesSize; i++) {
251 UidEntry e = mUidEntries.valueAt(i);
252 entries.add(e);
253 totalCpuTime += e.cpuTimeMicros;
Olivier Gaillard2c13c6f2018-07-16 16:55:58 +0100254 totalRecordedCallsCount += e.recordedCallCount;
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700255 totalCallsCount += e.callCount;
Fyodor Kupolovca348512018-01-10 18:05:53 -0800256 }
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700257
258 entries.sort(Comparator.<UidEntry>comparingDouble(value -> value.cpuTimeMicros).reversed());
259 String datasetSizeDesc = verbose ? "" : "(top 90% by cpu time) ";
260 StringBuilder sb = new StringBuilder();
Olivier Gaillard2c13c6f2018-07-16 16:55:58 +0100261 List<UidEntry> topEntries = verbose ? entries
262 : getHighestValues(entries, value -> value.cpuTimeMicros, 0.9);
263 pw.println("Per-UID raw data " + datasetSizeDesc
264 + "(package/uid, call_desc, cpu_time_micros, max_cpu_time_micros, "
265 + "latency_time_micros, max_latency_time_micros, exception_count, "
266 + "max_request_size_bytes, max_reply_size_bytes, recorded_call_count, "
267 + "call_count):");
268 for (UidEntry uidEntry : topEntries) {
269 for (CallStat e : uidEntry.getCallStatsList()) {
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700270 sb.setLength(0);
Olivier Gaillard2c13c6f2018-07-16 16:55:58 +0100271 sb.append(" ")
272 .append(uidToString(uidEntry.uid, appIdToPkgNameMap))
273 .append(',').append(e)
274 .append(',').append(e.cpuTimeMicros)
275 .append(',').append(e.maxCpuTimeMicros)
276 .append(',').append(e.latencyMicros)
277 .append(',').append(e.maxLatencyMicros)
278 .append(',').append(mDetailedTracking ? e.exceptionCount : '_')
279 .append(',').append(mDetailedTracking ? e.maxRequestSizeBytes : '_')
280 .append(',').append(mDetailedTracking ? e.maxReplySizeBytes : '_')
281 .append(',').append(e.recordedCallCount)
282 .append(',').append(e.callCount);
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700283 pw.println(sb);
Fyodor Kupolov3f3af612018-04-18 17:26:43 -0700284 }
Fyodor Kupolovca348512018-01-10 18:05:53 -0800285 }
Olivier Gaillard2c13c6f2018-07-16 16:55:58 +0100286 pw.println();
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700287 pw.println("Per-UID Summary " + datasetSizeDesc
Olivier Gaillard2c13c6f2018-07-16 16:55:58 +0100288 + "(cpu_time, % of total cpu_time, recorded_call_count, call_count, package/uid):");
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700289 List<UidEntry> summaryEntries = verbose ? entries
290 : getHighestValues(entries, value -> value.cpuTimeMicros, 0.9);
291 for (UidEntry entry : summaryEntries) {
292 String uidStr = uidToString(entry.uid, appIdToPkgNameMap);
Olivier Gaillard2c13c6f2018-07-16 16:55:58 +0100293 pw.println(String.format(" %10d %3.0f%% %8d %8d %s",
294 entry.cpuTimeMicros, 100d * entry.cpuTimeMicros / totalCpuTime,
295 entry.recordedCallCount, entry.callCount, uidStr));
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700296 }
297 pw.println();
298 pw.println(String.format(" Summary: total_cpu_time=%d, "
Olivier Gaillard2c13c6f2018-07-16 16:55:58 +0100299 + "calls_count=%d, avg_call_cpu_time=%.0f",
300 totalCpuTime, totalCallsCount, (double)totalCpuTime / totalRecordedCallsCount));
Olivier Gaillardf82d2e732018-06-07 11:45:35 +0100301 pw.println();
302
303 pw.println("Exceptions thrown (exception_count, class_name):");
304 List<Pair<String, Integer>> exceptionEntries = new ArrayList<>();
305 // We cannot use new ArrayList(Collection) constructor because MapCollections does not
306 // implement toArray method.
307 mExceptionCounts.entrySet().iterator().forEachRemaining(
Olivier Gaillard2c13c6f2018-07-16 16:55:58 +0100308 (e) -> exceptionEntries.add(Pair.create(e.getKey(), e.getValue())));
Olivier Gaillardf82d2e732018-06-07 11:45:35 +0100309 exceptionEntries.sort((e1, e2) -> Integer.compare(e2.second, e1.second));
310 for (Pair<String, Integer> entry : exceptionEntries) {
Olivier Gaillard2c13c6f2018-07-16 16:55:58 +0100311 pw.println(String.format(" %6d %s", entry.second, entry.first));
312 }
313
314 if (!mDetailedTracking && mPeriodicSamplingInterval != 1) {
315 pw.println("");
316 pw.println("/!\\ Displayed data is sampled. See sampling interval at the top.");
Olivier Gaillardf82d2e732018-06-07 11:45:35 +0100317 }
Fyodor Kupolovca348512018-01-10 18:05:53 -0800318 }
319
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700320 private static String uidToString(int uid, Map<Integer, String> pkgNameMap) {
321 int appId = UserHandle.getAppId(uid);
322 String pkgName = pkgNameMap == null ? null : pkgNameMap.get(appId);
323 String uidStr = UserHandle.formatUid(uid);
324 return pkgName == null ? uidStr : pkgName + '/' + uidStr;
Fyodor Kupolovca348512018-01-10 18:05:53 -0800325 }
326
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700327 protected long getThreadTimeMicro() {
328 return SystemClock.currentThreadTimeMicro();
329 }
330
331 protected int getCallingUid() {
332 return Binder.getCallingUid();
333 }
334
Olivier Gaillard11965ed2018-06-04 14:14:04 +0100335 protected long getElapsedRealtimeMicro() {
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700336 return SystemClock.elapsedRealtimeNanos() / 1000;
Olivier Gaillard121988e2018-05-15 20:49:47 +0100337 }
338
Olivier Gaillard2c13c6f2018-07-16 16:55:58 +0100339 private boolean shouldTrackCall() {
340 return mRandom.nextInt() % mPeriodicSamplingInterval == 0;
341 }
342
Fyodor Kupolovca348512018-01-10 18:05:53 -0800343 public static BinderCallsStats getInstance() {
344 return sInstance;
345 }
346
Fyodor Kupolov3f3af612018-04-18 17:26:43 -0700347 public void setDetailedTracking(boolean enabled) {
Olivier Gaillard1d7f6152018-07-03 13:57:58 +0100348 synchronized (mLock) {
Olivier Gaillard2c13c6f2018-07-16 16:55:58 +0100349 if (enabled != mDetailedTracking) {
350 mDetailedTracking = enabled;
351 reset();
352 }
Olivier Gaillard1d7f6152018-07-03 13:57:58 +0100353 }
354 }
355
356 public void setEnabled(boolean enabled) {
357 synchronized (mLock) {
358 if (enabled != mEnabled) {
359 mEnabled = enabled;
360 reset();
361 }
362 }
363 }
364
365 public void setSamplingInterval(int samplingInterval) {
366 synchronized (mLock) {
367 if (samplingInterval != mPeriodicSamplingInterval) {
368 mPeriodicSamplingInterval = samplingInterval;
369 reset();
370 }
Fyodor Kupolov3f3af612018-04-18 17:26:43 -0700371 }
Fyodor Kupolovca348512018-01-10 18:05:53 -0800372 }
373
Fyodor Kupolov3f3af612018-04-18 17:26:43 -0700374 public void reset() {
375 synchronized (mLock) {
376 mUidEntries.clear();
Olivier Gaillardf82d2e732018-06-07 11:45:35 +0100377 mExceptionCounts.clear();
Fyodor Kupolov8aa51242018-04-23 12:44:51 -0700378 mStartTime = System.currentTimeMillis();
Fyodor Kupolov3f3af612018-04-18 17:26:43 -0700379 }
Fyodor Kupolovca348512018-01-10 18:05:53 -0800380 }
381
Olivier Gaillard00bfb1b2018-07-10 11:25:09 +0100382 /**
383 * Aggregated data by uid/class/method to be sent through WestWorld.
384 */
385 public static class ExportedCallStat {
386 public int uid;
387 public String className;
388 public String methodName;
389 public long cpuTimeMicros;
390 public long maxCpuTimeMicros;
391 public long latencyMicros;
392 public long maxLatencyMicros;
393 public long callCount;
Olivier Gaillard2c13c6f2018-07-16 16:55:58 +0100394 public long recordedCallCount;
Olivier Gaillard00bfb1b2018-07-10 11:25:09 +0100395 public long maxRequestSizeBytes;
396 public long maxReplySizeBytes;
397 public long exceptionCount;
398 }
399
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700400 @VisibleForTesting
401 public static class CallStat {
402 public String className;
403 public int msg;
Olivier Gaillard103aae22018-06-07 18:20:10 +0100404 // Method name might be null when we cannot resolve the transaction code. For instance, if
405 // the binder was not generated by AIDL.
406 public @Nullable String methodName;
Olivier Gaillard2c13c6f2018-07-16 16:55:58 +0100407 // Number of calls for which we collected data for. We do not record data for all the calls
408 // when sampling is on.
409 public long recordedCallCount;
410 // Real number of total calls.
411 public long callCount;
412 // Total CPU of all for all the recorded calls.
413 // Approximate total CPU usage can be computed by
414 // cpuTimeMicros * callCount / recordedCallCount
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700415 public long cpuTimeMicros;
Olivier Gaillard11965ed2018-06-04 14:14:04 +0100416 public long maxCpuTimeMicros;
Olivier Gaillard2c13c6f2018-07-16 16:55:58 +0100417 // Total latency of all for all the recorded calls.
418 // Approximate average latency can be computed by
419 // latencyMicros * callCount / recordedCallCount
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700420 public long latencyMicros;
Olivier Gaillard58b56e32018-06-01 16:18:43 +0100421 public long maxLatencyMicros;
Olivier Gaillard11965ed2018-06-04 14:14:04 +0100422 // The following fields are only computed if mDetailedTracking is set.
Olivier Gaillard58b56e32018-06-01 16:18:43 +0100423 public long maxRequestSizeBytes;
424 public long maxReplySizeBytes;
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700425 public long exceptionCount;
Fyodor Kupolovca348512018-01-10 18:05:53 -0800426
427 CallStat() {
428 }
429
430 CallStat(String className, int msg) {
431 this.className = className;
432 this.msg = msg;
433 }
434
435 @Override
436 public boolean equals(Object o) {
437 if (this == o) {
438 return true;
439 }
440
441 CallStat callStat = (CallStat) o;
Fyodor Kupolov3f3af612018-04-18 17:26:43 -0700442 return msg == callStat.msg && (className.equals(callStat.className));
Fyodor Kupolovca348512018-01-10 18:05:53 -0800443 }
444
445 @Override
446 public int hashCode() {
447 int result = className.hashCode();
448 result = 31 * result + msg;
449 return result;
450 }
451
452 @Override
453 public String toString() {
Olivier Gaillard103aae22018-06-07 18:20:10 +0100454 return className + "#" + (methodName == null ? msg : methodName);
Fyodor Kupolovca348512018-01-10 18:05:53 -0800455 }
456 }
457
458 public static class CallSession {
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700459 long cpuTimeStarted;
460 long timeStarted;
Olivier Gaillard9429bf52018-05-15 23:25:03 +0100461 boolean exceptionThrown;
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700462 final CallStat callStat = new CallStat();
Fyodor Kupolovca348512018-01-10 18:05:53 -0800463 }
464
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700465 @VisibleForTesting
466 public static class UidEntry {
Fyodor Kupolovca348512018-01-10 18:05:53 -0800467 int uid;
Olivier Gaillard2c13c6f2018-07-16 16:55:58 +0100468 // Number of calls for which we collected data for. We do not record data for all the calls
469 // when sampling is on.
470 public long recordedCallCount;
471 // Real number of total calls.
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700472 public long callCount;
Olivier Gaillard2c13c6f2018-07-16 16:55:58 +0100473 // Total CPU of all for all the recorded calls.
474 // Approximate total CPU usage can be computed by
475 // cpuTimeMicros * callCount / recordedCallCount
476 public long cpuTimeMicros;
Fyodor Kupolovca348512018-01-10 18:05:53 -0800477
478 UidEntry(int uid) {
479 this.uid = uid;
480 }
481
482 // Aggregate time spent per each call name: call_desc -> cpu_time_micros
483 Map<CallStat, CallStat> mCallStats = new ArrayMap<>();
484
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700485 CallStat getOrCreate(CallStat callStat) {
486 CallStat mapCallStat = mCallStats.get(callStat);
487 // Only create CallStat if it's a new entry, otherwise update existing instance
488 if (mapCallStat == null) {
489 mapCallStat = new CallStat(callStat.className, callStat.msg);
490 mCallStats.put(mapCallStat, mapCallStat);
491 }
492 return mapCallStat;
493 }
494
495 /**
496 * Returns list of calls sorted by CPU time
497 */
498 public List<CallStat> getCallStatsList() {
499 List<CallStat> callStats = new ArrayList<>(mCallStats.keySet());
500 callStats.sort((o1, o2) -> {
501 if (o1.cpuTimeMicros < o2.cpuTimeMicros) {
502 return 1;
503 } else if (o1.cpuTimeMicros > o2.cpuTimeMicros) {
504 return -1;
505 }
506 return 0;
507 });
508 return callStats;
509 }
510
Fyodor Kupolovca348512018-01-10 18:05:53 -0800511 @Override
512 public String toString() {
513 return "UidEntry{" +
Olivier Gaillard121988e2018-05-15 20:49:47 +0100514 "cpuTimeMicros=" + cpuTimeMicros +
Fyodor Kupolovca348512018-01-10 18:05:53 -0800515 ", callCount=" + callCount +
516 ", mCallStats=" + mCallStats +
517 '}';
518 }
519
520 @Override
521 public boolean equals(Object o) {
522 if (this == o) {
523 return true;
524 }
525
526 UidEntry uidEntry = (UidEntry) o;
527 return uid == uidEntry.uid;
528 }
529
530 @Override
531 public int hashCode() {
532 return uid;
533 }
534 }
535
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700536 @VisibleForTesting
537 public SparseArray<UidEntry> getUidEntries() {
538 return mUidEntries;
539 }
540
541 @VisibleForTesting
Olivier Gaillardf82d2e732018-06-07 11:45:35 +0100542 public ArrayMap<String, Integer> getExceptionCounts() {
543 return mExceptionCounts;
544 }
545
546 @VisibleForTesting
Fyodor Kupolovcf0fe2d2018-05-22 18:50:04 -0700547 public static <T> List<T> getHighestValues(List<T> list, ToDoubleFunction<T> toDouble,
548 double percentile) {
549 List<T> sortedList = new ArrayList<>(list);
550 sortedList.sort(Comparator.comparingDouble(toDouble).reversed());
551 double total = 0;
552 for (T item : list) {
553 total += toDouble.applyAsDouble(item);
554 }
555 List<T> result = new ArrayList<>();
556 double runningSum = 0;
557 for (T item : sortedList) {
558 if (runningSum > percentile * total) {
559 break;
560 }
561 result.add(item);
562 runningSum += toDouble.applyAsDouble(item);
563 }
564 return result;
565 }
566
Fyodor Kupolovca348512018-01-10 18:05:53 -0800567}