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