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

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

Merge "Add limits to ProtoLog interned data in Perfetto traces." into main

parents 67b6b390 1a1a7a00
Loading
Loading
Loading
Loading
+26 −1
Original line number Diff line number Diff line
@@ -98,6 +98,8 @@ public abstract class PerfettoProtoLogImpl extends IProtoLogClient.Stub implemen
        TracingInstanceStartCallback, TracingInstanceStopCallback, TracingFlushCallback {
    private static final String LOG_TAG = "ProtoLog";
    public static final String NULL_STRING = "null";
    @VisibleForTesting
    public static int MAX_INTERNED_STRINGS_SIZE_BYTES_BEFORE_RESET = 4 * 1024 * 1024; // 4MB
    private final AtomicInteger mTracingInstances = new AtomicInteger();

    @NonNull
@@ -726,6 +728,18 @@ public abstract class PerfettoProtoLogImpl extends IProtoLogClient.Stub implemen
            @NonNull IProtoLogGroup logGroup, @NonNull String message) {
        final ProtoLogDataSource.IncrementalState incrementalState = ctx.getIncrementalState();

        final Long messageHash = hash(level, logGroup.name(), message);

        if (android.tracing.Flags.protologAutoClearIncrementalState()
                && !incrementalState.protologMessageInterningSet.contains(messageHash)) {
            final boolean sizeThresholdReached =
                    incrementalState.internedStringsSizeBytes + message.length()
                            >= MAX_INTERNED_STRINGS_SIZE_BYTES_BEFORE_RESET;
            if (sizeThresholdReached) {
                incrementalState.reset();
            }
        }

        if (!incrementalState.clearReported) {
            final ProtoOutputStream os = ctx.newTracePacket(8);
            os.write(SEQUENCE_FLAGS, SEQ_INCREMENTAL_STATE_CLEARED);
@@ -748,9 +762,9 @@ public abstract class PerfettoProtoLogImpl extends IProtoLogClient.Stub implemen
            os.end(protologViewerConfigToken);
        }

        final Long messageHash = hash(level, logGroup.name(), message);
        if (!incrementalState.protologMessageInterningSet.contains(messageHash)) {
            incrementalState.protologMessageInterningSet.add(messageHash);
            incrementalState.internedStringsSizeBytes += message.length();

            final ProtoOutputStream os = ctx.newTracePacket(128);

@@ -825,6 +839,16 @@ public abstract class PerfettoProtoLogImpl extends IProtoLogClient.Stub implemen
    ) {
        final ProtoLogDataSource.IncrementalState incrementalState = ctx.getIncrementalState();

        if (android.tracing.Flags.protologAutoClearIncrementalState()
                && !internMap.containsKey(string)) {
            final boolean sizeThresholdReached =
                    incrementalState.internedStringsSizeBytes + string.length()
                            >= MAX_INTERNED_STRINGS_SIZE_BYTES_BEFORE_RESET;
            if (sizeThresholdReached) {
                incrementalState.reset();
            }
        }

        if (!incrementalState.clearReported) {
            final ProtoOutputStream os = ctx.newTracePacket(8);
            os.write(SEQUENCE_FLAGS, SEQ_INCREMENTAL_STATE_CLEARED);
@@ -834,6 +858,7 @@ public abstract class PerfettoProtoLogImpl extends IProtoLogClient.Stub implemen
        if (!internMap.containsKey(string)) {
            final int internedIndex = internMap.size() + 1;
            internMap.put(string, internedIndex);
            incrementalState.internedStringsSizeBytes += string.length();

            final ProtoOutputStream os = ctx.newTracePacket(64);
            final long token = os.start(INTERNED_DATA);
+14 −0
Original line number Diff line number Diff line
@@ -237,7 +237,21 @@ public class ProtoLogDataSource extends DataSource<ProtoLogDataSource.Instance,
        public final Set<Long> protologMessageInterningSet = new HashSet<>();
        public final Map<String, Integer> argumentInterningMap = new HashMap<>();
        public final Map<String, Integer> stacktraceInterningMap = new HashMap<>();
        public long internedStringsSizeBytes = 0;
        public boolean clearReported = false;

        /**
         * Resets the incremental state, clearing all interned data. This is used to prevent
         * the state from growing too large during long tracing sessions.
         */
        public void reset() {
            protologGroupInterningSet.clear();
            protologMessageInterningSet.clear();
            argumentInterningMap.clear();
            stacktraceInterningMap.clear();
            internedStringsSizeBytes = 0;
            clearReported = false;
        }
    }

    public static class ProtoLogConfig {
+48 −0
Original line number Diff line number Diff line
@@ -29,9 +29,11 @@ import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
import static org.junit.Assume.assumeTrue;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import static perfetto.protos.TracePacketOuterClass.TracePacket.SequenceFlags.SEQ_INCREMENTAL_STATE_CLEARED;
import static perfetto.protos.TracePacketOuterClass.TracePacket.SequenceFlags.SEQ_NEEDS_INCREMENTAL_STATE;

import android.os.SystemClock;
@@ -97,6 +99,9 @@ public class ProcessedPerfettoProtoLogImplTest {

    private static ProtoLogViewerConfigReader sReader;

    private static int sOriginalMaxInternedStringsSize;


    public ProcessedPerfettoProtoLogImplTest() throws IOException { }

    @BeforeClass
@@ -186,6 +191,9 @@ public class ProcessedPerfettoProtoLogImplTest {
                (instance) -> sCacheUpdater.update(instance), TestProtoLogGroup.values(),
                sProtoLogConfigurationService);
        sProtoLog.enable();

        sOriginalMaxInternedStringsSize =
                PerfettoProtoLogImpl.MAX_INTERNED_STRINGS_SIZE_BYTES_BEFORE_RESET;
    }

    @Before
@@ -198,6 +206,8 @@ public class ProcessedPerfettoProtoLogImplTest {
    @After
    public void tearDown() {
        ProtoLogImpl.setSingleInstance(null);
        PerfettoProtoLogImpl.MAX_INTERNED_STRINGS_SIZE_BYTES_BEFORE_RESET =
                sOriginalMaxInternedStringsSize;
    }

    @Test
@@ -1306,6 +1316,44 @@ public class ProcessedPerfettoProtoLogImplTest {
                .that(sequenceFlag & incrementalStateFlag).isEqualTo(incrementalStateFlag);
    }

    @Test
    public void incrementalStateResetWhenStringsTooLarge() throws IOException {
        assumeTrue(android.tracing.Flags.protologAutoClearIncrementalState());
        PerfettoProtoLogImpl.MAX_INTERNED_STRINGS_SIZE_BYTES_BEFORE_RESET = 10;

        PerfettoTraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
                .enableProtoLog(true, List.of(), TEST_PROTOLOG_DATASOURCE_NAME)
                .build();

        final var writer = createTempWriter(mTracingDirectory);
        try {
            traceMonitor.start();

            // Log a 5-char string, should not reset yet.
            sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 6,
                    LogDataType.STRING, new Object[]{"12345"});

            // Log another 4-char string, should not reset yet (5 + 4 = 9).
            sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 6,
                    LogDataType.STRING, new Object[]{"abcd"});

            // Log a message string, should trigger reset because 9 + message_len > 10.
            sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, "message");
        } finally {
            traceMonitor.stop(writer);
        }

        final ResultReader reader = new ResultReader(writer.write());
        final var traceBytes = reader.readBytes(TraceType.PERFETTO, Tag.ALL);
        final var trace = Trace.parseFrom(traceBytes);

        final var clearPackets = trace.getPacketList().stream()
                .filter(it -> (it.getSequenceFlags()
                        & SEQ_INCREMENTAL_STATE_CLEARED.getNumber()) != 0)
                .toList();
        Truth.assertThat(clearPackets).hasSize(1);
    }

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