blob: dd56739cde57d9b9a0ed344a549528970dd3da2f [file] [log] [blame]
/*
* 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();
}
}