1 /*
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 
17 package com.android.internal.os;
18 
19 import static com.android.internal.os.BinderLatencyProto.Dims.SYSTEM_SERVER;
20 
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.content.Context;
24 import android.database.ContentObserver;
25 import android.net.Uri;
26 import android.os.Binder;
27 import android.os.Handler;
28 import android.os.Looper;
29 import android.os.Process;
30 import android.os.SystemClock;
31 import android.os.UserHandle;
32 import android.provider.Settings;
33 import android.text.format.DateFormat;
34 import android.util.ArrayMap;
35 import android.util.ArraySet;
36 import android.util.IntArray;
37 import android.util.KeyValueListParser;
38 import android.util.Pair;
39 import android.util.Slog;
40 import android.util.SparseArray;
41 
42 import com.android.internal.annotations.GuardedBy;
43 import com.android.internal.annotations.VisibleForTesting;
44 import com.android.internal.os.BinderInternal.CallSession;
45 
46 import java.io.PrintWriter;
47 import java.util.ArrayList;
48 import java.util.Collection;
49 import java.util.Comparator;
50 import java.util.List;
51 import java.util.Queue;
52 import java.util.Random;
53 import java.util.concurrent.ConcurrentLinkedQueue;
54 import java.util.function.ToDoubleFunction;
55 
56 /**
57  * Collects statistics about CPU time spent per binder call across multiple dimensions, e.g.
58  * per thread, uid or call description.
59  */
60 public class BinderCallsStats implements BinderInternal.Observer {
61     public static final boolean ENABLED_DEFAULT = true;
62     public static final boolean DETAILED_TRACKING_DEFAULT = true;
63     public static final int PERIODIC_SAMPLING_INTERVAL_DEFAULT = 1000;
64     public static final boolean DEFAULT_TRACK_SCREEN_INTERACTIVE = false;
65     public static final boolean DEFAULT_TRACK_DIRECT_CALLING_UID = true;
66     public static final boolean DEFAULT_IGNORE_BATTERY_STATUS = false;
67     public static final boolean DEFAULT_COLLECT_LATENCY_DATA = true;
68     public static final int MAX_BINDER_CALL_STATS_COUNT_DEFAULT = 1500;
69     public static final int SHARDING_MODULO_DEFAULT = 1;
70     private static final String DEBUG_ENTRY_PREFIX = "__DEBUG_";
71 
72     private static class OverflowBinder extends Binder {}
73 
74     private static final String TAG = "BinderCallsStats";
75     private static final int CALL_SESSIONS_POOL_SIZE = 100;
76     private static final int MAX_EXCEPTION_COUNT_SIZE = 50;
77     private static final String EXCEPTION_COUNT_OVERFLOW_NAME = "overflow";
78     // Default values for overflow entry. The work source uid does not use a default value in order
79     // to have on overflow entry per work source uid.
80     private static final Class<? extends Binder> OVERFLOW_BINDER = OverflowBinder.class;
81     private static final boolean OVERFLOW_SCREEN_INTERACTIVE = false;
82     private static final int OVERFLOW_DIRECT_CALLING_UID = -1;
83     private static final int OVERFLOW_TRANSACTION_CODE = -1;
84 
85     // Whether to collect all the data: cpu + exceptions + reply/request sizes.
86     private boolean mDetailedTracking = DETAILED_TRACKING_DEFAULT;
87     // If set to true, indicates that all transactions for specific UIDs are being
88     // recorded, ignoring sampling. The UidEntry.recordAllTransactions flag is also set
89     // for the UIDs being tracked.
90     private boolean mRecordingAllTransactionsForUid;
91     // Sampling period to control how often to track CPU usage. 1 means all calls, 100 means ~1 out
92     // of 100 requests.
93     private int mPeriodicSamplingInterval = PERIODIC_SAMPLING_INTERVAL_DEFAULT;
94     private int mMaxBinderCallStatsCount = MAX_BINDER_CALL_STATS_COUNT_DEFAULT;
95     @GuardedBy("mLock")
96     private final SparseArray<UidEntry> mUidEntries = new SparseArray<>();
97     @GuardedBy("mLock")
98     private final ArrayMap<String, Integer> mExceptionCounts = new ArrayMap<>();
99     private final Queue<CallSession> mCallSessionsPool = new ConcurrentLinkedQueue<>();
100     private final Object mLock = new Object();
101     private final Random mRandom;
102     private long mStartCurrentTime = System.currentTimeMillis();
103     private long mStartElapsedTime = SystemClock.elapsedRealtime();
104     private long mCallStatsCount = 0;
105     private boolean mAddDebugEntries = false;
106     private boolean mTrackDirectCallingUid = DEFAULT_TRACK_DIRECT_CALLING_UID;
107     private boolean mTrackScreenInteractive = DEFAULT_TRACK_SCREEN_INTERACTIVE;
108     private boolean mIgnoreBatteryStatus = DEFAULT_IGNORE_BATTERY_STATUS;
109     private boolean mCollectLatencyData = DEFAULT_COLLECT_LATENCY_DATA;
110 
111     // Controls how many APIs will be collected per device. 1 means all APIs, 10 means every 10th
112     // API will be collected.
113     private int mShardingModulo = SHARDING_MODULO_DEFAULT;
114     // Controls which shards will be collected on this device.
115     private int mShardingOffset;
116 
117     private CachedDeviceState.Readonly mDeviceState;
118     private CachedDeviceState.TimeInStateStopwatch mBatteryStopwatch;
119 
120     private static final int CALL_STATS_OBSERVER_DEBOUNCE_MILLIS = 5000;
121     private BinderLatencyObserver mLatencyObserver;
122     private BinderInternal.CallStatsObserver mCallStatsObserver;
123     private ArraySet<Integer> mSendUidsToObserver = new ArraySet<>(32);
124     private final Handler mCallStatsObserverHandler;
125     private Runnable mCallStatsObserverRunnable = new Runnable() {
126         @Override
127         public void run() {
128             if (mCallStatsObserver == null) {
129                 return;
130             }
131 
132             noteCallsStatsDelayed();
133 
134             synchronized (mLock) {
135                 int size = mSendUidsToObserver.size();
136                 for (int i = 0; i < size; i++) {
137                     UidEntry uidEntry = mUidEntries.get(mSendUidsToObserver.valueAt(i));
138                     if (uidEntry != null) {
139                         ArrayMap<CallStatKey, CallStat> callStats = uidEntry.mCallStats;
140                         final int csize = callStats.size();
141                         final ArrayList<CallStat> tmpCallStats = new ArrayList<>(csize);
142                         for (int j = 0; j < csize; j++) {
143                             tmpCallStats.add(callStats.valueAt(j).clone());
144                         }
145                         mCallStatsObserver.noteCallStats(uidEntry.workSourceUid,
146                                 uidEntry.incrementalCallCount, tmpCallStats
147                         );
148                         uidEntry.incrementalCallCount = 0;
149                         for (int j = callStats.size() - 1; j >= 0; j--) {
150                             callStats.valueAt(j).incrementalCallCount = 0;
151                         }
152                     }
153                 }
154                 mSendUidsToObserver.clear();
155             }
156         }
157     };
158 
159     private final Object mNativeTidsLock = new Object();
160     // @GuardedBy("mNativeTidsLock")  // Cannot mark it as "GuardedBy" because it's read
161     // directly, as a volatile field.
162     private volatile IntArray mNativeTids = new IntArray(0);
163 
164     /** Injector for {@link BinderCallsStats}. */
165     public static class Injector {
getRandomGenerator()166         public Random getRandomGenerator() {
167             return new Random();
168         }
169 
getHandler()170         public Handler getHandler() {
171             return new Handler(Looper.getMainLooper());
172         }
173 
174         /** Create a latency observer for the specified process. */
getLatencyObserver(int processSource)175         public BinderLatencyObserver getLatencyObserver(int processSource) {
176             return new BinderLatencyObserver(new BinderLatencyObserver.Injector(), processSource);
177         }
178     }
179 
BinderCallsStats(Injector injector)180     public BinderCallsStats(Injector injector) {
181         this(injector, SYSTEM_SERVER);
182     }
183 
BinderCallsStats(Injector injector, int processSource)184     public BinderCallsStats(Injector injector, int processSource) {
185         this.mRandom = injector.getRandomGenerator();
186         this.mCallStatsObserverHandler = injector.getHandler();
187         this.mLatencyObserver = injector.getLatencyObserver(processSource);
188         this.mShardingOffset = mRandom.nextInt(mShardingModulo);
189     }
190 
setDeviceState(@onNull CachedDeviceState.Readonly deviceState)191     public void setDeviceState(@NonNull CachedDeviceState.Readonly deviceState) {
192         if (mBatteryStopwatch != null) {
193             mBatteryStopwatch.close();
194         }
195         mDeviceState = deviceState;
196         mBatteryStopwatch = deviceState.createTimeOnBatteryStopwatch();
197     }
198 
199     /**
200      * Registers an observer for call stats, which is invoked periodically with accumulated
201      * binder call stats.
202      */
setCallStatsObserver( BinderInternal.CallStatsObserver callStatsObserver)203     public void setCallStatsObserver(
204             BinderInternal.CallStatsObserver callStatsObserver) {
205         mCallStatsObserver = callStatsObserver;
206         noteBinderThreadNativeIds();
207         noteCallsStatsDelayed();
208     }
209 
noteCallsStatsDelayed()210     private void noteCallsStatsDelayed() {
211         mCallStatsObserverHandler.removeCallbacks(mCallStatsObserverRunnable);
212         if (mCallStatsObserver != null) {
213             mCallStatsObserverHandler.postDelayed(mCallStatsObserverRunnable,
214                     CALL_STATS_OBSERVER_DEBOUNCE_MILLIS);
215         }
216     }
217 
218     @Override
219     @Nullable
callStarted(Binder binder, int code, int workSourceUid)220     public CallSession callStarted(Binder binder, int code, int workSourceUid) {
221         noteNativeThreadId();
222 
223         boolean collectCpu = canCollect();
224         // We always want to collect data for latency if it's enabled, regardless of device state.
225         if (!mCollectLatencyData && !collectCpu) {
226             return null;
227         }
228 
229         final CallSession s = obtainCallSession();
230         s.binderClass = binder.getClass();
231         s.transactionCode = code;
232         s.exceptionThrown = false;
233         s.cpuTimeStarted = -1;
234         s.timeStarted = -1;
235         s.recordedCall = shouldRecordDetailedData();
236 
237         if (collectCpu && (mRecordingAllTransactionsForUid || s.recordedCall)) {
238             s.cpuTimeStarted = getThreadTimeMicro();
239             s.timeStarted = getElapsedRealtimeMicro();
240         } else if (mCollectLatencyData) {
241             s.timeStarted = getElapsedRealtimeMicro();
242         }
243 
244         return s;
245     }
246 
obtainCallSession()247     private CallSession obtainCallSession() {
248         CallSession s = mCallSessionsPool.poll();
249         return s == null ? new CallSession() : s;
250     }
251 
252     @Override
callEnded(@ullable CallSession s, int parcelRequestSize, int parcelReplySize, int workSourceUid)253     public void callEnded(@Nullable CallSession s, int parcelRequestSize,
254             int parcelReplySize, int workSourceUid) {
255         if (s == null) {
256             return;
257         }
258 
259         processCallEnded(s, parcelRequestSize, parcelReplySize, workSourceUid);
260 
261         if (mCallSessionsPool.size() < CALL_SESSIONS_POOL_SIZE) {
262             mCallSessionsPool.add(s);
263         }
264     }
265 
processCallEnded(CallSession s, int parcelRequestSize, int parcelReplySize, int workSourceUid)266     private void processCallEnded(CallSession s,
267             int parcelRequestSize, int parcelReplySize, int workSourceUid) {
268         if (mCollectLatencyData) {
269             mLatencyObserver.callEnded(s);
270         }
271 
272         // Latency collection has already been processed so check if the rest should be processed.
273         if (!canCollect()) {
274             return;
275         }
276 
277         UidEntry uidEntry = null;
278         final boolean recordCall;
279         if (s.recordedCall) {
280             recordCall = true;
281         } else if (mRecordingAllTransactionsForUid) {
282             uidEntry = getUidEntry(workSourceUid);
283             recordCall = uidEntry.recordAllTransactions;
284         } else {
285             recordCall = false;
286         }
287 
288         final long duration;
289         final long latencyDuration;
290         if (recordCall) {
291             duration = getThreadTimeMicro() - s.cpuTimeStarted;
292             latencyDuration = getElapsedRealtimeMicro() - s.timeStarted;
293         } else {
294             duration = 0;
295             latencyDuration = 0;
296         }
297         final boolean screenInteractive = mTrackScreenInteractive
298                 ? mDeviceState.isScreenInteractive()
299                 : OVERFLOW_SCREEN_INTERACTIVE;
300         final int callingUid = mTrackDirectCallingUid
301                 ? getCallingUid()
302                 : OVERFLOW_DIRECT_CALLING_UID;
303 
304         synchronized (mLock) {
305             // This was already checked in #callStart but check again while synchronized.
306             if (!canCollect()) {
307                 return;
308             }
309 
310             if (uidEntry == null) {
311                 uidEntry = getUidEntry(workSourceUid);
312             }
313 
314             uidEntry.callCount++;
315             uidEntry.incrementalCallCount++;
316             if (recordCall) {
317                 uidEntry.cpuTimeMicros += duration;
318                 uidEntry.recordedCallCount++;
319 
320                 final CallStat callStat = uidEntry.getOrCreate(
321                         callingUid, s.binderClass, s.transactionCode,
322                         screenInteractive,
323                         mCallStatsCount >= mMaxBinderCallStatsCount);
324                 final boolean isNewCallStat = callStat.callCount == 0;
325                 if (isNewCallStat) {
326                     mCallStatsCount++;
327                 }
328 
329                 callStat.callCount++;
330                 callStat.incrementalCallCount++;
331                 callStat.recordedCallCount++;
332                 callStat.cpuTimeMicros += duration;
333                 callStat.maxCpuTimeMicros = Math.max(callStat.maxCpuTimeMicros, duration);
334                 callStat.latencyMicros += latencyDuration;
335                 callStat.maxLatencyMicros =
336                         Math.max(callStat.maxLatencyMicros, latencyDuration);
337                 if (mDetailedTracking) {
338                     callStat.exceptionCount += s.exceptionThrown ? 1 : 0;
339                     callStat.maxRequestSizeBytes =
340                             Math.max(callStat.maxRequestSizeBytes, parcelRequestSize);
341                     callStat.maxReplySizeBytes =
342                             Math.max(callStat.maxReplySizeBytes, parcelReplySize);
343                 }
344             } else {
345                 // Only record the total call count if we already track data for this key.
346                 // It helps to keep the memory usage down when sampling is enabled.
347                 final CallStat callStat = uidEntry.get(
348                         callingUid, s.binderClass, s.transactionCode,
349                         screenInteractive);
350                 if (callStat != null) {
351                     callStat.callCount++;
352                     callStat.incrementalCallCount++;
353                 }
354             }
355             if (mCallStatsObserver != null && !UserHandle.isCore(workSourceUid)) {
356                 mSendUidsToObserver.add(workSourceUid);
357             }
358         }
359     }
360 
shouldExport(ExportedCallStat e, boolean applySharding)361     private boolean shouldExport(ExportedCallStat e, boolean applySharding) {
362         if (!applySharding) {
363             return true;
364         }
365 
366         int hash = e.binderClass.hashCode();
367         hash = 31 * hash + e.transactionCode;
368         hash = 31 * hash + e.callingUid;
369         hash = 31 * hash + (e.screenInteractive ? 1231 : 1237);
370 
371         return (hash + mShardingOffset) % mShardingModulo == 0;
372     }
373 
getUidEntry(int uid)374     private UidEntry getUidEntry(int uid) {
375         UidEntry uidEntry = mUidEntries.get(uid);
376         if (uidEntry == null) {
377             uidEntry = new UidEntry(uid);
378             mUidEntries.put(uid, uidEntry);
379         }
380         return uidEntry;
381     }
382 
383     @Override
callThrewException(@ullable CallSession s, Exception exception)384     public void callThrewException(@Nullable CallSession s, Exception exception) {
385         if (s == null) {
386             return;
387         }
388         s.exceptionThrown = true;
389         try {
390             String className = exception.getClass().getName();
391             synchronized (mLock) {
392                 if (mExceptionCounts.size() >= MAX_EXCEPTION_COUNT_SIZE) {
393                     className = EXCEPTION_COUNT_OVERFLOW_NAME;
394                 }
395                 final Integer count = mExceptionCounts.get(className);
396                 mExceptionCounts.put(className, count == null ? 1 : count + 1);
397             }
398         } catch (RuntimeException e) {
399             // Do not propagate the exception. We do not want to swallow original exception.
400             Slog.wtf(TAG, "Unexpected exception while updating mExceptionCounts");
401         }
402     }
403 
noteNativeThreadId()404     private void noteNativeThreadId() {
405         final int tid = getNativeTid();
406         int index = mNativeTids.binarySearch(tid);
407         if (index >= 0) {
408             return;
409         }
410 
411         // Use the copy-on-write approach. The changes occur exceedingly infrequently, so
412         // this code path is exercised just a few times per boot
413         synchronized (mNativeTidsLock) {
414             IntArray nativeTids = mNativeTids;
415             index = nativeTids.binarySearch(tid);
416             if (index < 0) {
417                 IntArray copyOnWriteArray = new IntArray(nativeTids.size() + 1);
418                 copyOnWriteArray.addAll(nativeTids);
419                 copyOnWriteArray.add(-index - 1, tid);
420                 mNativeTids = copyOnWriteArray;
421             }
422         }
423 
424         noteBinderThreadNativeIds();
425     }
426 
noteBinderThreadNativeIds()427     private void noteBinderThreadNativeIds() {
428         if (mCallStatsObserver == null) {
429             return;
430         }
431 
432         mCallStatsObserver.noteBinderThreadNativeIds(getNativeTids());
433     }
434 
canCollect()435     private boolean canCollect() {
436         if (mRecordingAllTransactionsForUid) {
437             return true;
438         }
439         if (mIgnoreBatteryStatus) {
440             return true;
441         }
442         if (mDeviceState == null) {
443             return false;
444         }
445         if (mDeviceState.isCharging()) {
446             return false;
447         }
448         return true;
449     }
450 
451     /**
452      * This method is expensive to call.
453      */
getExportedCallStats()454     public ArrayList<ExportedCallStat> getExportedCallStats() {
455         return getExportedCallStats(false);
456     }
457 
458     /**
459      * This method is expensive to call.
460      * Exports call stats and applies sharding if requested.
461      */
462     @VisibleForTesting
getExportedCallStats(boolean applySharding)463     public ArrayList<ExportedCallStat> getExportedCallStats(boolean applySharding) {
464         // We do not collect all the data if detailed tracking is off.
465         if (!mDetailedTracking) {
466             return new ArrayList<>();
467         }
468 
469         ArrayList<ExportedCallStat> resultCallStats = new ArrayList<>();
470         synchronized (mLock) {
471             final int uidEntriesSize = mUidEntries.size();
472             for (int entryIdx = 0; entryIdx < uidEntriesSize; entryIdx++) {
473                 final UidEntry entry = mUidEntries.valueAt(entryIdx);
474                 for (CallStat stat : entry.getCallStatsList()) {
475                     ExportedCallStat e = getExportedCallStat(entry.workSourceUid, stat);
476                     if (shouldExport(e, applySharding)) {
477                         resultCallStats.add(e);
478                     }
479                 }
480             }
481         }
482 
483         // Resolve codes outside of the lock since it can be slow.
484         resolveBinderMethodNames(resultCallStats);
485 
486         // Debug entries added to help validate the data.
487         if (mAddDebugEntries && mBatteryStopwatch != null) {
488             resultCallStats.add(createDebugEntry("start_time_millis", mStartElapsedTime));
489             resultCallStats.add(createDebugEntry("end_time_millis", SystemClock.elapsedRealtime()));
490             resultCallStats.add(
491                     createDebugEntry("battery_time_millis", mBatteryStopwatch.getMillis()));
492             resultCallStats.add(createDebugEntry("sampling_interval", mPeriodicSamplingInterval));
493             resultCallStats.add(createDebugEntry("sharding_modulo", mShardingModulo));
494         }
495 
496         return resultCallStats;
497     }
498 
499     /**
500      * This method is expensive to call.
501      */
getExportedCallStats(int workSourceUid)502     public ArrayList<ExportedCallStat> getExportedCallStats(int workSourceUid) {
503         return getExportedCallStats(workSourceUid, false);
504     }
505 
506     /**
507      * This method is expensive to call.
508      * Exports call stats and applies sharding if requested.
509      */
510     @VisibleForTesting
getExportedCallStats( int workSourceUid, boolean applySharding)511     public ArrayList<ExportedCallStat> getExportedCallStats(
512                 int workSourceUid, boolean applySharding) {
513         ArrayList<ExportedCallStat> resultCallStats = new ArrayList<>();
514         synchronized (mLock) {
515             final UidEntry entry = getUidEntry(workSourceUid);
516             for (CallStat stat : entry.getCallStatsList()) {
517                 ExportedCallStat e = getExportedCallStat(workSourceUid, stat);
518                 if (shouldExport(e, applySharding)) {
519                     resultCallStats.add(e);
520                 }
521             }
522         }
523 
524         // Resolve codes outside of the lock since it can be slow.
525         resolveBinderMethodNames(resultCallStats);
526 
527         return resultCallStats;
528     }
529 
getExportedCallStat(int workSourceUid, CallStat stat)530     private ExportedCallStat getExportedCallStat(int workSourceUid, CallStat stat) {
531         ExportedCallStat exported = new ExportedCallStat();
532         exported.workSourceUid = workSourceUid;
533         exported.callingUid = stat.callingUid;
534         exported.className = stat.binderClass.getName();
535         exported.binderClass = stat.binderClass;
536         exported.transactionCode = stat.transactionCode;
537         exported.screenInteractive = stat.screenInteractive;
538         exported.cpuTimeMicros = stat.cpuTimeMicros;
539         exported.maxCpuTimeMicros = stat.maxCpuTimeMicros;
540         exported.latencyMicros = stat.latencyMicros;
541         exported.maxLatencyMicros = stat.maxLatencyMicros;
542         exported.recordedCallCount = stat.recordedCallCount;
543         exported.callCount = stat.callCount;
544         exported.maxRequestSizeBytes = stat.maxRequestSizeBytes;
545         exported.maxReplySizeBytes = stat.maxReplySizeBytes;
546         exported.exceptionCount = stat.exceptionCount;
547         return exported;
548     }
549 
resolveBinderMethodNames( ArrayList<ExportedCallStat> resultCallStats)550     private void resolveBinderMethodNames(
551             ArrayList<ExportedCallStat> resultCallStats) {
552         // Resolve codes outside of the lock since it can be slow.
553         ExportedCallStat previous = null;
554         String previousMethodName = null;
555         resultCallStats.sort(BinderCallsStats::compareByBinderClassAndCode);
556         BinderTransactionNameResolver resolver = new BinderTransactionNameResolver();
557         for (ExportedCallStat exported : resultCallStats) {
558             final boolean isClassDifferent = previous == null
559                     || !previous.className.equals(exported.className);
560             final boolean isCodeDifferent = previous == null
561                     || previous.transactionCode != exported.transactionCode;
562             final String methodName;
563             if (isClassDifferent || isCodeDifferent) {
564                 methodName = resolver.getMethodName(exported.binderClass, exported.transactionCode);
565             } else {
566                 methodName = previousMethodName;
567             }
568             previousMethodName = methodName;
569             exported.methodName = methodName;
570             previous = exported;
571         }
572     }
573 
createDebugEntry(String variableName, long value)574     private ExportedCallStat createDebugEntry(String variableName, long value) {
575         final int uid = Process.myUid();
576         final ExportedCallStat callStat = new ExportedCallStat();
577         callStat.className = "";
578         callStat.workSourceUid = uid;
579         callStat.callingUid = uid;
580         callStat.recordedCallCount = 1;
581         callStat.callCount = 1;
582         callStat.methodName = DEBUG_ENTRY_PREFIX + variableName;
583         callStat.latencyMicros = value;
584         return callStat;
585     }
586 
587     /** @hide */
getExportedExceptionStats()588     public ArrayMap<String, Integer> getExportedExceptionStats() {
589         synchronized (mLock) {
590             return new ArrayMap(mExceptionCounts);
591         }
592     }
593 
594     /** Writes the collected statistics to the supplied {@link PrintWriter}.*/
dump(PrintWriter pw, AppIdToPackageMap packageMap, int workSourceUid, boolean verbose)595     public void dump(PrintWriter pw, AppIdToPackageMap packageMap, int workSourceUid,
596             boolean verbose) {
597         synchronized (mLock) {
598             dumpLocked(pw, packageMap, workSourceUid, verbose);
599         }
600     }
601 
dumpLocked(PrintWriter pw, AppIdToPackageMap packageMap, int workSourceUid, boolean verbose)602     private void dumpLocked(PrintWriter pw, AppIdToPackageMap packageMap, int workSourceUid,
603             boolean verbose) {
604         if (workSourceUid != Process.INVALID_UID) {
605             verbose = true;
606         }
607         pw.print("Start time: ");
608         pw.println(DateFormat.format("yyyy-MM-dd HH:mm:ss", mStartCurrentTime));
609         pw.print("On battery time (ms): ");
610         pw.println(mBatteryStopwatch != null ? mBatteryStopwatch.getMillis() : 0);
611         pw.println("Sampling interval period: " + mPeriodicSamplingInterval);
612         pw.println("Sharding modulo: " + mShardingModulo);
613 
614         final String datasetSizeDesc = verbose ? "" : "(top 90% by cpu time) ";
615         final StringBuilder sb = new StringBuilder();
616         pw.println("Per-UID raw data " + datasetSizeDesc
617                 + "(package/uid, worksource, call_desc, screen_interactive, "
618                 + "cpu_time_micros, max_cpu_time_micros, "
619                 + "latency_time_micros, max_latency_time_micros, exception_count, "
620                 + "max_request_size_bytes, max_reply_size_bytes, recorded_call_count, "
621                 + "call_count):");
622         final List<ExportedCallStat> exportedCallStats;
623         if (workSourceUid != Process.INVALID_UID) {
624             exportedCallStats = getExportedCallStats(workSourceUid, true);
625         } else {
626             exportedCallStats = getExportedCallStats(true);
627         }
628         exportedCallStats.sort(BinderCallsStats::compareByCpuDesc);
629         for (ExportedCallStat e : exportedCallStats) {
630             if (e.methodName != null && e.methodName.startsWith(DEBUG_ENTRY_PREFIX)) {
631                 // Do not dump debug entries.
632                 continue;
633             }
634             sb.setLength(0);
635             sb.append("    ")
636                     .append(packageMap.mapUid(e.callingUid))
637                     .append(',')
638                     .append(packageMap.mapUid(e.workSourceUid))
639                     .append(',').append(e.className)
640                     .append('#').append(e.methodName)
641                     .append(',').append(e.screenInteractive)
642                     .append(',').append(e.cpuTimeMicros)
643                     .append(',').append(e.maxCpuTimeMicros)
644                     .append(',').append(e.latencyMicros)
645                     .append(',').append(e.maxLatencyMicros)
646                     .append(',').append(mDetailedTracking ? e.exceptionCount : '_')
647                     .append(',').append(mDetailedTracking ? e.maxRequestSizeBytes : '_')
648                     .append(',').append(mDetailedTracking ? e.maxReplySizeBytes : '_')
649                     .append(',').append(e.recordedCallCount)
650                     .append(',').append(e.callCount);
651             pw.println(sb);
652         }
653         pw.println();
654         final List<UidEntry> entries = new ArrayList<>();
655         long totalCallsCount = 0;
656         long totalRecordedCallsCount = 0;
657         long totalCpuTime = 0;
658 
659         if (workSourceUid != Process.INVALID_UID) {
660             UidEntry e = getUidEntry(workSourceUid);
661             entries.add(e);
662             totalCpuTime += e.cpuTimeMicros;
663             totalRecordedCallsCount += e.recordedCallCount;
664             totalCallsCount += e.callCount;
665         } else {
666             final int uidEntriesSize = mUidEntries.size();
667             for (int i = 0; i < uidEntriesSize; i++) {
668                 UidEntry e = mUidEntries.valueAt(i);
669                 entries.add(e);
670                 totalCpuTime += e.cpuTimeMicros;
671                 totalRecordedCallsCount += e.recordedCallCount;
672                 totalCallsCount += e.callCount;
673             }
674             entries.sort(
675                     Comparator.<UidEntry>comparingDouble(value -> value.cpuTimeMicros).reversed());
676         }
677 
678         pw.println("Per-UID Summary " + datasetSizeDesc
679                 + "(cpu_time, % of total cpu_time, recorded_call_count, call_count, package/uid):");
680         final List<UidEntry> summaryEntries = verbose ? entries
681                 : getHighestValues(entries, value -> value.cpuTimeMicros, 0.9);
682         for (UidEntry entry : summaryEntries) {
683             String uidStr = packageMap.mapUid(entry.workSourceUid);
684             pw.println(String.format("  %10d %3.0f%% %8d %8d %s",
685                     entry.cpuTimeMicros, 100d * entry.cpuTimeMicros / totalCpuTime,
686                     entry.recordedCallCount, entry.callCount, uidStr));
687         }
688         pw.println();
689         if (workSourceUid == Process.INVALID_UID) {
690             pw.println(String.format("  Summary: total_cpu_time=%d, "
691                             + "calls_count=%d, avg_call_cpu_time=%.0f",
692                     totalCpuTime, totalCallsCount,
693                     (double) totalCpuTime / totalRecordedCallsCount));
694             pw.println();
695         }
696 
697         pw.println("Exceptions thrown (exception_count, class_name):");
698         final List<Pair<String, Integer>> exceptionEntries = new ArrayList<>();
699         // We cannot use new ArrayList(Collection) constructor because MapCollections does not
700         // implement toArray method.
701         mExceptionCounts.entrySet().iterator().forEachRemaining(
702                 (e) -> exceptionEntries.add(Pair.create(e.getKey(), e.getValue())));
703         exceptionEntries.sort((e1, e2) -> Integer.compare(e2.second, e1.second));
704         for (Pair<String, Integer> entry : exceptionEntries) {
705             pw.println(String.format("  %6d %s", entry.second, entry.first));
706         }
707 
708         if (mPeriodicSamplingInterval != 1) {
709             pw.println("");
710             pw.println("/!\\ Displayed data is sampled. See sampling interval at the top.");
711         }
712     }
713 
getThreadTimeMicro()714     protected long getThreadTimeMicro() {
715         return SystemClock.currentThreadTimeMicro();
716     }
717 
getCallingUid()718     protected int getCallingUid() {
719         return Binder.getCallingUid();
720     }
721 
getNativeTid()722     protected int getNativeTid() {
723         return Process.myTid();
724     }
725 
726     /**
727      * Returns known Linux TIDs for threads taking incoming binder calls.
728      */
getNativeTids()729     public int[] getNativeTids() {
730         return mNativeTids.toArray();
731     }
732 
getElapsedRealtimeMicro()733     protected long getElapsedRealtimeMicro() {
734         return SystemClock.elapsedRealtimeNanos() / 1000;
735     }
736 
shouldRecordDetailedData()737     protected boolean shouldRecordDetailedData() {
738         return mRandom.nextInt(mPeriodicSamplingInterval) == 0;
739     }
740 
741     /**
742      * Sets to true to collect all the data.
743      */
setDetailedTracking(boolean enabled)744     public void setDetailedTracking(boolean enabled) {
745         synchronized (mLock) {
746             if (enabled != mDetailedTracking) {
747                 mDetailedTracking = enabled;
748                 reset();
749             }
750         }
751     }
752 
753     /**
754      * Whether to track the screen state.
755      */
setTrackScreenInteractive(boolean enabled)756     public void setTrackScreenInteractive(boolean enabled) {
757         synchronized (mLock) {
758             if (enabled != mTrackScreenInteractive) {
759                 mTrackScreenInteractive = enabled;
760                 reset();
761             }
762         }
763     }
764 
765     /**
766      * Whether to track direct caller uid.
767      */
setTrackDirectCallerUid(boolean enabled)768     public void setTrackDirectCallerUid(boolean enabled) {
769         synchronized (mLock) {
770             if (enabled != mTrackDirectCallingUid) {
771                 mTrackDirectCallingUid = enabled;
772                 reset();
773             }
774         }
775     }
776 
777     /**
778      * Whether to ignore battery status when collecting stats
779      */
setIgnoreBatteryStatus(boolean ignored)780     public void setIgnoreBatteryStatus(boolean ignored) {
781         synchronized (mLock) {
782             if (ignored != mIgnoreBatteryStatus) {
783                 mIgnoreBatteryStatus = ignored;
784                 reset();
785             }
786         }
787     }
788 
789     /**
790      * Marks the specified work source UID for total binder call tracking: detailed information
791      * will be recorded for all calls from this source ID.
792      *
793      * This is expensive and can cause memory pressure, therefore this mode should only be used
794      * for debugging.
795      */
recordAllCallsForWorkSourceUid(int workSourceUid)796     public void recordAllCallsForWorkSourceUid(int workSourceUid) {
797         setDetailedTracking(true);
798 
799         Slog.i(TAG, "Recording all Binder calls for UID: "  + workSourceUid);
800         UidEntry uidEntry = getUidEntry(workSourceUid);
801         uidEntry.recordAllTransactions = true;
802         mRecordingAllTransactionsForUid = true;
803     }
804 
setAddDebugEntries(boolean addDebugEntries)805     public void setAddDebugEntries(boolean addDebugEntries) {
806         mAddDebugEntries = addDebugEntries;
807     }
808 
809     /**
810      * Sets the maximum number of items to track.
811      */
setMaxBinderCallStats(int maxKeys)812     public void setMaxBinderCallStats(int maxKeys) {
813         if (maxKeys <= 0) {
814             Slog.w(TAG, "Ignored invalid max value (value must be positive): "
815                     + maxKeys);
816             return;
817         }
818 
819         synchronized (mLock) {
820             if (maxKeys != mMaxBinderCallStatsCount) {
821                 mMaxBinderCallStatsCount = maxKeys;
822                 reset();
823             }
824         }
825     }
826 
setSamplingInterval(int samplingInterval)827     public void setSamplingInterval(int samplingInterval) {
828         if (samplingInterval <= 0) {
829             Slog.w(TAG, "Ignored invalid sampling interval (value must be positive): "
830                     + samplingInterval);
831             return;
832         }
833 
834         synchronized (mLock) {
835             if (samplingInterval != mPeriodicSamplingInterval) {
836                 mPeriodicSamplingInterval = samplingInterval;
837                 reset();
838             }
839         }
840     }
841 
842     /** Updates the sharding modulo. */
setShardingModulo(int shardingModulo)843     public void setShardingModulo(int shardingModulo) {
844         if (shardingModulo <= 0) {
845             Slog.w(TAG, "Ignored invalid sharding modulo (value must be positive): "
846                     + shardingModulo);
847             return;
848         }
849 
850         synchronized (mLock) {
851             if (shardingModulo != mShardingModulo) {
852                 mShardingModulo = shardingModulo;
853                 mShardingOffset = mRandom.nextInt(shardingModulo);
854                 reset();
855             }
856         }
857     }
858 
859     /** Whether to collect latency histograms. */
setCollectLatencyData(boolean collectLatencyData)860     public void setCollectLatencyData(boolean collectLatencyData) {
861         mCollectLatencyData = collectLatencyData;
862     }
863 
864     /** Whether to collect latency histograms. */
865     @VisibleForTesting
getCollectLatencyData()866     public boolean getCollectLatencyData() {
867         return mCollectLatencyData;
868     }
869 
reset()870     public void reset() {
871         synchronized (mLock) {
872             mCallStatsCount = 0;
873             mUidEntries.clear();
874             mExceptionCounts.clear();
875             mStartCurrentTime = System.currentTimeMillis();
876             mStartElapsedTime = SystemClock.elapsedRealtime();
877             if (mBatteryStopwatch != null) {
878                 mBatteryStopwatch.reset();
879             }
880             mRecordingAllTransactionsForUid = false;
881             // Do not reset the latency observer as binder stats and latency will be pushed to WW
882             // at different intervals so the resets should not be coupled.
883         }
884     }
885 
886     /**
887      * Aggregated data by uid/class/method to be sent through statsd.
888      */
889     public static class ExportedCallStat {
890         public int callingUid;
891         public int workSourceUid;
892         public String className;
893         public String methodName;
894         public boolean screenInteractive;
895         public long cpuTimeMicros;
896         public long maxCpuTimeMicros;
897         public long latencyMicros;
898         public long maxLatencyMicros;
899         public long callCount;
900         public long recordedCallCount;
901         public long maxRequestSizeBytes;
902         public long maxReplySizeBytes;
903         public long exceptionCount;
904 
905         // Used internally.
906         Class<? extends Binder> binderClass;
907         int transactionCode;
908     }
909 
910     @VisibleForTesting
911     public static class CallStat {
912         // The UID who executed the transaction (i.e. Binder#getCallingUid).
913         public final int callingUid;
914         public final Class<? extends Binder> binderClass;
915         public final int transactionCode;
916         // True if the screen was interactive when the call ended.
917         public final boolean screenInteractive;
918         // Number of calls for which we collected data for. We do not record data for all the calls
919         // when sampling is on.
920         public long recordedCallCount;
921         // Roughly the real number of total calls. We only track only track the API call count once
922         // at least one non-sampled count happened.
923         public long callCount;
924         // Total CPU of all for all the recorded calls.
925         // Approximate total CPU usage can be computed by
926         // cpuTimeMicros * callCount / recordedCallCount
927         public long cpuTimeMicros;
928         public long maxCpuTimeMicros;
929         // Total latency of all for all the recorded calls.
930         // Approximate average latency can be computed by
931         // latencyMicros * callCount / recordedCallCount
932         public long latencyMicros;
933         public long maxLatencyMicros;
934         // The following fields are only computed if mDetailedTracking is set.
935         public long maxRequestSizeBytes;
936         public long maxReplySizeBytes;
937         public long exceptionCount;
938         // Call count since reset
939         public long incrementalCallCount;
940 
CallStat(int callingUid, Class<? extends Binder> binderClass, int transactionCode, boolean screenInteractive)941         public CallStat(int callingUid, Class<? extends Binder> binderClass, int transactionCode,
942                 boolean screenInteractive) {
943             this.callingUid = callingUid;
944             this.binderClass = binderClass;
945             this.transactionCode = transactionCode;
946             this.screenInteractive = screenInteractive;
947         }
948 
949         @Override
clone()950         public CallStat clone() {
951             CallStat clone = new CallStat(callingUid, binderClass, transactionCode,
952                     screenInteractive);
953             clone.recordedCallCount = recordedCallCount;
954             clone.callCount = callCount;
955             clone.cpuTimeMicros = cpuTimeMicros;
956             clone.maxCpuTimeMicros = maxCpuTimeMicros;
957             clone.latencyMicros = latencyMicros;
958             clone.maxLatencyMicros = maxLatencyMicros;
959             clone.maxRequestSizeBytes = maxRequestSizeBytes;
960             clone.maxReplySizeBytes = maxReplySizeBytes;
961             clone.exceptionCount = exceptionCount;
962             clone.incrementalCallCount = incrementalCallCount;
963             return clone;
964         }
965 
966         @Override
toString()967         public String toString() {
968             // This is expensive, but CallStat.toString() is only used for debugging.
969             String methodName = new BinderTransactionNameResolver().getMethodName(binderClass,
970                     transactionCode);
971             return "CallStat{"
972                     + "callingUid=" + callingUid
973                     + ", transaction=" + binderClass.getSimpleName() + '.' + methodName
974                     + ", callCount=" + callCount
975                     + ", incrementalCallCount=" + incrementalCallCount
976                     + ", recordedCallCount=" + recordedCallCount
977                     + ", cpuTimeMicros=" + cpuTimeMicros
978                     + ", latencyMicros=" + latencyMicros
979                     + '}';
980         }
981     }
982 
983     /** Key used to store CallStat object in a Map. */
984     public static class CallStatKey {
985         public int callingUid;
986         public Class<? extends Binder> binderClass;
987         public int transactionCode;
988         private boolean screenInteractive;
989 
990         @Override
equals(Object o)991         public boolean equals(Object o) {
992             if (this == o) {
993                 return true;
994             }
995 
996             final CallStatKey key = (CallStatKey) o;
997             return callingUid == key.callingUid
998                     && transactionCode == key.transactionCode
999                     && screenInteractive == key.screenInteractive
1000                     && (binderClass.equals(key.binderClass));
1001         }
1002 
1003         @Override
hashCode()1004         public int hashCode() {
1005             int result = binderClass.hashCode();
1006             result = 31 * result + transactionCode;
1007             result = 31 * result + callingUid;
1008             result = 31 * result + (screenInteractive ? 1231 : 1237);
1009             return result;
1010         }
1011     }
1012 
1013 
1014     @VisibleForTesting
1015     public static class UidEntry {
1016         // The UID who is responsible for the binder transaction. If the bluetooth process execute a
1017         // transaction on behalf of app foo, the workSourceUid will be the uid of app foo.
1018         public int workSourceUid;
1019         // Number of calls for which we collected data for. We do not record data for all the calls
1020         // when sampling is on.
1021         public long recordedCallCount;
1022         // Real number of total calls.
1023         public long callCount;
1024         // Total CPU of all for all the recorded calls.
1025         // Approximate total CPU usage can be computed by
1026         // cpuTimeMicros * callCount / recordedCallCount
1027         public long cpuTimeMicros;
1028         // Call count that gets reset after delivery to BatteryStats
1029         public long incrementalCallCount;
1030         // Indicates that all transactions for the UID must be tracked
1031         public boolean recordAllTransactions;
1032 
UidEntry(int uid)1033         UidEntry(int uid) {
1034             this.workSourceUid = uid;
1035         }
1036 
1037         // Aggregate time spent per each call name: call_desc -> cpu_time_micros
1038         private ArrayMap<CallStatKey, CallStat> mCallStats = new ArrayMap<>();
1039         private CallStatKey mTempKey = new CallStatKey();
1040 
1041         @Nullable
get(int callingUid, Class<? extends Binder> binderClass, int transactionCode, boolean screenInteractive)1042         CallStat get(int callingUid, Class<? extends Binder> binderClass, int transactionCode,
1043                 boolean screenInteractive) {
1044             // Use a global temporary key to avoid creating new objects for every lookup.
1045             mTempKey.callingUid = callingUid;
1046             mTempKey.binderClass = binderClass;
1047             mTempKey.transactionCode = transactionCode;
1048             mTempKey.screenInteractive = screenInteractive;
1049             return mCallStats.get(mTempKey);
1050         }
1051 
getOrCreate(int callingUid, Class<? extends Binder> binderClass, int transactionCode, boolean screenInteractive, boolean maxCallStatsReached)1052         CallStat getOrCreate(int callingUid, Class<? extends Binder> binderClass,
1053                 int transactionCode, boolean screenInteractive, boolean maxCallStatsReached) {
1054             CallStat mapCallStat = get(callingUid, binderClass, transactionCode, screenInteractive);
1055             // Only create CallStat if it's a new entry, otherwise update existing instance.
1056             if (mapCallStat == null) {
1057                 if (maxCallStatsReached) {
1058                     mapCallStat = get(OVERFLOW_DIRECT_CALLING_UID, OVERFLOW_BINDER,
1059                             OVERFLOW_TRANSACTION_CODE, OVERFLOW_SCREEN_INTERACTIVE);
1060                     if (mapCallStat != null) {
1061                         return mapCallStat;
1062                     }
1063 
1064                     callingUid = OVERFLOW_DIRECT_CALLING_UID;
1065                     binderClass = OVERFLOW_BINDER;
1066                     transactionCode = OVERFLOW_TRANSACTION_CODE;
1067                     screenInteractive = OVERFLOW_SCREEN_INTERACTIVE;
1068                 }
1069 
1070                 mapCallStat = new CallStat(callingUid, binderClass, transactionCode,
1071                         screenInteractive);
1072                 CallStatKey key = new CallStatKey();
1073                 key.callingUid = callingUid;
1074                 key.binderClass = binderClass;
1075                 key.transactionCode = transactionCode;
1076                 key.screenInteractive = screenInteractive;
1077                 mCallStats.put(key, mapCallStat);
1078             }
1079             return mapCallStat;
1080         }
1081 
1082         /**
1083          * Returns list of calls sorted by CPU time
1084          */
getCallStatsList()1085         public Collection<CallStat> getCallStatsList() {
1086             return mCallStats.values();
1087         }
1088 
1089         @Override
toString()1090         public String toString() {
1091             return "UidEntry{" +
1092                     "cpuTimeMicros=" + cpuTimeMicros +
1093                     ", callCount=" + callCount +
1094                     ", mCallStats=" + mCallStats +
1095                     '}';
1096         }
1097 
1098         @Override
equals(Object o)1099         public boolean equals(Object o) {
1100             if (this == o) {
1101                 return true;
1102             }
1103 
1104             UidEntry uidEntry = (UidEntry) o;
1105             return workSourceUid == uidEntry.workSourceUid;
1106         }
1107 
1108         @Override
hashCode()1109         public int hashCode() {
1110             return workSourceUid;
1111         }
1112     }
1113 
1114     @VisibleForTesting
getUidEntries()1115     public SparseArray<UidEntry> getUidEntries() {
1116         return mUidEntries;
1117     }
1118 
1119     @VisibleForTesting
getExceptionCounts()1120     public ArrayMap<String, Integer> getExceptionCounts() {
1121         return mExceptionCounts;
1122     }
1123 
getLatencyObserver()1124     public BinderLatencyObserver getLatencyObserver() {
1125         return mLatencyObserver;
1126     }
1127 
1128     @VisibleForTesting
getHighestValues(List<T> list, ToDoubleFunction<T> toDouble, double percentile)1129     public static <T> List<T> getHighestValues(List<T> list, ToDoubleFunction<T> toDouble,
1130             double percentile) {
1131         List<T> sortedList = new ArrayList<>(list);
1132         sortedList.sort(Comparator.comparingDouble(toDouble).reversed());
1133         double total = 0;
1134         for (T item : list) {
1135             total += toDouble.applyAsDouble(item);
1136         }
1137         List<T> result = new ArrayList<>();
1138         double runningSum = 0;
1139         for (T item : sortedList) {
1140             if (runningSum > percentile * total) {
1141                 break;
1142             }
1143             result.add(item);
1144             runningSum += toDouble.applyAsDouble(item);
1145         }
1146         return result;
1147     }
1148 
compareByCpuDesc( ExportedCallStat a, ExportedCallStat b)1149     private static int compareByCpuDesc(
1150             ExportedCallStat a, ExportedCallStat b) {
1151         return Long.compare(b.cpuTimeMicros, a.cpuTimeMicros);
1152     }
1153 
compareByBinderClassAndCode( ExportedCallStat a, ExportedCallStat b)1154     private static int compareByBinderClassAndCode(
1155             ExportedCallStat a, ExportedCallStat b) {
1156         int result = a.className.compareTo(b.className);
1157         return result != 0
1158                 ? result
1159                 : Integer.compare(a.transactionCode, b.transactionCode);
1160     }
1161 
1162     /** @hide */
startForBluetooth(Context context)1163     public static void startForBluetooth(Context context) {
1164         new BinderCallsStats.SettingsObserver(
1165                     context,
1166                     new BinderCallsStats(
1167                             new BinderCallsStats.Injector(),
1168                               com.android.internal.os.BinderLatencyProto.Dims.BLUETOOTH));
1169 
1170     }
1171 
1172 
1173 
1174     /**
1175      * Settings observer for other processes (not system_server).
1176      *
1177      * We do not want to collect cpu data from other processes so only latency collection should be
1178      * possible to enable.
1179      */
1180     public static class SettingsObserver extends ContentObserver {
1181         // Settings for BinderCallsStats.
1182         public static final String SETTINGS_ENABLED_KEY = "enabled";
1183         public static final String SETTINGS_DETAILED_TRACKING_KEY = "detailed_tracking";
1184         public static final String SETTINGS_UPLOAD_DATA_KEY = "upload_data";
1185         public static final String SETTINGS_SAMPLING_INTERVAL_KEY = "sampling_interval";
1186         public static final String SETTINGS_TRACK_SCREEN_INTERACTIVE_KEY = "track_screen_state";
1187         public static final String SETTINGS_TRACK_DIRECT_CALLING_UID_KEY = "track_calling_uid";
1188         public static final String SETTINGS_MAX_CALL_STATS_KEY = "max_call_stats_count";
1189         public static final String SETTINGS_IGNORE_BATTERY_STATUS_KEY = "ignore_battery_status";
1190         public static final String SETTINGS_SHARDING_MODULO_KEY = "sharding_modulo";
1191         // Settings for BinderLatencyObserver.
1192         public static final String SETTINGS_COLLECT_LATENCY_DATA_KEY = "collect_latency_data";
1193         public static final String SETTINGS_LATENCY_OBSERVER_SAMPLING_INTERVAL_KEY =
1194                 "latency_observer_sampling_interval";
1195         public static final String SETTINGS_LATENCY_OBSERVER_SHARDING_MODULO_KEY =
1196                 "latency_observer_sharding_modulo";
1197         public static final String SETTINGS_LATENCY_OBSERVER_PUSH_INTERVAL_MINUTES_KEY =
1198                 "latency_observer_push_interval_minutes";
1199         public static final String SETTINGS_LATENCY_HISTOGRAM_BUCKET_COUNT_KEY =
1200                 "latency_histogram_bucket_count";
1201         public static final String SETTINGS_LATENCY_HISTOGRAM_FIRST_BUCKET_SIZE_KEY =
1202                 "latency_histogram_first_bucket_size";
1203         public static final String SETTINGS_LATENCY_HISTOGRAM_BUCKET_SCALE_FACTOR_KEY =
1204                 "latency_histogram_bucket_scale_factor";
1205 
1206         private boolean mEnabled;
1207         private final Uri mUri = Settings.Global.getUriFor(Settings.Global.BINDER_CALLS_STATS);
1208         private final Context mContext;
1209         private final KeyValueListParser mParser = new KeyValueListParser(',');
1210         private final BinderCallsStats mBinderCallsStats;
1211 
SettingsObserver(Context context, BinderCallsStats binderCallsStats)1212         public SettingsObserver(Context context, BinderCallsStats binderCallsStats) {
1213             super(BackgroundThread.getHandler());
1214             mContext = context;
1215             context.getContentResolver().registerContentObserver(mUri, false, this);
1216             mBinderCallsStats = binderCallsStats;
1217             // Always kick once to ensure that we match current state
1218             onChange();
1219         }
1220 
1221         @Override
onChange(boolean selfChange, Uri uri, int userId)1222         public void onChange(boolean selfChange, Uri uri, int userId) {
1223             if (mUri.equals(uri)) {
1224                 onChange();
1225             }
1226         }
1227 
onChange()1228         void onChange() {
1229             try {
1230                 mParser.setString(Settings.Global.getString(mContext.getContentResolver(),
1231                         Settings.Global.BINDER_CALLS_STATS));
1232             } catch (IllegalArgumentException e) {
1233                 Slog.e(TAG, "Bad binder call stats settings", e);
1234             }
1235 
1236             // Cpu data collection should always be disabled for other processes.
1237             mBinderCallsStats.setDetailedTracking(false);
1238             mBinderCallsStats.setTrackScreenInteractive(false);
1239             mBinderCallsStats.setTrackDirectCallerUid(false);
1240 
1241             mBinderCallsStats.setIgnoreBatteryStatus(
1242                     mParser.getBoolean(SETTINGS_IGNORE_BATTERY_STATUS_KEY,
1243                             BinderCallsStats.DEFAULT_IGNORE_BATTERY_STATUS));
1244             mBinderCallsStats.setCollectLatencyData(
1245                     mParser.getBoolean(SETTINGS_COLLECT_LATENCY_DATA_KEY,
1246                             BinderCallsStats.DEFAULT_COLLECT_LATENCY_DATA));
1247 
1248             // Binder latency observer settings.
1249             configureLatencyObserver(mParser, mBinderCallsStats.getLatencyObserver());
1250 
1251             final boolean enabled =
1252                     mParser.getBoolean(SETTINGS_ENABLED_KEY, BinderCallsStats.ENABLED_DEFAULT);
1253             if (mEnabled != enabled) {
1254                 if (enabled) {
1255                     Binder.setObserver(mBinderCallsStats);
1256                 } else {
1257                     Binder.setObserver(null);
1258                 }
1259                 mEnabled = enabled;
1260                 mBinderCallsStats.reset();
1261                 mBinderCallsStats.setAddDebugEntries(enabled);
1262                 mBinderCallsStats.getLatencyObserver().reset();
1263             }
1264         }
1265 
1266         /** Configures the binder latency observer related settings. */
configureLatencyObserver( KeyValueListParser mParser, BinderLatencyObserver binderLatencyObserver)1267         public static void configureLatencyObserver(
1268                     KeyValueListParser mParser, BinderLatencyObserver binderLatencyObserver) {
1269             binderLatencyObserver.setSamplingInterval(mParser.getInt(
1270                     SETTINGS_LATENCY_OBSERVER_SAMPLING_INTERVAL_KEY,
1271                     BinderLatencyObserver.PERIODIC_SAMPLING_INTERVAL_DEFAULT));
1272             binderLatencyObserver.setShardingModulo(mParser.getInt(
1273                     SETTINGS_LATENCY_OBSERVER_SHARDING_MODULO_KEY,
1274                     BinderLatencyObserver.SHARDING_MODULO_DEFAULT));
1275             binderLatencyObserver.setHistogramBucketsParams(
1276                     mParser.getInt(
1277                             SETTINGS_LATENCY_HISTOGRAM_BUCKET_COUNT_KEY,
1278                             BinderLatencyObserver.BUCKET_COUNT_DEFAULT),
1279                     mParser.getInt(
1280                             SETTINGS_LATENCY_HISTOGRAM_FIRST_BUCKET_SIZE_KEY,
1281                             BinderLatencyObserver.FIRST_BUCKET_SIZE_DEFAULT),
1282                     mParser.getFloat(
1283                             SETTINGS_LATENCY_HISTOGRAM_BUCKET_SCALE_FACTOR_KEY,
1284                             BinderLatencyObserver.BUCKET_SCALE_FACTOR_DEFAULT));
1285             binderLatencyObserver.setPushInterval(mParser.getInt(
1286                     SETTINGS_LATENCY_OBSERVER_PUSH_INTERVAL_MINUTES_KEY,
1287                     BinderLatencyObserver.STATSD_PUSH_INTERVAL_MINUTES_DEFAULT));
1288         }
1289     }
1290 }
1291