Log SearchStats in the platform

Bug: b/173532925
Test: atest
CtsAppSearchTestCases FrameworksCoreTests:android.app.appsearch
FrameworksServicesTests:AppSearchImplTest
FrameworksServicesTests:com.android.server.appsearch.stats.PlatformLoggerTest

Change-Id: I6545cb6722ad6f4680e6b0f7800e1833ecc80e0e
This commit is contained in:
Xiaoyu Jin
2021-05-09 17:38:59 -07:00
parent ef7ebb5e84
commit d6d22f0ecf
6 changed files with 155 additions and 13 deletions

View File

@@ -25,6 +25,7 @@ import android.app.appsearch.aidl.IAppSearchManager;
import android.app.appsearch.aidl.IAppSearchResultCallback;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.SystemClock;
import android.util.Log;
import com.android.internal.util.Preconditions;
@@ -109,14 +110,19 @@ public class SearchResults implements Closeable {
try {
if (mIsFirstLoad) {
mIsFirstLoad = false;
long binderCallStartTimeMillis = SystemClock.elapsedRealtime();
if (mDatabaseName == null) {
// Global query, there's no one package-database combination to check.
mService.globalQuery(mPackageName, mQueryExpression,
mSearchSpec.getBundle(), mUserId, wrapCallback(executor, callback));
mSearchSpec.getBundle(), mUserId,
binderCallStartTimeMillis,
wrapCallback(executor, callback));
} else {
// Normal local query, pass in specified database.
mService.query(mPackageName, mDatabaseName, mQueryExpression,
mSearchSpec.getBundle(), mUserId, wrapCallback(executor, callback));
mSearchSpec.getBundle(), mUserId,
binderCallStartTimeMillis,
wrapCallback(executor, callback));
}
} else {
mService.getNextPage(mNextPageToken, mUserId, wrapCallback(executor, callback));

View File

@@ -139,6 +139,7 @@ interface IAppSearchManager {
* @param queryExpression String to search for
* @param searchSpecBundle SearchSpec bundle
* @param userId Id of the calling user
* @param binderCallStartTimeMillis start timestamp of binder call in Millis
* @param callback {@link AppSearchResult}<{@link Bundle}> of performing this
* operation.
*/
@@ -148,6 +149,7 @@ interface IAppSearchManager {
in String queryExpression,
in Bundle searchSpecBundle,
in int userId,
in long binderCallStartTimeMillis,
in IAppSearchResultCallback callback);
/**
@@ -158,6 +160,7 @@ interface IAppSearchManager {
* @param queryExpression String to search for
* @param searchSpecBundle SearchSpec bundle
* @param userId Id of the calling user
* @param binderCallStartTimeMillis start timestamp of binder call in Millis
* @param callback {@link AppSearchResult}<{@link Bundle}> of performing this
* operation.
*/
@@ -166,6 +169,7 @@ interface IAppSearchManager {
in String queryExpression,
in Bundle searchSpecBundle,
in int userId,
in long binderCallStartTimeMillis,
in IAppSearchResultCallback callback);
/**

View File

@@ -384,10 +384,10 @@ public class AppSearchManagerService extends SystemService {
Objects.requireNonNull(databaseName);
Objects.requireNonNull(documentBundles);
Objects.requireNonNull(callback);
long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
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;
@@ -498,32 +498,62 @@ public class AppSearchManagerService extends SystemService {
@NonNull String queryExpression,
@NonNull Bundle searchSpecBundle,
@UserIdInt int userId,
@ElapsedRealtimeLong long binderCallStartTimeMillis,
@NonNull IAppSearchResultCallback callback) {
Objects.requireNonNull(packageName);
Objects.requireNonNull(databaseName);
Objects.requireNonNull(queryExpression);
Objects.requireNonNull(searchSpecBundle);
Objects.requireNonNull(callback);
long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
int callingUid = Binder.getCallingUid();
int callingUserId = handleIncomingUser(userId, callingUid);
EXECUTOR.execute(() -> {
@AppSearchResult.ResultCode int statusCode = AppSearchResult.RESULT_OK;
PlatformLogger logger = null;
int operationSuccessCount = 0;
int operationFailureCount = 0;
try {
verifyUserUnlocked(callingUserId);
verifyCallingPackage(callingUid, packageName);
AppSearchImpl impl =
mImplInstanceManager.getAppSearchImpl(callingUserId);
logger = mLoggerInstanceManager.getPlatformLogger(callingUserId);
SearchResultPage searchResultPage =
impl.query(
packageName,
databaseName,
queryExpression,
new SearchSpec(searchSpecBundle),
/*logger=*/ null);
logger);
++operationSuccessCount;
invokeCallbackOnResult(
callback,
AppSearchResult.newSuccessfulResult(searchResultPage.getBundle()));
} catch (Throwable t) {
++operationFailureCount;
statusCode = throwableToFailedResult(t).getResultCode();
invokeCallbackOnError(callback, t);
} finally {
if (logger != null) {
int estimatedBinderLatencyMillis =
2 * (int) (totalLatencyStartTimeMillis - binderCallStartTimeMillis);
int totalLatencyMillis =
(int) (SystemClock.elapsedRealtime() - totalLatencyStartTimeMillis);
CallStats.Builder cBuilder = new CallStats.Builder(packageName,
databaseName)
.setCallType(CallStats.CALL_TYPE_SEARCH)
// TODO(b/173532925) check the existing binder call latency chart
// is good enough for us:
// http://dashboards/view/_72c98f9a_91d9_41d4_ab9a_bc14f79742b4
.setEstimatedBinderLatencyMillis(estimatedBinderLatencyMillis)
.setNumOperationsSucceeded(operationSuccessCount)
.setNumOperationsFailed(operationFailureCount);
cBuilder.getGeneralStatsBuilder()
.setStatusCode(statusCode)
.setTotalLatencyMillis(totalLatencyMillis);
logger.logStats(cBuilder.build());
}
}
});
}
@@ -534,17 +564,24 @@ public class AppSearchManagerService extends SystemService {
@NonNull String queryExpression,
@NonNull Bundle searchSpecBundle,
@UserIdInt int userId,
@ElapsedRealtimeLong long binderCallStartTimeMillis,
@NonNull IAppSearchResultCallback callback) {
Objects.requireNonNull(packageName);
Objects.requireNonNull(queryExpression);
Objects.requireNonNull(searchSpecBundle);
Objects.requireNonNull(callback);
long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
int callingUid = Binder.getCallingUid();
int callingUserId = handleIncomingUser(userId, callingUid);
EXECUTOR.execute(() -> {
@AppSearchResult.ResultCode int statusCode = AppSearchResult.RESULT_OK;
PlatformLogger logger = null;
int operationSuccessCount = 0;
int operationFailureCount = 0;
try {
verifyUserUnlocked(callingUserId);
verifyCallingPackage(callingUid, packageName);
logger = mLoggerInstanceManager.getPlatformLogger(callingUserId);
AppSearchImpl impl =
mImplInstanceManager.getAppSearchImpl(callingUserId);
SearchResultPage searchResultPage =
@@ -553,12 +590,36 @@ public class AppSearchManagerService extends SystemService {
new SearchSpec(searchSpecBundle),
packageName,
callingUid,
/*logger=*/ null);
logger);
++operationSuccessCount;
invokeCallbackOnResult(
callback,
AppSearchResult.newSuccessfulResult(searchResultPage.getBundle()));
} catch (Throwable t) {
} catch (Throwable t) {
++operationFailureCount;
statusCode = throwableToFailedResult(t).getResultCode();
invokeCallbackOnError(callback, t);
} finally {
if (logger != null) {
int estimatedBinderLatencyMillis =
2 * (int) (totalLatencyStartTimeMillis - binderCallStartTimeMillis);
int totalLatencyMillis =
(int) (SystemClock.elapsedRealtime() - totalLatencyStartTimeMillis);
// TODO(b/173532925) database would be nulluable once we remove generalStats
CallStats.Builder cBuilder = new CallStats.Builder(packageName,
/*database=*/ "")
.setCallType(CallStats.CALL_TYPE_GLOBAL_SEARCH)
// TODO(b/173532925) check the existing binder call latency chart
// is good enough for us:
// http://dashboards/view/_72c98f9a_91d9_41d4_ab9a_bc14f79742b4
.setEstimatedBinderLatencyMillis(estimatedBinderLatencyMillis)
.setNumOperationsSucceeded(operationSuccessCount)
.setNumOperationsFailed(operationFailureCount);
cBuilder.getGeneralStatsBuilder()
.setStatusCode(statusCode)
.setTotalLatencyMillis(totalLatencyMillis);
logger.logStats(cBuilder.build());
}
}
});
}

