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

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

Merge "Remove infinite money glitch from ProtoLog service" into main

parents 468627b1 acd97fa3
Loading
Loading
Loading
Loading
+3 −1
Original line number Diff line number Diff line
@@ -48,4 +48,6 @@ interface IProtoLogConfigurationService {
    }

    oneway void registerClient(IProtoLogClient client, in RegisterClientArgs args);

    oneway void unregisterClient(IProtoLogClient  client);
}
+8 −0
Original line number Diff line number Diff line
@@ -210,6 +210,14 @@ public abstract class PerfettoProtoLogImpl extends IProtoLogClient.Stub implemen
        mDataSource.unregisterOnStartCallback(this::onTracingInstanceStart);
        mDataSource.unregisterOnFlushCallback(this::onTracingFlush);
        mDataSource.unregisterOnStopCallback(this::onTracingInstanceStop);

        if (android.tracing.Flags.clientSideProtoLogging()) {
            try {
                mConfigurationService.unregisterClient(this);
            } catch (RemoteException e) {
                throw new RuntimeException("Failed to unregister ProtoLog client", e);
            }
        }
    }

    @NonNull
+120 −60
Original line number Diff line number Diff line
@@ -31,9 +31,11 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemService;
import android.content.Context;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.util.ArraySet;
import android.util.Log;
import android.util.proto.ProtoInputStream;
import android.util.proto.ProtoOutputStream;
@@ -45,6 +47,7 @@ import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
@@ -68,7 +71,7 @@ import java.util.TreeMap;
 */
@SystemService(Context.PROTOLOG_CONFIGURATION_SERVICE)
public class ProtoLogConfigurationServiceImpl extends IProtoLogConfigurationService.Stub
        implements ProtoLogConfigurationService {
        implements ProtoLogConfigurationService, IBinder.DeathRecipient {
    private static final String LOG_TAG = "ProtoLogConfigurationService";

    private final ProtoLogDataSource mDataSource;
@@ -79,20 +82,40 @@ public class ProtoLogConfigurationServiceImpl extends IProtoLogConfigurationServ
     * need to be dumped on flush.
     */
    private final Map<String, Integer> mConfigFileCounts = new HashMap<>();

    /**
     * Keeps track of the viewer config file of each client if available.
     * Container for data about a {@link IProtoLogClient} that needs to get cleaned up when the
     * client goes away.
     */
    private final Map<IProtoLogClient, String> mClientConfigFiles = new HashMap<>();
    private static final class ClientRecord {
        /** Immutable Binder.Stub for communication with the client. */
        @NonNull
        public final IProtoLogClient client;

        /** Immutable name of the viewer config file of each client if available. */
        @Nullable
        public final String configFile;

        /** Mutable set of ProtoLog groups registered for this client to actively trace. */
        @NonNull
        public final Set<String> groups = new ArraySet<>();

        public ClientRecord(@NonNull IProtoLogClient client, @Nullable String configFile) {
            this.client = client;
            this.configFile = configFile;
        }
    }

    /**
     * Keeps track of all the clients that are actively tracing.
     */
    private final Map<IBinder, ClientRecord> mClientRecords = new HashMap<>();

    /**
     * Keeps track of all the protolog groups that have been registered by clients and are still
     * being actively traced.
     */
    private final Set<String> mRegisteredGroups = new HashSet<>();
    /**
     * Keeps track of all the clients that are actively tracing a given protolog group.
     */
    private final Map<String, Set<IProtoLogClient>> mGroupToClients = new HashMap<>();

    /**
     * Keeps track of whether or not a given group should be logged to logcat.
@@ -151,14 +174,52 @@ public class ProtoLogConfigurationServiceImpl extends IProtoLogConfigurationServ
    @Override
    public void registerClient(@NonNull IProtoLogClient client, @NonNull RegisterClientArgs args)
            throws RemoteException {
        client.asBinder().linkToDeath(() -> onClientBinderDeath(client), /* flags */ 0);
        final IBinder clientBinder = client.asBinder();

        final String viewerConfigFile = args.viewerConfigFile;
        mClientRecords.put(clientBinder, new ClientRecord(client, viewerConfigFile));

        if (viewerConfigFile != null) {
            registerViewerConfigFile(client, viewerConfigFile);
            mConfigFileCounts.put(viewerConfigFile,
                    mConfigFileCounts.getOrDefault(viewerConfigFile, 0) + 1);
        }

        registerGroups(client, args.groups, args.groupsDefaultLogcatStatus);

        clientBinder.linkToDeath(this, /* flags= */ 0);
    }

    /**
     * Unregister the {@param client}.
     *
     * TODO: this lacks synchronization.
     */
    @Override
    public void unregisterClient(@Nullable IProtoLogClient client) {
        if (client == null) {
            return;
        }

        final IBinder clientBinder = client.asBinder();
        if (clientBinder != null) {
            clientBinder.unlinkToDeath(this, /* flags= */ 0);
        }

        // Retrieve the client record for cleanup.
        final ClientRecord clientRecord = mClientRecords.remove(clientBinder);
        if (clientRecord == null) {
            return;
        }

        if (clientRecord.configFile != null) {
            final var newCount = mConfigFileCounts.get(clientRecord.configFile) - 1;
            mConfigFileCounts.put(clientRecord.configFile, newCount);

            // Dump the tracing config now if no other client is going to dump the same config file.
            if (newCount == 0) {
                mViewerConfigFileTracer.trace(mDataSource, clientRecord.configFile);
            }
        }
    }

    @Override
