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

Commit 5de4d659 authored by Nicholas Ambur's avatar Nicholas Ambur
Browse files

rework LatencyTracker for testing

Modify the VisibleForTesting methods of the LatencyTracker class to
improve the testibility. The class is modified to support overriding
callbacks when actions/outcomes are taken by LatencyTracker based on
its inputs.

Test classes using LatencyTracker can now verify:
1. When PerfettoTrigger has been triggered
2. When FrameworkStatsLog is written to

This CL fixes a bug where only the global enable flag is
checked when calling onActionStart and onActionEnd. It also
adds a new enabled check for when the public logAction API is called.

Test: atest LatencyTrackerTest
Bug: 269254242
Change-Id: I4f8d21bca4a9e52fb3875e88387b8c8641f64c94
parent 31302573
Loading
Loading
Loading
Loading
+162 −46
Original line number Diff line number Diff line
@@ -48,6 +48,7 @@ import static com.android.internal.util.LatencyTracker.ActionProperties.SAMPLE_I
import static com.android.internal.util.LatencyTracker.ActionProperties.TRACE_THRESHOLD_SUFFIX;

import android.Manifest;
import android.annotation.ElapsedRealtimeLong;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -55,7 +56,6 @@ import android.annotation.RequiresPermission;
import android.app.ActivityThread;
import android.content.Context;
import android.os.Build;
import android.os.ConditionVariable;
import android.os.SystemClock;
import android.os.Trace;
import android.provider.DeviceConfig;
@@ -79,7 +79,7 @@ import java.util.concurrent.TimeUnit;
 * Class to track various latencies in SystemUI. It then writes the latency to statsd and also
 * outputs it to logcat so these latencies can be captured by tests and then used for dashboards.
 * <p>
 * This is currently only in Keyguard so it can be shared between SystemUI and Keyguard, but
 * This is currently only in Keyguard. It can be shared between SystemUI and Keyguard, but
 * eventually we'd want to merge these two packages together so Keyguard can use common classes
 * that are shared with SystemUI.
 */
@@ -285,8 +285,6 @@ public class LatencyTracker {
            UIACTION_LATENCY_REPORTED__ACTION__ACTION_REQUEST_IME_HIDDEN,
    };

    private static LatencyTracker sLatencyTracker;

    private final Object mLock = new Object();
    @GuardedBy("mLock")
    private final SparseArray<Session> mSessions = new SparseArray<>();
