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

Commit d986d3e1 authored by Pablo Gamito's avatar Pablo Gamito Committed by Android (Google) Code Review
Browse files

Merge changes from topic "perfettoprotologimpl-refactor-processed-and-unprocessed" into main

* changes:
  Fallback to a NoOp ProtoLogImpl when viewer config is missing
  Ensure we always de-duplicate groups on ProtoLog init
  Wrap ProtoInputStream in autoclosable class
  Simplify nested logic
  Don't try to log to logcat before the service is ready to trace messages to logcat
  Update PerfettoProtoLogImpl constructor usages to use unprocessed and processed implementations where relevant
  Update tests
  Implement unprocessed version of PerfettoProtoLogImpl
  Create ProcessedPerfettoProtoLogImpl class
  Turn PerfettoProtoLogImpl class into abstract class to support both a version for processed and unprocessed protologs
parents f8795f6d 4f2392c6
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ android_test {
        "apct-perftests-utils",
        "collector-device-lib",
        "platform-test-annotations",
        "perfetto_trace_java_protos",
    ],
    test_suites: [
        "device-tests",
+32 −5
Original line number Diff line number Diff line
@@ -17,10 +17,12 @@ package com.android.internal.protolog;

import android.app.Activity;
import android.os.Bundle;
import android.os.ServiceManager.ServiceNotFoundException;
import android.perftests.utils.Stats;

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;

@@ -31,6 +33,8 @@ 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;

@@ -65,24 +69,48 @@ public class ProtoLogPerfTest {
        };
    }

    private IProtoLog mProcessedProtoLogger;
    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() {
        ProtoLog.init(TestProtoLogGroup.values());
    }

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

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

    @Test
    public void log_Processed_NoArgs() {
        final var protoLog = ProtoLog.getSingleInstance();
        final var perfMonitor = new PerfMonitor();

        while (perfMonitor.keepRunning()) {
            protoLog.log(
            mProcessedProtoLogger.log(
                    LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 123,
                    0, (Object[]) null);
        }
@@ -90,11 +118,10 @@ public class ProtoLogPerfTest {

    @Test
    public void log_Processed_WithArgs() {
        final var protoLog = ProtoLog.getSingleInstance();
        final var perfMonitor = new PerfMonitor();

        while (perfMonitor.keepRunning()) {
            protoLog.log(
            mProcessedProtoLogger.log(
                    LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 123,
                    0b1110101001010100,
                    new Object[]{"test", 1, 2, 3, 0.4, 0.5, 0.6, true});
+56 −0
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.annotation.NonNull;
import android.annotation.Nullable;
import android.util.proto.ProtoInputStream;

import java.io.FileInputStream;
import java.io.IOException;

public final class AutoClosableProtoInputStream implements AutoCloseable {
    @NonNull
    private final ProtoInputStream mProtoInputStream;
    @Nullable
    private final FileInputStream mFileInputStream;

    public AutoClosableProtoInputStream(@NonNull FileInputStream fileInputStream) {
        mProtoInputStream = new ProtoInputStream(fileInputStream);
        mFileInputStream = fileInputStream;
    }

    public AutoClosableProtoInputStream(@NonNull byte[] input) {
        mProtoInputStream = new ProtoInputStream(input);
        mFileInputStream = null;
    }

    /**
     * @return the ProtoInputStream this class is wrapping
     */
    @NonNull
    public ProtoInputStream get() {
        return mProtoInputStream;
    }

    @Override
    public void close() throws IOException {
        if (mFileInputStream != null) {
            mFileInputStream.close();
        }
    }
}
+90 −0
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.text.TextUtils;
import android.util.Log;

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

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * Class should only be used as a temporary solution to missing viewer config file on device.
 * In particular this class should only be initialized in Robolectric tests, if it's being used
 * otherwise please report it.
 *
 * @deprecated
 */
@Deprecated
public class NoViewerConfigProtoLogImpl implements IProtoLog {
    private static final String LOG_TAG = "ProtoLog";

    @Override
    public void log(LogLevel logLevel, IProtoLogGroup group, long messageHash, int paramsMask,
            Object[] args) {
        Log.w(LOG_TAG, "ProtoLogging is not available due to missing viewer config file...");
        logMessage(logLevel, group.getTag(), "PROTOLOG#" + messageHash + "("
                + Arrays.stream(args).map(Object::toString).collect(Collectors.joining()) + ")");
    }

    @Override
    public void log(LogLevel logLevel, IProtoLogGroup group, String messageString, Object... args) {
        logMessage(logLevel, group.getTag(), TextUtils.formatSimple(messageString, args));
    }

    @Override
    public boolean isProtoEnabled() {
        return false;
    }

    @Override
    public int startLoggingToLogcat(String[] groups, ILogger logger) {
        return 0;
    }

    @Override
    public int stopLoggingToLogcat(String[] groups, ILogger logger) {
        return 0;
    }

    @Override
    public boolean isEnabled(IProtoLogGroup group, LogLevel level) {
        return false;
    }

    @Override
    public List<IProtoLogGroup> getRegisteredGroups() {
        return List.of();
    }

    private void logMessage(LogLevel logLevel, String tag, String message) {
        switch (logLevel) {
            case VERBOSE -> Log.v(tag, message);
            case INFO -> Log.i(tag, message);
            case DEBUG -> Log.d(tag, message);
            case WARN -> Log.w(tag, message);
            case ERROR -> Log.e(tag, message);
            case WTF -> Log.wtf(tag, message);
        }
    }
}
+32 −176
Original line number Diff line number Diff line
@@ -60,18 +60,16 @@ import android.util.ArraySet;
import android.util.Log;
import android.util.LongArray;
import android.util.Slog;
import android.util.proto.ProtoInputStream;
import android.util.proto.ProtoOutputStream;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLogConfigurationServiceImpl.RegisterClientArgs;
import com.android.internal.protolog.common.ILogger;
import com.android.internal.protolog.common.IProtoLog;
import com.android.internal.protolog.common.IProtoLogGroup;
import com.android.internal.protolog.common.LogDataType;
import com.android.internal.protolog.common.LogLevel;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
@@ -93,26 +91,18 @@ import java.util.stream.Stream;
/**
 * A service for the ProtoLog logging system.
 */
public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProtoLog {
public abstract class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProtoLog {
    private static final String LOG_TAG = "ProtoLog";
    public static final String NULL_STRING = "null";
    private final AtomicInteger mTracingInstances = new AtomicInteger();

    @NonNull
    private final ProtoLogDataSource mDataSource;
    @Nullable
    private final ProtoLogViewerConfigReader mViewerConfigReader;
    @Deprecated
    @Nullable
    private final ViewerConfigInputStreamProvider mViewerConfigInputStreamProvider;
    protected final ProtoLogDataSource mDataSource;
    @NonNull
    private final TreeMap<String, IProtoLogGroup> mLogGroups = new TreeMap<>();
    protected final TreeMap<String, IProtoLogGroup> mLogGroups = new TreeMap<>();
    @NonNull
    private final Runnable mCacheUpdater;

    @Nullable // null when the flag android.tracing.client_side_proto_logging is not flipped
    private final IProtoLogConfigurationService mProtoLogConfigurationService;

    @NonNull
    private final int[] mDefaultLogLevelCounts = new int[LogLevel.values().length];
    @NonNull
@@ -121,68 +111,15 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto
    private final Map<String, Integer> mCollectStackTraceGroupCounts = new ArrayMap<>();

    private final Lock mBackgroundServiceLock = new ReentrantLock();
    private ExecutorService mBackgroundLoggingService = Executors.newSingleThreadExecutor();

    public PerfettoProtoLogImpl(@NonNull IProtoLogGroup[] groups)
            throws ServiceManager.ServiceNotFoundException {
        this(null, null, null, () -> {}, groups);
    }

    public PerfettoProtoLogImpl(@NonNull Runnable cacheUpdater, @NonNull IProtoLogGroup[] groups)
            throws ServiceManager.ServiceNotFoundException {
        this(null, null, null, cacheUpdater, groups);
    }

    public PerfettoProtoLogImpl(
            @NonNull String viewerConfigFilePath,
            @NonNull Runnable cacheUpdater,
            @NonNull IProtoLogGroup[] groups) throws ServiceManager.ServiceNotFoundException {
        this(viewerConfigFilePath,
                null,
                new ProtoLogViewerConfigReader(() -> {
                    try {
                        return new ProtoInputStream(new FileInputStream(viewerConfigFilePath));
                    } catch (FileNotFoundException e) {
                        throw new RuntimeException(
                                "Failed to load viewer config file " + viewerConfigFilePath, e);
                    }
                }),
                cacheUpdater, groups);
    }

    @Deprecated
    @VisibleForTesting
    public PerfettoProtoLogImpl(
            @Nullable ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
            @Nullable ProtoLogViewerConfigReader viewerConfigReader,
            @NonNull Runnable cacheUpdater,
            @NonNull IProtoLogGroup[] groups,
            @NonNull ProtoLogDataSourceBuilder dataSourceBuilder,
            @NonNull ProtoLogConfigurationService configurationService) {
        this(null, viewerConfigInputStreamProvider, viewerConfigReader, cacheUpdater,
                groups, dataSourceBuilder, configurationService);
    }
    protected ExecutorService mBackgroundLoggingService = Executors.newSingleThreadExecutor();

    @VisibleForTesting
    public PerfettoProtoLogImpl(
            @Nullable String viewerConfigFilePath,
            @Nullable ProtoLogViewerConfigReader viewerConfigReader,
            @NonNull Runnable cacheUpdater,
            @NonNull IProtoLogGroup[] groups,
            @NonNull ProtoLogDataSourceBuilder dataSourceBuilder,
            @NonNull ProtoLogConfigurationService configurationService) {
        this(viewerConfigFilePath, null, viewerConfigReader, cacheUpdater,
                groups, dataSourceBuilder, configurationService);
    }
    // Set to true once this is ready to accept protolog to logcat requests.
    private boolean mLogcatReady = false;

    private PerfettoProtoLogImpl(
            @Nullable String viewerConfigFilePath,
            @Nullable ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
            @Nullable ProtoLogViewerConfigReader viewerConfigReader,
    protected PerfettoProtoLogImpl(
            @NonNull Runnable cacheUpdater,
            @NonNull IProtoLogGroup[] groups) throws ServiceManager.ServiceNotFoundException {
        this(viewerConfigFilePath, viewerConfigInputStreamProvider, viewerConfigReader,
                cacheUpdater, groups,
        this(cacheUpdater, groups,
                ProtoLogDataSource::new,
                android.tracing.Flags.clientSideProtoLogging() ?
                    IProtoLogConfigurationService.Stub.asInterface(
@@ -191,19 +128,11 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto
        );
    }

    private PerfettoProtoLogImpl(
            @Nullable String viewerConfigFilePath,
            @Nullable ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
            @Nullable ProtoLogViewerConfigReader viewerConfigReader,
    protected PerfettoProtoLogImpl(
            @NonNull Runnable cacheUpdater,
            @NonNull IProtoLogGroup[] groups,
            @NonNull ProtoLogDataSourceBuilder dataSourceBuilder,
            @Nullable IProtoLogConfigurationService configurationService) {
        if (viewerConfigFilePath != null && viewerConfigInputStreamProvider != null) {
            throw new RuntimeException("Only one of viewerConfigFilePath and "
                    + "viewerConfigInputStreamProvider can be set");
        }

        mDataSource = dataSourceBuilder.build(
                this::onTracingInstanceStart,
                this::onTracingFlush,
@@ -219,55 +148,33 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto
        // for some messages logged right after the construction of this class.
        mDataSource.register(params);

        if (viewerConfigInputStreamProvider == null && viewerConfigFilePath != null) {
            viewerConfigInputStreamProvider = new ViewerConfigInputStreamProvider() {
                @NonNull
                @Override
                public ProtoInputStream getInputStream() {
                    try {
                        return new ProtoInputStream(new FileInputStream(viewerConfigFilePath));
                    } catch (FileNotFoundException e) {
                        throw new RuntimeException(
                                "Failed to load viewer config file " + viewerConfigFilePath, e);
                    }
                }
            };
        }

        this.mViewerConfigInputStreamProvider = viewerConfigInputStreamProvider;
        this.mViewerConfigReader = viewerConfigReader;
        this.mCacheUpdater = cacheUpdater;

        registerGroupsLocally(groups);

        if (android.tracing.Flags.clientSideProtoLogging()) {
            mProtoLogConfigurationService = configurationService;
            Objects.requireNonNull(mProtoLogConfigurationService,
            Objects.requireNonNull(configurationService,
                    "A null ProtoLog Configuration Service was provided!");

            try {
                var args = new ProtoLogConfigurationServiceImpl.RegisterClientArgs();

                if (viewerConfigFilePath != null) {
                    args.setViewerConfigFile(viewerConfigFilePath);
                }
                var args = createConfigurationServiceRegisterClientArgs();

                final var groupArgs = Stream.of(groups)
                        .map(group -> new ProtoLogConfigurationServiceImpl.RegisterClientArgs
                        .map(group -> new RegisterClientArgs
                                .GroupConfig(group.name(), group.isLogToLogcat()))
                        .toArray(ProtoLogConfigurationServiceImpl
                                .RegisterClientArgs.GroupConfig[]::new);
                        .toArray(RegisterClientArgs.GroupConfig[]::new);
                args.setGroups(groupArgs);

                mProtoLogConfigurationService.registerClient(this, args);
                configurationService.registerClient(this, args);
            } catch (RemoteException e) {
                throw new RuntimeException("Failed to register ProtoLog client");
            }
        } else {
            mProtoLogConfigurationService = null;
        }
    }

    @NonNull
    protected abstract RegisterClientArgs createConfigurationServiceRegisterClientArgs();

    /**
     * Main log method, do not call directly.
     */
@@ -334,9 +241,6 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto
     * @return status code
     */
    public int startLoggingToLogcat(String[] groups, @NonNull ILogger logger) {
        if (mViewerConfigReader != null) {
            mViewerConfigReader.loadViewerConfig(groups, logger);
        }
        return setTextLogging(true, logger, groups);
    }

@@ -347,9 +251,6 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto
     * @return status code
     */
    public int stopLoggingToLogcat(String[] groups, @NonNull ILogger logger) {
        if (mViewerConfigReader != null) {
            mViewerConfigReader.unloadViewerConfig(groups, logger);
        }
        return setTextLogging(false, logger, groups);
    }

@@ -372,21 +273,8 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto
        // we might want to manually specify an id for the group with a collision
        verifyNoCollisionsOrDuplicates(protoLogGroups);

        final var groupsLoggingToLogcat = new ArrayList<String>();
        for (IProtoLogGroup protoLogGroup : protoLogGroups) {
            mLogGroups.put(protoLogGroup.name(), protoLogGroup);

            if (protoLogGroup.isLogToLogcat()) {
                groupsLoggingToLogcat.add(protoLogGroup.name());
            }
        }

        if (mViewerConfigReader != null) {
            // Load in background to avoid delay in boot process.
            // The caveat is that any log message that is also logged to logcat will not be
            // successfully decoded until this completes.
            mBackgroundLoggingService.execute(() -> mViewerConfigReader
                    .loadViewerConfig(groupsLoggingToLogcat.toArray(new String[0])));
        }
    }

@@ -403,6 +291,10 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto
        }
    }

    protected void readyToLogToLogcat() {
        mLogcatReady = true;
    }

    /**
     * Responds to a shell command.
     */
@@ -499,57 +391,21 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto
    }

    @Deprecated
    private void dumpViewerConfig() {
        if (mViewerConfigInputStreamProvider == null) {
            // No viewer config available
            return;
        }

        Log.d(LOG_TAG, "Dumping viewer config to trace");

        Utils.dumpViewerConfig(mDataSource, mViewerConfigInputStreamProvider);
    abstract void dumpViewerConfig();

        Log.d(LOG_TAG, "Dumped viewer config to trace");
    }
    @NonNull
    abstract String getLogcatMessageString(@NonNull Message message);

    private void logToLogcat(String tag, LogLevel level, Message message,
    private void logToLogcat(@NonNull String tag, @NonNull LogLevel level, @NonNull Message message,
            @Nullable Object[] args) {
        String messageString;
        if (mViewerConfigReader == null) {
            messageString = message.getMessage();

            if (messageString == null) {
                Log.e(LOG_TAG, "Failed to decode message for logcat. "
                        + "Message not available without ViewerConfig to decode the hash.");
            }
        } else {
            messageString = message.getMessage(mViewerConfigReader);

            if (messageString == null) {
                Log.e(LOG_TAG, "Failed to decode message for logcat. "
                        + "Message hash either not available in viewerConfig file or "
                        + "not loaded into memory from file before decoding.");
            }
        }

        if (messageString == null) {
            StringBuilder builder = new StringBuilder("UNKNOWN MESSAGE");
            if (args != null) {
                builder.append(" args = (");
                builder.append(String.join(", ", Arrays.stream(args)
                        .map(it -> {
                            if (it == null) {
                                return "null";
                            } else {
                                return it.toString();
                            }
                        }).toList()));
                builder.append(")");
            }
            messageString = builder.toString();
            args = new Object[0];
        if (!mLogcatReady) {
            Log.w(LOG_TAG, "Trying to log a protolog message with hash "
                    + message.getMessageHash() + " to logcat before the service is ready to accept "
                    + "such requests.");
            return;
        }

        String messageString = getLogcatMessageString(message);
        logToLogcat(tag, level, messageString, args);
    }

Loading