• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.tracing.perfetto;
18 
19 import static android.internal.perfetto.protos.TestEventOuterClass.TestEvent.PAYLOAD;
20 import static android.internal.perfetto.protos.TestEventOuterClass.TestEvent.TestPayload.SINGLE_INT;
21 import static android.internal.perfetto.protos.TracePacketOuterClass.TracePacket.FOR_TESTING;
22 
23 import static java.io.File.createTempFile;
24 import static java.nio.file.Files.createTempDirectory;
25 
26 import android.internal.perfetto.protos.DataSourceConfigOuterClass.DataSourceConfig;
27 import android.internal.perfetto.protos.TestConfigOuterClass.TestConfig;
28 import android.tools.ScenarioBuilder;
29 import android.tools.Tag;
30 import android.tools.io.TraceType;
31 import android.tools.traces.TraceConfig;
32 import android.tools.traces.TraceConfigs;
33 import android.tools.traces.io.ResultReader;
34 import android.tools.traces.io.ResultWriter;
35 import android.tools.traces.monitors.PerfettoTraceMonitor;
36 import android.tools.traces.monitors.TraceMonitor;
37 import android.util.proto.ProtoInputStream;
38 import android.util.proto.ProtoOutputStream;
39 
40 import androidx.test.ext.junit.runners.AndroidJUnit4;
41 
42 import com.google.common.truth.Truth;
43 import com.google.protobuf.InvalidProtocolBufferException;
44 
45 import org.junit.Before;
46 import org.junit.BeforeClass;
47 import org.junit.Test;
48 import org.junit.runner.RunWith;
49 
50 import perfetto.protos.PerfettoConfig;
51 import perfetto.protos.TracePacketOuterClass;
52 
53 import java.io.File;
54 import java.io.IOException;
55 import java.util.ArrayList;
56 import java.util.List;
57 import java.util.concurrent.CountDownLatch;
58 import java.util.concurrent.TimeUnit;
59 import java.util.concurrent.atomic.AtomicBoolean;
60 import java.util.concurrent.atomic.AtomicInteger;
61 import java.util.concurrent.atomic.AtomicReference;
62 
63 @RunWith(AndroidJUnit4.class)
64 public class DataSourceTest {
65     private final File mTracingDirectory = createTempDirectory("temp").toFile();
66 
67     private final ResultWriter mWriter = new ResultWriter()
68             .forScenario(new ScenarioBuilder()
69                     .forClass(createTempFile("temp", "").getName()).build())
70             .withOutputDir(mTracingDirectory)
71             .setRunComplete();
72 
73     private final TraceConfigs mTraceConfig = new TraceConfigs(
74             new TraceConfig(false, true, false),
75             new TraceConfig(false, true, false),
76             new TraceConfig(false, true, false),
77             new TraceConfig(false, true, false)
78     );
79 
80     private static TestDataSource sTestDataSource;
81 
82     private static TestDataSource.DataSourceInstanceProvider sInstanceProvider;
83     private static TestDataSource.TlsStateProvider sTlsStateProvider;
84     private static TestDataSource.IncrementalStateProvider sIncrementalStateProvider;
85 
DataSourceTest()86     public DataSourceTest() throws IOException {}
87 
88     @BeforeClass
beforeAll()89     public static void beforeAll() {
90         Producer.init(InitArguments.DEFAULTS);
91         setupProviders();
92         sTestDataSource = new TestDataSource(
93                 (ds, idx, configStream) -> sInstanceProvider.provide(ds, idx, configStream),
94                 args -> sTlsStateProvider.provide(args),
95                 args -> sIncrementalStateProvider.provide(args));
96         sTestDataSource.register(DataSourceParams.DEFAULTS);
97     }
98 
setupProviders()99     private static void setupProviders() {
100         sInstanceProvider = (ds, idx, configStream) ->
101                 new TestDataSource.TestDataSourceInstance(ds, idx);
102         sTlsStateProvider = args -> new TestDataSource.TestTlsState();
103         sIncrementalStateProvider = args -> new TestDataSource.TestIncrementalState();
104     }
105 
106     @Before
setup()107     public void setup() {
108         setupProviders();
109     }
110 
111     @Test
canTraceData()112     public void canTraceData() throws InvalidProtocolBufferException {
113         final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
114                 .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
115                         .setName(sTestDataSource.name).build()).build();
116 
117         try {
118             traceMonitor.start();
119 
120             sTestDataSource.trace((ctx) -> {
121                 final ProtoOutputStream protoOutputStream = ctx.newTracePacket();
122                 long forTestingToken = protoOutputStream.start(FOR_TESTING);
123                 long payloadToken = protoOutputStream.start(PAYLOAD);
124                 protoOutputStream.write(SINGLE_INT, 10);
125                 protoOutputStream.end(payloadToken);
126                 protoOutputStream.end(forTestingToken);
127             });
128         } finally {
129             traceMonitor.stop(mWriter);
130         }
131 
132         final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
133         final byte[] rawProtoFromFile = reader.readBytes(TraceType.PERFETTO, Tag.ALL);
134         assert rawProtoFromFile != null;
135         final perfetto.protos.TraceOuterClass.Trace trace = perfetto.protos.TraceOuterClass.Trace
136                 .parseFrom(rawProtoFromFile);
137 
138         Truth.assertThat(trace.getPacketCount()).isGreaterThan(0);
139         final List<TracePacketOuterClass.TracePacket> tracePackets = trace.getPacketList()
140                 .stream().filter(TracePacketOuterClass.TracePacket::hasForTesting).toList();
141         final List<TracePacketOuterClass.TracePacket> matchingPackets = tracePackets.stream()
142                 .filter(it -> it.getForTesting().getPayload().getSingleInt() == 10).toList();
143         Truth.assertThat(matchingPackets).hasSize(1);
144     }
145 
146     @Test
canUseTlsStateForCustomState()147     public void canUseTlsStateForCustomState() {
148         final int expectedStateTestValue = 10;
149         final AtomicInteger actualStateTestValue = new AtomicInteger();
150 
151         final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
152                 .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
153                         .setName(sTestDataSource.name).build()).build();
154 
155         try {
156             traceMonitor.start();
157 
158             sTestDataSource.trace((ctx) -> {
159                 TestDataSource.TestTlsState state = ctx.getCustomTlsState();
160                 state.testStateValue = expectedStateTestValue;
161             });
162 
163             sTestDataSource.trace((ctx) -> {
164                 TestDataSource.TestTlsState state = ctx.getCustomTlsState();
165                 actualStateTestValue.set(state.testStateValue);
166             });
167         } finally {
168             traceMonitor.stop(mWriter);
169         }
170 
171         Truth.assertThat(actualStateTestValue.get()).isEqualTo(expectedStateTestValue);
172     }
173 
174     @Test
eachInstanceHasOwnTlsState()175     public void eachInstanceHasOwnTlsState() {
176         final int[] expectedStateTestValues = new int[] { 1, 2 };
177         final int[] actualStateTestValues = new int[] { 0, 0 };
178 
179         final TraceMonitor traceMonitor1 = PerfettoTraceMonitor.newBuilder()
180                 .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
181                         .setName(sTestDataSource.name).build()).build();
182         final TraceMonitor traceMonitor2 = PerfettoTraceMonitor.newBuilder()
183                 .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
184                         .setName(sTestDataSource.name).build()).build();
185 
186         try {
187             traceMonitor1.start();
188             try {
189                 traceMonitor2.start();
190 
191                 AtomicInteger index = new AtomicInteger(0);
192                 sTestDataSource.trace((ctx) -> {
193                     TestDataSource.TestTlsState state = ctx.getCustomTlsState();
194                     state.testStateValue = expectedStateTestValues[index.getAndIncrement()];
195                 });
196 
197                 index.set(0);
198                 sTestDataSource.trace((ctx) -> {
199                     TestDataSource.TestTlsState state = ctx.getCustomTlsState();
200                     actualStateTestValues[index.getAndIncrement()] = state.testStateValue;
201                 });
202             } finally {
203                 traceMonitor1.stop(mWriter);
204             }
205         } finally {
206             traceMonitor2.stop(mWriter);
207         }
208 
209         Truth.assertThat(actualStateTestValues[0]).isEqualTo(expectedStateTestValues[0]);
210         Truth.assertThat(actualStateTestValues[1]).isEqualTo(expectedStateTestValues[1]);
211     }
212 
213     @Test
eachThreadHasOwnTlsState()214     public void eachThreadHasOwnTlsState() throws InterruptedException {
215         final int thread1ExpectedStateValue = 1;
216         final int thread2ExpectedStateValue = 2;
217 
218         final AtomicInteger thread1ActualStateValue = new AtomicInteger();
219         final AtomicInteger thread2ActualStateValue = new AtomicInteger();
220 
221         final CountDownLatch setUpLatch = new CountDownLatch(2);
222         final CountDownLatch setStateLatch = new CountDownLatch(2);
223         final CountDownLatch setOutStateLatch = new CountDownLatch(2);
224 
225         final RunnableCreator createTask = (stateValue, stateOut) -> () -> {
226             Producer.init(InitArguments.DEFAULTS);
227 
228             setUpLatch.countDown();
229 
230             try {
231                 setUpLatch.await(3, TimeUnit.SECONDS);
232             } catch (InterruptedException e) {
233                 throw new RuntimeException(e);
234             }
235 
236             sTestDataSource.trace((ctx) -> {
237                 TestDataSource.TestTlsState state = ctx.getCustomTlsState();
238                 state.testStateValue = stateValue;
239                 setStateLatch.countDown();
240             });
241 
242             try {
243                 setStateLatch.await(3, TimeUnit.SECONDS);
244             } catch (InterruptedException e) {
245                 throw new RuntimeException(e);
246             }
247 
248             sTestDataSource.trace((ctx) -> {
249                 stateOut.set(ctx.getCustomTlsState().testStateValue);
250                 setOutStateLatch.countDown();
251             });
252         };
253 
254         final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
255                 .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
256                         .setName(sTestDataSource.name).build()).build();
257 
258         try {
259             traceMonitor.start();
260 
261             new Thread(
262                     createTask.create(thread1ExpectedStateValue, thread1ActualStateValue)).start();
263             new Thread(
264                     createTask.create(thread2ExpectedStateValue, thread2ActualStateValue)).start();
265 
266             setOutStateLatch.await(3, TimeUnit.SECONDS);
267 
268         } finally {
269             traceMonitor.stop(mWriter);
270         }
271 
272         Truth.assertThat(thread1ActualStateValue.get()).isEqualTo(thread1ExpectedStateValue);
273         Truth.assertThat(thread2ActualStateValue.get()).isEqualTo(thread2ExpectedStateValue);
274     }
275 
276     @Test
incrementalStateIsReset()277     public void incrementalStateIsReset() throws InterruptedException {
278 
279         final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
280                 .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
281                         .setName(sTestDataSource.name).build())
282                 .setIncrementalTimeout(10)
283                 .build();
284 
285         final AtomicInteger testStateValue = new AtomicInteger();
286         try {
287             traceMonitor.start();
288 
289             sTestDataSource.trace(ctx -> ctx.getIncrementalState().testStateValue = 1);
290 
291             // Timeout to make sure the incremental state is cleared.
292             Thread.sleep(1000);
293 
294             sTestDataSource.trace(ctx ->
295                     testStateValue.set(ctx.getIncrementalState().testStateValue));
296         } finally {
297             traceMonitor.stop(mWriter);
298         }
299 
300         Truth.assertThat(testStateValue.get()).isNotEqualTo(1);
301     }
302 
303     @Test
getInstanceConfigOnCreateInstance()304     public void getInstanceConfigOnCreateInstance() throws IOException {
305         final int expectedDummyIntValue = 10;
306         AtomicReference<ProtoInputStream> configStream = new AtomicReference<>();
307         sInstanceProvider = (ds, idx, config) -> {
308             configStream.set(config);
309             return new TestDataSource.TestDataSourceInstance(ds, idx);
310         };
311 
312         final TraceMonitor monitor = PerfettoTraceMonitor.newBuilder()
313                 .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
314                         .setName(sTestDataSource.name)
315                         .setForTesting(PerfettoConfig.TestConfig.newBuilder().setDummyFields(
316                                 PerfettoConfig.TestConfig.DummyFields.newBuilder()
317                                         .setFieldInt32(expectedDummyIntValue)
318                                         .build())
319                                 .build())
320                         .build())
321                 .build();
322 
323         try {
324             monitor.start();
325         } finally {
326             monitor.stop(mWriter);
327         }
328 
329         int configDummyIntValue = 0;
330         while (configStream.get().nextField() != ProtoInputStream.NO_MORE_FIELDS) {
331             if (configStream.get().getFieldNumber()
332                     == (int) DataSourceConfig.FOR_TESTING) {
333                 final long forTestingToken = configStream.get()
334                         .start(DataSourceConfig.FOR_TESTING);
335                 while (configStream.get().nextField() != ProtoInputStream.NO_MORE_FIELDS) {
336                     if (configStream.get().getFieldNumber()
337                             == (int) TestConfig.DUMMY_FIELDS) {
338                         final long dummyFieldsToken = configStream.get()
339                                 .start(TestConfig.DUMMY_FIELDS);
340                         while (configStream.get().nextField() != ProtoInputStream.NO_MORE_FIELDS) {
341                             if (configStream.get().getFieldNumber()
342                                     == (int) TestConfig.DummyFields.FIELD_INT32) {
343                                 int val = configStream.get().readInt(
344                                         TestConfig.DummyFields.FIELD_INT32);
345                                 if (val != 0) {
346                                     configDummyIntValue = val;
347                                     break;
348                                 }
349                             }
350                         }
351                         configStream.get().end(dummyFieldsToken);
352                         break;
353                     }
354                 }
355                 configStream.get().end(forTestingToken);
356                 break;
357             }
358         }
359 
360         Truth.assertThat(configDummyIntValue).isEqualTo(expectedDummyIntValue);
361     }
362 
363     @Test
multipleTraceInstances()364     public void multipleTraceInstances() throws IOException, InterruptedException {
365         final int instanceCount = 3;
366 
367         final List<TraceMonitor> monitors = new ArrayList<>();
368         final List<ResultWriter> writers = new ArrayList<>();
369 
370         for (int i = 0; i < instanceCount; i++) {
371             final ResultWriter writer = new ResultWriter()
372                     .forScenario(new ScenarioBuilder()
373                             .forClass(createTempFile("temp", "").getName()).build())
374                     .withOutputDir(mTracingDirectory)
375                     .setRunComplete();
376             writers.add(writer);
377         }
378 
379         // Start at 1 because 0 is considered null value so payload will be ignored in that case
380         TestDataSource.TestTlsState.lastIndex = 1;
381 
382         final AtomicInteger traceCallCount = new AtomicInteger();
383         final CountDownLatch latch = new CountDownLatch(instanceCount);
384 
385         try {
386             // Start instances
387             for (int i = 0; i < instanceCount; i++) {
388                 final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
389                         .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
390                                 .setName(sTestDataSource.name).build()).build();
391                 monitors.add(traceMonitor);
392                 traceMonitor.start();
393             }
394 
395             // Trace the stateIndex of the tracing instance.
396             sTestDataSource.trace(ctx -> {
397                 final int testIntValue = ctx.getCustomTlsState().stateIndex;
398                 traceCallCount.incrementAndGet();
399 
400                 final ProtoOutputStream os = ctx.newTracePacket();
401                 long forTestingToken = os.start(FOR_TESTING);
402                 long payloadToken = os.start(PAYLOAD);
403                 os.write(SINGLE_INT, testIntValue);
404                 os.end(payloadToken);
405                 os.end(forTestingToken);
406 
407                 latch.countDown();
408             });
409         } finally {
410             // Stop instances
411             for (int i = 0; i < instanceCount; i++) {
412                 final TraceMonitor monitor = monitors.get(i);
413                 final ResultWriter writer = writers.get(i);
414                 monitor.stop(writer);
415             }
416         }
417 
418         latch.await(3, TimeUnit.SECONDS);
419         Truth.assertThat(traceCallCount.get()).isEqualTo(instanceCount);
420 
421         for (int i = 0; i < instanceCount; i++) {
422             final int expectedTracedValue = i + 1;
423             final ResultWriter writer = writers.get(i);
424             final ResultReader reader = new ResultReader(writer.write(), mTraceConfig);
425             final byte[] rawProtoFromFile = reader.readBytes(TraceType.PERFETTO, Tag.ALL);
426             assert rawProtoFromFile != null;
427             final perfetto.protos.TraceOuterClass.Trace trace =
428                     perfetto.protos.TraceOuterClass.Trace.parseFrom(rawProtoFromFile);
429 
430             Truth.assertThat(trace.getPacketCount()).isGreaterThan(0);
431             final List<TracePacketOuterClass.TracePacket> tracePackets = trace.getPacketList()
432                     .stream().filter(TracePacketOuterClass.TracePacket::hasForTesting).toList();
433             Truth.assertWithMessage("One packet has for testing data")
434                     .that(tracePackets).hasSize(1);
435 
436             final List<TracePacketOuterClass.TracePacket> matchingPackets =
437                     tracePackets.stream()
438                             .filter(it -> it.getForTesting().getPayload()
439                                     .getSingleInt() == expectedTracedValue).toList();
440             Truth.assertWithMessage(
441                             "One packet has testing data with a payload with the expected value")
442                     .that(matchingPackets).hasSize(1);
443         }
444     }
445 
446     @Test
onStartCallbackTriggered()447     public void onStartCallbackTriggered() throws InterruptedException {
448         final CountDownLatch latch = new CountDownLatch(1);
449 
450         final AtomicBoolean callbackCalled = new AtomicBoolean(false);
451         sInstanceProvider = (ds, idx, config) -> new TestDataSource.TestDataSourceInstance(
452                         ds,
453                         idx,
454                         (args) -> {
455                             callbackCalled.set(true);
456                             latch.countDown();
457                         },
458                         (args) -> {},
459                         (args) -> {}
460         );
461 
462         final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
463                 .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
464                         .setName(sTestDataSource.name).build()).build();
465 
466         Truth.assertThat(callbackCalled.get()).isFalse();
467         try {
468             traceMonitor.start();
469             latch.await(3, TimeUnit.SECONDS);
470             Truth.assertThat(callbackCalled.get()).isTrue();
471         } finally {
472             traceMonitor.stop(mWriter);
473         }
474     }
475 
476     @Test
onFlushCallbackTriggered()477     public void onFlushCallbackTriggered() throws InterruptedException {
478         final CountDownLatch latch = new CountDownLatch(1);
479         final AtomicBoolean callbackCalled = new AtomicBoolean(false);
480         sInstanceProvider = (ds, idx, config) ->
481                 new TestDataSource.TestDataSourceInstance(
482                         ds,
483                         idx,
484                         (args) -> {},
485                         (args) -> {
486                             callbackCalled.set(true);
487                             latch.countDown();
488                         },
489                         (args) -> {}
490                 );
491 
492         final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
493                 .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
494                         .setName(sTestDataSource.name).build()).build();
495 
496         try {
497             traceMonitor.start();
498             Truth.assertThat(callbackCalled.get()).isFalse();
499             sTestDataSource.trace((ctx) -> {
500                 final ProtoOutputStream protoOutputStream = ctx.newTracePacket();
501                 long forTestingToken = protoOutputStream.start(FOR_TESTING);
502                 long payloadToken = protoOutputStream.start(PAYLOAD);
503                 protoOutputStream.write(SINGLE_INT, 10);
504                 protoOutputStream.end(payloadToken);
505                 protoOutputStream.end(forTestingToken);
506             });
507         } finally {
508             traceMonitor.stop(mWriter);
509         }
510 
511         latch.await(3, TimeUnit.SECONDS);
512         Truth.assertThat(callbackCalled.get()).isTrue();
513     }
514 
515     @Test
onStopCallbackTriggered()516     public void onStopCallbackTriggered() throws InterruptedException {
517         final CountDownLatch latch = new CountDownLatch(1);
518         final AtomicBoolean callbackCalled = new AtomicBoolean(false);
519         sInstanceProvider = (ds, idx, config) ->
520                 new TestDataSource.TestDataSourceInstance(
521                         ds,
522                         idx,
523                         (args) -> {},
524                         (args) -> {},
525                         (args) -> {
526                             callbackCalled.set(true);
527                             latch.countDown();
528                         }
529                 );
530 
531         final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
532                 .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
533                         .setName(sTestDataSource.name).build()).build();
534 
535         try {
536             traceMonitor.start();
537             Truth.assertThat(callbackCalled.get()).isFalse();
538         } finally {
539             traceMonitor.stop(mWriter);
540         }
541 
542         latch.await(3, TimeUnit.SECONDS);
543         Truth.assertThat(callbackCalled.get()).isTrue();
544     }
545 
546     @Test
canUseDataSourceInstanceToCreateTlsState()547     public void canUseDataSourceInstanceToCreateTlsState() throws InvalidProtocolBufferException {
548         final Object testObject = new Object();
549 
550         sInstanceProvider = (ds, idx, configStream) -> {
551             final TestDataSource.TestDataSourceInstance dsInstance =
552                     new TestDataSource.TestDataSourceInstance(ds, idx);
553             dsInstance.testObject = testObject;
554             return dsInstance;
555         };
556 
557         sTlsStateProvider = args -> {
558             final TestDataSource.TestTlsState tlsState = new TestDataSource.TestTlsState();
559 
560             try (TestDataSource.TestDataSourceInstance dataSourceInstance =
561                          args.getDataSourceInstanceLocked()) {
562                 if (dataSourceInstance != null) {
563                     tlsState.testStateValue = dataSourceInstance.testObject.hashCode();
564                 }
565             }
566 
567             return tlsState;
568         };
569 
570         final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
571                 .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
572                         .setName(sTestDataSource.name).build()).build();
573 
574         try {
575             traceMonitor.start();
576             sTestDataSource.trace((ctx) -> {
577                 final ProtoOutputStream protoOutputStream = ctx.newTracePacket();
578                 long forTestingToken = protoOutputStream.start(FOR_TESTING);
579                 long payloadToken = protoOutputStream.start(PAYLOAD);
580                 protoOutputStream.write(SINGLE_INT, ctx.getCustomTlsState().testStateValue);
581                 protoOutputStream.end(payloadToken);
582                 protoOutputStream.end(forTestingToken);
583             });
584         } finally {
585             traceMonitor.stop(mWriter);
586         }
587 
588         final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
589         final byte[] rawProtoFromFile = reader.readBytes(TraceType.PERFETTO, Tag.ALL);
590         assert rawProtoFromFile != null;
591         final perfetto.protos.TraceOuterClass.Trace trace = perfetto.protos.TraceOuterClass.Trace
592                 .parseFrom(rawProtoFromFile);
593 
594         Truth.assertThat(trace.getPacketCount()).isGreaterThan(0);
595         final List<TracePacketOuterClass.TracePacket> tracePackets = trace.getPacketList()
596                 .stream().filter(TracePacketOuterClass.TracePacket::hasForTesting).toList();
597         final List<TracePacketOuterClass.TracePacket> matchingPackets = tracePackets.stream()
598                 .filter(it -> it.getForTesting().getPayload().getSingleInt()
599                         == testObject.hashCode()).toList();
600         Truth.assertThat(matchingPackets).hasSize(1);
601     }
602 
603     @Test
canUseDataSourceInstanceToCreateIncrementalState()604     public void canUseDataSourceInstanceToCreateIncrementalState()
605             throws InvalidProtocolBufferException {
606         final Object testObject = new Object();
607 
608         sInstanceProvider = (ds, idx, configStream) -> {
609             final TestDataSource.TestDataSourceInstance dsInstance =
610                     new TestDataSource.TestDataSourceInstance(ds, idx);
611             dsInstance.testObject = testObject;
612             return dsInstance;
613         };
614 
615         sIncrementalStateProvider = args -> {
616             final TestDataSource.TestIncrementalState incrementalState =
617                     new TestDataSource.TestIncrementalState();
618 
619             try (TestDataSource.TestDataSourceInstance dataSourceInstance =
620                     args.getDataSourceInstanceLocked()) {
621                 if (dataSourceInstance != null) {
622                     incrementalState.testStateValue = dataSourceInstance.testObject.hashCode();
623                 }
624             }
625 
626             return incrementalState;
627         };
628 
629         final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
630                 .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
631                         .setName(sTestDataSource.name).build()).build();
632 
633         try {
634             traceMonitor.start();
635             sTestDataSource.trace((ctx) -> {
636                 final ProtoOutputStream protoOutputStream = ctx.newTracePacket();
637                 long forTestingToken = protoOutputStream.start(FOR_TESTING);
638                 long payloadToken = protoOutputStream.start(PAYLOAD);
639                 protoOutputStream.write(SINGLE_INT, ctx.getIncrementalState().testStateValue);
640                 protoOutputStream.end(payloadToken);
641                 protoOutputStream.end(forTestingToken);
642             });
643         } finally {
644             traceMonitor.stop(mWriter);
645         }
646 
647         final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
648         final byte[] rawProtoFromFile = reader.readBytes(TraceType.PERFETTO, Tag.ALL);
649         assert rawProtoFromFile != null;
650         final perfetto.protos.TraceOuterClass.Trace trace = perfetto.protos.TraceOuterClass.Trace
651                 .parseFrom(rawProtoFromFile);
652 
653         Truth.assertThat(trace.getPacketCount()).isGreaterThan(0);
654         final List<TracePacketOuterClass.TracePacket> tracePackets = trace.getPacketList()
655                 .stream().filter(TracePacketOuterClass.TracePacket::hasForTesting).toList();
656         final List<TracePacketOuterClass.TracePacket> matchingPackets = tracePackets.stream()
657                 .filter(it -> it.getForTesting().getPayload().getSingleInt()
658                         == testObject.hashCode()).toList();
659         Truth.assertThat(matchingPackets).hasSize(1);
660     }
661 
662     @Test
canTraceOnFlush()663     public void canTraceOnFlush() throws InvalidProtocolBufferException, InterruptedException {
664         final int singleIntValue = 101;
665         sInstanceProvider = (ds, idx, config) ->
666                 new TestDataSource.TestDataSourceInstance(
667                         ds,
668                         idx,
669                         (args) -> {},
670                         (args) -> sTestDataSource.trace(ctx -> {
671                             final ProtoOutputStream protoOutputStream = ctx.newTracePacket();
672                             long forTestingToken = protoOutputStream.start(FOR_TESTING);
673                             long payloadToken = protoOutputStream.start(PAYLOAD);
674                             protoOutputStream.write(SINGLE_INT, singleIntValue);
675                             protoOutputStream.end(payloadToken);
676                             protoOutputStream.end(forTestingToken);
677                         }),
678                         (args) -> {}
679                 );
680 
681         final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
682                 .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
683                         .setName(sTestDataSource.name).build()).build();
684 
685         try {
686             traceMonitor.start();
687         } finally {
688             traceMonitor.stop(mWriter);
689         }
690 
691         final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
692         final byte[] rawProtoFromFile = reader.readBytes(TraceType.PERFETTO, Tag.ALL);
693         assert rawProtoFromFile != null;
694         final perfetto.protos.TraceOuterClass.Trace trace = perfetto.protos.TraceOuterClass.Trace
695                 .parseFrom(rawProtoFromFile);
696 
697         Truth.assertThat(trace.getPacketCount()).isGreaterThan(0);
698         final List<TracePacketOuterClass.TracePacket> tracePackets = trace.getPacketList()
699                 .stream().filter(TracePacketOuterClass.TracePacket::hasForTesting).toList();
700         final List<TracePacketOuterClass.TracePacket> matchingPackets = tracePackets.stream()
701                 .filter(it -> it.getForTesting().getPayload().getSingleInt()
702                         == singleIntValue).toList();
703         Truth.assertThat(matchingPackets).hasSize(1);
704     }
705 
706     interface RunnableCreator {
create(int state, AtomicInteger stateOut)707         Runnable create(int state, AtomicInteger stateOut);
708     }
709 }
710