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

Commit e961eb84 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Migrate ProtoLogPerfTest to kotlin and use PerfStatusReporter insteas...

Merge "Migrate ProtoLogPerfTest to kotlin and use PerfStatusReporter insteas of custom implementation" into main
parents 3089ce95 771d21d4
Loading
Loading
Loading
Loading
+5 −1
Original line number Diff line number Diff line
@@ -22,13 +22,17 @@ filegroup {
android_test {
    name: "UiTracingPerfTests",
    team: "trendy_team_windowing_tools",
    srcs: ["src/**/*.java"],
    srcs: [
        "src/**/*.java",
        "src/**/*.kt",
    ],
    static_libs: [
        "androidx.annotation_annotation",
        "androidx.test.rules",
        "apct-perftests-utils",
        "collector-device-lib",
        "collector-device-lib-platform",
        "flickerlib-parsers",
        "perfetto_trace_java_protos",
        "platform-test-annotations",
    ],
+0 −275
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.protolog;

import android.app.Activity;
import android.os.Bundle;
import android.os.ServiceManager.ServiceNotFoundException;
import android.perftests.utils.Stats;
import android.platform.test.annotations.Postsubmit;
import android.tracing.perfetto.DataSourceParams;
import android.tracing.perfetto.InitArguments;
import android.tracing.perfetto.Producer;

import androidx.test.InstrumentationRegistry;

import com.android.internal.protolog.common.IProtoLog;
import com.android.internal.protolog.common.IProtoLogGroup;
import com.android.internal.protolog.common.LogLevel;

import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

import perfetto.protos.ProtologCommon;

import java.util.ArrayList;
import java.util.Collection;

@RunWith(Parameterized.class)
@Postsubmit
public class ProtoLogPerfTest {
    private final boolean mLogToProto;
    private final boolean mLogToLogcat;

    /**
     * Generates the parameters used for this test class
     */
    @Parameters(name = "LOG_TO_{0}")
    public static Collection<Object[]> params() {
        var params = new ArrayList<Object[]>();

        for (LogTo logTo : LogTo.values()) {
            params.add(new Object[] { logTo });
        }

        return params;
    }

    public ProtoLogPerfTest(LogTo logTo) {
        mLogToProto = switch (logTo) {
            case ALL, PROTO_ONLY -> true;
            case LOGCAT_ONLY, NONE -> false;
        };

        mLogToLogcat = switch (logTo) {
            case ALL, LOGCAT_ONLY -> true;
            case PROTO_ONLY, NONE -> false;
        };
    }

    private IProtoLog mProcessedProtoLogger;
    private static ProtoLogDataSource sTestDataSource;
    private static final String TEST_PROTOLOG_DATASOURCE_NAME = "test.android.protolog";
    private static final String MOCK_TEST_FILE_PATH = "mock/file/path";
    private static final perfetto.protos.Protolog.ProtoLogViewerConfig VIEWER_CONFIG =
            perfetto.protos.Protolog.ProtoLogViewerConfig.newBuilder()
                .addGroups(
                        perfetto.protos.Protolog.ProtoLogViewerConfig.Group.newBuilder()
                                .setId(1)
                                .setName(TestProtoLogGroup.TEST_GROUP.toString())
                                .setTag(TestProtoLogGroup.TEST_GROUP.getTag())
                ).addMessages(
                        perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.newBuilder()
                                .setMessageId(123)
                                .setMessage("My Test Debug Log Message %b")
                                .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_DEBUG)
                                .setGroupId(1)
                                .setLocation("com/test/MyTestClass.java:123")
                ).build();

    @BeforeClass
    public static void init() {
        Producer.init(InitArguments.DEFAULTS);

        sTestDataSource = new ProtoLogDataSource(TEST_PROTOLOG_DATASOURCE_NAME);
        DataSourceParams params =
                new DataSourceParams.Builder()
                        .setBufferExhaustedPolicy(
                                DataSourceParams
                                        .PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP)
                        .build();
        sTestDataSource.register(params);

        ProtoLog.init(TestProtoLogGroup.values());
    }

    @Before
    public void setUp() throws ServiceNotFoundException {
        TestProtoLogGroup.TEST_GROUP.setLogToProto(mLogToProto);
        TestProtoLogGroup.TEST_GROUP.setLogToLogcat(mLogToLogcat);

        mProcessedProtoLogger = new ProcessedPerfettoProtoLogImpl(
                sTestDataSource,
                MOCK_TEST_FILE_PATH,
                () -> new AutoClosableProtoInputStream(VIEWER_CONFIG.toByteArray()),
                (instance) -> {},
                TestProtoLogGroup.values()
        );
    }

    @Test
    public void log_Processed_NoArgs() {
        final var perfMonitor = new PerfMonitor();

        while (perfMonitor.keepRunning()) {
            mProcessedProtoLogger.log(
                    LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 123,
                    0, (Object[]) null);
        }
    }

    @Test
    public void log_Processed_WithArgs() {
        final var perfMonitor = new PerfMonitor();

        while (perfMonitor.keepRunning()) {
            mProcessedProtoLogger.log(
                    LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 123,
                    0b1110101001010100,
                    new Object[]{"test", 1, 2, 3, 0.4, 0.5, 0.6, true});
        }
    }

    @Test
    public void log_Unprocessed_NoArgs() {
        final var perfMonitor = new PerfMonitor();

        while (perfMonitor.keepRunning()) {
            ProtoLog.d(TestProtoLogGroup.TEST_GROUP, "Test message");
        }
    }

    @Test
    public void log_Unprocessed_WithArgs() {
        final var perfMonitor = new PerfMonitor();

        while (perfMonitor.keepRunning()) {
            ProtoLog.d(TestProtoLogGroup.TEST_GROUP, "Test message %s, %d, %b", "arg1", 2, true);
        }
    }

    private class PerfMonitor {
        private int mIteration = 0;

        private static final int WARM_UP_ITERATIONS = 10;
        private static final int ITERATIONS = 1000;

        private final ArrayList<Long> mResults = new ArrayList<>();

        private long mStartTimeNs;

        public boolean keepRunning() {
            final long currentTime = System.nanoTime();

            mIteration++;

            if (mIteration > ITERATIONS) {
                reportResults();
                return false;
            }

            if (mIteration > WARM_UP_ITERATIONS) {
                mResults.add(currentTime - mStartTimeNs);
            }

            mStartTimeNs = System.nanoTime();
            return true;
        }

        public void reportResults() {
            final var stats = new Stats(mResults);

            Bundle status = new Bundle();
            status.putLong("protologging_time_mean_ns", (long) stats.getMean());
            status.putLong("protologging_time_median_ns", (long) stats.getMedian());
            InstrumentationRegistry.getInstrumentation().sendStatus(Activity.RESULT_OK, status);
        }
    }

    private enum TestProtoLogGroup implements IProtoLogGroup {
        TEST_GROUP(true, true, false, "WindowManagetProtoLogTest");

        private final boolean mEnabled;
        private volatile boolean mLogToProto;
        private volatile boolean mLogToLogcat;
        private final String mTag;

        /**
         * @param enabled set to false to exclude all log statements for this group from
         *     compilation, they will not be available in runtime.
         * @param logToProto enable binary logging for the group
         * @param logToLogcat enable text logging for the group
         * @param tag name of the source of the logged message
         */
        TestProtoLogGroup(boolean enabled, boolean logToProto, boolean logToLogcat, String tag) {
            this.mEnabled = enabled;
            this.mLogToProto = logToProto;
            this.mLogToLogcat = logToLogcat;
            this.mTag = tag;
        }

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

        @Override
        public boolean isLogToProto() {
            return mLogToProto;
        }

        @Override
        public boolean isLogToLogcat() {
            return mLogToLogcat;
        }

        @Override
        public boolean isLogToAny() {
            return mLogToLogcat || mLogToProto;
        }

        @Override
        public String getTag() {
            return mTag;
        }

        @Override
        public void setLogToProto(boolean logToProto) {
            this.mLogToProto = logToProto;
        }

        @Override
        public void setLogToLogcat(boolean logToLogcat) {
            this.mLogToLogcat = logToLogcat;
        }

        @Override
        public int getId() {
            return ordinal();
        }
    }

    private enum LogTo {
        PROTO_ONLY,
        LOGCAT_ONLY,
        ALL,
        NONE,
    }
}
+309 −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.protolog

import android.os.ServiceManager
import android.perftests.utils.PerfStatusReporter
import android.platform.test.annotations.Postsubmit
import android.tools.ScenarioBuilder
import android.tools.traces.busyWaitForDataSourceRegistration
import android.tools.traces.busyWaitTracingSessionDoesntExist
import android.tools.traces.busyWaitTracingSessionExists
import android.tools.traces.io.ResultWriter
import android.tools.traces.monitors.PerfettoTraceMonitor
import android.tools.traces.monitors.PerfettoTraceMonitor.Companion.newBuilder
import android.tracing.perfetto.DataSourceParams
import android.tracing.perfetto.InitArguments
import android.tracing.perfetto.Producer
import androidx.test.platform.app.InstrumentationRegistry
import com.android.internal.protolog.common.IProtoLog
import com.android.internal.protolog.common.IProtoLogGroup
import com.android.internal.protolog.common.LogLevel
import java.io.File
import kotlin.concurrent.Volatile
import org.junit.After
import org.junit.Before
import org.junit.BeforeClass
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.junit.runners.Parameterized.Parameters
import perfetto.protos.Protolog
import perfetto.protos.ProtologCommon

@RunWith(Parameterized::class)
@Postsubmit
class ProtoLogPerfTest(logType: LogType) {

    @get:Rule val perfStatusReporter = PerfStatusReporter()

    private val mLogToProto: Boolean
    private val mLogToLogcat: Boolean
    private val mCollectStackTrace: Boolean

    private var mProcessedProtoLogger: IProtoLog? = null
    private var mPerfettoTracingMonitor: PerfettoTraceMonitor? = null

    init {
        mLogToProto =
            when (logType) {
                LogType.ALL,
                LogType.PROTO_AND_STACKTRACE_ONLY,
                LogType.ALL_NO_STACKTRACE,
                LogType.PROTO_ONLY -> true
                LogType.LOGCAT_ONLY,
                LogType.NONE -> false
            }

        mLogToLogcat =
            when (logType) {
                LogType.ALL,
                LogType.ALL_NO_STACKTRACE,
                LogType.LOGCAT_ONLY -> true
                LogType.PROTO_AND_STACKTRACE_ONLY,
                LogType.PROTO_ONLY,
                LogType.NONE -> false
            }

        mCollectStackTrace =
            when (logType) {
                LogType.ALL,
                LogType.PROTO_AND_STACKTRACE_ONLY -> true
                LogType.ALL_NO_STACKTRACE,
                LogType.LOGCAT_ONLY,
                LogType.PROTO_ONLY,
                LogType.NONE -> false
            }
    }

    @Before
    @Throws(ServiceManager.ServiceNotFoundException::class)
    fun setUp() {
        TEST_GROUP.isLogToProto = mLogToProto
        TEST_GROUP.isLogToLogcat = mLogToLogcat

        mProcessedProtoLogger =
            ProcessedPerfettoProtoLogImpl(
                sTestDataSource!!,
                MOCK_TEST_FILE_PATH,
                { AutoClosableProtoInputStream(VIEWER_CONFIG.toByteArray()) },
                { instance: IProtoLog? -> },
                arrayOf(TEST_GROUP),
            )

        val groupOverrides =
            if (mLogToProto) {
                listOf(
                    PerfettoTraceMonitor.Builder.ProtoLogGroupOverride(
                        groupName = TEST_GROUP.name()!!,
                        logFrom = LogLevel.VERBOSE,
                        collectStackTrace = mCollectStackTrace,
                    )
                )
            } else {
                listOf(
                    PerfettoTraceMonitor.Builder.ProtoLogGroupOverride(
                        groupName = TEST_GROUP.name()!!,
                        logFrom = LogLevel.WTF,
                        collectStackTrace = mCollectStackTrace,
                    )
                )
            }

        mPerfettoTracingMonitor =
            newBuilder()
                .setUniqueSessionName(TRACE_SESSION_NAME)
                .enableProtoLog(
                    logAll = mLogToProto,
                    groupOverrides = groupOverrides,
                    dataSourceName = TEST_PROTOLOG_DATASOURCE_NAME,
                )
                .build()
        mPerfettoTracingMonitor?.start()
        busyWaitTracingSessionExists(TRACE_SESSION_NAME)
    }

    @After
    fun tearDown() {
        val dir: File = tempDataSourceDir()
        val writer: ResultWriter = createDummyWriter(dir)
        mPerfettoTracingMonitor?.stop(writer)

        busyWaitTracingSessionDoesntExist(TRACE_SESSION_NAME)
    }

    @Test
    fun log_Processed_NoArgs() {
        val state = perfStatusReporter.benchmarkState
        while (state.keepRunning()) {
            mProcessedProtoLogger!!.log(LogLevel.INFO, TEST_GROUP, 123, 0, null as Array<Any?>?)
        }
    }

    @Test
    fun log_Processed_WithArgs() {
        val state = perfStatusReporter.benchmarkState
        while (state.keepRunning()) {
            mProcessedProtoLogger!!.log(
                LogLevel.INFO,
                TEST_GROUP,
                123,
                59988,
                arrayOf<Any>("test", 1, 2, 3, 0.4, 0.5, 0.6, true),
            )
        }
    }

    @Test
    fun log_Unprocessed_NoArgs() {
        val state = perfStatusReporter.benchmarkState
        while (state.keepRunning()) {
            ProtoLog.d(TEST_GROUP, "Test message")
        }
    }

    @Test
    fun log_Unprocessed_WithArgs() {
        val state = perfStatusReporter.benchmarkState
        while (state.keepRunning()) {
            ProtoLog.d(TEST_GROUP, "Test message %s, %d, %b", "arg1", 2, true)
        }
    }

    private fun tempDataSourceDir(): File {
        val tempDir = InstrumentationRegistry.getInstrumentation().context.cacheDir
        return File(tempDir, "DataSourcePerfTest_Dummy_" + System.nanoTime())
    }

    // Helper to create a dummy ResultWriter
    private fun createDummyWriter(scenarioDir: File): ResultWriter {
        return ResultWriter()
            .forScenario(ScenarioBuilder().forClass("DataSourcePerfTest").build())
            .withOutputDir(scenarioDir)
            .setRunComplete()
    }

    enum class LogType {
        PROTO_ONLY,
        LOGCAT_ONLY,
        PROTO_AND_STACKTRACE_ONLY,
        ALL_NO_STACKTRACE,
        ALL,
        NONE,
    }

    companion object {
        /** Generates the parameters used for this test class */
        @Parameters(name = "LOG_TO_{0}")
        @JvmStatic
        fun params(): MutableCollection<Array<Any?>?> {
            val params = ArrayList<Array<Any?>?>()

            for (logTo in LogType.entries) {
                params.add(arrayOf(logTo))
            }

            return params
        }

        const val TRACE_SESSION_NAME = "protolog-perf-test"

        val TEST_GROUP =
            object : IProtoLogGroup {
                private val mEnabled: Boolean = true
                @field:Volatile private var mLogToProto: Boolean = true
                @field:Volatile private var mLogToLogcat: Boolean = true
                private val mTag: String = "WindowManagerProtoLogTest"

                override fun isEnabled(): Boolean {
                    return mEnabled
                }

                override fun isLogToProto(): Boolean {
                    return mLogToProto
                }

                override fun isLogToLogcat(): Boolean {
                    return mLogToLogcat
                }

                override fun isLogToAny(): Boolean {
                    return mLogToLogcat || mLogToProto
                }

                override fun getTag(): String? {
                    return mTag
                }

                override fun setLogToProto(logToProto: Boolean) {
                    this.mLogToProto = logToProto
                }

                override fun setLogToLogcat(logToLogcat: Boolean) {
                    this.mLogToLogcat = logToLogcat
                }

                override fun name(): String? {
                    return "TEST_GROUP"
                }

                override fun getId(): Int {
                    return 1
                }
            }

        private var sTestDataSource: ProtoLogDataSource? = null
        private const val TEST_PROTOLOG_DATASOURCE_NAME = "test.android.protolog"
        private const val MOCK_TEST_FILE_PATH = "mock/file/path"
        private val VIEWER_CONFIG: Protolog.ProtoLogViewerConfig =
            Protolog.ProtoLogViewerConfig.newBuilder()
                .addGroups(
                    Protolog.ProtoLogViewerConfig.Group.newBuilder()
                        .setId(TEST_GROUP.id)
                        .setName(TEST_GROUP.name())
                        .setTag(TEST_GROUP.tag)
                )
                .addMessages(
                    Protolog.ProtoLogViewerConfig.MessageData.newBuilder()
                        .setMessageId(123)
                        .setMessage("My Test Debug Log Message %b")
                        .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_DEBUG)
                        .setGroupId(1)
                        .setLocation("com/test/MyTestClass.java:123")
                )
                .build()

        @BeforeClass
        @JvmStatic
        fun init() {
            Producer.init(InitArguments.DEFAULTS)

            sTestDataSource = ProtoLogDataSource(TEST_PROTOLOG_DATASOURCE_NAME)
            val params =
                DataSourceParams.Builder()
                    .setBufferExhaustedPolicy(
                        DataSourceParams.PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP
                    )
                    .build()
            sTestDataSource!!.register(params)

            ProtoLog.init(TEST_GROUP)

            busyWaitForDataSourceRegistration(TEST_PROTOLOG_DATASOURCE_NAME)
        }
    }
}