Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b7d75ad7 authored by Xiaoyu Jin's avatar Xiaoyu Jin Committed by Android (Google) Code Review
Browse files

Merge "Add AppSearchConfig in AppSearch" into sc-dev

parents c6273f7f 951f7563
Loading
Loading
Loading
Loading
+249 −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;

import android.annotation.NonNull;
import android.os.Bundle;
import android.provider.DeviceConfig;
import android.provider.DeviceConfig.OnPropertiesChangedListener;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;

import java.util.Objects;
import java.util.concurrent.Executor;

/**
 * It contains all the keys for the flags, as well as caches some of latest flag values from
 * DeviceConfig.
 *
 * <p>Though the latest flag values can always be retrieved by calling {@code
 * DeviceConfig.getProperty}, we want to cache some of those values. For example, the sampling
 * intervals for logging, they are needed for each api call and it would be a little expensive to
 * call
 * {@code DeviceConfig.getProperty} every time.
 *
 * <p>Listener is registered to DeviceConfig keep the cached value up to date.
 *
 * <p>This class is thread-safe.
 *
 * @hide
 */
public final class AppSearchConfig implements AutoCloseable {
    /**
     * It would be used as default min time interval between samples in millis if there is no value
     * set for {@link AppSearchConfig#KEY_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS} in DeviceConfig.
     */
    @VisibleForTesting
    static final long DEFAULT_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS = 50;

    /**
     * It would be used as default sampling interval if there is no value
     * set for {@link AppSearchConfig#KEY_SAMPLING_INTERVAL_DEFAULT} in DeviceConfig.
     */
    @VisibleForTesting
    static final int DEFAULT_SAMPLING_INTERVAL = 10;

    /*
     * Keys for ALL the flags stored in DeviceConfig.
     */
    public static final String KEY_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS =
            "min_time_interval_between_samples_millis";
    public static final String KEY_SAMPLING_INTERVAL_DEFAULT = "sampling_interval_default";
    public static final String KEY_SAMPLING_INTERVAL_FOR_BATCH_CALL_STATS =
            "sampling_interval_for_batch_call_stats";
    public static final String KEY_SAMPLING_INTERVAL_FOR_PUT_DOCUMENT_STATS =
            "sampling_interval_for_put_document_stats";

    // Array contains all the corresponding keys for the cached values.
    private static final String[] KEYS_TO_ALL_CACHED_VALUES = {
            KEY_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
            KEY_SAMPLING_INTERVAL_DEFAULT,
            KEY_SAMPLING_INTERVAL_FOR_BATCH_CALL_STATS,
            KEY_SAMPLING_INTERVAL_FOR_PUT_DOCUMENT_STATS
    };

    // Lock needed for all the operations in this class.
    private final Object mLock = new Object();

    /**
     * Bundle to hold all the cached flag values corresponding to
     * {@link AppSearchConfig#KEYS_TO_ALL_CACHED_VALUES}.
     */
    @GuardedBy("mLock")
    private final Bundle mBundleLocked = new Bundle();


    @GuardedBy("mLock")
    private boolean mIsClosedLocked = false;

    /** Listener to update cached flag values from DeviceConfig. */
    private final OnPropertiesChangedListener mOnDeviceConfigChangedListener =
            properties -> {
                if (!properties.getNamespace().equals(DeviceConfig.NAMESPACE_APPSEARCH)) {
                    return;
                }

                updateCachedValues(properties);
            };

    /**
     * Creates an instance of {@link AppSearchConfig}.
     *
     * @param executor used to fetch and cache the flag values from DeviceConfig during creation or
     *                 config change.
     */
    @NonNull
    public static AppSearchConfig create(@NonNull Executor executor) {
        Objects.requireNonNull(executor);
        AppSearchConfig configManager = new AppSearchConfig();
        configManager.initialize(executor);
        return configManager;
    }

    private AppSearchConfig() {
    }

