Loading apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java +2 −1 Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ import android.app.appsearch.util.SchemaMigrationUtil; import android.os.Bundle; import android.os.ParcelableException; import android.os.RemoteException; import android.os.SystemClock; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; Loading Loading @@ -270,8 +271,8 @@ public final class AppSearchSession implements Closeable { documentBundles.add(documents.get(i).getBundle()); } try { // TODO(b/173532925) a timestamp needs to be sent here to calculate binder latency mService.putDocuments(mPackageName, mDatabaseName, documentBundles, mUserId, /*binderCallStartTimeMillis=*/ SystemClock.elapsedRealtime(), new IAppSearchBatchResultCallback.Stub() { public void onResult(AppSearchBatchResult result) { executor.execute(() -> callback.onResult(result)); Loading apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl +2 −0 Original line number Diff line number Diff line Loading @@ -94,6 +94,7 @@ interface IAppSearchManager { * @param databaseName The name of the database where this document lives. * @param documentBundes List of GenericDocument bundles. * @param userId Id of the calling user * @param binderCallStartTimeMillis start timestamp of binder call in Millis * @param callback * If the call fails to start, {@link IAppSearchBatchResultCallback#onSystemError} * will be called with the cause throwable. Otherwise, Loading @@ -106,6 +107,7 @@ interface IAppSearchManager { in String databaseName, in List<Bundle> documentBundles, in int userId, in long binderCallStartTimeMillis, in IAppSearchBatchResultCallback callback); /** Loading apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java +43 −2 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.server.appsearch; import static android.app.appsearch.AppSearchResult.throwableToFailedResult; import static android.os.UserHandle.USER_NULL; import android.annotation.ElapsedRealtimeLong; import android.annotation.NonNull; import android.annotation.UserIdInt; import android.app.ActivityManager; Loading Loading @@ -45,6 +46,7 @@ import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.os.ParcelableException; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; import android.util.ArrayMap; Loading @@ -57,6 +59,9 @@ import com.android.internal.util.Preconditions; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.appsearch.external.localstorage.AppSearchImpl; import com.android.server.appsearch.external.localstorage.stats.CallStats; import com.android.server.appsearch.stats.LoggerInstanceManager; import com.android.server.appsearch.stats.PlatformLogger; import java.io.DataInputStream; import java.io.DataOutputStream; Loading @@ -80,6 +85,7 @@ public class AppSearchManagerService extends SystemService { private PackageManagerInternal mPackageManagerInternal; private ImplInstanceManager mImplInstanceManager; private UserManager mUserManager; private LoggerInstanceManager mLoggerInstanceManager; // Never call shutdownNow(). It will cancel the futures it's returned. And since // Executor#execute won't return anything, we will hang forever waiting for the execution. Loading @@ -106,6 +112,7 @@ public class AppSearchManagerService extends SystemService { mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); mImplInstanceManager = ImplInstanceManager.getInstance(mContext); mUserManager = mContext.getSystemService(UserManager.class); mLoggerInstanceManager = LoggerInstanceManager.getInstance(); registerReceivers(); } Loading Loading @@ -147,6 +154,7 @@ public class AppSearchManagerService extends SystemService { private void handleUserRemoved(@UserIdInt int userId) { try { mImplInstanceManager.removeAppSearchImplForUser(userId); mLoggerInstanceManager.removePlatformLoggerForUser(userId); Slog.i(TAG, "Removed AppSearchImpl instance for user: " + userId); } catch (Throwable t) { Slog.e(TAG, "Unable to remove data for user: " + userId, t); Loading Loading @@ -274,6 +282,7 @@ public class AppSearchManagerService extends SystemService { @NonNull String databaseName, @NonNull List<Bundle> documentBundles, @UserIdInt int userId, @ElapsedRealtimeLong long binderCallStartTimeMillis, @NonNull IAppSearchBatchResultCallback callback) { Preconditions.checkNotNull(packageName); Preconditions.checkNotNull(databaseName); Loading @@ -282,6 +291,11 @@ public class AppSearchManagerService extends SystemService { int callingUid = Binder.getCallingUid(); int callingUserId = handleIncomingUser(userId, callingUid); EXECUTOR.execute(() -> { long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime(); @AppSearchResult.ResultCode int statusCode = AppSearchResult.RESULT_OK; PlatformLogger logger = null; int operationSuccessCount = 0; int operationFailureCount = 0; try { verifyUserUnlocked(callingUserId); verifyCallingPackage(callingUid, packageName); Loading @@ -289,20 +303,46 @@ public class AppSearchManagerService extends SystemService { new AppSearchBatchResult.Builder<>(); AppSearchImpl impl = mImplInstanceManager.getAppSearchImpl(callingUserId); logger = mLoggerInstanceManager.getPlatformLogger(callingUserId); for (int i = 0; i < documentBundles.size(); i++) { GenericDocument document = new GenericDocument(documentBundles.get(i)); try { impl.putDocument(packageName, databaseName, document, /*logger=*/ null); impl.putDocument(packageName, databaseName, document, logger); resultBuilder.setSuccess(document.getUri(), /*result=*/ null); ++operationSuccessCount; } catch (Throwable t) { resultBuilder.setResult(document.getUri(), throwableToFailedResult(t)); AppSearchResult<Void> result = throwableToFailedResult(t); resultBuilder.setResult(document.getUri(), result); // for failures, we would just log the one for last failure statusCode = result.getResultCode(); ++operationFailureCount; } } invokeCallbackOnResult(callback, resultBuilder.build()); } catch (Throwable t) { invokeCallbackOnError(callback, t); } finally { if (logger != null) { CallStats.Builder cBuilder = new CallStats.Builder(packageName, databaseName) .setCallType(CallStats.CALL_TYPE_PUT_DOCUMENTS) // TODO(b/173532925) check the existing binder call latency chart // is good enough for us: // http://dashboards/view/_72c98f9a_91d9_41d4_ab9a_bc14f79742b4 .setEstimatedBinderLatencyMillis( 2 * (int) (totalLatencyStartTimeMillis - binderCallStartTimeMillis)) .setNumOperationsSucceeded(operationSuccessCount) .setNumOperationsFailed(operationFailureCount); cBuilder.getGeneralStatsBuilder() .setStatusCode(statusCode) .setTotalLatencyMillis( (int) (SystemClock.elapsedRealtime() - totalLatencyStartTimeMillis)); logger.logStats(cBuilder.build()); } } }); } Loading Loading @@ -717,6 +757,7 @@ public class AppSearchManagerService extends SystemService { try { verifyUserUnlocked(callingUserId); mImplInstanceManager.getOrCreateAppSearchImpl(mContext, callingUserId); mLoggerInstanceManager.getOrCreatePlatformLogger(getContext(), callingUserId); invokeCallbackOnResult(callback, AppSearchResult.newSuccessfulResult(null)); } catch (Throwable t) { invokeCallbackOnError(callback, t); Loading apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java +0 −1 Original line number Diff line number Diff line Loading @@ -106,7 +106,6 @@ public final class ImplInstanceManager { * * @param userId The multi-user userId of the user that need to be removed. */ @NonNull public void removeAppSearchImplForUser(@UserIdInt int userId) { synchronized (mInstancesLocked) { mInstancesLocked.remove(userId); Loading apex/appsearch/service/java/com/android/server/appsearch/stats/LoggerInstanceManager.java 0 → 100644 +131 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 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 com.android.server.appsearch.stats; import android.annotation.NonNull; import android.annotation.UserIdInt; import android.content.Context; import android.util.SparseArray; import android.util.SparseIntArray; import com.android.internal.annotations.GuardedBy; import com.android.server.appsearch.AppSearchManagerService; /** * Manages the lifecycle of instances of {@link PlatformLogger}. * * <p>These instances are managed per unique device-user. */ public final class LoggerInstanceManager { // TODO(b/173532925) flags to control those three // So probably we can't pass those three in the constructor but need to fetch the latest value // every time we need them in the logger. private static final int MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS = 100; private static final int DEFAULT_SAMPLING_RATIO = 10; private static volatile LoggerInstanceManager sLoggerInstanceManager; @GuardedBy("mInstancesLocked") private final SparseArray<PlatformLogger> mInstancesLocked = new SparseArray<>(); private LoggerInstanceManager() { } /** * Gets an instance of {@link LoggerInstanceManager} to be used. * * <p>If no instance has been initialized yet, a new one will be created. Otherwise, the * existing instance will be returned. */ @NonNull public static LoggerInstanceManager getInstance() { if (sLoggerInstanceManager == null) { synchronized (LoggerInstanceManager.class) { if (sLoggerInstanceManager == null) { sLoggerInstanceManager = new LoggerInstanceManager(); } } } return sLoggerInstanceManager; } /** * Gets an instance of PlatformLogger for the given user, or creates one if none exists. * * @param context The context * @param userId The multi-user userId of the device user calling AppSearch * @return An initialized {@link PlatformLogger} for this user */ @NonNull public PlatformLogger getOrCreatePlatformLogger( @NonNull Context context, @UserIdInt int userId) { synchronized (mInstancesLocked) { PlatformLogger instance = mInstancesLocked.get(userId); if (instance == null) { instance = new PlatformLogger(context, userId, new PlatformLogger.Config( MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS, DEFAULT_SAMPLING_RATIO, // TODO(b/173532925) re-enable sampling ratios for different stats types // once we have P/H flag manager setup in ag/13977824 /*samplingRatios=*/ new SparseIntArray())); mInstancesLocked.put(userId, instance); } return instance; } } /** * Gets an instance of PlatformLogger for the given user. * * <p>This method should only be called by an initialized SearchSession, which has been already * created the PlatformLogger instance for the given user. * * @param userId The multi-user userId of the device user calling AppSearch * @return An initialized {@link PlatformLogger} for this user * @throws IllegalStateException if {@link PlatformLogger} haven't created for the given user. */ @NonNull public PlatformLogger getPlatformLogger(@UserIdInt int userId) { synchronized (mInstancesLocked) { PlatformLogger instance = mInstancesLocked.get(userId); if (instance == null) { // Impossible scenario, user cannot call an uninitialized SearchSession, // getInstance should always find the instance for the given user and never try to // create an instance for this user again. throw new IllegalStateException( "PlatformLogger has never been created for this user: " + userId); } return instance; } } /** * Remove an instance of {@link PlatformLogger} for the given user. * * <p>This method should only be called if {@link AppSearchManagerService} receives an * ACTION_USER_REMOVED, which the logger instance of given user should be removed. * * @param userId The multi-user userId of the user that need to be removed. */ public void removePlatformLoggerForUser(@UserIdInt int userId) { synchronized (mInstancesLocked) { mInstancesLocked.remove(userId); } } } Loading
apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java +2 −1 Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ import android.app.appsearch.util.SchemaMigrationUtil; import android.os.Bundle; import android.os.ParcelableException; import android.os.RemoteException; import android.os.SystemClock; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; Loading Loading @@ -270,8 +271,8 @@ public final class AppSearchSession implements Closeable { documentBundles.add(documents.get(i).getBundle()); } try { // TODO(b/173532925) a timestamp needs to be sent here to calculate binder latency mService.putDocuments(mPackageName, mDatabaseName, documentBundles, mUserId, /*binderCallStartTimeMillis=*/ SystemClock.elapsedRealtime(), new IAppSearchBatchResultCallback.Stub() { public void onResult(AppSearchBatchResult result) { executor.execute(() -> callback.onResult(result)); Loading
apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl +2 −0 Original line number Diff line number Diff line Loading @@ -94,6 +94,7 @@ interface IAppSearchManager { * @param databaseName The name of the database where this document lives. * @param documentBundes List of GenericDocument bundles. * @param userId Id of the calling user * @param binderCallStartTimeMillis start timestamp of binder call in Millis * @param callback * If the call fails to start, {@link IAppSearchBatchResultCallback#onSystemError} * will be called with the cause throwable. Otherwise, Loading @@ -106,6 +107,7 @@ interface IAppSearchManager { in String databaseName, in List<Bundle> documentBundles, in int userId, in long binderCallStartTimeMillis, in IAppSearchBatchResultCallback callback); /** Loading
apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java +43 −2 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.server.appsearch; import static android.app.appsearch.AppSearchResult.throwableToFailedResult; import static android.os.UserHandle.USER_NULL; import android.annotation.ElapsedRealtimeLong; import android.annotation.NonNull; import android.annotation.UserIdInt; import android.app.ActivityManager; Loading Loading @@ -45,6 +46,7 @@ import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.os.ParcelableException; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; import android.util.ArrayMap; Loading @@ -57,6 +59,9 @@ import com.android.internal.util.Preconditions; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.appsearch.external.localstorage.AppSearchImpl; import com.android.server.appsearch.external.localstorage.stats.CallStats; import com.android.server.appsearch.stats.LoggerInstanceManager; import com.android.server.appsearch.stats.PlatformLogger; import java.io.DataInputStream; import java.io.DataOutputStream; Loading @@ -80,6 +85,7 @@ public class AppSearchManagerService extends SystemService { private PackageManagerInternal mPackageManagerInternal; private ImplInstanceManager mImplInstanceManager; private UserManager mUserManager; private LoggerInstanceManager mLoggerInstanceManager; // Never call shutdownNow(). It will cancel the futures it's returned. And since // Executor#execute won't return anything, we will hang forever waiting for the execution. Loading @@ -106,6 +112,7 @@ public class AppSearchManagerService extends SystemService { mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); mImplInstanceManager = ImplInstanceManager.getInstance(mContext); mUserManager = mContext.getSystemService(UserManager.class); mLoggerInstanceManager = LoggerInstanceManager.getInstance(); registerReceivers(); } Loading Loading @@ -147,6 +154,7 @@ public class AppSearchManagerService extends SystemService { private void handleUserRemoved(@UserIdInt int userId) { try { mImplInstanceManager.removeAppSearchImplForUser(userId); mLoggerInstanceManager.removePlatformLoggerForUser(userId); Slog.i(TAG, "Removed AppSearchImpl instance for user: " + userId); } catch (Throwable t) { Slog.e(TAG, "Unable to remove data for user: " + userId, t); Loading Loading @@ -274,6 +282,7 @@ public class AppSearchManagerService extends SystemService { @NonNull String databaseName, @NonNull List<Bundle> documentBundles, @UserIdInt int userId, @ElapsedRealtimeLong long binderCallStartTimeMillis, @NonNull IAppSearchBatchResultCallback callback) { Preconditions.checkNotNull(packageName); Preconditions.checkNotNull(databaseName); Loading @@ -282,6 +291,11 @@ public class AppSearchManagerService extends SystemService { int callingUid = Binder.getCallingUid(); int callingUserId = handleIncomingUser(userId, callingUid); EXECUTOR.execute(() -> { long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime(); @AppSearchResult.ResultCode int statusCode = AppSearchResult.RESULT_OK; PlatformLogger logger = null; int operationSuccessCount = 0; int operationFailureCount = 0; try { verifyUserUnlocked(callingUserId); verifyCallingPackage(callingUid, packageName); Loading @@ -289,20 +303,46 @@ public class AppSearchManagerService extends SystemService { new AppSearchBatchResult.Builder<>(); AppSearchImpl impl = mImplInstanceManager.getAppSearchImpl(callingUserId); logger = mLoggerInstanceManager.getPlatformLogger(callingUserId); for (int i = 0; i < documentBundles.size(); i++) { GenericDocument document = new GenericDocument(documentBundles.get(i)); try { impl.putDocument(packageName, databaseName, document, /*logger=*/ null); impl.putDocument(packageName, databaseName, document, logger); resultBuilder.setSuccess(document.getUri(), /*result=*/ null); ++operationSuccessCount; } catch (Throwable t) { resultBuilder.setResult(document.getUri(), throwableToFailedResult(t)); AppSearchResult<Void> result = throwableToFailedResult(t); resultBuilder.setResult(document.getUri(), result); // for failures, we would just log the one for last failure statusCode = result.getResultCode(); ++operationFailureCount; } } invokeCallbackOnResult(callback, resultBuilder.build()); } catch (Throwable t) { invokeCallbackOnError(callback, t); } finally { if (logger != null) { CallStats.Builder cBuilder = new CallStats.Builder(packageName, databaseName) .setCallType(CallStats.CALL_TYPE_PUT_DOCUMENTS) // TODO(b/173532925) check the existing binder call latency chart // is good enough for us: // http://dashboards/view/_72c98f9a_91d9_41d4_ab9a_bc14f79742b4 .setEstimatedBinderLatencyMillis( 2 * (int) (totalLatencyStartTimeMillis - binderCallStartTimeMillis)) .setNumOperationsSucceeded(operationSuccessCount) .setNumOperationsFailed(operationFailureCount); cBuilder.getGeneralStatsBuilder() .setStatusCode(statusCode) .setTotalLatencyMillis( (int) (SystemClock.elapsedRealtime() - totalLatencyStartTimeMillis)); logger.logStats(cBuilder.build()); } } }); } Loading Loading @@ -717,6 +757,7 @@ public class AppSearchManagerService extends SystemService { try { verifyUserUnlocked(callingUserId); mImplInstanceManager.getOrCreateAppSearchImpl(mContext, callingUserId); mLoggerInstanceManager.getOrCreatePlatformLogger(getContext(), callingUserId); invokeCallbackOnResult(callback, AppSearchResult.newSuccessfulResult(null)); } catch (Throwable t) { invokeCallbackOnError(callback, t); Loading
apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java +0 −1 Original line number Diff line number Diff line Loading @@ -106,7 +106,6 @@ public final class ImplInstanceManager { * * @param userId The multi-user userId of the user that need to be removed. */ @NonNull public void removeAppSearchImplForUser(@UserIdInt int userId) { synchronized (mInstancesLocked) { mInstancesLocked.remove(userId); Loading
apex/appsearch/service/java/com/android/server/appsearch/stats/LoggerInstanceManager.java 0 → 100644 +131 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 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 com.android.server.appsearch.stats; import android.annotation.NonNull; import android.annotation.UserIdInt; import android.content.Context; import android.util.SparseArray; import android.util.SparseIntArray; import com.android.internal.annotations.GuardedBy; import com.android.server.appsearch.AppSearchManagerService; /** * Manages the lifecycle of instances of {@link PlatformLogger}. * * <p>These instances are managed per unique device-user. */ public final class LoggerInstanceManager { // TODO(b/173532925) flags to control those three // So probably we can't pass those three in the constructor but need to fetch the latest value // every time we need them in the logger. private static final int MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS = 100; private static final int DEFAULT_SAMPLING_RATIO = 10; private static volatile LoggerInstanceManager sLoggerInstanceManager; @GuardedBy("mInstancesLocked") private final SparseArray<PlatformLogger> mInstancesLocked = new SparseArray<>(); private LoggerInstanceManager() { } /** * Gets an instance of {@link LoggerInstanceManager} to be used. * * <p>If no instance has been initialized yet, a new one will be created. Otherwise, the * existing instance will be returned. */ @NonNull public static LoggerInstanceManager getInstance() { if (sLoggerInstanceManager == null) { synchronized (LoggerInstanceManager.class) { if (sLoggerInstanceManager == null) { sLoggerInstanceManager = new LoggerInstanceManager(); } } } return sLoggerInstanceManager; } /** * Gets an instance of PlatformLogger for the given user, or creates one if none exists. * * @param context The context * @param userId The multi-user userId of the device user calling AppSearch * @return An initialized {@link PlatformLogger} for this user */ @NonNull public PlatformLogger getOrCreatePlatformLogger( @NonNull Context context, @UserIdInt int userId) { synchronized (mInstancesLocked) { PlatformLogger instance = mInstancesLocked.get(userId); if (instance == null) { instance = new PlatformLogger(context, userId, new PlatformLogger.Config( MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS, DEFAULT_SAMPLING_RATIO, // TODO(b/173532925) re-enable sampling ratios for different stats types // once we have P/H flag manager setup in ag/13977824 /*samplingRatios=*/ new SparseIntArray())); mInstancesLocked.put(userId, instance); } return instance; } } /** * Gets an instance of PlatformLogger for the given user. * * <p>This method should only be called by an initialized SearchSession, which has been already * created the PlatformLogger instance for the given user. * * @param userId The multi-user userId of the device user calling AppSearch * @return An initialized {@link PlatformLogger} for this user * @throws IllegalStateException if {@link PlatformLogger} haven't created for the given user. */ @NonNull public PlatformLogger getPlatformLogger(@UserIdInt int userId) { synchronized (mInstancesLocked) { PlatformLogger instance = mInstancesLocked.get(userId); if (instance == null) { // Impossible scenario, user cannot call an uninitialized SearchSession, // getInstance should always find the instance for the given user and never try to // create an instance for this user again. throw new IllegalStateException( "PlatformLogger has never been created for this user: " + userId); } return instance; } } /** * Remove an instance of {@link PlatformLogger} for the given user. * * <p>This method should only be called if {@link AppSearchManagerService} receives an * ACTION_USER_REMOVED, which the logger instance of given user should be removed. * * @param userId The multi-user userId of the user that need to be removed. */ public void removePlatformLoggerForUser(@UserIdInt int userId) { synchronized (mInstancesLocked) { mInstancesLocked.remove(userId); } } }