1 /* 2 * Copyright (C) 2024 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 package android.os; 17 18 import static android.os.ProfilingTrigger.TriggerType; 19 20 import android.annotation.FlaggedApi; 21 import android.annotation.IntDef; 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.content.Context; 25 import android.os.profiling.Flags; 26 import android.util.Log; 27 28 import com.android.internal.annotations.GuardedBy; 29 import com.android.internal.annotations.VisibleForTesting; 30 31 import java.io.File; 32 import java.io.IOException; 33 import java.lang.annotation.Retention; 34 import java.lang.annotation.RetentionPolicy; 35 import java.nio.file.Files; 36 import java.nio.file.Path; 37 import java.util.ArrayList; 38 import java.util.List; 39 import java.util.UUID; 40 import java.util.concurrent.Executor; 41 import java.util.function.Consumer; 42 43 /** 44 * <p> 45 * This class allows the caller to: 46 * <ul> 47 * <li>Request profiling and listen for results. Profiling types supported are: system traces, 48 * java heap dumps, heap profiles, and stack traces.</li> 49 * <li>Register triggers for the system to capture profiling on the apps behalf.</li> 50 * </ul> 51 * </p> 52 * 53 * <p> 54 * The {@link #requestProfiling} API can be used to begin profiling. Profiling may be ended manually 55 * using the CancellationSignal provided in the request, or as a result of a timeout. The timeout 56 * may be either the system default or caller defined in the parameter bundle for select types. 57 * </p> 58 * 59 * <p> 60 * The profiling results are delivered to the requesting app's data directory and a pointer to the 61 * file will be received using the app provided listeners. 62 * </p> 63 * 64 * <p> 65 * Apps can provide listeners in one or both of two ways: 66 * <ul> 67 * <li>A request-specific listener included with the request. This will trigger only with a result 68 * from the request it was provided with.</li> 69 * <li>A global listener provided by {@link #registerForAllProfilingResults}. This will be triggered 70 * for all results belonging to your app. This listener is the only way to receive results from 71 * system triggered profiling instances set up with {@link #addProfilingTriggers}.</li> 72 * </ul> 73 * </p> 74 * 75 * <p> 76 * Requests are rate limited and not guaranteed to be filled. Rate limiting can be disabled for 77 * local testing of {@link #requestProfiling} using the shell command 78 * {@code device_config put profiling_testing rate_limiter.disabled true} 79 * </p> 80 * 81 * <p> 82 * For local testing, profiling results can be accessed more easily by enabling debug mode. This 83 * will retain output files in a temporary system directory. The locations of the retained files 84 * will be available in logcat. The behavior and command varies by version: 85 * <ul> 86 * <li>For Android versions 16 and above, debug mode will retain both unredacted (where applicable) 87 * and redacted results in the temporary directory. It can be enabled with the shell command 88 * {@code device_config put profiling_testing delete_temporary_results.disabled true} and disabled 89 * by setting that same value back to false. Retained results are accessible on all build types. 90 * </li> 91 * <li>For Android version 15, debug mode will retain only the unredacted result (where applicable) 92 * in the temporary directory. It can be enabled with the shell command 93 * {@code device_config put profiling_testing delete_unredacted_trace.disabled true} and disabled 94 * by setting that same value back to false. The retained unredacted file can only be accessed on 95 * builds with root access. To access the redacted output file on an unrooted device, apps can copy 96 * the file from {@code /pkg/files/profiling/file.type} to {@code /pkg/cache/file.type}. 97 * </li> 98 * </ul> 99 * </p> 100 * 101 * <p> 102 * In order to test profiling triggers, enable testing mode for your app with the shell command 103 * {@code device_config put profiling_testing system_triggered_profiling.testing_package_name 104 * com.your.app} which will: 105 * <ul> 106 * <li>Ensure that a background trace is running.</li> 107 * <li>Allow all triggers for the provided package name to pass the system level rate limiter. 108 * This mode will continue until manually stopped with the shell command 109 * {@code device_config delete profiling_testing 110 * system_triggered_profiling.testing_package_name}. 111 * </li> 112 * </ul> 113 * </p> 114 * 115 * <p> 116 * Results are redacted and contain specific information about the requesting process only. 117 * </p> 118 */ 119 @FlaggedApi(Flags.FLAG_TELEMETRY_APIS) 120 public final class ProfilingManager { 121 private static final String TAG = ProfilingManager.class.getSimpleName(); 122 private static final boolean DEBUG = false; 123 124 /** Profiling type for {@link #requestProfiling} to request a java heap dump. */ 125 public static final int PROFILING_TYPE_JAVA_HEAP_DUMP = 1; 126 127 /** Profiling type for {@link #requestProfiling} to request a heap profile. */ 128 public static final int PROFILING_TYPE_HEAP_PROFILE = 2; 129 130 /** Profiling type for {@link #requestProfiling} to request a stack sample. */ 131 public static final int PROFILING_TYPE_STACK_SAMPLING = 3; 132 133 /** Profiling type for {@link #requestProfiling} to request a system trace. */ 134 public static final int PROFILING_TYPE_SYSTEM_TRACE = 4; 135 136 /* Begin public API defined keys. */ 137 /* End public API defined keys. */ 138 139 /* Begin not-public API defined keys/values. */ 140 /** 141 * Can only be used with profiling type heap profile, stack sampling, or system trace. 142 * Value of type int. 143 * @hide 144 */ 145 public static final String KEY_DURATION_MS = "KEY_DURATION_MS"; 146 147 /** 148 * Can only be used with profiling type heap profile. Value of type long. 149 * @hide 150 */ 151 public static final String KEY_SAMPLING_INTERVAL_BYTES = "KEY_SAMPLING_INTERVAL_BYTES"; 152 153 /** 154 * Can only be used with profiling type heap profile. Value of type boolean. 155 * @hide 156 */ 157 public static final String KEY_TRACK_JAVA_ALLOCATIONS = "KEY_TRACK_JAVA_ALLOCATIONS"; 158 159 /** 160 * Can only be used with profiling type stack sampling. Value of type int. 161 * @hide 162 */ 163 public static final String KEY_FREQUENCY_HZ = "KEY_FREQUENCY_HZ"; 164 165 /** 166 * Can be used with all profiling types. Value of type int. 167 * @hide 168 */ 169 public static final String KEY_SIZE_KB = "KEY_SIZE_KB"; 170 171 /** 172 * Can be used with profiling type system trace. 173 * Value of type int must be one of: 174 * {@link VALUE_BUFFER_FILL_POLICY_DISCARD} 175 * {@link VALUE_BUFFER_FILL_POLICY_RING_BUFFER} 176 * @hide 177 */ 178 public static final String KEY_BUFFER_FILL_POLICY = "KEY_BUFFER_FILL_POLICY"; 179 180 /** @hide */ 181 public static final int VALUE_BUFFER_FILL_POLICY_DISCARD = 1; 182 183 /** @hide */ 184 public static final int VALUE_BUFFER_FILL_POLICY_RING_BUFFER = 2; 185 /* End not-public API defined keys/values. */ 186 187 /** 188 * @hide * 189 */ 190 @IntDef( 191 prefix = {"PROFILING_TYPE_"}, 192 value = { 193 PROFILING_TYPE_JAVA_HEAP_DUMP, 194 PROFILING_TYPE_HEAP_PROFILE, 195 PROFILING_TYPE_STACK_SAMPLING, 196 PROFILING_TYPE_SYSTEM_TRACE, 197 }) 198 @Retention(RetentionPolicy.SOURCE) 199 public @interface ProfilingType {} 200 201 private final Object mLock = new Object(); 202 private final Context mContext; 203 204 /** @hide */ 205 @VisibleForTesting 206 @GuardedBy("mLock") 207 public final ArrayList<ProfilingRequestCallbackWrapper> mCallbacks = new ArrayList<>(); 208 209 /** @hide */ 210 @VisibleForTesting 211 @GuardedBy("mLock") 212 public IProfilingService mProfilingService; 213 214 /** 215 * Constructor for ProfilingManager. 216 * 217 * @hide 218 */ ProfilingManager(Context context)219 public ProfilingManager(Context context) { 220 mContext = context; 221 } 222 223 /** 224 * Request system profiling. 225 * 226 * <p class="note"> 227 * Note: use of this API directly is not recommended for most use cases. 228 * Consider using the 229 * <a href="https://developer.android.com/reference/androidx/core/os/Profiling">higher level 230 * wrappers provided by AndroidX</a> that will construct the request correctly, supporting 231 * available options with simplified request parameters. 232 * </p> 233 * 234 * <p> 235 * Both a listener and an executor must be set at the time of the request for the request to 236 * be considered for fulfillment. Listener/executor pairs can be set in this method, with 237 * {@link #registerForAllProfilingResults}, or both. The listener and executor must be set 238 * together, in the same call. If no listener and executor combination is set, the request 239 * will be discarded and no callback will be received. 240 * </p> 241 * 242 * <p> 243 * Requests will be rate limited and are not guaranteed to be filled. 244 * </p> 245 * 246 * <p> 247 * There might be a delay before profiling begins. 248 * For continuous profiling types (system tracing, stack sampling, and heap profiling), 249 * we recommend starting the collection early and stopping it with {@code cancellationSignal} 250 * immediately after the area of interest to ensure that the section you want profiled is 251 * captured. 252 * For heap dumps, we recommend testing locally to ensure that the heap dump is collected at 253 * the proper time. 254 * </p> 255 * 256 * @param profilingType Type of profiling to collect. 257 * @param parameters Bundle of request related parameters. If the bundle contains any 258 * unrecognized parameters, the request will be fail with 259 * {@link android.os.ProfilingResult#ERROR_FAILED_INVALID_REQUEST}. If the 260 * values for the parameters are out of supported range, the closest possible 261 * in range value will be chosen. 262 * Use of <a href= 263 * "https://developer.android.com/reference/androidx/core/os/Profiling"> 264 * androidx wrappers</a> is recommended over generating this directly. 265 * @param tag Caller defined data to help identify the output. 266 * The first 20 alphanumeric characters, plus dashes, will be lowercased 267 * and included in the output filename. 268 * @param cancellationSignal for caller requested cancellation. 269 * Results will be returned if available. 270 * If this is null, the requesting app will not be able to stop the collection. 271 * The collection will stop after timing out with either the provided 272 * configurations or with system defaults 273 * @param executor The executor to call back with. 274 * Will only be used for the listener provided in this method. 275 * If this is null, and no global executor and listener combinations are 276 * registered at the time of the request, the request will be dropped. 277 * @param listener Listener to be triggered with result. Any global listeners registered via 278 * {@link #registerForAllProfilingResults} will also be triggered. If this is 279 * null, and no global listener and executor combinations are registered at 280 * the time of the request, the request will be dropped. 281 */ requestProfiling( @rofilingType int profilingType, @Nullable Bundle parameters, @Nullable String tag, @Nullable CancellationSignal cancellationSignal, @Nullable Executor executor, @Nullable Consumer<ProfilingResult> listener)282 public void requestProfiling( 283 @ProfilingType int profilingType, 284 @Nullable Bundle parameters, 285 @Nullable String tag, 286 @Nullable CancellationSignal cancellationSignal, 287 @Nullable Executor executor, 288 @Nullable Consumer<ProfilingResult> listener) { 289 synchronized (mLock) { 290 try { 291 final UUID key = UUID.randomUUID(); 292 293 if (executor != null && listener != null) { 294 // Listeners are provided, store them. 295 mCallbacks.add(new ProfilingRequestCallbackWrapper(executor, listener, key)); 296 } else if (mCallbacks.isEmpty()) { 297 // No listeners have been registered by any path, toss the request. 298 throw new IllegalArgumentException( 299 "No listeners have been registered. Request has been discarded."); 300 } 301 // If neither case above was hit, app wide listeners were provided. Continue. 302 303 final IProfilingService service = getOrCreateIProfilingServiceLocked(false); 304 if (service == null) { 305 executor.execute(() -> listener.accept( 306 new ProfilingResult(ProfilingResult.ERROR_UNKNOWN, null, tag, 307 "ProfilingService is not available", 308 Flags.systemTriggeredProfilingNew() 309 ? ProfilingTrigger.TRIGGER_TYPE_NONE : 0))); 310 if (DEBUG) Log.d(TAG, "ProfilingService is not available"); 311 return; 312 } 313 314 String packageName = mContext.getPackageName(); 315 if (packageName == null) { 316 executor.execute(() -> listener.accept( 317 new ProfilingResult(ProfilingResult.ERROR_UNKNOWN, null, tag, 318 "Failed to resolve package name", 319 Flags.systemTriggeredProfilingNew() 320 ? ProfilingTrigger.TRIGGER_TYPE_NONE : 0))); 321 if (DEBUG) Log.d(TAG, "Failed to resolve package name."); 322 return; 323 } 324 325 // For key, use most and least significant bits so we can create an identical UUID 326 // after passing over binder. 327 service.requestProfiling(profilingType, parameters, tag, 328 key.getMostSignificantBits(), key.getLeastSignificantBits(), 329 packageName); 330 if (cancellationSignal != null) { 331 cancellationSignal.setOnCancelListener( 332 () -> { 333 synchronized (mLock) { 334 try { 335 service.requestCancel(key.getMostSignificantBits(), 336 key.getLeastSignificantBits()); 337 } catch (RemoteException e) { 338 // Ignore, request in flight already and we can't stop it. 339 } 340 } 341 } 342 ); 343 } 344 } catch (RemoteException e) { 345 if (DEBUG) Log.d(TAG, "Binder exception processing request", e); 346 executor.execute(() -> listener.accept( 347 new ProfilingResult(ProfilingResult.ERROR_UNKNOWN, null, tag, 348 "Binder exception processing request", 349 Flags.systemTriggeredProfilingNew() 350 ? ProfilingTrigger.TRIGGER_TYPE_NONE : 0))); 351 throw new RuntimeException("Unable to request profiling."); 352 } 353 } 354 } 355 356 /** 357 * Register a listener to be called for all profiling results for this uid. Listeners set here 358 * will be called in addition to any provided with the request. 359 * 360 * <p class="note"> Note: If a callback attempt fails (for example, because your app is killed 361 * while a trace is in progress) re-delivery may be attempted using a listener added via this 362 * method. </p> 363 * 364 * @param executor The executor to call back with. 365 * @param listener Listener to be triggered with result. 366 */ registerForAllProfilingResults( @onNull Executor executor, @NonNull Consumer<ProfilingResult> listener)367 public void registerForAllProfilingResults( 368 @NonNull Executor executor, 369 @NonNull Consumer<ProfilingResult> listener) { 370 synchronized (mLock) { 371 // Only notify {@link mProfilingService} of a general listener being added if it already 372 // exists as registering it also handles the notifying. 373 boolean shouldNotifyService = mProfilingService != null; 374 375 if (getOrCreateIProfilingServiceLocked(true) == null) { 376 // If the binder object was not successfully registered then this listener will 377 // not ever be triggered. 378 executor.execute(() -> listener.accept(new ProfilingResult( 379 ProfilingResult.ERROR_UNKNOWN, null, null, 380 "Binder exception processing request", 381 Flags.systemTriggeredProfilingNew() 382 ? ProfilingTrigger.TRIGGER_TYPE_NONE : 0))); 383 return; 384 } 385 mCallbacks.add(new ProfilingRequestCallbackWrapper(executor, listener, null)); 386 387 if (shouldNotifyService) { 388 // Notify service that a general listener was added. General listeners are also used 389 // for queued callbacks if any are waiting. 390 try { 391 mProfilingService.generalListenerAdded(); 392 } catch (RemoteException e) { 393 // Do nothing. Binder callback is already registered, but service won't know 394 // there is a general listener so queued callbacks won't occur. 395 Log.d(TAG, "Exception notifying service of general callback," 396 + " queued callbacks will not occur.", e); 397 } 398 } 399 } 400 } 401 402 /** 403 * Unregister a listener that was to be called for all profiling results. If no listener is 404 * provided, all listeners for this process that were not submitted with a profiling request 405 * will be removed. 406 * 407 * @param listener Listener to unregister and no longer be triggered with the results. 408 * Null to remove all global listeners for this uid. 409 */ unregisterForAllProfilingResults( @ullable Consumer<ProfilingResult> listener)410 public void unregisterForAllProfilingResults( 411 @Nullable Consumer<ProfilingResult> listener) { 412 synchronized (mLock) { 413 if (mCallbacks.isEmpty()) { 414 // No callbacks, nothing to remove. 415 return; 416 } 417 418 if (listener == null) { 419 // Remove all global listeners. 420 ArrayList<ProfilingRequestCallbackWrapper> listenersToRemove = new ArrayList<>(); 421 for (int i = 0; i < mCallbacks.size(); i++) { 422 ProfilingRequestCallbackWrapper wrapper = mCallbacks.get(i); 423 // Only remove global listeners which are not tied to a specific request. These 424 // can be identified by checking that they do not have an associated key. 425 if (wrapper.mKey == null) { 426 listenersToRemove.add(wrapper); 427 } 428 } 429 mCallbacks.removeAll(listenersToRemove); 430 } else { 431 // Remove the provided listener only. 432 for (int i = 0; i < mCallbacks.size(); i++) { 433 ProfilingRequestCallbackWrapper wrapper = mCallbacks.get(i); 434 if (listener.equals(wrapper.mListener)) { 435 mCallbacks.remove(i); 436 return; 437 } 438 } 439 } 440 } 441 } 442 443 /** 444 * <p> 445 * Register the provided list of triggers for this process. 446 * </p> 447 * 448 * <p> 449 * Profiling triggers are system events that an app can register interest in, and then receive 450 * profiling data when any of the registered triggers occur. There is no guarantee that these 451 * triggers will be filled. Results, if available, will be delivered only to a global listener 452 * added using {@link #registerForAllProfilingResults}. 453 *</p> 454 * 455 * <p> 456 * Only one of each trigger type can be added at a time. 457 * <ul> 458 * <li>If the provided list contains a trigger type that is already registered then the new one 459 * will replace the existing one.</li> 460 * <li>If the provided list contains more than one trigger object for a trigger type then only 461 * one will be kept.</li> 462 * </ul> 463 * </p> 464 * 465 * <p> 466 * Apps can define their own per-trigger rate limiting to help ensure they receive results 467 * aligned with their needs. More details can be found at 468 * {@link ProfilingTrigger.Builder#setRateLimitingPeriodHours}. 469 * </p> 470 */ 471 @FlaggedApi(Flags.FLAG_SYSTEM_TRIGGERED_PROFILING_NEW) addProfilingTriggers(@onNull List<ProfilingTrigger> triggers)472 public void addProfilingTriggers(@NonNull List<ProfilingTrigger> triggers) { 473 synchronized (mLock) { 474 if (triggers.isEmpty()) { 475 // No triggers are being added, nothing to do. 476 if (DEBUG) Log.d(TAG, "Trying to add an empty list of triggers."); 477 return; 478 } 479 480 final IProfilingService service = getOrCreateIProfilingServiceLocked(false); 481 if (service == null) { 482 // If we can't access service then we can't do anything. Return. 483 if (DEBUG) Log.d(TAG, "ProfilingService is not available, triggers will be lost."); 484 return; 485 } 486 487 String packageName = mContext.getPackageName(); 488 if (packageName == null) { 489 if (DEBUG) Log.d(TAG, "Failed to resolve package name."); 490 return; 491 } 492 493 try { 494 service.addProfilingTriggers(toValueParcelList(triggers), packageName); 495 } catch (RemoteException e) { 496 if (DEBUG) Log.d(TAG, "Binder exception processing request", e); 497 throw new RuntimeException("Unable to add profiling triggers."); 498 } 499 } 500 } 501 502 @FlaggedApi(Flags.FLAG_SYSTEM_TRIGGERED_PROFILING_NEW) toValueParcelList( List<ProfilingTrigger> triggerList)503 private List<ProfilingTriggerValueParcel> toValueParcelList( 504 List<ProfilingTrigger> triggerList) { 505 List<ProfilingTriggerValueParcel> triggerValueParcelList = 506 new ArrayList<ProfilingTriggerValueParcel>(); 507 508 for (int i = 0; i < triggerList.size(); i++) { 509 triggerValueParcelList.add(triggerList.get(i).toValueParcel()); 510 } 511 512 return triggerValueParcelList; 513 } 514 515 /** Remove triggers for this process with trigger types in the provided list. */ 516 @FlaggedApi(Flags.FLAG_SYSTEM_TRIGGERED_PROFILING_NEW) removeProfilingTriggersByType(@onNull @riggerType int[] triggers)517 public void removeProfilingTriggersByType(@NonNull @TriggerType int[] triggers) { 518 synchronized (mLock) { 519 if (triggers.length == 0) { 520 // No triggers are being removed, nothing to do. 521 if (DEBUG) Log.d(TAG, "Trying to remove an empty list of triggers."); 522 return; 523 } 524 525 final IProfilingService service = getOrCreateIProfilingServiceLocked(false); 526 if (service == null) { 527 // If we can't access service then we can't do anything. Return. 528 if (DEBUG) { 529 Log.d(TAG, "ProfilingService is not available, triggers will not be removed."); 530 } 531 return; 532 } 533 534 String packageName = mContext.getPackageName(); 535 if (packageName == null) { 536 if (DEBUG) Log.d(TAG, "Failed to resolve package name."); 537 return; 538 } 539 540 try { 541 service.removeProfilingTriggers(triggers, packageName); 542 } catch (RemoteException e) { 543 if (DEBUG) Log.d(TAG, "Binder exception processing request", e); 544 throw new RuntimeException("Unable to remove profiling triggers."); 545 } 546 } 547 } 548 549 /** Remove all triggers for this process. */ 550 @FlaggedApi(Flags.FLAG_SYSTEM_TRIGGERED_PROFILING_NEW) clearProfilingTriggers()551 public void clearProfilingTriggers() { 552 synchronized (mLock) { 553 final IProfilingService service = getOrCreateIProfilingServiceLocked(false); 554 if (service == null) { 555 // If we can't access service then we can't do anything. Return. 556 if (DEBUG) { 557 Log.d(TAG, "ProfilingService is not available, triggers will not be removed."); 558 } 559 return; 560 } 561 562 String packageName = mContext.getPackageName(); 563 if (packageName == null) { 564 if (DEBUG) Log.d(TAG, "Failed to resolve package name."); 565 return; 566 } 567 568 try { 569 service.clearProfilingTriggers(packageName); 570 } catch (RemoteException e) { 571 if (DEBUG) Log.d(TAG, "Binder exception processing request", e); 572 throw new RuntimeException("Unable to clear profiling triggers."); 573 } 574 } 575 } 576 577 /** @hide */ 578 @VisibleForTesting 579 @GuardedBy("mLock") getOrCreateIProfilingServiceLocked( boolean isGeneralListener)580 public @Nullable IProfilingService getOrCreateIProfilingServiceLocked( 581 boolean isGeneralListener) { 582 // We only register the callback with registerResultsCallback once per binder object, and we 583 // only create one binder object per ProfilingManager instance. If the object already exists 584 // then it was successfully created and registered previously so we can just return it. 585 if (mProfilingService != null) { 586 return mProfilingService; 587 } 588 589 mProfilingService = IProfilingService.Stub.asInterface( 590 ProfilingFrameworkInitializer.getProfilingServiceManager() 591 .getProfilingServiceRegisterer().get()); 592 if (mProfilingService == null) { 593 // Service is not accessible, all requests will fail. 594 return mProfilingService; 595 } 596 try { 597 mProfilingService.registerResultsCallback(isGeneralListener, 598 new IProfilingResultCallback.Stub() { 599 600 /** 601 * Called by {@link ProfilingService} when a result is ready, 602 * both for success and failure. 603 */ 604 @Override 605 public void sendResult(@Nullable String resultFile, long keyMostSigBits, 606 long keyLeastSigBits, int status, @Nullable String tag, 607 @Nullable String error, int triggerType) { 608 synchronized (mLock) { 609 if (mCallbacks.isEmpty()) { 610 // This shouldn't happen - no callbacks, nowhere to report this 611 // result. 612 if (DEBUG) Log.d(TAG, "No callbacks"); 613 mProfilingService = null; 614 return; 615 } 616 617 // This shouldn't be true, but if the file is null ensure the status 618 // represents a failure. 619 final boolean overrideStatusToError = resultFile == null 620 && status == ProfilingResult.ERROR_NONE; 621 622 UUID key = new UUID(keyMostSigBits, keyLeastSigBits); 623 int removeListenerPos = -1; 624 for (int i = 0; i < mCallbacks.size(); i++) { 625 ProfilingRequestCallbackWrapper wrapper = mCallbacks.get(i); 626 if (key.equals(wrapper.mKey)) { 627 // At most 1 listener can have a key matching this result: 628 // the one registered with the request, remove that one 629 // only. 630 if (removeListenerPos == -1) { 631 removeListenerPos = i; 632 } else { 633 // This should never happen. 634 if (DEBUG) { 635 Log.d(TAG, 636 "More than 1 listener with the same key"); 637 } 638 } 639 } else if (wrapper.mKey != null) { 640 // If the key is not null, and doesn't matched the result 641 // key, then this key belongs to another request and should 642 // not be triggered. 643 continue; 644 } 645 646 // TODO: b/337017299 - check resultFile is valid before 647 // returning Now trigger the callback for any listener that 648 // doesn't belong to another request. 649 wrapper.mExecutor.execute(() -> wrapper.mListener.accept( 650 new ProfilingResult(overrideStatusToError 651 ? ProfilingResult.ERROR_UNKNOWN : status, 652 getAppFileDir() + resultFile, tag, error, 653 triggerType))); 654 } 655 656 // Remove the single listener that was tied to the request, if 657 // applicable. 658 if (removeListenerPos != -1) { 659 mCallbacks.remove(removeListenerPos); 660 } 661 } 662 } 663 664 /** 665 * Called by {@link ProfilingService} when a trace is ready and needs to be 666 * copied to callers internal storage. 667 * 668 * This method will open a new file and pass back the FileDescriptor for 669 * ProfilingService to write to via a new binder call. 670 * 671 * Takes in key most/least significant bits which represent the key that 672 * will be used to associate this back to a profiling session which will 673 * write to the generated file. 674 */ 675 @Override 676 public void generateFile(String filePathRelative, String fileName, 677 long keyMostSigBits, long keyLeastSigBits) { 678 synchronized (mLock) { 679 String filePathAbsolute = getAppFileDir() + filePathRelative; 680 try { 681 // Ensure the profiling directory exists. Create it if it 682 // doesn't. 683 final File profilingDir = new File(filePathAbsolute); 684 if (!profilingDir.exists()) { 685 profilingDir.mkdir(); 686 } 687 688 // Create the profiling file for the output to be written to. 689 final File profilingFile = new File( 690 filePathAbsolute + fileName); 691 profilingFile.createNewFile(); 692 if (!profilingFile.exists()) { 693 // Failed to create output file. Result may be lost. 694 if (DEBUG) Log.d(TAG, "Output file couldn't be created"); 695 return; 696 } 697 698 // Wrap the new output file in a {@link ParcelFileDescriptor} to 699 // send back to {@link ProfilingService} to write to. 700 ParcelFileDescriptor pfd = ParcelFileDescriptor.open( 701 profilingFile, 702 ParcelFileDescriptor.MODE_READ_WRITE); 703 IProfilingService service = 704 getOrCreateIProfilingServiceLocked(false); 705 706 if (service == null) { 707 // Unable to send file descriptor because we have nowhere to 708 // send it to. Result may be lost. Close descriptor and 709 // delete file. 710 if (DEBUG) Log.d(TAG, "Unable to send file descriptor"); 711 tryToCleanupGeneratedFile(pfd, profilingFile); 712 return; 713 } 714 715 try { 716 // Send the file descriptor to service to write to. 717 service.receiveFileDescriptor(pfd, keyMostSigBits, 718 keyLeastSigBits); 719 } catch (RemoteException e) { 720 // If we failed to send it, try to clean it up as it won't 721 // be used. 722 if (DEBUG) { 723 Log.d(TAG, "Failed sending file descriptor to service", 724 e); 725 } 726 tryToCleanupGeneratedFile(pfd, profilingFile); 727 } 728 } catch (Exception e) { 729 // Failure prepping output file. Result may be lost. 730 if (DEBUG) Log.d(TAG, "Exception preparing file", e); 731 return; 732 } 733 } 734 } 735 736 /** 737 * Attempt to clean up the files created for service by closing the file 738 * descriptor and deleting the file. This is intended for error cases where 739 * the descriptor could not be sent. If it was successfully sent, service 740 * will handle closing it and requesting a delete if necessary. 741 */ 742 private void tryToCleanupGeneratedFile(ParcelFileDescriptor fileDescriptor, 743 File file) { 744 if (fileDescriptor != null) { 745 try { 746 fileDescriptor.close(); 747 } catch (IOException e) { 748 // Nothing else we can do, ignore. 749 if (DEBUG) Log.d(TAG, "Failed to cleanup file descriptor", e); 750 } 751 } 752 753 if (file != null) { 754 try { 755 file.delete(); 756 } catch (SecurityException e) { 757 // Nothing else we can do, ignore. 758 if (DEBUG) Log.d(TAG, "Failed to cleanup file", e); 759 } 760 } 761 } 762 763 /** 764 * Delete a file. To be used only for files created by {@link generateFile}. 765 */ 766 @Override 767 public void deleteFile(String relativeFilePathAndName) { 768 try { 769 Files.delete(Path.of(getAppFileDir() + relativeFilePathAndName)); 770 } catch (Exception exception) { 771 if (DEBUG) Log.e(TAG, "Failed to delete file.", exception); 772 } 773 } 774 775 private String getAppFileDir() { 776 return mContext.getFilesDir().getPath(); 777 } 778 }); 779 } catch (RemoteException e) { 780 if (DEBUG) Log.d(TAG, "Exception registering service callback", e); 781 throw new RuntimeException("Unable to register profiling result callback." 782 + " All Profiling requests will fail."); 783 } 784 return mProfilingService; 785 } 786 787 private static final class ProfilingRequestCallbackWrapper { 788 /** executor provided with callback request */ 789 final @NonNull Executor mExecutor; 790 791 /** listener provided with callback request */ 792 final @NonNull Consumer<ProfilingResult> mListener; 793 794 /** 795 * Unique key generated with each profiling request {@link #requestProfiling}, but not with 796 * requests to register a listener only {@link #registerForAllProfilingResults}. 797 * 798 * Key is used to match the result with the listener added with the request so that it can 799 * removed after being triggered while the general registered callbacks remain active. 800 */ 801 final @Nullable UUID mKey; 802 ProfilingRequestCallbackWrapper(@onNull Executor executor, @NonNull Consumer<ProfilingResult> listener, @Nullable UUID key)803 ProfilingRequestCallbackWrapper(@NonNull Executor executor, 804 @NonNull Consumer<ProfilingResult> listener, 805 @Nullable UUID key) { 806 mExecutor = executor; 807 mListener = listener; 808 mKey = key; 809 } 810 } 811 } 812