View File

@@ -17,6 +17,7 @@
package com.android.server.appsearch.stats;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.appsearch.exceptions.AppSearchException;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -198,7 +199,12 @@ public final class PlatformLogger implements AppSearchLogger {
@Override
public void logStats(@NonNull SearchStats stats) throws AppSearchException {
// TODO(b/173532925): Implement
Objects.requireNonNull(stats);
synchronized (mLock) {
if (shouldLogForTypeLocked(CallStats.CALL_TYPE_SEARCH)) {
logStatsImplLocked(stats);
}
}
}
/**
@@ -242,7 +248,9 @@ public final class PlatformLogger implements AppSearchLogger {
//
// Something is wrong while calculating the hash code for database
// this shouldn't happen since we always use "MD5" and "UTF-8"
Log.e(TAG, "Error calculating hash code for database " + database, e);
if (database != null) {
Log.e(TAG, "Error calculating hash code for database " + database, e);
}
}
}
@@ -277,7 +285,55 @@ public final class PlatformLogger implements AppSearchLogger {
//
// Something is wrong while calculating the hash code for database
// this shouldn't happen since we always use "MD5" and "UTF-8"
Log.e(TAG, "Error calculating hash code for database " + database, e);
if (database != null) {
Log.e(TAG, "Error calculating hash code for database " + database, e);
}
}
}
@GuardedBy("mLock")
private void logStatsImplLocked(@NonNull SearchStats stats) {
mLastPushTimeMillisLocked = SystemClock.elapsedRealtime();
ExtraStats extraStats = createExtraStatsLocked(stats.getPackageName(),
CallStats.CALL_TYPE_SEARCH);
String database = stats.getDatabase();
try {
int hashCodeForDatabase = calculateHashCodeMd5(database);
FrameworkStatsLog.write(FrameworkStatsLog.APP_SEARCH_QUERY_STATS_REPORTED,
extraStats.mSamplingRatio,
extraStats.mSkippedSampleCount,
extraStats.mPackageUid,
hashCodeForDatabase,
stats.getStatusCode(),
stats.getTotalLatencyMillis(),
stats.getRewriteSearchSpecLatencyMillis(),
stats.getRewriteSearchResultLatencyMillis(),
stats.getVisibilityScope(),
stats.getNativeLatencyMillis(),
stats.getTermCount(),
stats.getQueryLength(),
stats.getFilteredNamespaceCount(),
stats.getFilteredSchemaTypeCount(),
stats.getRequestedPageSize(),
stats.getCurrentPageReturnedResultCount(),
stats.isFirstPage(),
stats.getParseQueryLatencyMillis(),
stats.getRankingStrategy(),
stats.getScoredDocumentCount(),
stats.getScoringLatencyMillis(),
stats.getRankingLatencyMillis(),
stats.getDocumentRetrievingLatencyMillis(),
stats.getResultWithSnippetsCount());
} catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
// TODO(b/184204720) report hashing error to Westworld
// We need to set a special value(e.g. 0xFFFFFFFF) for the hashing of the database,
// so in the dashboard we know there is some error for hashing.
//
// Something is wrong while calculating the hash code for database
// this shouldn't happen since we always use "MD5" and "UTF-8"
if (database != null) {
Log.e(TAG, "Error calculating hash code for database " + database, e);
}
}
}
@@ -285,13 +341,20 @@ public final class PlatformLogger implements AppSearchLogger {
* Calculate the hash code as an integer by returning the last four bytes of its MD5.
*
* @param str a string
* @return hash code as an integer
* @return hash code as an integer. returns -1 if str is null.
* @throws AppSearchException if either algorithm or encoding does not exist.
*/
@VisibleForTesting
@NonNull
static int calculateHashCodeMd5(@NonNull String str) throws
static int calculateHashCodeMd5(@Nullable String str) throws
NoSuchAlgorithmException, UnsupportedEncodingException {
if (str == null) {
// Just return -1 if caller doesn't have database name
// For some stats like globalQuery, databaseName can be null.
// Since in atom it is an integer, we have to return something here.
return -1;
}
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(str.getBytes(/*charsetName=*/ "UTF-8"));
byte[] digest = md.digest();

View File

@@ -195,6 +195,12 @@ public class PlatformLoggerTest {
assertThat(longHashCode).isEqualTo(calculateHashCodeMd5withBigInteger(longStr));
}
@Test
public void testCalculateHashCode_MD5_strIsNull() throws
NoSuchAlgorithmException, UnsupportedEncodingException {
assertThat(PlatformLogger.calculateHashCodeMd5(/*str=*/ null)).isEqualTo(-1);
}
@Test
public void testShouldLogForTypeLocked_trueWhenSampleRatioIsOne() {
final int samplingRatio = 1;

View File

@@ -746,7 +746,8 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
@Override
public void query(String packageName, String databaseName, String queryExpression,
Bundle searchSpecBundle, int userId, IAppSearchResultCallback callback)
Bundle searchSpecBundle, int userId, long binderCallStartTimeMillis,
IAppSearchResultCallback callback)
throws RemoteException {
final String key = getKey(userId, databaseName);
if (!mDocumentMap.containsKey(key)) {
@@ -776,7 +777,8 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
@Override
public void globalQuery(String packageName, String queryExpression, Bundle searchSpecBundle,
int userId, IAppSearchResultCallback callback) throws RemoteException {
int userId, long binderCallStartTimeMillis, IAppSearchResultCallback callback)
throws RemoteException {
ignore(callback);
}