|
|
|
|
@@ -0,0 +1,203 @@
|
|
|
|
|
/*
|
|
|
|
|
* 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.Nullable;
|
|
|
|
|
import android.annotation.UserIdInt;
|
|
|
|
|
import android.app.StatsManager;
|
|
|
|
|
import android.content.Context;
|
|
|
|
|
import android.os.UserHandle;
|
|
|
|
|
import android.util.Log;
|
|
|
|
|
import android.util.StatsEvent;
|
|
|
|
|
|
|
|
|
|
import com.android.server.appsearch.AppSearchUserInstance;
|
|
|
|
|
import com.android.server.appsearch.AppSearchUserInstanceManager;
|
|
|
|
|
|
|
|
|
|
import com.google.android.icing.proto.DocumentStorageInfoProto;
|
|
|
|
|
import com.google.android.icing.proto.IndexStorageInfoProto;
|
|
|
|
|
import com.google.android.icing.proto.SchemaStoreStorageInfoProto;
|
|
|
|
|
import com.google.android.icing.proto.StorageInfoProto;
|
|
|
|
|
|
|
|
|
|
import java.util.List;
|
|
|
|
|
import java.util.Objects;
|
|
|
|
|
import java.util.concurrent.Executor;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Implements statsd pullers for AppSearch.
|
|
|
|
|
*
|
|
|
|
|
* <p>This class registers pullers to statsd, which will be called once a day to obtain AppSearch
|
|
|
|
|
* statistics that cannot be sent to statsd in real time by {@link PlatformLogger}.
|
|
|
|
|
*
|
|
|
|
|
* @hide
|
|
|
|
|
*/
|
|
|
|
|
public final class StatsCollector implements StatsManager.StatsPullAtomCallback {
|
|
|
|
|
private static final String TAG = "AppSearchStatsCollector";
|
|
|
|
|
|
|
|
|
|
private static volatile StatsCollector sStatsCollector;
|
|
|
|
|
private final StatsManager mStatsManager;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Gets an instance of {@link StatsCollector} 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 StatsCollector getInstance(@NonNull Context context,
|
|
|
|
|
@NonNull Executor executor) {
|
|
|
|
|
Objects.requireNonNull(context);
|
|
|
|
|
Objects.requireNonNull(executor);
|
|
|
|
|
if (sStatsCollector == null) {
|
|
|
|
|
synchronized (StatsCollector.class) {
|
|
|
|
|
if (sStatsCollector == null) {
|
|
|
|
|
sStatsCollector = new StatsCollector(context, executor);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return sStatsCollector;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private StatsCollector(@NonNull Context context, @NonNull Executor executor) {
|
|
|
|
|
mStatsManager = context.getSystemService(StatsManager.class);
|
|
|
|
|
if (mStatsManager != null) {
|
|
|
|
|
registerAtom(AppSearchStatsLog.APP_SEARCH_STORAGE_INFO, /*policy=*/ null, executor);
|
|
|
|
|
Log.d(TAG, "atoms registered");
|
|
|
|
|
} else {
|
|
|
|
|
Log.e(TAG, "could not get StatsManager, atoms not registered");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* {@inheritDoc}
|
|
|
|
|
*
|
|
|
|
|
* @return {@link StatsManager#PULL_SUCCESS} with list of atoms (potentially empty) if pull
|
|
|
|
|
* succeeded, {@link StatsManager#PULL_SKIP} if pull was too frequent or atom ID is
|
|
|
|
|
* unexpected.
|
|
|
|
|
*/
|
|
|
|
|
@Override
|
|
|
|
|
public int onPullAtom(int atomTag, @NonNull List<StatsEvent> data) {
|
|
|
|
|
Objects.requireNonNull(data);
|
|
|
|
|
switch (atomTag) {
|
|
|
|
|
case AppSearchStatsLog.APP_SEARCH_STORAGE_INFO:
|
|
|
|
|
return pullAppSearchStorageInfo(data);
|
|
|
|
|
default:
|
|
|
|
|
Log.e(TAG, "unexpected atom ID " + atomTag);
|
|
|
|
|
return StatsManager.PULL_SKIP;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static int pullAppSearchStorageInfo(@NonNull List<StatsEvent> data) {
|
|
|
|
|
AppSearchUserInstanceManager userInstanceManager =
|
|
|
|
|
AppSearchUserInstanceManager.getInstance();
|
|
|
|
|
List<UserHandle> userHandles = userInstanceManager.getAllUserHandles();
|
|
|
|
|
for (int i = 0; i < userHandles.size(); i++) {
|
|
|
|
|
UserHandle userHandle = userHandles.get(i);
|
|
|
|
|
try {
|
|
|
|
|
AppSearchUserInstance userInstance = userInstanceManager.getUserInstance(
|
|
|
|
|
userHandle);
|
|
|
|
|
StorageInfoProto storageInfoProto =
|
|
|
|
|
userInstance.getAppSearchImpl().getRawStorageInfoProto();
|
|
|
|
|
data.add(buildStatsEvent(userHandle.getIdentifier(), storageInfoProto));
|
|
|
|
|
} catch (Throwable t) {
|
|
|
|
|
Log.e(TAG,
|
|
|
|
|
"Failed to pull the storage info for user " + userHandle.toString(),
|
|
|
|
|
t);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Skip the report if there is no data.
|
|
|
|
|
if (data.isEmpty()) {
|
|
|
|
|
return StatsManager.PULL_SKIP;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return StatsManager.PULL_SUCCESS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Registers and configures the callback for the pulled atom.
|
|
|
|
|
*
|
|
|
|
|
* @param atomId The id of the atom
|
|
|
|
|
* @param policy Optional metadata specifying the timeout, cool down time etc. statsD would
|
|
|
|
|
* use default values if it is null
|
|
|
|
|
* @param executor The executor in which to run the callback
|
|
|
|
|
*/
|
|
|
|
|
private void registerAtom(int atomId, @Nullable StatsManager.PullAtomMetadata policy,
|
|
|
|
|
@NonNull Executor executor) {
|
|
|
|
|
mStatsManager.setPullAtomCallback(atomId, policy, executor, /*callback=*/this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static StatsEvent buildStatsEvent(@UserIdInt int userId,
|
|
|
|
|
@NonNull StorageInfoProto storageInfoProto) {
|
|
|
|
|
return AppSearchStatsLog.buildStatsEvent(
|
|
|
|
|
AppSearchStatsLog.APP_SEARCH_STORAGE_INFO,
|
|
|
|
|
userId,
|
|
|
|
|
storageInfoProto.getTotalStorageSize(),
|
|
|
|
|
getDocumentStorageInfoBytes(storageInfoProto.getDocumentStorageInfo()),
|
|
|
|
|
getSchemaStoreStorageInfoBytes(storageInfoProto.getSchemaStoreStorageInfo()),
|
|
|
|
|
getIndexStorageInfoBytes(storageInfoProto.getIndexStorageInfo()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static byte[] getDocumentStorageInfoBytes(
|
|
|
|
|
@NonNull DocumentStorageInfoProto proto) {
|
|
|
|
|
// Make sure we only log the fields defined in the atom in case new fields are added in
|
|
|
|
|
// IcingLib
|
|
|
|
|
DocumentStorageInfoProto.Builder builder = DocumentStorageInfoProto.newBuilder();
|
|
|
|
|
builder.setNumAliveDocuments(proto.getNumAliveDocuments())
|
|
|
|
|
.setNumDeletedDocuments(proto.getNumDeletedDocuments())
|
|
|
|
|
.setNumExpiredDocuments(proto.getNumExpiredDocuments())
|
|
|
|
|
.setDocumentStoreSize(proto.getDocumentStoreSize())
|
|
|
|
|
.setDocumentLogSize(proto.getDocumentLogSize())
|
|
|
|
|
.setKeyMapperSize(proto.getKeyMapperSize())
|
|
|
|
|
.setDocumentIdMapperSize(proto.getDocumentIdMapperSize())
|
|
|
|
|
.setScoreCacheSize(proto.getScoreCacheSize())
|
|
|
|
|
.setFilterCacheSize(proto.getFilterCacheSize())
|
|
|
|
|
.setCorpusMapperSize(proto.getCorpusMapperSize())
|
|
|
|
|
.setCorpusScoreCacheSize(proto.getCorpusScoreCacheSize())
|
|
|
|
|
.setNamespaceIdMapperSize(proto.getNamespaceIdMapperSize())
|
|
|
|
|
.setNumNamespaces(proto.getNumNamespaces());
|
|
|
|
|
return builder.build().toByteArray();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static byte[] getSchemaStoreStorageInfoBytes(
|
|
|
|
|
@NonNull SchemaStoreStorageInfoProto proto) {
|
|
|
|
|
// Make sure we only log the fields defined in the atom in case new fields are added in
|
|
|
|
|
// IcingLib
|
|
|
|
|
SchemaStoreStorageInfoProto.Builder builder = SchemaStoreStorageInfoProto.newBuilder();
|
|
|
|
|
builder.setSchemaStoreSize(proto.getSchemaStoreSize())
|
|
|
|
|
.setNumSchemaTypes(proto.getNumSchemaTypes())
|
|
|
|
|
.setNumTotalSections(proto.getNumTotalSections())
|
|
|
|
|
.setNumSchemaTypesSectionsExhausted(proto.getNumSchemaTypesSectionsExhausted());
|
|
|
|
|
return builder.build().toByteArray();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static byte[] getIndexStorageInfoBytes(
|
|
|
|
|
@NonNull IndexStorageInfoProto proto) {
|
|
|
|
|
// Make sure we only log the fields defined in the atom in case new fields are added in
|
|
|
|
|
// IcingLib
|
|
|
|
|
IndexStorageInfoProto.Builder builder = IndexStorageInfoProto.newBuilder();
|
|
|
|
|
builder.setIndexSize(proto.getIndexSize())
|
|
|
|
|
.setLiteIndexLexiconSize(proto.getLiteIndexLexiconSize())
|
|
|
|
|
.setLiteIndexHitBufferSize(proto.getLiteIndexHitBufferSize())
|
|
|
|
|
.setMainIndexLexiconSize(proto.getMainIndexLexiconSize())
|
|
|
|
|
.setMainIndexStorageSize(proto.getMainIndexStorageSize())
|
|
|
|
|
.setMainIndexBlockSize(proto.getMainIndexBlockSize())
|
|
|
|
|
.setNumBlocks(proto.getNumBlocks())
|
|
|
|
|
.setMinFreeFraction(proto.getMinFreeFraction());
|
|
|
|
|
return builder.build().toByteArray();
|
|
|
|
|
}
|
|
|
|
|
}
|