script-astra/Android/Sdk/sources/android-35/android/os/ProfilingManager.java
localadmin 4380f00a78 init
2025-01-20 18:15:20 +03:00

432 lines
20 KiB
Java

/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.os;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.os.profiling.Flags;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import java.io.File;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.UUID;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
* API for apps to request and listen for app specific profiling.
*/
@FlaggedApi(Flags.FLAG_TELEMETRY_APIS)
public final class ProfilingManager {
private static final String TAG = ProfilingManager.class.getSimpleName();
private static final boolean DEBUG = false;
/** Profiling type for {@link #requestProfiling} to request a java heap dump. */
public static final int PROFILING_TYPE_JAVA_HEAP_DUMP = 1;
/** Profiling type for {@link #requestProfiling} to request a heap profile. */
public static final int PROFILING_TYPE_HEAP_PROFILE = 2;
/** Profiling type for {@link #requestProfiling} to request a stack sample. */
public static final int PROFILING_TYPE_STACK_SAMPLING = 3;
/** Profiling type for {@link #requestProfiling} to request a system trace. */
public static final int PROFILING_TYPE_SYSTEM_TRACE = 4;
/* Begin public API defined keys. */
/* End public API defined keys. */
/* Begin not-public API defined keys. */
/**
* Can only be used with profiling type heap profile, stack sampling, or system trace.
* Value of type int.
* @hide */
public static final String KEY_DURATION_MS = "KEY_DURATION_MS";
/**
* Can only be used with profiling type heap profile. Value of type long.
* @hide */
public static final String KEY_SAMPLING_INTERVAL_BYTES = "KEY_SAMPLING_INTERVAL_BYTES";
/**
* Can only be used with profiling type heap profile. Value of type boolean.
* @hide */
public static final String KEY_TRACK_JAVA_ALLOCATIONS = "KEY_TRACK_JAVA_ALLOCATIONS";
/**
* Can only be used with profiling type stack sampling. Value of type int.
* @hide */
public static final String KEY_FREQUENCY_HZ = "KEY_FREQUENCY_HZ";
/**
* Can be used with all profiling types. Value of type int.
* @hide */
public static final String KEY_SIZE_KB = "KEY_SIZE_KB";
/* End not-public API defined keys. */
/**
* @hide *
*/
@IntDef(
prefix = {"PROFILING_TYPE_"},
value = {
PROFILING_TYPE_JAVA_HEAP_DUMP,
PROFILING_TYPE_HEAP_PROFILE,
PROFILING_TYPE_STACK_SAMPLING,
PROFILING_TYPE_SYSTEM_TRACE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ProfilingType {}
private final Object mLock = new Object();
private final Context mContext;
/** @hide */
@VisibleForTesting
@GuardedBy("mLock")
public final ArrayList<ProfilingRequestCallbackWrapper> mCallbacks = new ArrayList<>();
@GuardedBy("mLock")
private IProfilingService mProfilingService;
/**
* Constructor for ProfilingManager.
*
* @hide
*/
public ProfilingManager(Context context) {
mContext = context;
}
/**
* Request system profiling.
*
* <p class="note"> Note: use of this API directly is not recommended for most use cases.
* Please use the higher level wrappers provided by androidx that will construct the request
* correctly based on available options and simplified user provided request parameters.</p>
*
* <p class="note"> Note: requests are not guaranteed to be filled.</p>
*
* <p class="note"> Note: Both a listener and executor must be set for the request to be
* considered for fulfillment.
* Listeners can be set in this method, with {@link #registerForAllProfilingResults}, or both.
* If no listener and executor is set the request will be discarded.</p>
*
* @param profilingType Type of profiling to collect.
* @param parameters Bundle of request related parameters. If the bundle contains any
* unrecognized parameters, the request will be fail with
* {@link #ProfilingResult#ERROR_FAILED_INVALID_REQUEST}. If the values for
* the parameters are out of supported range, the closest possible in range
* value will be chosen.
* Use of androidx wrappers is recommended over generating this directly.
* @param tag Caller defined data to help identify the output.
* The first 20 alphanumeric characters, plus dashes, will be lowercased
* and included in the output filename.
* @param cancellationSignal for caller requested cancellation.
* Results will be returned if available.
* If this is null, the requesting app will not be able to stop the collection.
* The collection will stop after timing out with either the provided
* configurations or with system defaults
* @param executor The executor to call back with.
* Will only be used for the listener provided in this method.
* If this is null, and no global executor and listener combinations are
* registered at the time of the request, the request will be dropped.
* @param listener Listener to be triggered with result. Any global listeners registered via
* {@link #registerForAllProfilingResults} will also be triggered. If this is
* null, and no global listener and executor combinations are registered at
* the time of the request, the request will be dropped.
*/
public void requestProfiling(
@ProfilingType int profilingType,
@Nullable Bundle parameters,
@Nullable String tag,
@Nullable CancellationSignal cancellationSignal,
@Nullable Executor executor,
@Nullable Consumer<ProfilingResult> listener) {
synchronized (mLock) {
try {
final UUID key = UUID.randomUUID();
if (executor != null && listener != null) {
// Listeners are provided, store them.
mCallbacks.add(new ProfilingRequestCallbackWrapper(executor, listener, key));
} else if (mCallbacks.isEmpty()) {
// No listeners have been registered by any path, toss the request.
throw new IllegalArgumentException(
"No listeners have been registered. Request has been discarded.");
}
// If neither case above was hit, app wide listeners were provided. Continue.
final IProfilingService service = getIProfilingServiceLocked();
if (service == null) {
executor.execute(() -> listener.accept(
new ProfilingResult(ProfilingResult.ERROR_UNKNOWN, null, tag,
"ProfilingService is not available")));
if (DEBUG) Log.d(TAG, "ProfilingService is not available");
return;
}
// For key, use most and least significant bits so we can create an identical UUID
// after passing over binder.
service.requestProfiling(profilingType, parameters,
mContext.getFilesDir().getPath(), tag,
key.getMostSignificantBits(), key.getLeastSignificantBits());
if (cancellationSignal != null) {
cancellationSignal.setOnCancelListener(
() -> {
synchronized (mLock) {
try {
service.requestCancel(key.getMostSignificantBits(),
key.getLeastSignificantBits());
} catch (RemoteException e) {
// Ignore, request in flight already and we can't stop it.
}
}
}
);
}
} catch (RemoteException e) {
if (DEBUG) Log.d(TAG, "Binder exception processing request", e);
executor.execute(() -> listener.accept(
new ProfilingResult(ProfilingResult.ERROR_UNKNOWN, null, tag,
"Binder exception processing request")));
throw new RuntimeException("Unable to request profiling.");
}
}
}
/**
* Register a listener to be called for all profiling results for this uid. Listeners set here
* will be called in addition to any provided with the request.
*
* @param executor The executor to call back with.
* @param listener Listener to be triggered with result.
*/
public void registerForAllProfilingResults(
@NonNull Executor executor,
@NonNull Consumer<ProfilingResult> listener) {
synchronized (mLock) {
if (getIProfilingServiceLocked() == null) {
// If the binder object was not successfully registered then this listener will
// not ever be triggered.
executor.execute(() -> listener.accept(new ProfilingResult(
ProfilingResult.ERROR_UNKNOWN, null, null,
"Binder exception processing request")));
return;
}
mCallbacks.add(new ProfilingRequestCallbackWrapper(executor, listener, null));
}
}
/**
* Unregister a listener that was to be called for all profiling results. If no listener is
* provided, all listeners for this process that were not submitted with a profiling request
* will be removed.
*
* @param listener Listener to unregister and no longer be triggered with the results.
* Null to remove all global listeners for this uid.
*/
public void unregisterForAllProfilingResults(
@Nullable Consumer<ProfilingResult> listener) {
synchronized (mLock) {
if (mCallbacks.isEmpty()) {
// No callbacks, nothing to remove.
return;
}
if (listener == null) {
// Remove all global listeners.
ArrayList<ProfilingRequestCallbackWrapper> listenersToRemove = new ArrayList<>();
for (int i = 0; i < mCallbacks.size(); i++) {
ProfilingRequestCallbackWrapper wrapper = mCallbacks.get(i);
// Only remove global listeners which are not tied to a specific request. These
// can be identified by checking that they do not have an associated key.
if (wrapper.mKey == null) {
listenersToRemove.add(wrapper);
}
}
mCallbacks.removeAll(listenersToRemove);
} else {
// Remove the provided listener only.
for (int i = 0; i < mCallbacks.size(); i++) {
ProfilingRequestCallbackWrapper wrapper = mCallbacks.get(i);
if (listener.equals(wrapper.mListener)) {
mCallbacks.remove(i);
return;
}
}
}
}
}
@GuardedBy("mLock")
private @Nullable IProfilingService getIProfilingServiceLocked() {
if (mProfilingService != null) {
return mProfilingService;
}
mProfilingService = IProfilingService.Stub.asInterface(
ProfilingFrameworkInitializer.getProfilingServiceManager()
.getProfilingServiceRegisterer().get());
if (mProfilingService == null) {
// Service is not accessible, all requests will fail.
return mProfilingService;
}
try {
mProfilingService.registerResultsCallback(new IProfilingResultCallback.Stub() {
/**
* Called by {@link ProfilingService} when a result is ready,
* both for success and failure.
*
* @return whether there are additional callbacks backed by this binder object.
*/
@Override
public boolean sendResult(@Nullable String resultFile, long keyMostSigBits,
long keyLeastSigBits, int status, @Nullable String tag,
@Nullable String error) {
synchronized (mLock) {
if (mCallbacks.isEmpty()) {
// This shouldn't happen - no callbacks, nowhere to report this result.
if (DEBUG) Log.d(TAG, "No callbacks");
mProfilingService = null;
return false;
}
// This shouldn't be true, but if the file is null ensure the status
// represents a failure.
final boolean overrideStatusToError = resultFile == null
&& status == ProfilingResult.ERROR_NONE;
UUID key = new UUID(keyMostSigBits, keyLeastSigBits);
int removeListenerPos = -1;
for (int i = 0; i < mCallbacks.size(); i++) {
ProfilingRequestCallbackWrapper wrapper = mCallbacks.get(i);
if (key.equals(wrapper.mKey)) {
// At most 1 listener can have a key matching this result: the one
// registered with the request, remove that one only.
if (removeListenerPos == -1) {
removeListenerPos = i;
} else {
// This should never happen.
if (DEBUG) Log.d(TAG, "More than 1 listener with the same key");
}
} else if (wrapper.mKey != null) {
// If the key is not null, and doesn't matched the result key, then
// this key belongs to another request and should not be triggered.
continue;
}
// TODO: b/337017299 - check resultFile is valid before returning
// Now trigger the callback for any listener that doesn't belong to
// another request.
wrapper.mExecutor.execute(() -> wrapper.mListener.accept(
new ProfilingResult(overrideStatusToError
? ProfilingResult.ERROR_UNKNOWN : status,
resultFile, tag, error)));
}
// Remove the single listener that was tied to the request, if applicable.
if (removeListenerPos != -1) {
mCallbacks.remove(removeListenerPos);
}
if (mCallbacks.isEmpty()) {
mProfilingService = null;
return false;
}
return true;
}
}
/**
* Called by {@link ProfilingService} when a trace is ready and need to be copied
* to callers internal storage.
*
* This method will open a new file and pass back the FileDescriptor for
* ProfilingService to write to.
*/
@Override
public ParcelFileDescriptor generateFile(String filePathAbsolute, String fileName) {
try {
// Ensure the profiling directory exists. Create it if it doesn't.
final File profilingDir = new File(filePathAbsolute);
if (!profilingDir.exists()) {
profilingDir.mkdir();
}
// Create the profiling file for the output to be written to.
final File profilingFile = new File(filePathAbsolute + fileName);
profilingFile.createNewFile();
if (!profilingFile.exists()) {
// Failed to create output file. Result will be lost.
if (DEBUG) Log.d(TAG, "Output file couldn't be created");
return null;
}
// Wrap the new output file in a {@link ParcelFileDescriptor} and
// pass back to {@link ProfilingService} to write to.
ParcelFileDescriptor pfd = ParcelFileDescriptor.open(profilingFile,
ParcelFileDescriptor.MODE_READ_WRITE);
return pfd;
} catch (Exception e) {
// Failure prepping output file. Result will be lost.
if (DEBUG) Log.d(TAG, "Exception preparing file", e);
return null;
}
}
});
} catch (RemoteException e) {
if (DEBUG) Log.d(TAG, "Exception registering service callback", e);
throw new RuntimeException("Unable to register profiling result callback."
+ " All Profiling requests will fail.");
}
return mProfilingService;
}
private static final class ProfilingRequestCallbackWrapper {
/** executor provided with callback request */
final @NonNull Executor mExecutor;
/** listener provided with callback request */
final @NonNull Consumer<ProfilingResult> mListener;
/**
* Unique key generated with each profiling request {@link #requestProfiling}, but not with
* requests to register a listener only {@link #registerForAllProfilingResults}.
*
* Key is used to match the result with the listener added with the request so that it can
* removed after being triggered while the general registered callbacks remain active.
*/
final @Nullable UUID mKey;
ProfilingRequestCallbackWrapper(@NonNull Executor executor,
@NonNull Consumer<ProfilingResult> listener,
@Nullable UUID key) {
mExecutor = executor;
mListener = listener;
mKey = key;
}
}
}