@@ -214,11 +275,21 @@ public class ProtoLogConfigurationServiceImpl extends IProtoLogConfigurationServ
        return isLoggingToLogcat;
    }

    private void registerViewerConfigFile(
            @NonNull IProtoLogClient client, @NonNull String viewerConfigFile) {
        final var count = mConfigFileCounts.getOrDefault(viewerConfigFile, 0);
        mConfigFileCounts.put(viewerConfigFile, count + 1);
        mClientConfigFiles.put(client, viewerConfigFile);
    /**
     * Legacy method (no longer called) inherited from {@link IBinder.DeathRecipient}.
     *
     * Because the method is non-default, it has to be implemented, but the newer version taking an
     * IBinder will always be called instead.
     */
    public void binderDied() {
    }

    /**
     * Unregister client when its owner dies - inherited from {@link IBinder.DeathRecipient}
     */
    @Override
    public void binderDied(@NonNull IBinder clientBinder) {
        unregisterClient(IProtoLogClient.Stub.asInterface(clientBinder));
    }

    private void registerGroups(@NonNull IProtoLogClient client, @NonNull String[] groups,
@@ -230,14 +301,17 @@ public class ProtoLogConfigurationServiceImpl extends IProtoLogConfigurationServ
                        + " and logcatStatuses has length " + logcatStatuses.length);
        }

        final var clientRecord = mClientRecords.get(client.asBinder());
        if (clientRecord == null) {
            throw new RuntimeException("Client " + client + " is not registered");
        }

        for (int i = 0; i < groups.length; i++) {
            String group = groups[i];
            boolean logcatStatus = logcatStatuses[i];

            mRegisteredGroups.add(group);

            mGroupToClients.putIfAbsent(group, new HashSet<>());
            mGroupToClients.get(group).add(client);
            clientRecord.groups.add(group);

            if (!mLogGroupToLogcatStatus.containsKey(group)) {
                mLogGroupToLogcatStatus.put(group, logcatStatus);
@@ -253,33 +327,18 @@ public class ProtoLogConfigurationServiceImpl extends IProtoLogConfigurationServ
    private void toggleProtoLogToLogcat(
            @NonNull PrintWriter pw, boolean enabled, @NonNull String[] groups
    ) {
        final var clientToGroups = new HashMap<IProtoLogClient, Set<String>>();

        for (String group : groups) {
            final var clients = mGroupToClients.get(group);

            if (clients == null) {
                // No clients associated to this group
                var warning = "Attempting to toggle log to logcat for group " + group
                        + " with no registered clients. This is a no-op.";
                Log.w(LOG_TAG, warning);
                pw.println("WARNING: " + warning);
                continue;
            }
        // For each client, if its groups intersect the given list, send the command to toggle.
        for (var clientRecord : mClientRecords.values()) {
            final var affectedGroups = new ArraySet<>(clientRecord.groups);
            affectedGroups.retainAll(Arrays.asList(groups));

            for (IProtoLogClient client : clients) {
                clientToGroups.putIfAbsent(client, new HashSet<>());
                clientToGroups.get(client).add(group);
            }
        }

        for (IProtoLogClient client : clientToGroups.keySet()) {
            if (!affectedGroups.isEmpty()) {
                final var clientGroups = affectedGroups.toArray(new String[0]);
                try {
                final var clientGroups = clientToGroups.get(client).toArray(new String[0]);
                pw.println("Toggling logcat logging for client " + client.toString()
                    pw.println("Toggling logcat logging for client " + clientRecord.client
                            + " to " + enabled + " for groups: ["
                            + String.join(", ", clientGroups) + "]");
                client.toggleLogcat(enabled, clientGroups);
                    clientRecord.client.toggleLogcat(enabled, clientGroups);
                    pw.println("- Done");
                } catch (RemoteException e) {
                    pw.println("- Failed");
@@ -287,7 +346,21 @@ public class ProtoLogConfigurationServiceImpl extends IProtoLogConfigurationServ
                            "Failed to toggle logcat status for groups on client", e);
                }
            }
        }

        // Groups that actually have no clients associated indicate some kind of a bug.
        Set<String> noOpGroups = new ArraySet<>(groups);
        mClientRecords.forEach((k, v) -> noOpGroups.removeAll(v.groups));

        // Send out a warning in logcat and the PrintWriter for unrecognized groups.
        for (String group : noOpGroups) {
            var warning = "Attempting to toggle log to logcat for group " + group
                    + " with no registered clients. This is a no-op.";
            Log.w(LOG_TAG, warning);
            pw.println("WARNING: " + warning);
        }

        // Flip the status of the groups in our record-keeping.
        for (String group : groups) {
            mLogGroupToLogcatStatus.put(group, enabled);
        }
@@ -319,19 +392,6 @@ public class ProtoLogConfigurationServiceImpl extends IProtoLogConfigurationServ
        });
    }

    private void onClientBinderDeath(@NonNull IProtoLogClient client) {
        // Dump the tracing config now if no other client is going to dump the same config file.
        String configFile = mClientConfigFiles.get(client);
        if (configFile != null) {
            final var newCount = mConfigFileCounts.get(configFile) - 1;
            mConfigFileCounts.put(configFile, newCount);
            boolean lastProcessWithViewerConfig = newCount == 0;
            if (lastProcessWithViewerConfig) {
                mViewerConfigFileTracer.trace(mDataSource, configFile);
            }
        }
    }

    private static void writeViewerConfigGroup(
            @NonNull ProtoInputStream pis, @NonNull ProtoOutputStream os) throws IOException {
        final long inGroupToken = pis.start(GROUPS);