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

Commit dd838c25 authored by Milo Sredkov's avatar Milo Sredkov
Browse files

Add config for Native Binder Stats

Add Settings.Global.NATIVE_BINDER_STATS and a ContentObserver which
listens for changes. Define the parameters for sharding that will be
used by libbinder.

This is implemented similarly to the existing BINDER_CALLS_STATS

Bug: 299356196
Test: FrameworksCoreTests:com.android.internal.os.NativeBinderStatsTest
Flag: EXEMPT this change is adding flags using the legacy path
Change-Id: I6baf7971fb98777acc7df3c02cf1e2d5e4925cd5
parent 5c7913e3
Loading
Loading
Loading
Loading
+20 −0
Original line number Diff line number Diff line
@@ -19878,6 +19878,26 @@ public final class Settings {
        @Readable
        public static final String BINDER_CALLS_STATS = "binder_calls_stats";
        /**
         * Native binder stats settings.
         *
         * These parameters are represented by a comma-delimited key-value list.
         *
         * The following strings are supported as keys:
         * <pre>
         *     enabled                      (boolean)
         *     process_sharding             (int)
         *     spam_sharding                (int)
         *     call_sharding                (int)
         *     system_process_sharding      (int)
         *     system_spam_sharding         (int)
         *     system_call_sharding         (int)
         * </pre>
         *
         * @hide
         */
        public static final String NATIVE_BINDER_STATS = "native_binder_stats";
        /**
         * Looper stats settings.
         *
+160 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.internal.os;

import android.content.Context;
import android.database.ContentObserver;
import android.net.Uri;
import android.provider.Settings;
import android.util.KeyValueListParser;
import android.util.Slog;

import com.android.internal.annotations.VisibleForTesting;

/**
 * Coordinates native binder stats collection. Propagates settings to the processes that use
 * libbinder for tracking stats.
 */
public class NativeBinderStats {
    private static final String TAG = "NativeBinderStats";

    static final boolean DEFAULT_ENABLED = false;
    static final int DEFAULT_PROCESS_SHARDING = 50;
    static final int DEFAULT_SPAM_SHARDING = 10;
    static final int DEFAULT_CALL_SHARDING = 20;
    static final int DEFAULT_SYSTEM_PROCESS_SHARDING = 10;
    static final int DEFAULT_SYSTEM_SPAM_SHARDING = 50;
    static final int DEFAULT_SYSTEM_CALL_SHARDING = 100;

    /** Whether native binder stats are enabled. */
    @VisibleForTesting public boolean mEnabled = DEFAULT_ENABLED;

    /**
     * The inverse probability that a given process will track binder stats. E.g. 100 means that
     * 1% of the processes will report stats. 0 is a special value that means no process will
     * report stats.
     */
    @VisibleForTesting public int mProcessSharding = DEFAULT_PROCESS_SHARDING;

    /**
     * The inverse probability that a given AIDL method will be selected for spam detection and
     * reporting (provided the containing process is selected for stats). 0 means no spam
     * tracking.
     */
    @VisibleForTesting public int mSpamSharding = DEFAULT_SPAM_SHARDING;

    /**
     * The inverse probability that a given AIDL method will be selected for call stats
     * (provided the containing process is selected for stats). 0 means no call stats.
     */
    @VisibleForTesting public int mCallSharding = DEFAULT_CALL_SHARDING;

    /** Like {@link #mProcessSharding} but for system_server. */
    @VisibleForTesting public int mSystemProcessSharding = DEFAULT_SYSTEM_PROCESS_SHARDING;

    /** Like {@link #mSpamSharding} but for system_server. */
    @VisibleForTesting public int mSystemSpamSharding = DEFAULT_SYSTEM_SPAM_SHARDING;

    /** Like {@link #mCallSharding} but for system_server. */
    @VisibleForTesting public int mSystemCallSharding = DEFAULT_SYSTEM_CALL_SHARDING;

    private final Context mContext;

    private final SettingsObserver mSettingsObserver;

    public NativeBinderStats(Context context) {
        mContext = context;
        mSettingsObserver = new SettingsObserver(context);
    }

    public void systemReady() {
        // Start observing settings.
        mSettingsObserver.register();
    }

    @VisibleForTesting
    public class SettingsObserver extends ContentObserver {
        private static final String KEY_ENABLED = "enabled";
        private static final String KEY_PROCESS_SHARDING = "process_sharding";
        private static final String KEY_SPAM_SHARDING = "spam_sharding";
        private static final String KEY_CALL_SHARDING = "call_sharding";
        private static final String KEY_SYSTEM_PROCESS_SHARDING = "system_process_sharding";
        private static final String KEY_SYSTEM_SPAM_SHARDING = "system_spam_sharding";
        private static final String KEY_SYSTEM_CALL_SHARDING = "system_call_sharding";

        private final Uri mUri = Settings.Global.getUriFor(Settings.Global.NATIVE_BINDER_STATS);
        private final KeyValueListParser mParser = new KeyValueListParser(',');
        private final Context mContext;

        SettingsObserver(Context context) {
            super(BackgroundThread.getHandler());
            mContext = context;
        }

        void register() {
            mContext.getContentResolver().registerContentObserver(mUri, false, this);
            // Trigger update so we get the initial state.
            onChange();
        }

        @Override
        public void onChange(boolean selfChange, Uri uri, int userId) {
            if (mUri.equals(uri)) {
                onChange();
            }
        }

        void onChange() {
            try {
                mParser.setString(
                        Settings.Global.getString(
                                mContext.getContentResolver(),
                                Settings.Global.NATIVE_BINDER_STATS));
            } catch (IllegalArgumentException e) {
                Slog.e(TAG, "Bad native binder stats settings", e);
            }

            mEnabled = mParser.getBoolean(KEY_ENABLED, DEFAULT_ENABLED);
            mProcessSharding = mParser.getInt(KEY_PROCESS_SHARDING, DEFAULT_PROCESS_SHARDING);
            mSpamSharding = mParser.getInt(KEY_SPAM_SHARDING, DEFAULT_SPAM_SHARDING);
            mCallSharding = mParser.getInt(KEY_CALL_SHARDING, DEFAULT_CALL_SHARDING);
            mSystemProcessSharding =
                    mParser.getInt(KEY_SYSTEM_PROCESS_SHARDING, DEFAULT_SYSTEM_PROCESS_SHARDING);
            mSystemSpamSharding =
                    mParser.getInt(KEY_SYSTEM_SPAM_SHARDING, DEFAULT_SYSTEM_SPAM_SHARDING);
            mSystemCallSharding =
                    mParser.getInt(KEY_SYSTEM_CALL_SHARDING, DEFAULT_SYSTEM_CALL_SHARDING);
            // TODO(b/407694522): If settings change, propagate to other processes.
            Slog.i(
                    TAG,
                    String.format(
                            "Native binder stats settings changed: %b, %d, %d, %d, %d,  %d, %d",
                            mEnabled,
                            mProcessSharding,
                            mSpamSharding,
                            mCallSharding,
                            mSystemProcessSharding,
                            mSystemSpamSharding,
                            mSystemCallSharding));
        }
    }

    @VisibleForTesting
    public SettingsObserver getSettingsObserverForTesting() {
        return mSettingsObserver;
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -257,6 +257,7 @@ android_ravenwood_test {
        "android.view.flags-aconfig-java",
        "ext",
        "framework",
        "frameworks-base-testutils",
        "framework-res",
        "libprotobuf-java-lite",
        "org.apache.http.legacy.stubs",
+137 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.internal.os;

import static com.google.common.truth.Truth.assertThat;

import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;

import android.content.Context;
import android.content.ContextWrapper;
import android.platform.test.annotations.DisabledOnRavenwood;
import android.platform.test.annotations.Presubmit;
import android.provider.Settings;
import android.test.mock.MockContentResolver;

import androidx.test.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;

import com.android.internal.util.test.FakeSettingsProvider;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

@SmallTest
@RunWith(AndroidJUnit4.class)
@Presubmit
@DisabledOnRavenwood(blockedBy = MockContentResolver.class)
public class NativeBinderStatsTest {
    private Context mContext;
    private MockContentResolver mResolver;

    @Before
    public void setup() {
        mContext = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
        mResolver = new MockContentResolver(mContext);
        mResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
        when(mContext.getContentResolver()).thenReturn(mResolver);
    }

    @After
    public void tearDown() {
        FakeSettingsProvider.clearSettingsProvider();
    }

    @Test
    public void testSettingsObserver_disabledByDefault() {
        NativeBinderStats nativeBinderStats = new NativeBinderStats(mContext);
        nativeBinderStats.systemReady();

        assertThat(nativeBinderStats.mEnabled).isFalse();
    }

    @Test
    public void testSettingsObserver_enabled() {
        Settings.Global.putString(mResolver, Settings.Global.NATIVE_BINDER_STATS, "enabled=true");
        NativeBinderStats nativeBinderStats = new NativeBinderStats(mContext);
        nativeBinderStats.systemReady();

        assertThat(nativeBinderStats.mEnabled).isTrue();
        assertThat(nativeBinderStats.mProcessSharding)
                .isEqualTo(NativeBinderStats.DEFAULT_PROCESS_SHARDING);
        assertThat(nativeBinderStats.mSpamSharding)
                .isEqualTo(NativeBinderStats.DEFAULT_SPAM_SHARDING);
        assertThat(nativeBinderStats.mCallSharding)
                .isEqualTo(NativeBinderStats.DEFAULT_CALL_SHARDING);
        assertThat(nativeBinderStats.mSystemProcessSharding)
                .isEqualTo(NativeBinderStats.DEFAULT_SYSTEM_PROCESS_SHARDING);
        assertThat(nativeBinderStats.mSystemSpamSharding)
                .isEqualTo(NativeBinderStats.DEFAULT_SYSTEM_SPAM_SHARDING);
        assertThat(nativeBinderStats.mSystemCallSharding)
                .isEqualTo(NativeBinderStats.DEFAULT_SYSTEM_CALL_SHARDING);
    }

    @Test
    public void testSettingsObserver_disabled() {
        Settings.Global.putString(mResolver, Settings.Global.NATIVE_BINDER_STATS, "enabled=false");
        NativeBinderStats nativeBinderStats = new NativeBinderStats(mContext);
        nativeBinderStats.systemReady();

        assertThat(nativeBinderStats.mEnabled).isFalse();
    }

    @Test
    public void testSettingsObserver_changed() {
        Settings.Global.putString(mResolver, Settings.Global.NATIVE_BINDER_STATS, "enabled=true");
        NativeBinderStats nativeBinderStats = new NativeBinderStats(mContext);
        nativeBinderStats.systemReady();

        assertThat(nativeBinderStats.mEnabled).isTrue();

        Settings.Global.putString(mResolver, Settings.Global.NATIVE_BINDER_STATS, "enabled=false");
        // FakeSettingsProvider doesn't support notifications, so we need to notify manually.
        // This assumes that SettingsObserver is registered, which we cannot check because
        // registerContentObserver is final.
        nativeBinderStats.getSettingsObserverForTesting().onChange(
                false, Settings.Global.getUriFor(Settings.Global.NATIVE_BINDER_STATS), 0);

        assertThat(nativeBinderStats.mEnabled).isFalse();
    }

    @Test
    public void testSettingsObserver_allValues() {
        Settings.Global.putString(
                mResolver,
                Settings.Global.NATIVE_BINDER_STATS,
                "enabled=true,process_sharding=5,spam_sharding=1,call_sharding=2,"
                    + "system_process_sharding=1,system_spam_sharding=5,system_call_sharding=10");
        NativeBinderStats nativeBinderStats = new NativeBinderStats(mContext);
        nativeBinderStats.systemReady();

        assertThat(nativeBinderStats.mEnabled).isTrue();
        assertThat(nativeBinderStats.mProcessSharding).isEqualTo(5);
        assertThat(nativeBinderStats.mSpamSharding).isEqualTo(1);
        assertThat(nativeBinderStats.mCallSharding).isEqualTo(2);
        assertThat(nativeBinderStats.mSystemProcessSharding).isEqualTo(1);
        assertThat(nativeBinderStats.mSystemSpamSharding).isEqualTo(5);
        assertThat(nativeBinderStats.mSystemCallSharding).isEqualTo(10);
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -104,6 +104,7 @@ public class SettingsBackupTest {
                    Settings.Global.BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS,
                    Settings.Global.BATTERY_STATS_CONSTANTS,
                    Settings.Global.BINDER_CALLS_STATS,
                    Settings.Global.NATIVE_BINDER_STATS,
                    Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE,
                    Settings.Global.BLE_SCAN_LOW_POWER_WINDOW_MS,
                    Settings.Global.BLE_SCAN_LOW_POWER_INTERVAL_MS,
Loading