xref: /aosp_15_r20/cts/hostsidetests/statsdatom/src/android/cts/statsdatom/perfetto/PerfettoTests.java (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
1 /*
2  * Copyright (C) 2021 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.cts.statsdatom.perfetto;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import android.cts.statsdatom.lib.AtomTestUtils;
22 import android.cts.statsdatom.lib.ConfigUtils;
23 import android.cts.statsdatom.lib.DeviceUtils;
24 import android.cts.statsdatom.lib.ReportUtils;
25 
26 import com.android.internal.os.StatsdConfigProto;
27 import com.android.internal.os.StatsdConfigProto.Alert;
28 import com.android.internal.os.StatsdConfigProto.FieldMatcher;
29 import com.android.internal.os.StatsdConfigProto.PerfettoDetails;
30 import com.android.internal.os.StatsdConfigProto.StatsdConfig;
31 import com.android.internal.os.StatsdConfigProto.Subscription;
32 import com.android.internal.os.StatsdConfigProto.TimeUnit;
33 import com.android.internal.os.StatsdConfigProto.ValueMetric;
34 import com.android.os.AtomsProto;
35 import com.android.os.AtomsProto.AppBreadcrumbReported;
36 import com.android.os.AtomsProto.Atom;
37 import com.android.os.AtomsProto.PerfettoTrigger;
38 import com.android.os.AtomsProto.PerfettoUploaded;
39 import com.android.os.AtomsProto.TracingServiceReportEvent;
40 import com.android.os.StatsLog.EventMetricData;
41 import com.android.tradefed.build.IBuildInfo;
42 import com.android.tradefed.log.LogUtil;
43 import com.android.tradefed.log.LogUtil.CLog;
44 import com.android.tradefed.testtype.DeviceTestCase;
45 import com.android.tradefed.testtype.IBuildReceiver;
46 import com.android.tradefed.util.CommandResult;
47 import com.android.tradefed.util.CommandStatus;
48 import com.android.tradefed.util.RunUtil;
49 
50 import com.google.protobuf.ByteString;
51 
52 import perfetto.protos.PerfettoConfig.DataSourceConfig;
53 import perfetto.protos.PerfettoConfig.FtraceConfig;
54 import perfetto.protos.PerfettoConfig.TraceConfig;
55 
56 import java.util.ArrayList;
57 import java.util.List;
58 import java.util.Random;
59 
60 public class PerfettoTests extends DeviceTestCase implements IBuildReceiver {
61 
62     private static final int WAIT_AFTER_START_PERFETTO_MS = 3000;
63 
64     // Config constants
65     // These were chosen to match the statsd <-> Perfetto CTS integration
66     // test.
67     private static final int APP_BREADCRUMB_REPORTED_MATCH_START_ID = 1;
68     private static final int METRIC_ID = 8;
69     private static final int ALERT_ID = 11;
70     private static final int SUBSCRIPTION_ID_PERFETTO = 42;
71 
72     private boolean defaultSystemTracingConfigurationHasChanged = false;
73 
74     @Override
setUp()75     protected void setUp() throws Exception {
76         super.setUp();
77 
78         // Default Android configuration can only change for device type that doesn't require SystemTracingEnabled
79         // by default in CDD.
80         String chars = getDevice().getProperty("ro.build.characteristics");
81         if (!isSystemTracingEnabled() && chars.contains("automotive")) {
82             enableSystemTracing();
83             defaultSystemTracingConfigurationHasChanged = true;
84         }
85 
86         ConfigUtils.removeConfig(getDevice());
87         ReportUtils.clearReports(getDevice());
88         RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_LONG);
89     }
90 
91     @Override
tearDown()92     protected void tearDown() throws Exception {
93         // Disable SystemTracing if previously enabled at test setUp()
94         if (defaultSystemTracingConfigurationHasChanged) {
95             disableSystemTracing();
96             defaultSystemTracingConfigurationHasChanged = false;
97         }
98         // Deadline to finish trace collection
99         final long deadLine = System.currentTimeMillis() + 10000;
100         while (isSystemTracingEnabled()) {
101             if (System.currentTimeMillis() > deadLine) {
102                 CLog.w("/sys/kernel/debug/tracing/tracing_on is still 1 after 10 secs : " + isSystemTracingEnabled());
103                 break;
104             }
105             CLog.d("Waiting to finish collecting traces. ");
106             Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
107         }
108 
109         ConfigUtils.removeConfig(getDevice());
110         ReportUtils.clearReports(getDevice());
111         super.tearDown();
112     }
113 
114     @Override
setBuild(IBuildInfo buildInfo)115     public void setBuild(IBuildInfo buildInfo) {
116     }
117 
testPerfettoUploadedIncidentdAtoms()118     public void testPerfettoUploadedIncidentdAtoms() throws Exception {
119         if (DeviceUtils.hasFeature(getDevice(), DeviceUtils.FEATURE_WATCH)) return;
120         resetPerfettoGuardrails();
121 
122         StatsdConfig.Builder config = getStatsdConfig(getPerfettoIncidentConfig());
123         ConfigUtils.addEventMetric(config, AtomsProto.Atom.PERFETTO_UPLOADED_FIELD_NUMBER);
124         ConfigUtils.uploadConfig(getDevice(), config);
125 
126         startPerfettoTrace();
127         RunUtil.getDefault().sleep(WAIT_AFTER_START_PERFETTO_MS);
128 
129         // While the trace would not have finished in this time, we expect at least
130         // the trace to have been started.
131         List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
132         assertThat(extractPerfettoUploadedEvents(data))
133                 .containsAtLeast(
134                         PerfettoUploaded.Event.PERFETTO_TRACE_BEGIN,
135                         PerfettoUploaded.Event.PERFETTO_ON_CONNECT,
136                         PerfettoUploaded.Event.PERFETTO_TRACED_ENABLE_TRACING,
137                         PerfettoUploaded.Event.PERFETTO_TRACED_START_TRACING);
138     }
139 
testSkipReportAtoms()140     public void testSkipReportAtoms() throws Exception {
141         if (DeviceUtils.hasFeature(getDevice(), DeviceUtils.FEATURE_WATCH)) return;
142         resetPerfettoGuardrails();
143 
144         StatsdConfig.Builder config = getStatsdConfig(getPerfettoReportConfig(true));
145         ConfigUtils.addEventMetric(config, AtomsProto.Atom.PERFETTO_UPLOADED_FIELD_NUMBER);
146         ConfigUtils.uploadConfig(getDevice(), config);
147 
148         startPerfettoTrace();
149         RunUtil.getDefault().sleep(WAIT_AFTER_START_PERFETTO_MS);
150 
151         List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
152         assertThat(extractPerfettoUploadedEvents(data))
153                 .containsAtLeast(
154                         PerfettoUploaded.Event.PERFETTO_TRACE_BEGIN,
155                         PerfettoUploaded.Event.PERFETTO_ON_CONNECT,
156                         PerfettoUploaded.Event.PERFETTO_TRACED_ENABLE_TRACING,
157                         PerfettoUploaded.Event.PERFETTO_TRACED_START_TRACING);
158     }
159 
testReportAtoms()160     public void testReportAtoms() throws Exception {
161         if (DeviceUtils.hasFeature(getDevice(), DeviceUtils.FEATURE_WATCH)) return;
162         resetPerfettoGuardrails();
163 
164         StatsdConfig.Builder config = getStatsdConfig(getPerfettoReportConfig(false));
165         ConfigUtils.addEventMetric(config, AtomsProto.Atom.PERFETTO_UPLOADED_FIELD_NUMBER);
166         ConfigUtils.addEventMetric(config, Atom.TRACING_SERVICE_REPORT_EVENT_FIELD_NUMBER);
167         ConfigUtils.uploadConfig(getDevice(), config);
168 
169         startPerfettoTrace();
170         RunUtil.getDefault().sleep(WAIT_AFTER_START_PERFETTO_MS);
171 
172         List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
173         assertThat(extractPerfettoUploadedEvents(data))
174                 .containsAtLeast(
175                         PerfettoUploaded.Event.PERFETTO_CMD_FW_REPORT_BEGIN,
176                         PerfettoUploaded.Event.PERFETTO_CMD_FW_REPORT_HANDOFF);
177         assertThat(extractReportEvents(data))
178                 .containsExactly(
179                         TracingServiceReportEvent.Event.TRACING_SERVICE_REPORT_BEGIN,
180                         TracingServiceReportEvent.Event.TRACING_SERVICE_REPORT_BIND_PERM_INCORRECT);
181     }
182 
testPerfettoTriggerAtoms()183     public void testPerfettoTriggerAtoms() throws Exception {
184         if (DeviceUtils.hasFeature(getDevice(), DeviceUtils.FEATURE_WATCH)) return;
185 
186         StatsdConfig.Builder config = ConfigUtils.createConfigBuilder("AID_NOBODY");
187         ConfigUtils.addEventMetric(config, AtomsProto.Atom.PERFETTO_TRIGGER_FIELD_NUMBER);
188         ConfigUtils.uploadConfig(getDevice(), config);
189 
190         runTriggerPerfetto();
191         RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_LONG);
192 
193         List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
194         assertThat(data).hasSize(1);
195         assertThat(extractPerfettoTriggerEvents(data))
196                 .containsExactly(
197                         PerfettoTrigger.Event.PERFETTO_TRACED_TRIGGER);
198     }
199 
getPerfettoIncidentConfig()200     private ByteString getPerfettoIncidentConfig() {
201         TraceConfig.IncidentReportConfig incident =
202                 TraceConfig.IncidentReportConfig.newBuilder()
203                         .setSkipIncidentd(true)
204                         .build();
205         return getBasePerfettoConfigBuilder()
206                 .setIncidentReportConfig(incident)
207                 .build()
208                 .toByteString();
209     }
210 
getPerfettoReportConfig(boolean skipReport)211     private ByteString getPerfettoReportConfig(boolean skipReport) {
212         TraceConfig.AndroidReportConfig config = TraceConfig.AndroidReportConfig.newBuilder()
213                 .setSkipReport(skipReport)
214                 .setReporterServicePackage("android.cts")
215                 .setReporterServiceClass("android.cts.class")
216                 .setUsePipeInFrameworkForTesting(true)
217                 .build();
218         return getBasePerfettoConfigBuilder()
219                 .setAndroidReportConfig(config)
220                 .build()
221                 .toByteString();
222     }
223 
getBasePerfettoConfigBuilder()224     private TraceConfig.Builder getBasePerfettoConfigBuilder() {
225         TraceConfig.Builder builder = TraceConfig.newBuilder();
226 
227         TraceConfig.BufferConfig buffer =
228                 TraceConfig.BufferConfig.newBuilder().setSizeKb(128).build();
229         builder.addBuffers(buffer);
230 
231         FtraceConfig ftraceConfig =
232                 FtraceConfig.newBuilder().addFtraceEvents("sched/sched_switch").build();
233         DataSourceConfig dataSourceConfig =
234                 DataSourceConfig.newBuilder()
235                         .setName("linux.ftrace")
236                         .setTargetBuffer(0)
237                         .setFtraceConfig(ftraceConfig)
238                         .build();
239         TraceConfig.DataSource dataSource =
240                 TraceConfig.DataSource.newBuilder().setConfig(dataSourceConfig).build();
241         builder.addDataSources(dataSource);
242 
243         builder.setDurationMs(500);
244         builder.setAllowUserBuildTracing(true);
245 
246         // To avoid being hit with guardrails firing in multiple test runs back
247         // to back, we set a unique session key for each config.
248         Random random = new Random();
249         StringBuilder sessionNameBuilder = new StringBuilder("statsd-cts-atom-");
250         sessionNameBuilder.append(random.nextInt() & Integer.MAX_VALUE);
251         builder.setUniqueSessionName(sessionNameBuilder.toString());
252 
253         return builder;
254     }
255 
extractPerfettoUploadedEvents( List<EventMetricData> input)256     private List<PerfettoUploaded.Event> extractPerfettoUploadedEvents(
257             List<EventMetricData> input) {
258         List<PerfettoUploaded.Event> output = new ArrayList<>();
259         for (EventMetricData data : input) {
260             if (data.getAtom().hasPerfettoUploaded()) {
261                 output.add(data.getAtom().getPerfettoUploaded().getEvent());
262             }
263         }
264         return output;
265     }
266 
extractPerfettoTriggerEvents( List<EventMetricData> input)267     private List<PerfettoTrigger.Event> extractPerfettoTriggerEvents(
268             List<EventMetricData> input) {
269         List<PerfettoTrigger.Event> output = new ArrayList<>();
270         for (EventMetricData data : input) {
271             if (data.getAtom().hasPerfettoTrigger()) {
272                 output.add(data.getAtom().getPerfettoTrigger().getEvent());
273             }
274         }
275         return output;
276     }
277 
extractReportEvents( List<EventMetricData> input)278     private List<TracingServiceReportEvent.Event> extractReportEvents(
279             List<EventMetricData> input) {
280         List<TracingServiceReportEvent.Event> output = new ArrayList<>();
281         for (EventMetricData data : input) {
282             if (data.getAtom().hasTracingServiceReportEvent()) {
283                 output.add(data.getAtom().getTracingServiceReportEvent().getEvent());
284             }
285         }
286         return output;
287     }
288 
289     /**
290      * Resets the state of the Perfetto guardrails. This avoids that the test fails if it's run too
291      * close of for too many times and hits the upload limit.
292      */
runTriggerPerfetto()293     private void runTriggerPerfetto() throws Exception {
294         final String cmd = "trigger_perfetto cts.test.trigger";
295         CommandResult cr = getDevice().executeShellV2Command(cmd);
296         if (cr.getStatus() != CommandStatus.SUCCESS) {
297             throw new Exception(
298                     String.format(
299                             "Error while executing %s: %s %s",
300                             cmd, cr.getStdout(), cr.getStderr()));
301         }
302     }
303 
304     /**
305      * Resets the state of the Perfetto guardrails. This avoids that the test fails if it's run too
306      * close of for too many times and hits the upload limit.
307      */
resetPerfettoGuardrails()308     private void resetPerfettoGuardrails() throws Exception {
309         final String cmd = "perfetto --reset-guardrails";
310         CommandResult cr = getDevice().executeShellV2Command(cmd);
311         if (cr.getStatus() != CommandStatus.SUCCESS) {
312             throw new Exception(
313                     String.format(
314                             "Error while executing %s: %s %s",
315                             cmd, cr.getStdout(), cr.getStderr()));
316         }
317     }
318 
startPerfettoTrace()319     private void startPerfettoTrace() throws Exception {
320         getDevice()
321                 .executeShellCommand(
322                         String.format(
323                                 "cmd stats log-app-breadcrumb %d %d",
324                                 1, AppBreadcrumbReported.State.START.getNumber()));
325     }
326 
getStatsdConfig(ByteString config)327     private final StatsdConfig.Builder getStatsdConfig(ByteString config) throws Exception {
328         return ConfigUtils.createConfigBuilder("AID_NOBODY")
329                 .addSubscription(
330                         Subscription.newBuilder()
331                                 .setId(SUBSCRIPTION_ID_PERFETTO)
332                                 .setRuleType(Subscription.RuleType.ALERT)
333                                 .setRuleId(ALERT_ID)
334                                 .setPerfettoDetails(
335                                         PerfettoDetails.newBuilder()
336                                                 .setTraceConfig(config)))
337                 .addValueMetric(
338                         ValueMetric.newBuilder()
339                                 .setId(METRIC_ID)
340                                 .setWhat(APP_BREADCRUMB_REPORTED_MATCH_START_ID)
341                                 .setBucket(TimeUnit.ONE_MINUTE)
342                                 // Get the label field's value:
343                                 .setValueField(
344                                         FieldMatcher.newBuilder()
345                                                 .setField(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
346                                                 .addChild(
347                                                         FieldMatcher.newBuilder()
348                                                                 .setField(
349                                                                         AppBreadcrumbReported
350                                                                                 .LABEL_FIELD_NUMBER))))
351                 .addAtomMatcher(
352                         StatsdConfigProto.AtomMatcher.newBuilder()
353                                 .setId(APP_BREADCRUMB_REPORTED_MATCH_START_ID)
354                                 .setSimpleAtomMatcher(
355                                         StatsdConfigProto.SimpleAtomMatcher.newBuilder()
356                                                 .setAtomId(
357                                                         Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
358                                                 .addFieldValueMatcher(
359                                                         ConfigUtils.createFvm(
360                                                                         AppBreadcrumbReported
361                                                                                 .STATE_FIELD_NUMBER)
362                                                                 .setEqInt(
363                                                                         AppBreadcrumbReported.State
364                                                                                 .START
365                                                                                 .getNumber()))))
366                 .addAlert(
367                         Alert.newBuilder()
368                                 .setId(ALERT_ID)
369                                 .setMetricId(METRIC_ID)
370                                 .setNumBuckets(4)
371                                 .setRefractoryPeriodSecs(0)
372                                 .setTriggerIfSumGt(0))
373                 .addNoReportMetric(METRIC_ID);
374     }
375 
probe(String path)376     private String probe(String path) throws Exception {
377         return getDevice().executeShellCommand("if [ -e " + path + " ] ; then"
378                 + " cat " + path + " ; else echo -1 ; fi");
379     }
380 
enableSystemTracing()381     private void enableSystemTracing() throws Exception {
382         getDevice().executeShellCommand("setprop persist.traced.enable 1");
383     }
384 
disableSystemTracing()385     private void disableSystemTracing() throws Exception {
386         getDevice().executeShellCommand("setprop persist.traced.enable 0");
387     }
388 
389     /**
390      * Determines whether perfetto enabled the kernel ftrace tracer.
391      */
isSystemTracingEnabled()392     protected boolean isSystemTracingEnabled() throws Exception {
393         final String traceFsPath = "/sys/kernel/tracing/tracing_on";
394         String tracing_on = probe(traceFsPath);
395         if (tracing_on.startsWith("0")) return false;
396         if (tracing_on.startsWith("1")) return true;
397 
398         // fallback to debugfs
399         LogUtil.CLog.d("Unexpected state for %s = %s. Falling back to debugfs", traceFsPath,
400                 tracing_on);
401 
402         final String debugFsPath = "/sys/kernel/debug/tracing/tracing_on";
403         tracing_on = probe(debugFsPath);
404         if (tracing_on.startsWith("0")) return false;
405         if (tracing_on.startsWith("1")) return true;
406         throw new Exception(String.format("Unexpected state for %s = %s", traceFsPath, tracing_on));
407     }
408 }
409