@@ -294,20 +292,21 @@ public class LatencyTracker {
    private final SparseArray<ActionProperties> mActionPropertiesMap = new SparseArray<>();
    @GuardedBy("mLock")
    private boolean mEnabled;
    @VisibleForTesting
    public final ConditionVariable mDeviceConfigPropertiesUpdated = new ConditionVariable();

    public static LatencyTracker getInstance(Context context) {
        if (sLatencyTracker == null) {
            synchronized (LatencyTracker.class) {
                if (sLatencyTracker == null) {
                    sLatencyTracker = new LatencyTracker();
    // Wrapping this in a holder class achieves lazy loading behavior
    private static final class SLatencyTrackerHolder {
        private static final LatencyTracker sLatencyTracker = new LatencyTracker();
    }
            }
        }
        return sLatencyTracker;

    public static LatencyTracker getInstance(Context context) {
        return SLatencyTrackerHolder.sLatencyTracker;
    }

    /**
     * Constructor for LatencyTracker
     *
     * <p>This constructor is only visible for test classes to inject their own consumer callbacks
     */
    @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
    @VisibleForTesting
    public LatencyTracker() {
@@ -349,11 +348,8 @@ public class LatencyTracker {
                        properties.getInt(actionName + TRACE_THRESHOLD_SUFFIX,
                                legacyActionTraceThreshold)));
            }
            if (DEBUG) {
                Log.d(TAG, "updated action properties: " + mActionPropertiesMap);
            }
            onDeviceConfigPropertiesUpdated(mActionPropertiesMap);
        }
        mDeviceConfigPropertiesUpdated.open();
    }

    /**
@@ -477,7 +473,7 @@ public class LatencyTracker {
     */
    public void onActionStart(@Action int action, String tag) {
        synchronized (mLock) {
            if (!isEnabled()) {
            if (!isEnabled(action)) {
                return;
            }
            // skip if the action is already instrumenting.
@@ -501,7 +497,7 @@ public class LatencyTracker {
     */
    public void onActionEnd(@Action int action) {
        synchronized (mLock) {
            if (!isEnabled()) {
            if (!isEnabled(action)) {
                return;
            }
            Session session = mSessions.get(action);
@@ -539,6 +535,24 @@ public class LatencyTracker {
        }
    }

    /**
     * Testing API to get the time when a given action was started.
     *
     * @param action Action which to retrieve start time from
     * @return Elapsed realtime timestamp when the action started. -1 if the action is not active.
     * @hide
     */
    @VisibleForTesting
    @ElapsedRealtimeLong
    public long getActiveActionStartTime(@Action int action) {
        synchronized (mLock) {
            if (mSessions.contains(action)) {
                return mSessions.get(action).mStartRtc;
            }
            return -1;
        }
    }

    /**
     * Logs an action that has started and ended. This needs to be called from the main thread.
     *
@@ -549,6 +563,9 @@ public class LatencyTracker {
        boolean shouldSample;
        int traceThreshold;
        synchronized (mLock) {
            if (!isEnabled(action)) {
                return;
            }
            ActionProperties actionProperties = mActionPropertiesMap.get(action);
            if (actionProperties == null) {
                return;
@@ -559,28 +576,24 @@ public class LatencyTracker {
            traceThreshold = actionProperties.getTraceThreshold();
        }

        if (traceThreshold > 0 && duration >= traceThreshold) {
            PerfettoTrigger.trigger(getTraceTriggerNameForAction(action));
        }
        boolean shouldTriggerPerfettoTrace = traceThreshold > 0 && duration >= traceThreshold;

        logActionDeprecated(action, duration, shouldSample);
        if (DEBUG) {
            Log.i(TAG, "logAction: " + getNameOfAction(STATSD_ACTION[action])
                    + " duration=" + duration
                    + " shouldSample=" + shouldSample
                    + " shouldTriggerPerfettoTrace=" + shouldTriggerPerfettoTrace);
        }

    /**
     * Logs an action that has started and ended. This needs to be called from the main thread.
     *
     * @param action The action to end. One of the ACTION_* values.
     * @param duration The duration of the action in ms.
     * @param writeToStatsLog Whether to write the measured latency to FrameworkStatsLog.
     */
    public static void logActionDeprecated(
            @Action int action, int duration, boolean writeToStatsLog) {
        Log.i(TAG, getNameOfAction(STATSD_ACTION[action]) + " latency=" + duration);
        EventLog.writeEvent(EventLogTags.SYSUI_LATENCY, action, duration);

        if (writeToStatsLog) {
            FrameworkStatsLog.write(
                    FrameworkStatsLog.UI_ACTION_LATENCY_REPORTED, STATSD_ACTION[action], duration);
        if (shouldTriggerPerfettoTrace) {
            onTriggerPerfetto(getTraceTriggerNameForAction(action));
        }
        if (shouldSample) {
            onLogToFrameworkStats(
                    new FrameworkStatsLogEvent(action, FrameworkStatsLog.UI_ACTION_LATENCY_REPORTED,
                            STATSD_ACTION[action], duration)
            );
        }
    }

@@ -642,10 +655,10 @@ public class LatencyTracker {
    }

    @VisibleForTesting
    static class ActionProperties {
    public static class ActionProperties {
        static final String ENABLE_SUFFIX = "_enable";
        static final String SAMPLE_INTERVAL_SUFFIX = "_sample_interval";
        // TODO: migrate all usages of the legacy trace theshold property
        // TODO: migrate all usages of the legacy trace threshold property
        static final String LEGACY_TRACE_THRESHOLD_SUFFIX = "";
        static final String TRACE_THRESHOLD_SUFFIX = "_trace_threshold";

@@ -655,7 +668,8 @@ public class LatencyTracker {
        private final int mSamplingInterval;
        private final int mTraceThreshold;

        ActionProperties(
        @VisibleForTesting
        public ActionProperties(
                @Action int action,
                boolean enabled,
                int samplingInterval,
@@ -668,20 +682,24 @@ public class LatencyTracker {
            this.mTraceThreshold = traceThreshold;
        }

        @VisibleForTesting
        @Action
        int getAction() {
        public int getAction() {
            return mAction;
        }

        boolean isEnabled() {
        @VisibleForTesting
        public boolean isEnabled() {
            return mEnabled;
        }

        int getSamplingInterval() {
        @VisibleForTesting
        public int getSamplingInterval() {
            return mSamplingInterval;
        }

        int getTraceThreshold() {
        @VisibleForTesting
        public int getTraceThreshold() {
            return mTraceThreshold;
        }

@@ -694,5 +712,103 @@ public class LatencyTracker {
                    + ", mTraceThreshold=" + mTraceThreshold
                    + "}";
        }

        @Override
        public boolean equals(@Nullable Object o) {
            if (this == o) {
                return true;
            }
            if (o == null) {
                return false;
            }
            if (!(o instanceof ActionProperties)) {
                return false;
            }
            ActionProperties that = (ActionProperties) o;
            return mAction == that.mAction
                    && mEnabled == that.mEnabled
                    && mSamplingInterval == that.mSamplingInterval
                    && mTraceThreshold == that.mTraceThreshold;
        }

        @Override
        public int hashCode() {
            int _hash = 1;
            _hash = 31 * _hash + mAction;
            _hash = 31 * _hash + Boolean.hashCode(mEnabled);
            _hash = 31 * _hash + mSamplingInterval;
            _hash = 31 * _hash + mTraceThreshold;
            return _hash;
        }
    }

    /**
     * Testing method intended to be overridden to determine when the LatencyTracker's device
     * properties are updated.
     */
    @VisibleForTesting
    public void onDeviceConfigPropertiesUpdated(SparseArray<ActionProperties> actionProperties) {
        if (DEBUG) {
            Log.d(TAG, "onDeviceConfigPropertiesUpdated: " + actionProperties);
        }
    }

    /**
     * Testing class intended to be overridden to determine when LatencyTracker triggers perfetto.
     */
    @VisibleForTesting
    public void onTriggerPerfetto(String triggerName) {
        if (DEBUG) {
            Log.i(TAG, "onTriggerPerfetto: triggerName=" + triggerName);
        }
        PerfettoTrigger.trigger(triggerName);
    }

    /**
     * Testing method intended to be overridden to determine when LatencyTracker writes to
     * FrameworkStatsLog.
     */
    @VisibleForTesting
    public void onLogToFrameworkStats(FrameworkStatsLogEvent event) {
        if (DEBUG) {
            Log.i(TAG, "onLogToFrameworkStats: event=" + event);
        }
        FrameworkStatsLog.write(event.logCode, event.statsdAction, event.durationMillis);
    }

    /**
     * Testing class intended to reject what should be written to the {@link FrameworkStatsLog}
     *
     * <p>This class is used in {@link #onLogToFrameworkStats(FrameworkStatsLogEvent)} for test code
     * to observer when and what information is being logged by {@link LatencyTracker}
     */
    @VisibleForTesting
    public static class FrameworkStatsLogEvent {

        @VisibleForTesting
        public final int action;
        @VisibleForTesting
        public final int logCode;
        @VisibleForTesting
        public final int statsdAction;
        @VisibleForTesting
        public final int durationMillis;

        private FrameworkStatsLogEvent(int action, int logCode, int statsdAction,
                int durationMillis) {
            this.action = action;
            this.logCode = logCode;
            this.statsdAction = statsdAction;
            this.durationMillis = durationMillis;
        }

        @Override
        public String toString() {
            return "FrameworkStatsLogEvent{"
                    + " logCode=" + logCode
                    + ", statsdAction=" + statsdAction
                    + ", durationMillis=" + durationMillis
                    + "}";
        }
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ android_test {
        "BinderProxyCountingTestService/src/**/*.java",
        "BinderDeathRecipientHelperApp/src/**/*.java",
        "aidl/**/I*.aidl",
        ":FrameworksCoreTestDoubles-sources",
    ],

    aidl: {
+67 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.util;

import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_VOICE_INTERACTION;
import static com.android.internal.util.FrameworkStatsLog.UI_ACTION_LATENCY_REPORTED;
import static com.android.internal.util.LatencyTracker.ACTION_SHOW_VOICE_INTERACTION;

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

import androidx.test.ext.junit.runners.AndroidJUnit4;

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

import java.util.List;

/**
 * This test class verifies the additional methods which {@link FakeLatencyTracker} exposes.
 *
 * <p>The typical {@link LatencyTracker} behavior test coverage is present in
 * {@link LatencyTrackerTest}
 */
@RunWith(AndroidJUnit4.class)
public class FakeLatencyTrackerTest {

    private FakeLatencyTracker mFakeLatencyTracker;

    @Before
    public void setUp() throws Exception {
        mFakeLatencyTracker = FakeLatencyTracker.create();
    }

    @Test
    public void testForceEnabled() throws Exception {
        mFakeLatencyTracker.logAction(ACTION_SHOW_VOICE_INTERACTION, 1234);

        assertThat(mFakeLatencyTracker.getEventsWrittenToFrameworkStats(
                ACTION_SHOW_VOICE_INTERACTION)).isEmpty();

        mFakeLatencyTracker.forceEnabled(ACTION_SHOW_VOICE_INTERACTION, 1000);
        mFakeLatencyTracker.logAction(ACTION_SHOW_VOICE_INTERACTION, 1234);
        List<LatencyTracker.FrameworkStatsLogEvent> events =
                mFakeLatencyTracker.getEventsWrittenToFrameworkStats(
                        ACTION_SHOW_VOICE_INTERACTION);
        assertThat(events).hasSize(1);
        assertThat(events.get(0).logCode).isEqualTo(UI_ACTION_LATENCY_REPORTED);
        assertThat(events.get(0).statsdAction).isEqualTo(
                UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_VOICE_INTERACTION);
        assertThat(events.get(0).durationMillis).isEqualTo(1234);
    }
}
+225 −77

File changed.

Preview size limit exceeded, changes collapsed.

+19 −0
Original line number Diff line number Diff line
package {
    // See: http://go/android-license-faq
    // A large-scale-change added 'default_applicable_licenses' to import
    // all of the 'license_kinds' from "frameworks_base_license"
    // to get the below license kinds:
    //   SPDX-license-identifier-Apache-2.0
    //   SPDX-license-identifier-BSD
    //   legacy_unencumbered
    default_applicable_licenses: ["frameworks_base_license"],
}

filegroup {
    name: "FrameworksCoreTestDoubles-sources",
    srcs: ["src/**/*.java"],
    visibility: [
        "//frameworks/base/core/tests/coretests",
        "//frameworks/base/services/tests/voiceinteractiontests",
    ],
}
Loading