    /**
     * Initializes the {@link AppSearchConfig}
     *
     * <p>It fetches the custom properties from DeviceConfig if available.
     *
     * @param executor listener would be run on to handle P/H flag change.
     */
    private void initialize(@NonNull Executor executor) {
        executor.execute(() -> {
            // Attach the callback to get updates on those properties.
            DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_APPSEARCH,
                    executor,
                    mOnDeviceConfigChangedListener);

            DeviceConfig.Properties properties = DeviceConfig.getProperties(
                    DeviceConfig.NAMESPACE_APPSEARCH, KEYS_TO_ALL_CACHED_VALUES);
            updateCachedValues(properties);
        });
    }

    // TODO(b/173532925) check this will be called. If we have a singleton instance for this
    //  class, probably we don't need it.
    @Override
    public void close() {
        synchronized (mLock) {
            if (mIsClosedLocked) {
                return;
            }

            DeviceConfig.removeOnPropertiesChangedListener(mOnDeviceConfigChangedListener);
            mIsClosedLocked = true;
        }
    }

    /** Returns cached value for minTimeIntervalBetweenSamplesMillis. */
    public long getCachedMinTimeIntervalBetweenSamplesMillis() {
        synchronized (mLock) {
            throwIfClosedLocked();
            return mBundleLocked.getLong(KEY_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
                    DEFAULT_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS);
        }
    }

    /**
     * Returns cached value for default sampling interval for all the stats NOT listed in
     * the configuration.
     *
     * <p>For example, sampling_interval=10 means that one out of every 10 stats was logged.
     */
    public int getCachedSamplingIntervalDefault() {
        synchronized (mLock) {
            throwIfClosedLocked();
            return mBundleLocked.getInt(KEY_SAMPLING_INTERVAL_DEFAULT, DEFAULT_SAMPLING_INTERVAL);
        }
    }

    /**
     * Returns cached value for sampling interval for batch calls.
     *
     * <p>For example, sampling_interval=10 means that one out of every 10 stats was logged.
     */
    public int getCachedSamplingIntervalForBatchCallStats() {
        synchronized (mLock) {
            throwIfClosedLocked();
            return mBundleLocked.getInt(KEY_SAMPLING_INTERVAL_FOR_BATCH_CALL_STATS,
                    getCachedSamplingIntervalDefault());
        }
    }

    /**
     * Returns cached value for sampling interval for putDocument.
     *
     * <p>For example, sampling_interval=10 means that one out of every 10 stats was logged.
     */
    public int getCachedSamplingIntervalForPutDocumentStats() {
        synchronized (mLock) {
            throwIfClosedLocked();
            return mBundleLocked.getInt(KEY_SAMPLING_INTERVAL_FOR_PUT_DOCUMENT_STATS,
                    getCachedSamplingIntervalDefault());
        }
    }

    @GuardedBy("mLock")
    private void throwIfClosedLocked() {
        if (mIsClosedLocked) {
            throw new IllegalStateException("Trying to use a closed AppSearchConfig instance.");
        }
    }

    private void updateCachedValues(@NonNull DeviceConfig.Properties properties) {
        for (String key : properties.getKeyset()) {
            updateCachedValue(key, properties);
        }
    }

    private void updateCachedValue(@NonNull String key,
            @NonNull DeviceConfig.Properties properties) {
        if (properties.getString(key, /*defaultValue=*/ null) == null) {
            // Key is missing or value is just null. That is not expected if the key is
            // defined in the configuration.
            //
            // We choose NOT to put the default value in the bundle.
            // Instead, we let the getters handle what default value should be returned.
            //
            // Also we keep the old value in the bundle. So getters can still
            // return last valid value.
            return;
        }

        switch (key) {
            case KEY_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS:
                synchronized (mLock) {
                    mBundleLocked.putLong(key,
                            properties.getLong(key,
                                    DEFAULT_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS));
                }
                break;
            case KEY_SAMPLING_INTERVAL_DEFAULT:
            case KEY_SAMPLING_INTERVAL_FOR_BATCH_CALL_STATS:
            case KEY_SAMPLING_INTERVAL_FOR_PUT_DOCUMENT_STATS:
                synchronized (mLock) {
                    mBundleLocked.putInt(key, properties.getInt(key, DEFAULT_SAMPLING_INTERVAL));
                }
                break;
            default:
                break;
        }
    }
}
+35 −35
Original line number Diff line number Diff line
@@ -72,7 +72,7 @@ public final class PlatformLogger implements AppSearchLogger {
     *
     * <p> We can have correct extrapolated number by adding those counts back when we log
     * the same type of stats next time. E.g. the true count of an event could be estimated as:
     * SUM(sampling_ratio * (num_skipped_sample + 1)) as est_count
     * SUM(sampling_interval * (num_skipped_sample + 1)) as est_count
     *
     * <p>The key to the SparseArray is {@link CallStats.CallType}
     */
@@ -105,42 +105,42 @@ public final class PlatformLogger implements AppSearchLogger {
        // logging again.
        private final long mMinTimeIntervalBetweenSamplesMillis;

        // Default sampling ratio for all types of stats
        private final int mDefaultSamplingRatio;
        // Default sampling interval for all types of stats
        private final int mDefaultSamplingInterval;

        /**
         * Sampling ratios for different types of stats
         * Sampling intervals for different types of stats
         *
         * <p>This SparseArray is passed by client and is READ-ONLY. The key to that SparseArray is
         * {@link CallStats.CallType}
         *
         * <p>If sampling ratio is missing for certain stats type,
         * {@link Config#mDefaultSamplingRatio} will be used.
         * <p>If sampling interval is missing for certain stats type,
         * {@link Config#mDefaultSamplingInterval} will be used.
         *
         * <p>E.g. sampling ratio=10 means that one out of every 10 stats was logged. If sampling
         * ratio is 1, we will log each sample and it acts as if the sampling is disabled.
         * <p>E.g. sampling interval=10 means that one out of every 10 stats was logged. If sampling
         * interval is 1, we will log each sample and it acts as if the sampling is disabled.
         */
        @NonNull
        private final SparseIntArray mSamplingRatios;
        private final SparseIntArray mSamplingIntervals;

        /**
         * Configuration for {@link PlatformLogger}
         *
         * @param minTimeIntervalBetweenSamplesMillis minimum time interval apart in Milliseconds
         *                                            required for two consecutive stats logged
         * @param defaultSamplingRatio                default sampling ratio
         * @param samplingRatios                      SparseArray to customize sampling ratio for
         * @param defaultSamplingInterval             default sampling interval
         * @param samplingIntervals                   SparseArray to customize sampling interval for
         *                                            different stat types
         */
        public Config(long minTimeIntervalBetweenSamplesMillis,
                int defaultSamplingRatio,
                @NonNull SparseIntArray samplingRatios) {
                int defaultSamplingInterval,
                @NonNull SparseIntArray samplingIntervals) {
            // TODO(b/173532925) Probably we can get rid of those three after we have p/h flags
            // for them.
            // e.g. we can just call DeviceConfig.get(SAMPLING_RATIO_FOR_PUT_DOCUMENTS).
            // e.g. we can just call DeviceConfig.get(SAMPLING_INTERVAL_FOR_PUT_DOCUMENTS).
            mMinTimeIntervalBetweenSamplesMillis = minTimeIntervalBetweenSamplesMillis;
            mDefaultSamplingRatio = defaultSamplingRatio;
            mSamplingRatios = samplingRatios;
            mDefaultSamplingInterval = defaultSamplingInterval;
            mSamplingIntervals = samplingIntervals;
        }
    }

@@ -150,14 +150,14 @@ public final class PlatformLogger implements AppSearchLogger {
    static final class ExtraStats {
        // UID for the calling package of the stats.
        final int mPackageUid;
        // sampling ratio for the call type of the stats.
        final int mSamplingRatio;
        // sampling interval for the call type of the stats.
        final int mSamplingInterval;
        // number of samplings skipped before the current one for the same call type.
        final int mSkippedSampleCount;

        ExtraStats(int packageUid, int samplingRatio, int skippedSampleCount) {
        ExtraStats(int packageUid, int samplingInterval, int skippedSampleCount) {
            mPackageUid = packageUid;
            mSamplingRatio = samplingRatio;
            mSamplingInterval = samplingInterval;
            mSkippedSampleCount = skippedSampleCount;
        }
    }
@@ -243,7 +243,7 @@ public final class PlatformLogger implements AppSearchLogger {
        try {
            int hashCodeForDatabase = calculateHashCodeMd5(database);
            AppSearchStatsLog.write(AppSearchStatsLog.APP_SEARCH_CALL_STATS_REPORTED,
                    extraStats.mSamplingRatio,
                    extraStats.mSamplingInterval,
                    extraStats.mSkippedSampleCount,
                    extraStats.mPackageUid,
                    hashCodeForDatabase,
@@ -275,7 +275,7 @@ public final class PlatformLogger implements AppSearchLogger {
        try {
            int hashCodeForDatabase = calculateHashCodeMd5(database);
            AppSearchStatsLog.write(AppSearchStatsLog.APP_SEARCH_PUT_DOCUMENT_STATS_REPORTED,
                    extraStats.mSamplingRatio,
                    extraStats.mSamplingInterval,
                    extraStats.mSkippedSampleCount,
                    extraStats.mPackageUid,
                    hashCodeForDatabase,
@@ -312,7 +312,7 @@ public final class PlatformLogger implements AppSearchLogger {
        try {
            int hashCodeForDatabase = calculateHashCodeMd5(database);
            AppSearchStatsLog.write(AppSearchStatsLog.APP_SEARCH_QUERY_STATS_REPORTED,
                    extraStats.mSamplingRatio,
                    extraStats.mSamplingInterval,
                    extraStats.mSkippedSampleCount,
                    extraStats.mPackageUid,
                    hashCodeForDatabase,
@@ -355,7 +355,7 @@ public final class PlatformLogger implements AppSearchLogger {
        ExtraStats extraStats = createExtraStatsLocked(/*packageName=*/ null,
                CallStats.CALL_TYPE_INITIALIZE);
        AppSearchStatsLog.write(AppSearchStatsLog.APP_SEARCH_INITIALIZE_STATS_REPORTED,
                extraStats.mSamplingRatio,
                extraStats.mSamplingInterval,
                extraStats.mSkippedSampleCount,
                extraStats.mPackageUid,
                stats.getStatusCode(),
@@ -428,14 +428,14 @@ public final class PlatformLogger implements AppSearchLogger {
            packageUid = getPackageUidAsUserLocked(packageName);
        }

        int samplingRatio = mConfig.mSamplingRatios.get(callType,
                mConfig.mDefaultSamplingRatio);
        int samplingInterval = mConfig.mSamplingIntervals.get(callType,
                mConfig.mDefaultSamplingInterval);

        int skippedSampleCount = mSkippedSampleCountLocked.get(callType,
                /*valueOfKeyIfNotFound=*/ 0);
        mSkippedSampleCountLocked.put(callType, 0);

        return new ExtraStats(packageUid, samplingRatio, skippedSampleCount);
        return new ExtraStats(packageUid, samplingInterval, skippedSampleCount);
    }

    /**
@@ -450,11 +450,11 @@ public final class PlatformLogger implements AppSearchLogger {
    // rate limiting.
    @VisibleForTesting
    boolean shouldLogForTypeLocked(@CallStats.CallType int callType) {
        int samplingRatio = mConfig.mSamplingRatios.get(callType,
                mConfig.mDefaultSamplingRatio);
        int samplingInterval = mConfig.mSamplingIntervals.get(callType,
                mConfig.mDefaultSamplingInterval);

        // Sampling
        if (!shouldSample(samplingRatio)) {
        if (!shouldSample(samplingInterval)) {
            return false;
        }

@@ -475,15 +475,15 @@ public final class PlatformLogger implements AppSearchLogger {
    /**
     * Checks if the stats should be "sampled"
     *
     * @param samplingRatio sampling ratio
     * @param samplingInterval sampling interval
     * @return if the stats should be sampled
     */
    private boolean shouldSample(int samplingRatio) {
        if (samplingRatio <= 0) {
    private boolean shouldSample(int samplingInterval) {
        if (samplingInterval <= 0) {
            return false;
        }

        return mRng.nextInt((int) samplingRatio) == 0;
        return mRng.nextInt((int) samplingInterval) == 0;
    }

    /**
+5 −1
Original line number Diff line number Diff line
@@ -31,7 +31,10 @@ android_test {
    name: "FrameworksMockingServicesTests",
    defaults: ["FrameworkMockingServicesTests-jni-defaults"],

    srcs: ["src/**/*.java", "src/**/*.kt"],
    srcs: [
        "src/**/*.java",
        "src/**/*.kt",
    ],

    static_libs: [
        "services.core",
@@ -41,6 +44,7 @@ android_test {
        "service-jobscheduler",
        "service-permission.impl",
        "service-blobstore",
        "service-appsearch",
        "androidx.test.runner",
        "androidx.test.ext.truth",
        "mockito-target-extended-minus-junit4",
+286 −0

File added.

Preview size limit exceeded, changes collapsed.

+48 −48

File changed.

Preview size limit exceeded, changes collapsed.