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 android.annotation.FlaggedApi; 19 import android.annotation.IntDef; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.content.Context; 23 import android.os.profiling.Flags; 24 import android.util.Log; 25 26 import com.android.internal.annotations.GuardedBy; 27 import com.android.internal.annotations.VisibleForTesting; 28 29 import java.io.File; 30 import java.io.IOException; 31 import java.lang.annotation.Retention; 32 import java.lang.annotation.RetentionPolicy; 33 import java.nio.file.Files; 34 import java.nio.file.Path; 35 import java.util.ArrayList; 36 import java.util.UUID; 37 import java.util.concurrent.Executor; 38 import java.util.function.Consumer; 39 40 /** 41 * API for apps to request and listen for app specific profiling. 42 */ 43 @FlaggedApi(Flags.FLAG_TELEMETRY_APIS) 44 public final class ProfilingManager { 45 private static final String TAG = ProfilingManager.class.getSimpleName(); 46 private static final boolean DEBUG = false; 47 48 /** Profiling type for {@link #requestProfiling} to request a java heap dump. */ 49 public static final int PROFILING_TYPE_JAVA_HEAP_DUMP = 1; 50 51 /** Profiling type for {@link #requestProfiling} to request a heap profile. */ 52 public static final int PROFILING_TYPE_HEAP_PROFILE = 2; 53 54 /** Profiling type for {@link #requestProfiling} to request a stack sample. */ 55 public static final int PROFILING_TYPE_STACK_SAMPLING = 3; 56 57 /** Profiling type for {@link #requestProfiling} to request a system trace. */ 58 public static final int PROFILING_TYPE_SYSTEM_TRACE = 4; 59 60 /* Begin public API defined keys. */ 61 /* End public API defined keys. */ 62 63 /* Begin not-public API defined keys/values. */ 64 /** 65 * Can only be used with profiling type heap profile, stack sampling, or system trace. 66 * Value of type int. 67 * @hide 68 */ 69 public static final String KEY_DURATION_MS = "KEY_DURATION_MS"; 70 71 /** 72 * Can only be used with profiling type heap profile. Value of type long. 73 * @hide 74 */ 75 public static final String KEY_SAMPLING_INTERVAL_BYTES = "KEY_SAMPLING_INTERVAL_BYTES"; 76 77 /** 78 * Can only be used with profiling type heap profile. Value of type boolean. 79 * @hide 80 */ 81 public static final String KEY_TRACK_JAVA_ALLOCATIONS = "KEY_TRACK_JAVA_ALLOCATIONS"; 82 83 /** 84 * Can only be used with profiling type stack sampling. Value of type int. 85 * @hide 86 */ 87 public static final String KEY_FREQUENCY_HZ = "KEY_FREQUENCY_HZ"; 88 89 /** 90 * Can be used with all profiling types. Value of type int. 91 * @hide 92 */ 93 public static final String KEY_SIZE_KB = "KEY_SIZE_KB"; 94 95 /** 96 * Can be used with profiling type system trace. 97 * Value of type int must be one of: 98 * {@link VALUE_BUFFER_FILL_POLICY_DISCARD} 99 * {@link VALUE_BUFFER_FILL_POLICY_RING_BUFFER} 100 * @hide 101 */ 102 public static final String KEY_BUFFER_FILL_POLICY = "KEY_BUFFER_FILL_POLICY"; 103 104 /** @hide */ 105 public static final int VALUE_BUFFER_FILL_POLICY_DISCARD = 1; 106 107 /** @hide */ 108 public static final int VALUE_BUFFER_FILL_POLICY_RING_BUFFER = 2; 109 /* End not-public API defined keys/values. */ 110 111 /** 112 * @hide * 113 */ 114 @IntDef( 115 prefix = {"PROFILING_TYPE_"}, 116 value = { 117 PROFILING_TYPE_JAVA_HEAP_DUMP, 118 PROFILING_TYPE_HEAP_PROFILE, 119 PROFILING_TYPE_STACK_SAMPLING, 120 PROFILING_TYPE_SYSTEM_TRACE, 121 }) 122 @Retention(RetentionPolicy.SOURCE) 123 public @interface ProfilingType {} 124 125 private final Object mLock = new Object(); 126 private final Context mContext; 127 128 /** @hide */ 129 @VisibleForTesting 130 @GuardedBy("mLock") 131 public final ArrayList<ProfilingRequestCallbackWrapper> mCallbacks = new ArrayList<>(); 132 133 /** @hide */ 134 @VisibleForTesting 135 @GuardedBy("mLock") 136 public IProfilingService mProfilingService; 137 138 /** 139 * Constructor for ProfilingManager. 140 * 141 * @hide 142 */ ProfilingManager(Context context)143 public ProfilingManager(Context context) { 144 mContext = context; 145 } 146 147 /** 148 * Request system profiling. 149 * 150 * <p class="note"> 151 * Note: use of this API directly is not recommended for most use cases. 152 * Consider using the higher level wrappers provided by AndroidX that will construct the 153 * request correctly, supporting available options with simplified request parameters 154 * </p> 155 * 156 * <p> 157 * Both a listener and an executor must be set at the time of the request for the request to 158 * be considered for fulfillment. Listener/executor pairs can be set in this method, with 159 * {@link registerForAllProfilingResults}, or both. The listener and executor must be set 160 * together, in the same call. If no listener and executor combination is set, the request 161 * will be discarded and no callback will be received. 162 * </p> 163 * 164 * <p> 165 * Requests will be rate limited and are not guaranteed to be filled. 166 * </p> 167 * 168 * <p> 169 * There might be a delay before profiling begins. 170 * For continuous profiling types (system tracing, stack sampling, and heap profiling), 171 * we recommend starting the collection early and stopping it with {@link cancellationSignal} 172 * immediately after the area of interest to ensure that the section you want profiled is 173 * captured. 174 * For heap dumps, we recommend testing locally to ensure that the heap dump is collected at 175 * the proper time. 176 * </p> 177 * 178 * @param profilingType Type of profiling to collect. 179 * @param parameters Bundle of request related parameters. If the bundle contains any 180 * unrecognized parameters, the request will be fail with 181 * {@link #ProfilingResult#ERROR_FAILED_INVALID_REQUEST}. If the values for 182 * the parameters are out of supported range, the closest possible in range 183 * value will be chosen. 184 * Use of androidx wrappers is recommended over generating this directly. 185 * @param tag Caller defined data to help identify the output. 186 * The first 20 alphanumeric characters, plus dashes, will be lowercased 187 * and included in the output filename. 188 * @param cancellationSignal for caller requested cancellation. 189 * Results will be returned if available. 190 * If this is null, the requesting app will not be able to stop the collection. 191 * The collection will stop after timing out with either the provided 192 * configurations or with system defaults 193 * @param executor The executor to call back with. 194 * Will only be used for the listener provided in this method. 195 * If this is null, and no global executor and listener combinations are 196 * registered at the time of the request, the request will be dropped. 197 * @param listener Listener to be triggered with result. Any global listeners registered via 198 * {@link #registerForAllProfilingResults} will also be triggered. If this is 199 * null, and no global listener and executor combinations are registered at 200 * the time of the request, the request will be dropped. 201 */ requestProfiling( @rofilingType int profilingType, @Nullable Bundle parameters, @Nullable String tag, @Nullable CancellationSignal cancellationSignal, @Nullable Executor executor, @Nullable Consumer<ProfilingResult> listener)202 public void requestProfiling( 203 @ProfilingType int profilingType, 204 @Nullable Bundle parameters, 205 @Nullable String tag, 206 @Nullable CancellationSignal cancellationSignal, 207 @Nullable Executor executor, 208 @Nullable Consumer<ProfilingResult> listener) { 209 synchronized (mLock) { 210 try { 211 final UUID key = UUID.randomUUID(); 212 213 if (executor != null && listener != null) { 214 // Listeners are provided, store them. 215 mCallbacks.add(new ProfilingRequestCallbackWrapper(executor, listener, key)); 216 } else if (mCallbacks.isEmpty()) { 217 // No listeners have been registered by any path, toss the request. 218 throw new IllegalArgumentException( 219 "No listeners have been registered. Request has been discarded."); 220 } 221 // If neither case above was hit, app wide listeners were provided. Continue. 222 223 final IProfilingService service = getOrCreateIProfilingServiceLocked(false); 224 if (service == null) { 225 executor.execute(() -> listener.accept( 226 new ProfilingResult(ProfilingResult.ERROR_UNKNOWN, null, tag, 227 "ProfilingService is not available"))); 228 if (DEBUG) Log.d(TAG, "ProfilingService is not available"); 229 return; 230 } 231 232 String packageName = mContext.getPackageName(); 233 if (packageName == null) { 234 executor.execute(() -> listener.accept( 235 new ProfilingResult(ProfilingResult.ERROR_UNKNOWN, null, tag, 236 "Failed to resolve package name"))); 237 if (DEBUG) Log.d(TAG, "Failed to resolve package name."); 238 return; 239 } 240 241 // For key, use most and least significant bits so we can create an identical UUID 242 // after passing over binder. 243 service.requestProfiling(profilingType, parameters, 244 mContext.getFilesDir().getPath(), tag, 245 key.getMostSignificantBits(), key.getLeastSignificantBits(), 246 packageName); 247 if (cancellationSignal != null) { 248 cancellationSignal.setOnCancelListener( 249 () -> { 250 synchronized (mLock) { 251 try { 252 service.requestCancel(key.getMostSignificantBits(), 253 key.getLeastSignificantBits()); 254 } catch (RemoteException e) { 255 // Ignore, request in flight already and we can't stop it. 256 } 257 } 258 } 259 ); 260 } 261 } catch (RemoteException e) { 262 if (DEBUG) Log.d(TAG, "Binder exception processing request", e); 263 executor.execute(() -> listener.accept( 264 new ProfilingResult(ProfilingResult.ERROR_UNKNOWN, null, tag, 265 "Binder exception processing request"))); 266 throw new RuntimeException("Unable to request profiling."); 267 } 268 } 269 } 270 271 /** 272 * Register a listener to be called for all profiling results for this uid. Listeners set here 273 * will be called in addition to any provided with the request. 274 * 275 * <p class="note"> Note: If a callback attempt fails (for example, because your app is killed 276 * while a trace is in progress) re-delivery may be attempted using a listener added via this 277 * method. </p> 278 * 279 * @param executor The executor to call back with. 280 * @param listener Listener to be triggered with result. 281 */ registerForAllProfilingResults( @onNull Executor executor, @NonNull Consumer<ProfilingResult> listener)282 public void registerForAllProfilingResults( 283 @NonNull Executor executor, 284 @NonNull Consumer<ProfilingResult> listener) { 285 synchronized (mLock) { 286 // Only notify {@link mProfilingService} of a general listener being added if it already 287 // exists as registering it also handles the notifying. 288 boolean shouldNotifyService = mProfilingService != null; 289 290 if (getOrCreateIProfilingServiceLocked(true) == null) { 291 // If the binder object was not successfully registered then this listener will 292 // not ever be triggered. 293 executor.execute(() -> listener.accept(new ProfilingResult( 294 ProfilingResult.ERROR_UNKNOWN, null, null, 295 "Binder exception processing request"))); 296 return; 297 } 298 mCallbacks.add(new ProfilingRequestCallbackWrapper(executor, listener, null)); 299 300 if (shouldNotifyService) { 301 // Notify service that a general listener was added. General listeners are also used 302 // for queued callbacks if any are waiting. 303 try { 304 mProfilingService.generalListenerAdded(); 305 } catch (RemoteException e) { 306 // Do nothing. Binder callback is already registered, but service won't know 307 // there is a general listener so queued callbacks won't occur. 308 Log.d(TAG, "Exception notifying service of general callback," 309 + " queued callbacks will not occur.", e); 310 } 311 } 312 } 313 } 314 315 /** 316 * Unregister a listener that was to be called for all profiling results. If no listener is 317 * provided, all listeners for this process that were not submitted with a profiling request 318 * will be removed. 319 * 320 * @param listener Listener to unregister and no longer be triggered with the results. 321 * Null to remove all global listeners for this uid. 322 */ unregisterForAllProfilingResults( @ullable Consumer<ProfilingResult> listener)323 public void unregisterForAllProfilingResults( 324 @Nullable Consumer<ProfilingResult> listener) { 325 synchronized (mLock) { 326 if (mCallbacks.isEmpty()) { 327 // No callbacks, nothing to remove. 328 return; 329 } 330 331 if (listener == null) { 332 // Remove all global listeners. 333 ArrayList<ProfilingRequestCallbackWrapper> listenersToRemove = new ArrayList<>(); 334 for (int i = 0; i < mCallbacks.size(); i++) { 335 ProfilingRequestCallbackWrapper wrapper = mCallbacks.get(i); 336 // Only remove global listeners which are not tied to a specific request. These 337 // can be identified by checking that they do not have an associated key. 338 if (wrapper.mKey == null) { 339 listenersToRemove.add(wrapper); 340 } 341 } 342 mCallbacks.removeAll(listenersToRemove); 343 } else { 344 // Remove the provided listener only. 345 for (int i = 0; i < mCallbacks.size(); i++) { 346 ProfilingRequestCallbackWrapper wrapper = mCallbacks.get(i); 347 if (listener.equals(wrapper.mListener)) { 348 mCallbacks.remove(i); 349 return; 350 } 351 } 352 } 353 } 354 } 355 356 357 /** @hide */ 358 @VisibleForTesting 359 @GuardedBy("mLock") getOrCreateIProfilingServiceLocked( boolean isGeneralListener)360 public @Nullable IProfilingService getOrCreateIProfilingServiceLocked( 361 boolean isGeneralListener) { 362 // We only register the callback with registerResultsCallback once per binder object, and we 363 // only create one binder object per ProfilingManager instance. If the object already exists 364 // then it was successfully created and registered previously so we can just return it. 365 if (mProfilingService != null) { 366 return mProfilingService; 367 } 368 369 mProfilingService = IProfilingService.Stub.asInterface( 370 ProfilingFrameworkInitializer.getProfilingServiceManager() 371 .getProfilingServiceRegisterer().get()); 372 if (mProfilingService == null) { 373 // Service is not accessible, all requests will fail. 374 return mProfilingService; 375 } 376 try { 377 mProfilingService.registerResultsCallback(isGeneralListener, 378 new IProfilingResultCallback.Stub() { 379 380 /** 381 * Called by {@link ProfilingService} when a result is ready, 382 * both for success and failure. 383 */ 384 @Override 385 public void sendResult(@Nullable String resultFile, long keyMostSigBits, 386 long keyLeastSigBits, int status, @Nullable String tag, 387 @Nullable String error) { 388 synchronized (mLock) { 389 if (mCallbacks.isEmpty()) { 390 // This shouldn't happen - no callbacks, nowhere to report this 391 // result. 392 if (DEBUG) Log.d(TAG, "No callbacks"); 393 mProfilingService = null; 394 return; 395 } 396 397 // This shouldn't be true, but if the file is null ensure the status 398 // represents a failure. 399 final boolean overrideStatusToError = resultFile == null 400 && status == ProfilingResult.ERROR_NONE; 401 402 UUID key = new UUID(keyMostSigBits, keyLeastSigBits); 403 int removeListenerPos = -1; 404 for (int i = 0; i < mCallbacks.size(); i++) { 405 ProfilingRequestCallbackWrapper wrapper = mCallbacks.get(i); 406 if (key.equals(wrapper.mKey)) { 407 // At most 1 listener can have a key matching this result: 408 // the one registered with the request, remove that one 409 // only. 410 if (removeListenerPos == -1) { 411 removeListenerPos = i; 412 } else { 413 // This should never happen. 414 if (DEBUG) { 415 Log.d(TAG, 416 "More than 1 listener with the same key"); 417 } 418 } 419 } else if (wrapper.mKey != null) { 420 // If the key is not null, and doesn't matched the result 421 // key, then this key belongs to another request and should 422 // not be triggered. 423 continue; 424 } 425 426 // TODO: b/337017299 - check resultFile is valid before 427 // returning Now trigger the callback for any listener that 428 // doesn't belong to another request. 429 wrapper.mExecutor.execute(() -> wrapper.mListener.accept( 430 new ProfilingResult(overrideStatusToError 431 ? ProfilingResult.ERROR_UNKNOWN : status, 432 resultFile, tag, error))); 433 } 434 435 // Remove the single listener that was tied to the request, if 436 // applicable. 437 if (removeListenerPos != -1) { 438 mCallbacks.remove(removeListenerPos); 439 } 440 } 441 } 442 443 /** 444 * Called by {@link ProfilingService} when a trace is ready and needs to be 445 * copied to callers internal storage. 446 * 447 * This method will open a new file and pass back the FileDescriptor for 448 * ProfilingService to write to via a new binder call. 449 * 450 * Takes in key most/least significant bits which represent the key that 451 * will be used to associate this back to a profiling session which will 452 * write to the generated file. 453 */ 454 @Override 455 public void generateFile(String filePathAbsolute, String fileName, 456 long keyMostSigBits, long keyLeastSigBits) { 457 synchronized (mLock) { 458 try { 459 // Ensure the profiling directory exists. Create it if it 460 // doesn't. 461 final File profilingDir = new File(filePathAbsolute); 462 if (!profilingDir.exists()) { 463 profilingDir.mkdir(); 464 } 465 466 // Create the profiling file for the output to be written to. 467 final File profilingFile = new File( 468 filePathAbsolute + fileName); 469 profilingFile.createNewFile(); 470 if (!profilingFile.exists()) { 471 // Failed to create output file. Result may be lost. 472 if (DEBUG) Log.d(TAG, "Output file couldn't be created"); 473 return; 474 } 475 476 // Wrap the new output file in a {@link ParcelFileDescriptor} to 477 // send back to {@link ProfilingService} to write to. 478 ParcelFileDescriptor pfd = ParcelFileDescriptor.open( 479 profilingFile, 480 ParcelFileDescriptor.MODE_READ_WRITE); 481 IProfilingService service = 482 getOrCreateIProfilingServiceLocked(false); 483 484 if (service == null) { 485 // Unable to send file descriptor because we have nowhere to 486 // send it to. Result may be lost. Close descriptor and 487 // delete file. 488 if (DEBUG) Log.d(TAG, "Unable to send file descriptor"); 489 tryToCleanupGeneratedFile(pfd, profilingFile); 490 return; 491 } 492 493 try { 494 // Send the file descriptor to service to write to. 495 service.receiveFileDescriptor(pfd, keyMostSigBits, 496 keyLeastSigBits); 497 } catch (RemoteException e) { 498 // If we failed to send it, try to clean it up as it won't 499 // be used. 500 if (DEBUG) { 501 Log.d(TAG, "Failed sending file descriptor to service", 502 e); 503 } 504 tryToCleanupGeneratedFile(pfd, profilingFile); 505 } 506 } catch (Exception e) { 507 // Failure prepping output file. Result may be lost. 508 if (DEBUG) Log.d(TAG, "Exception preparing file", e); 509 return; 510 } 511 } 512 } 513 514 /** 515 * Attempt to clean up the files created for service by closing the file 516 * descriptor and deleting the file. This is intended for error cases where 517 * the descriptor could not be sent. If it was successfully sent, service 518 * will handle closing it and requesting a delete if necessary. 519 */ 520 private void tryToCleanupGeneratedFile(ParcelFileDescriptor fileDescriptor, 521 File file) { 522 if (fileDescriptor != null) { 523 try { 524 fileDescriptor.close(); 525 } catch (IOException e) { 526 // Nothing else we can do, ignore. 527 if (DEBUG) Log.d(TAG, "Failed to cleanup file descriptor", e); 528 } 529 } 530 531 if (file != null) { 532 try { 533 file.delete(); 534 } catch (SecurityException e) { 535 // Nothing else we can do, ignore. 536 if (DEBUG) Log.d(TAG, "Failed to cleanup file", e); 537 } 538 } 539 } 540 541 /** 542 * Delete a file. To be used only for files created by {@link generateFile}. 543 */ 544 @Override 545 public void deleteFile(String filePathAndName) { 546 try { 547 Files.delete(Path.of(filePathAndName)); 548 } catch (Exception exception) { 549 if (DEBUG) Log.e(TAG, "Failed to delete file.", exception); 550 } 551 } 552 }); 553 } catch (RemoteException e) { 554 if (DEBUG) Log.d(TAG, "Exception registering service callback", e); 555 throw new RuntimeException("Unable to register profiling result callback." 556 + " All Profiling requests will fail."); 557 } 558 return mProfilingService; 559 } 560 561 private static final class ProfilingRequestCallbackWrapper { 562 /** executor provided with callback request */ 563 final @NonNull Executor mExecutor; 564 565 /** listener provided with callback request */ 566 final @NonNull Consumer<ProfilingResult> mListener; 567 568 /** 569 * Unique key generated with each profiling request {@link #requestProfiling}, but not with 570 * requests to register a listener only {@link #registerForAllProfilingResults}. 571 * 572 * Key is used to match the result with the listener added with the request so that it can 573 * removed after being triggered while the general registered callbacks remain active. 574 */ 575 final @Nullable UUID mKey; 576 ProfilingRequestCallbackWrapper(@onNull Executor executor, @NonNull Consumer<ProfilingResult> listener, @Nullable UUID key)577 ProfilingRequestCallbackWrapper(@NonNull Executor executor, 578 @NonNull Consumer<ProfilingResult> listener, 579 @Nullable UUID key) { 580 mExecutor = executor; 581 mListener = listener; 582 mKey = key; 583 } 584 } 585 } 586