1 /* 2 * Copyright (C) 2018 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.server; 18 19 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE; 20 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE; 21 22 import static com.android.internal.os.BinderCallsStats.SettingsObserver.SETTINGS_COLLECT_LATENCY_DATA_KEY; 23 import static com.android.internal.os.BinderCallsStats.SettingsObserver.SETTINGS_DETAILED_TRACKING_KEY; 24 import static com.android.internal.os.BinderCallsStats.SettingsObserver.SETTINGS_ENABLED_KEY; 25 import static com.android.internal.os.BinderCallsStats.SettingsObserver.SETTINGS_IGNORE_BATTERY_STATUS_KEY; 26 import static com.android.internal.os.BinderCallsStats.SettingsObserver.SETTINGS_MAX_CALL_STATS_KEY; 27 import static com.android.internal.os.BinderCallsStats.SettingsObserver.SETTINGS_SAMPLING_INTERVAL_KEY; 28 import static com.android.internal.os.BinderCallsStats.SettingsObserver.SETTINGS_SHARDING_MODULO_KEY; 29 import static com.android.internal.os.BinderCallsStats.SettingsObserver.SETTINGS_TRACK_DIRECT_CALLING_UID_KEY; 30 import static com.android.internal.os.BinderCallsStats.SettingsObserver.SETTINGS_TRACK_SCREEN_INTERACTIVE_KEY; 31 32 import android.app.ActivityThread; 33 import android.content.Context; 34 import android.content.pm.PackageInfo; 35 import android.content.pm.PackageManager; 36 import android.content.pm.PackageManager.NameNotFoundException; 37 import android.database.ContentObserver; 38 import android.net.Uri; 39 import android.os.BatteryStatsInternal; 40 import android.os.Binder; 41 import android.os.ParcelFileDescriptor; 42 import android.os.Process; 43 import android.os.ShellCommand; 44 import android.os.SystemProperties; 45 import android.os.UserHandle; 46 import android.provider.Settings; 47 import android.util.ArrayMap; 48 import android.util.ArraySet; 49 import android.util.KeyValueListParser; 50 import android.util.Slog; 51 52 import com.android.internal.os.AppIdToPackageMap; 53 import com.android.internal.os.BackgroundThread; 54 import com.android.internal.os.BinderCallsStats; 55 import com.android.internal.os.BinderInternal; 56 import com.android.internal.os.CachedDeviceState; 57 import com.android.internal.util.DumpUtils; 58 59 import java.io.FileDescriptor; 60 import java.io.PrintWriter; 61 import java.util.ArrayList; 62 import java.util.Collection; 63 import java.util.List; 64 65 public class BinderCallsStatsService extends Binder { 66 67 private static final String TAG = "BinderCallsStatsService"; 68 private static final String SERVICE_NAME = "binder_calls_stats"; 69 70 private static final String PERSIST_SYS_BINDER_CALLS_DETAILED_TRACKING 71 = "persist.sys.binder_calls_detailed_tracking"; 72 73 /** Resolves the work source of an incoming binder transaction. */ 74 static class AuthorizedWorkSourceProvider implements BinderInternal.WorkSourceProvider { 75 private ArraySet<Integer> mAppIdTrustlist; 76 AuthorizedWorkSourceProvider()77 AuthorizedWorkSourceProvider() { 78 mAppIdTrustlist = new ArraySet<>(); 79 } 80 resolveWorkSourceUid(int untrustedWorkSourceUid)81 public int resolveWorkSourceUid(int untrustedWorkSourceUid) { 82 final int callingUid = getCallingUid(); 83 final int appId = UserHandle.getAppId(callingUid); 84 if (mAppIdTrustlist.contains(appId)) { 85 final int workSource = untrustedWorkSourceUid; 86 final boolean isWorkSourceSet = workSource != Binder.UNSET_WORKSOURCE; 87 return isWorkSourceSet ? workSource : callingUid; 88 } 89 return callingUid; 90 } 91 systemReady(Context context)92 public void systemReady(Context context) { 93 mAppIdTrustlist = createAppidTrustlist(context); 94 } 95 dump(PrintWriter pw, AppIdToPackageMap packageMap)96 public void dump(PrintWriter pw, AppIdToPackageMap packageMap) { 97 pw.println("AppIds of apps that can set the work source:"); 98 final ArraySet<Integer> trustlist = mAppIdTrustlist; 99 for (Integer appId : trustlist) { 100 pw.println("\t- " + packageMap.mapAppId(appId)); 101 } 102 } 103 getCallingUid()104 protected int getCallingUid() { 105 return Binder.getCallingUid(); 106 } 107 createAppidTrustlist(Context context)108 private ArraySet<Integer> createAppidTrustlist(Context context) { 109 // Use a local copy instead of mAppIdTrustlist to prevent concurrent read access. 110 final ArraySet<Integer> trustlist = new ArraySet<>(); 111 112 // We trust our own process. 113 trustlist.add(UserHandle.getAppId(Process.myUid())); 114 // We only need to initialize it once. UPDATE_DEVICE_STATS is a system permission. 115 final PackageManager pm = context.getPackageManager(); 116 final String[] permissions = { android.Manifest.permission.UPDATE_DEVICE_STATS }; 117 final int queryFlags = MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE; 118 final List<PackageInfo> packages = 119 pm.getPackagesHoldingPermissions(permissions, queryFlags); 120 final int packagesSize = packages.size(); 121 for (int i = 0; i < packagesSize; i++) { 122 final PackageInfo pkgInfo = packages.get(i); 123 try { 124 final int uid = pm.getPackageUid(pkgInfo.packageName, queryFlags); 125 final int appId = UserHandle.getAppId(uid); 126 trustlist.add(appId); 127 } catch (NameNotFoundException e) { 128 Slog.e(TAG, "Cannot find uid for package name " + pkgInfo.packageName, e); 129 } 130 } 131 return trustlist; 132 } 133 } 134 135 /** Listens for flag changes. */ 136 private static class SettingsObserver extends ContentObserver { 137 private boolean mEnabled; 138 private final Uri mUri = Settings.Global.getUriFor(Settings.Global.BINDER_CALLS_STATS); 139 private final Context mContext; 140 private final KeyValueListParser mParser = new KeyValueListParser(','); 141 private final BinderCallsStats mBinderCallsStats; 142 private final AuthorizedWorkSourceProvider mWorkSourceProvider; 143 SettingsObserver(Context context, BinderCallsStats binderCallsStats, AuthorizedWorkSourceProvider workSourceProvider)144 SettingsObserver(Context context, BinderCallsStats binderCallsStats, 145 AuthorizedWorkSourceProvider workSourceProvider) { 146 super(BackgroundThread.getHandler()); 147 mContext = context; 148 context.getContentResolver().registerContentObserver(mUri, false, this, 149 UserHandle.USER_SYSTEM); 150 mBinderCallsStats = binderCallsStats; 151 mWorkSourceProvider = workSourceProvider; 152 // Always kick once to ensure that we match current state 153 onChange(); 154 } 155 156 @Override onChange(boolean selfChange, Uri uri, int userId)157 public void onChange(boolean selfChange, Uri uri, int userId) { 158 if (mUri.equals(uri)) { 159 onChange(); 160 } 161 } 162 onChange()163 public void onChange() { 164 // Do not overwrite the default set manually. 165 if (!SystemProperties.get(PERSIST_SYS_BINDER_CALLS_DETAILED_TRACKING).isEmpty()) { 166 return; 167 } 168 169 try { 170 mParser.setString(Settings.Global.getString(mContext.getContentResolver(), 171 Settings.Global.BINDER_CALLS_STATS)); 172 } catch (IllegalArgumentException e) { 173 Slog.e(TAG, "Bad binder call stats settings", e); 174 } 175 mBinderCallsStats.setDetailedTracking(mParser.getBoolean( 176 SETTINGS_DETAILED_TRACKING_KEY, BinderCallsStats.DETAILED_TRACKING_DEFAULT)); 177 mBinderCallsStats.setSamplingInterval(mParser.getInt( 178 SETTINGS_SAMPLING_INTERVAL_KEY, 179 BinderCallsStats.PERIODIC_SAMPLING_INTERVAL_DEFAULT)); 180 mBinderCallsStats.setMaxBinderCallStats(mParser.getInt( 181 SETTINGS_MAX_CALL_STATS_KEY, 182 BinderCallsStats.MAX_BINDER_CALL_STATS_COUNT_DEFAULT)); 183 mBinderCallsStats.setTrackScreenInteractive( 184 mParser.getBoolean(SETTINGS_TRACK_SCREEN_INTERACTIVE_KEY, 185 BinderCallsStats.DEFAULT_TRACK_SCREEN_INTERACTIVE)); 186 mBinderCallsStats.setTrackDirectCallerUid( 187 mParser.getBoolean(SETTINGS_TRACK_DIRECT_CALLING_UID_KEY, 188 BinderCallsStats.DEFAULT_TRACK_DIRECT_CALLING_UID)); 189 mBinderCallsStats.setIgnoreBatteryStatus( 190 mParser.getBoolean(SETTINGS_IGNORE_BATTERY_STATUS_KEY, 191 BinderCallsStats.DEFAULT_IGNORE_BATTERY_STATUS)); 192 mBinderCallsStats.setShardingModulo(mParser.getInt( 193 SETTINGS_SHARDING_MODULO_KEY, 194 BinderCallsStats.SHARDING_MODULO_DEFAULT)); 195 196 mBinderCallsStats.setCollectLatencyData( 197 mParser.getBoolean(SETTINGS_COLLECT_LATENCY_DATA_KEY, 198 BinderCallsStats.DEFAULT_COLLECT_LATENCY_DATA)); 199 // Binder latency observer settings. 200 BinderCallsStats.SettingsObserver.configureLatencyObserver( 201 mParser, 202 mBinderCallsStats.getLatencyObserver()); 203 204 final boolean enabled = 205 mParser.getBoolean(SETTINGS_ENABLED_KEY, BinderCallsStats.ENABLED_DEFAULT); 206 if (mEnabled != enabled) { 207 if (enabled) { 208 Binder.setObserver(mBinderCallsStats); 209 Binder.setProxyTransactListener( 210 new Binder.PropagateWorkSourceTransactListener()); 211 Binder.setWorkSourceProvider(mWorkSourceProvider); 212 } else { 213 Binder.setObserver(null); 214 Binder.setProxyTransactListener(null); 215 Binder.setWorkSourceProvider((x) -> Binder.getCallingUid()); 216 } 217 mEnabled = enabled; 218 mBinderCallsStats.reset(); 219 mBinderCallsStats.setAddDebugEntries(enabled); 220 mBinderCallsStats.getLatencyObserver().reset(); 221 } 222 } 223 } 224 225 /** 226 * @hide Only for use within the system server. 227 */ 228 public static class Internal { 229 private final BinderCallsStats mBinderCallsStats; 230 Internal(BinderCallsStats binderCallsStats)231 Internal(BinderCallsStats binderCallsStats) { 232 this.mBinderCallsStats = binderCallsStats; 233 } 234 235 /** @see BinderCallsStats#reset */ reset()236 public void reset() { 237 mBinderCallsStats.reset(); 238 } 239 240 /** 241 * @see BinderCallsStats#getExportedCallStats. 242 * 243 * Note that binder calls stats will be reset by statsd every time 244 * the data is exported. 245 */ getExportedCallStats()246 public ArrayList<BinderCallsStats.ExportedCallStat> getExportedCallStats() { 247 return mBinderCallsStats.getExportedCallStats(); 248 } 249 250 /** @see BinderCallsStats#getExportedExceptionStats */ getExportedExceptionStats()251 public ArrayMap<String, Integer> getExportedExceptionStats() { 252 return mBinderCallsStats.getExportedExceptionStats(); 253 } 254 } 255 256 public static class LifeCycle extends SystemService { 257 private BinderCallsStatsService mService; 258 private BinderCallsStats mBinderCallsStats; 259 private AuthorizedWorkSourceProvider mWorkSourceProvider; 260 LifeCycle(Context context)261 public LifeCycle(Context context) { 262 super(context); 263 } 264 265 @Override onStart()266 public void onStart() { 267 mBinderCallsStats = new BinderCallsStats(new BinderCallsStats.Injector()); 268 mWorkSourceProvider = new AuthorizedWorkSourceProvider(); 269 mService = new BinderCallsStatsService( 270 mBinderCallsStats, mWorkSourceProvider); 271 publishLocalService(Internal.class, new Internal(mBinderCallsStats)); 272 publishBinderService(SERVICE_NAME, mService); 273 boolean detailedTrackingEnabled = SystemProperties.getBoolean( 274 PERSIST_SYS_BINDER_CALLS_DETAILED_TRACKING, false); 275 276 if (detailedTrackingEnabled) { 277 Slog.i(TAG, "Enabled CPU usage tracking for binder calls. Controlled by " 278 + PERSIST_SYS_BINDER_CALLS_DETAILED_TRACKING 279 + " or via dumpsys binder_calls_stats --enable-detailed-tracking"); 280 mBinderCallsStats.setDetailedTracking(true); 281 } 282 } 283 284 @Override onBootPhase(int phase)285 public void onBootPhase(int phase) { 286 if (SystemService.PHASE_SYSTEM_SERVICES_READY == phase) { 287 CachedDeviceState.Readonly deviceState = getLocalService( 288 CachedDeviceState.Readonly.class); 289 mBinderCallsStats.setDeviceState(deviceState); 290 291 BatteryStatsInternal batteryStatsInternal = getLocalService( 292 BatteryStatsInternal.class); 293 mBinderCallsStats.setCallStatsObserver(new BinderInternal.CallStatsObserver() { 294 @Override 295 public void noteCallStats(int workSourceUid, long incrementalCallCount, 296 Collection<BinderCallsStats.CallStat> callStats) { 297 batteryStatsInternal.noteBinderCallStats(workSourceUid, 298 incrementalCallCount, callStats); 299 } 300 301 @Override 302 public void noteBinderThreadNativeIds(int[] binderThreadNativeTids) { 303 batteryStatsInternal.noteBinderThreadNativeIds(binderThreadNativeTids); 304 } 305 }); 306 307 // It needs to be called before mService.systemReady to make sure the observer is 308 // initialized before installing it. 309 mWorkSourceProvider.systemReady(getContext()); 310 mService.systemReady(getContext()); 311 } 312 } 313 } 314 315 private SettingsObserver mSettingsObserver; 316 private final BinderCallsStats mBinderCallsStats; 317 private final AuthorizedWorkSourceProvider mWorkSourceProvider; 318 BinderCallsStatsService(BinderCallsStats binderCallsStats, AuthorizedWorkSourceProvider workSourceProvider)319 BinderCallsStatsService(BinderCallsStats binderCallsStats, 320 AuthorizedWorkSourceProvider workSourceProvider) { 321 mBinderCallsStats = binderCallsStats; 322 mWorkSourceProvider = workSourceProvider; 323 } 324 systemReady(Context context)325 public void systemReady(Context context) { 326 mSettingsObserver = new SettingsObserver(context, mBinderCallsStats, mWorkSourceProvider); 327 } 328 reset()329 public void reset() { 330 Slog.i(TAG, "Resetting stats"); 331 mBinderCallsStats.reset(); 332 } 333 334 @Override dump(FileDescriptor fd, PrintWriter pw, String[] args)335 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 336 if (!DumpUtils.checkDumpAndUsageStatsPermission(ActivityThread.currentApplication(), 337 SERVICE_NAME, pw)) { 338 return; 339 } 340 341 boolean verbose = false; 342 int worksourceUid = Process.INVALID_UID; 343 if (args != null) { 344 for (int i = 0; i < args.length; i++) { 345 String arg = args[i]; 346 if ("-a".equals(arg)) { 347 verbose = true; 348 } else if ("-h".equals(arg)) { 349 pw.println("dumpsys binder_calls_stats options:"); 350 pw.println(" -a: Verbose"); 351 pw.println(" --work-source-uid <UID>: Dump binder calls from the UID"); 352 return; 353 } else if ("--work-source-uid".equals(arg)) { 354 i++; 355 if (i >= args.length) { 356 throw new IllegalArgumentException( 357 "Argument expected after \"" + arg + "\""); 358 } 359 String uidArg = args[i]; 360 try { 361 worksourceUid = Integer.parseInt(uidArg); 362 } catch (NumberFormatException e) { 363 pw.println("Invalid UID: " + uidArg); 364 return; 365 } 366 } 367 } 368 369 if (args.length > 0 && worksourceUid == Process.INVALID_UID) { 370 // For compatibility, support "cmd"-style commands when passed to "dumpsys". 371 BinderCallsStatsShellCommand command = new BinderCallsStatsShellCommand(pw); 372 int status = command.exec(this, null, FileDescriptor.out, FileDescriptor.err, args); 373 if (status == 0) { 374 return; 375 } 376 } 377 } 378 mBinderCallsStats.dump(pw, AppIdToPackageMap.getSnapshot(), worksourceUid, verbose); 379 } 380 381 @Override handleShellCommand(ParcelFileDescriptor in, ParcelFileDescriptor out, ParcelFileDescriptor err, String[] args)382 public int handleShellCommand(ParcelFileDescriptor in, ParcelFileDescriptor out, 383 ParcelFileDescriptor err, String[] args) { 384 ShellCommand command = new BinderCallsStatsShellCommand(null); 385 int status = command.exec(this, in.getFileDescriptor(), out.getFileDescriptor(), 386 err.getFileDescriptor(), args); 387 if (status != 0) { 388 command.onHelp(); 389 } 390 return status; 391 } 392 393 private class BinderCallsStatsShellCommand extends ShellCommand { 394 private final PrintWriter mPrintWriter; 395 BinderCallsStatsShellCommand(PrintWriter printWriter)396 BinderCallsStatsShellCommand(PrintWriter printWriter) { 397 mPrintWriter = printWriter; 398 } 399 400 @Override getOutPrintWriter()401 public PrintWriter getOutPrintWriter() { 402 if (mPrintWriter != null) { 403 return mPrintWriter; 404 } 405 return super.getOutPrintWriter(); 406 } 407 408 @Override onCommand(String cmd)409 public int onCommand(String cmd) { 410 PrintWriter pw = getOutPrintWriter(); 411 if (cmd == null) { 412 return -1; 413 } 414 415 switch (cmd) { 416 case "--reset": 417 reset(); 418 pw.println("binder_calls_stats reset."); 419 break; 420 case "--enable": 421 Binder.setObserver(mBinderCallsStats); 422 break; 423 case "--disable": 424 Binder.setObserver(null); 425 break; 426 case "--no-sampling": 427 mBinderCallsStats.setSamplingInterval(1); 428 break; 429 case "--enable-detailed-tracking": 430 SystemProperties.set(PERSIST_SYS_BINDER_CALLS_DETAILED_TRACKING, "1"); 431 mBinderCallsStats.setDetailedTracking(true); 432 pw.println("Detailed tracking enabled"); 433 break; 434 case "--disable-detailed-tracking": 435 SystemProperties.set(PERSIST_SYS_BINDER_CALLS_DETAILED_TRACKING, ""); 436 mBinderCallsStats.setDetailedTracking(false); 437 pw.println("Detailed tracking disabled"); 438 break; 439 case "--dump-worksource-provider": 440 mBinderCallsStats.setDetailedTracking(true); 441 mWorkSourceProvider.dump(pw, AppIdToPackageMap.getSnapshot()); 442 break; 443 case "--work-source-uid": 444 String uidArg = getNextArgRequired(); 445 try { 446 int uid = Integer.parseInt(uidArg); 447 mBinderCallsStats.recordAllCallsForWorkSourceUid(uid); 448 } catch (NumberFormatException e) { 449 pw.println("Invalid UID: " + uidArg); 450 return -1; 451 } 452 break; 453 default: 454 return handleDefaultCommands(cmd); 455 } 456 return 0; 457 } 458 459 @Override onHelp()460 public void onHelp() { 461 PrintWriter pw = getOutPrintWriter(); 462 pw.println("binder_calls_stats commands:"); 463 pw.println(" --reset: Reset stats"); 464 pw.println(" --enable: Enable tracking binder calls"); 465 pw.println(" --disable: Disables tracking binder calls"); 466 pw.println(" --no-sampling: Tracks all calls"); 467 pw.println(" --enable-detailed-tracking: Enables detailed tracking"); 468 pw.println(" --disable-detailed-tracking: Disables detailed tracking"); 469 pw.println(" --work-source-uid <UID>: Track all binder calls from the UID"); 470 } 471 } 472 } 473