1 /*
2  * Copyright (C) 2017 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 package android.cts.statsdatom.statsd;
17 
18 import static android.cts.statsdatom.statsd.DeviceAtomTestCase.DEVICE_SIDE_TEST_APK;
19 import static android.cts.statsdatom.statsd.DeviceAtomTestCase.DEVICE_SIDE_TEST_PACKAGE;
20 
21 import static com.google.common.truth.Truth.assertThat;
22 import static com.google.common.truth.Truth.assertWithMessage;
23 
24 import android.os.BatteryStatsProto;
25 import android.os.StatsDataDumpProto;
26 import android.service.battery.BatteryServiceDumpProto;
27 import android.service.batterystats.BatteryStatsServiceDumpProto;
28 import android.service.procstats.ProcessStatsServiceDumpProto;
29 
30 import com.android.annotations.Nullable;
31 import com.android.internal.os.StatsdConfigProto.AtomMatcher;
32 import com.android.internal.os.StatsdConfigProto.EventMetric;
33 import com.android.internal.os.StatsdConfigProto.FieldFilter;
34 import com.android.internal.os.StatsdConfigProto.FieldMatcher;
35 import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
36 import com.android.internal.os.StatsdConfigProto.GaugeMetric;
37 import com.android.internal.os.StatsdConfigProto.Predicate;
38 import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher;
39 import com.android.internal.os.StatsdConfigProto.SimplePredicate;
40 import com.android.internal.os.StatsdConfigProto.StatsdConfig;
41 import com.android.internal.os.StatsdConfigProto.TimeUnit;
42 import com.android.os.AtomsProto.AppBreadcrumbReported;
43 import com.android.os.AtomsProto.Atom;
44 import com.android.os.AtomsProto.ProcessStatsPackageProto;
45 import com.android.os.AtomsProto.ProcessStatsProto;
46 import com.android.os.AtomsProto.ProcessStatsStateProto;
47 import com.android.os.StatsLog.ConfigMetricsReport;
48 import com.android.os.StatsLog.ConfigMetricsReportList;
49 import com.android.os.StatsLog.DurationMetricData;
50 import com.android.os.StatsLog.EventMetricData;
51 import com.android.os.StatsLog.GaugeBucketInfo;
52 import com.android.os.StatsLog.GaugeMetricData;
53 import com.android.os.StatsLog.CountMetricData;
54 import com.android.os.StatsLog.StatsLogReport;
55 import com.android.os.StatsLog.ValueMetricData;
56 import com.android.tradefed.device.DeviceNotAvailableException;
57 import com.android.tradefed.log.LogUtil;
58 import com.android.tradefed.util.CommandResult;
59 import com.android.tradefed.util.CommandStatus;
60 
61 import com.google.common.collect.Range;
62 import com.google.common.io.Files;
63 import com.google.protobuf.ByteString;
64 
65 import perfetto.protos.PerfettoConfig.DataSourceConfig;
66 import perfetto.protos.PerfettoConfig.FtraceConfig;
67 import perfetto.protos.PerfettoConfig.TraceConfig;
68 
69 import java.io.File;
70 import java.text.SimpleDateFormat;
71 import java.util.ArrayList;
72 import java.util.Arrays;
73 import java.util.Comparator;
74 import java.util.Date;
75 import java.util.HashMap;
76 import java.util.LinkedList;
77 import java.util.List;
78 import java.util.Map;
79 import java.util.Queue;
80 import java.util.Random;
81 import java.util.Set;
82 import java.util.function.Function;
83 import java.util.regex.Matcher;
84 import java.util.regex.Pattern;
85 import java.util.stream.Collectors;
86 
87 /**
88  * Base class for testing Statsd atoms.
89  * Validates reporting of statsd logging based on different events
90  */
91 public class AtomTestCase extends BaseTestCase {
92 
93     /**
94      * Run tests that are optional; they are not valid CTS tests per se, since not all devices can
95      * be expected to pass them, but can be run, if desired, to ensure they work when appropriate.
96      */
97     public static final boolean OPTIONAL_TESTS_ENABLED = false;
98 
99     public static final String UPDATE_CONFIG_CMD = "cmd stats config update";
100     public static final String DUMP_REPORT_CMD = "cmd stats dump-report";
101     public static final String DUMP_BATTERY_CMD = "dumpsys battery";
102     public static final String DUMP_BATTERYSTATS_CMD = "dumpsys batterystats";
103     public static final String DUMPSYS_STATS_CMD = "dumpsys stats";
104     public static final String DUMP_PROCSTATS_CMD = "dumpsys procstats";
105     public static final String REMOVE_CONFIG_CMD = "cmd stats config remove";
106     /** ID of the config, which evaluates to -1572883457. */
107     public static final long CONFIG_ID = "cts_config".hashCode();
108 
109     public static final String FEATURE_AUDIO_OUTPUT = "android.hardware.audio.output";
110     public static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive";
111     public static final String FEATURE_BLUETOOTH = "android.hardware.bluetooth";
112     public static final String FEATURE_BLUETOOTH_LE = "android.hardware.bluetooth_le";
113     public static final String FEATURE_CAMERA = "android.hardware.camera";
114     public static final String FEATURE_CAMERA_FLASH = "android.hardware.camera.flash";
115     public static final String FEATURE_CAMERA_FRONT = "android.hardware.camera.front";
116     public static final String FEATURE_LOCATION_GPS = "android.hardware.location.gps";
117     public static final String FEATURE_PC = "android.hardware.type.pc";
118     public static final String FEATURE_PICTURE_IN_PICTURE = "android.software.picture_in_picture";
119     public static final String FEATURE_TELEPHONY = "android.hardware.telephony";
120     public static final String FEATURE_WATCH = "android.hardware.type.watch";
121     public static final String FEATURE_WIFI = "android.hardware.wifi";
122 
123     // Telephony phone types
124     public static final int PHONE_TYPE_GSM = 1;
125     public static final int PHONE_TYPE_CDMA = 2;
126     public static final int PHONE_TYPE_CDMA_LTE = 6;
127 
128     protected static final int WAIT_TIME_SHORT = 500;
129     protected static final int WAIT_TIME_LONG = 2_000;
130 
131     protected static final long SCREEN_STATE_CHANGE_TIMEOUT = 4000;
132     protected static final long SCREEN_STATE_POLLING_INTERVAL = 500;
133 
134     protected static final long NS_PER_SEC = (long) 1E+9;
135 
136     @Override
setUp()137     protected void setUp() throws Exception {
138         super.setUp();
139 
140         // Uninstall to clear the history in case it's still on the device.
141         removeConfig(CONFIG_ID);
142         getReportList(); // Clears data.
143     }
144 
145     @Override
tearDown()146     protected void tearDown() throws Exception {
147         removeConfig(CONFIG_ID);
148         getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
149         super.tearDown();
150     }
151 
152     /**
153      * Determines whether logcat indicates that incidentd fired since the given device date.
154      */
didIncidentdFireSince(String date)155     protected boolean didIncidentdFireSince(String date) throws Exception {
156         final String INCIDENTD_TAG = "incidentd";
157         final String INCIDENTD_STARTED_STRING = "reportIncident";
158         // TODO: Do something more robust than this in case of delayed logging.
159         Thread.sleep(1000);
160         String log = getLogcatSince(date, String.format(
161                 "-s %s -e %s", INCIDENTD_TAG, INCIDENTD_STARTED_STRING));
162         return log.contains(INCIDENTD_STARTED_STRING);
163     }
164 
checkDeviceFor(String methodName)165     protected boolean checkDeviceFor(String methodName) throws Exception {
166         try {
167             installPackage(DEVICE_SIDE_TEST_APK, true);
168             runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".Checkers", methodName);
169             // Test passes, meaning that the answer is true.
170             LogUtil.CLog.d(methodName + "() indicates true.");
171             return true;
172         } catch (AssertionError e) {
173             // Method is designed to fail if the answer is false.
174             LogUtil.CLog.d(methodName + "() indicates false.");
175             return false;
176         }
177     }
178 
179     /**
180      * Returns a protobuf-encoded perfetto config that enables the kernel
181      * ftrace tracer with sched_switch for 10 seconds.
182      */
getPerfettoConfig()183     protected ByteString getPerfettoConfig() {
184         TraceConfig.Builder builder = TraceConfig.newBuilder();
185 
186         TraceConfig.BufferConfig buffer = TraceConfig.BufferConfig
187             .newBuilder()
188             .setSizeKb(128)
189             .build();
190         builder.addBuffers(buffer);
191 
192         FtraceConfig ftraceConfig = FtraceConfig.newBuilder()
193             .addFtraceEvents("sched/sched_switch")
194             .build();
195         DataSourceConfig dataSourceConfig = DataSourceConfig.newBuilder()
196             .setName("linux.ftrace")
197             .setTargetBuffer(0)
198             .setFtraceConfig(ftraceConfig)
199             .build();
200         TraceConfig.DataSource dataSource = TraceConfig.DataSource
201             .newBuilder()
202             .setConfig(dataSourceConfig)
203             .build();
204         builder.addDataSources(dataSource);
205 
206         builder.setDurationMs(10000);
207         builder.setAllowUserBuildTracing(true);
208 
209         // To avoid being hit with guardrails firing in multiple test runs back
210         // to back, we set a unique session key for each config.
211         Random random = new Random();
212         StringBuilder sessionNameBuilder = new StringBuilder("statsd-cts-");
213         sessionNameBuilder.append(random.nextInt() & Integer.MAX_VALUE);
214         builder.setUniqueSessionName(sessionNameBuilder.toString());
215 
216         return builder.build().toByteString();
217     }
218 
219     /**
220      * Resets the state of the Perfetto guardrails. This avoids that the test fails if it's
221      * run too close of for too many times and hits the upload limit.
222      */
resetPerfettoGuardrails()223     protected void resetPerfettoGuardrails() throws Exception {
224         final String cmd = "perfetto --reset-guardrails";
225         CommandResult cr = getDevice().executeShellV2Command(cmd);
226         if (cr.getStatus() != CommandStatus.SUCCESS)
227             throw new Exception(String.format("Error while executing %s: %s %s", cmd, cr.getStdout(), cr.getStderr()));
228     }
229 
probe(String path)230     private String probe(String path) throws Exception {
231         return getDevice().executeShellCommand("if [ -e " + path + " ] ; then"
232                 + " cat " + path + " ; else echo -1 ; fi");
233     }
234 
235     /**
236      * Determines whether perfetto enabled the kernel ftrace tracer.
237      */
isSystemTracingEnabled()238     protected boolean isSystemTracingEnabled() throws Exception {
239         final String traceFsPath = "/sys/kernel/tracing/tracing_on";
240         String tracing_on = probe(traceFsPath);
241         if (tracing_on.startsWith("0")) return false;
242         if (tracing_on.startsWith("1")) return true;
243 
244         // fallback to debugfs
245         LogUtil.CLog.d("Unexpected state for %s = %s. Falling back to debugfs", traceFsPath,
246                 tracing_on);
247 
248         final String debugFsPath = "/sys/kernel/debug/tracing/tracing_on";
249         tracing_on = probe(debugFsPath);
250         if (tracing_on.startsWith("0")) return false;
251         if (tracing_on.startsWith("1")) return true;
252         throw new Exception(String.format("Unexpected state for %s = %s", traceFsPath, tracing_on));
253     }
254 
createConfigBuilder()255     protected static StatsdConfig.Builder createConfigBuilder() {
256       return StatsdConfig.newBuilder()
257           .setId(CONFIG_ID)
258           .addAllowedLogSource("AID_SYSTEM")
259           .addAllowedLogSource("AID_BLUETOOTH")
260           // TODO(b/134091167): Fix bluetooth source name issue in Auto platform.
261           .addAllowedLogSource("com.android.bluetooth")
262           .addAllowedLogSource("AID_LMKD")
263           .addAllowedLogSource("AID_RADIO")
264           .addAllowedLogSource("AID_ROOT")
265           .addAllowedLogSource("AID_STATSD")
266           .addAllowedLogSource("com.android.systemui")
267           .addAllowedLogSource(DeviceAtomTestCase.DEVICE_SIDE_TEST_PACKAGE)
268           .addDefaultPullPackages("AID_RADIO")
269           .addDefaultPullPackages("AID_SYSTEM")
270           .addWhitelistedAtomIds(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER);
271     }
272 
createAndUploadConfig(int atomTag)273     protected void createAndUploadConfig(int atomTag) throws Exception {
274         StatsdConfig.Builder conf = createConfigBuilder();
275         addAtomEvent(conf, atomTag);
276         uploadConfig(conf);
277     }
278 
uploadConfig(StatsdConfig.Builder config)279     protected void uploadConfig(StatsdConfig.Builder config) throws Exception {
280         uploadConfig(config.build());
281     }
282 
uploadConfig(StatsdConfig config)283     protected void uploadConfig(StatsdConfig config) throws Exception {
284         LogUtil.CLog.d("Uploading the following config:\n" + config.toString());
285         File configFile = File.createTempFile("statsdconfig", ".config");
286         configFile.deleteOnExit();
287         Files.write(config.toByteArray(), configFile);
288         String remotePath = "/data/local/tmp/" + configFile.getName();
289         getDevice().pushFile(configFile, remotePath);
290         getDevice().executeShellCommand(
291                 String.join(" ", "cat", remotePath, "|", UPDATE_CONFIG_CMD,
292                         String.valueOf(CONFIG_ID)));
293         getDevice().executeShellCommand("rm " + remotePath);
294     }
295 
removeConfig(long configId)296     protected void removeConfig(long configId) throws Exception {
297         getDevice().executeShellCommand(
298                 String.join(" ", REMOVE_CONFIG_CMD, String.valueOf(configId)));
299     }
300 
301     /** Gets the statsd report and sorts it. Note that this also deletes that report from statsd. */
getEventMetricDataList()302     protected List<EventMetricData> getEventMetricDataList() throws Exception {
303         ConfigMetricsReportList reportList = getReportList();
304         return getEventMetricDataList(reportList);
305     }
306 
307     /**
308      *  Gets a List of sorted ConfigMetricsReports from ConfigMetricsReportList.
309      */
getSortedConfigMetricsReports( ConfigMetricsReportList configMetricsReportList)310     protected List<ConfigMetricsReport> getSortedConfigMetricsReports(
311             ConfigMetricsReportList configMetricsReportList) {
312         return configMetricsReportList.getReportsList().stream()
313                 .sorted(Comparator.comparing(ConfigMetricsReport::getCurrentReportWallClockNanos))
314                 .collect(Collectors.toList());
315     }
316 
317     /**
318      * Extracts and sorts the EventMetricData from the given ConfigMetricsReportList (which must
319      * contain a single report).
320      */
getEventMetricDataList(ConfigMetricsReportList reportList)321     protected List<EventMetricData> getEventMetricDataList(ConfigMetricsReportList reportList)
322             throws Exception {
323         assertThat(reportList.getReportsCount()).isEqualTo(1);
324         ConfigMetricsReport report = reportList.getReports(0);
325 
326         List<EventMetricData> data = new ArrayList<>();
327         for (StatsLogReport metric : report.getMetricsList()) {
328             data.addAll(metric.getEventMetrics().getDataList());
329         }
330         data.sort(Comparator.comparing(EventMetricData::getElapsedTimestampNanos));
331 
332         LogUtil.CLog.d("Get EventMetricDataList as following:\n");
333         for (EventMetricData d : data) {
334             LogUtil.CLog.d("Atom at " + d.getElapsedTimestampNanos() + ":\n" + d.getAtom().toString());
335         }
336         return data;
337     }
338 
getGaugeMetricDataList()339     protected List<Atom> getGaugeMetricDataList() throws Exception {
340         return getGaugeMetricDataList(/*checkTimestampTruncated=*/false);
341     }
342 
getGaugeMetricDataList(boolean checkTimestampTruncated)343     protected List<Atom> getGaugeMetricDataList(boolean checkTimestampTruncated) throws Exception {
344         ConfigMetricsReportList reportList = getReportList();
345         assertThat(reportList.getReportsCount()).isEqualTo(1);
346 
347         // only config
348         ConfigMetricsReport report = reportList.getReports(0);
349         assertThat(report.getMetricsCount()).isEqualTo(1);
350 
351         List<Atom> data = new ArrayList<>();
352         for (GaugeMetricData gaugeMetricData :
353                 report.getMetrics(0).getGaugeMetrics().getDataList()) {
354             assertThat(gaugeMetricData.getBucketInfoCount()).isEqualTo(1);
355             GaugeBucketInfo bucketInfo = gaugeMetricData.getBucketInfo(0);
356             for (Atom atom : bucketInfo.getAtomList()) {
357                 data.add(atom);
358             }
359             if (checkTimestampTruncated) {
360                 for (long timestampNs: bucketInfo.getElapsedTimestampNanosList()) {
361                     assertTimestampIsTruncated(timestampNs);
362                 }
363             }
364         }
365 
366         LogUtil.CLog.d("Get GaugeMetricDataList as following:\n");
367         for (Atom d : data) {
368             LogUtil.CLog.d("Atom:\n" + d.toString());
369         }
370         return data;
371     }
372 
373     /**
374      * Gets the statsd report and extract duration metric data.
375      * Note that this also deletes that report from statsd.
376      */
getDurationMetricDataList()377     protected List<DurationMetricData> getDurationMetricDataList() throws Exception {
378         ConfigMetricsReportList reportList = getReportList();
379         assertThat(reportList.getReportsCount()).isEqualTo(1);
380         ConfigMetricsReport report = reportList.getReports(0);
381 
382         List<DurationMetricData> data = new ArrayList<>();
383         for (StatsLogReport metric : report.getMetricsList()) {
384             data.addAll(metric.getDurationMetrics().getDataList());
385         }
386 
387         LogUtil.CLog.d("Got DurationMetricDataList as following:\n");
388         for (DurationMetricData d : data) {
389             LogUtil.CLog.d("Duration " + d);
390         }
391         return data;
392     }
393 
394     /**
395      * Gets the statsd report and extract count metric data.
396      * Note that this also deletes that report from statsd.
397      */
getCountMetricDataList()398     protected List<CountMetricData> getCountMetricDataList() throws Exception {
399         ConfigMetricsReportList reportList = getReportList();
400         assertThat(reportList.getReportsCount()).isEqualTo(1);
401         ConfigMetricsReport report = reportList.getReports(0);
402 
403         List<CountMetricData> data = new ArrayList<>();
404         for (StatsLogReport metric : report.getMetricsList()) {
405             data.addAll(metric.getCountMetrics().getDataList());
406         }
407 
408         LogUtil.CLog.d("Got CountMetricDataList as following:\n");
409         for (CountMetricData d : data) {
410             LogUtil.CLog.d("Count " + d);
411         }
412         return data;
413     }
414 
415     /**
416      * Gets the statsd report and extract value metric data.
417      * Note that this also deletes that report from statsd.
418      */
getValueMetricDataList()419     protected List<ValueMetricData> getValueMetricDataList() throws Exception {
420         ConfigMetricsReportList reportList = getReportList();
421         assertThat(reportList.getReportsCount()).isEqualTo(1);
422         ConfigMetricsReport report = reportList.getReports(0);
423 
424         List<ValueMetricData> data = new ArrayList<>();
425         for (StatsLogReport metric : report.getMetricsList()) {
426             data.addAll(metric.getValueMetrics().getDataList());
427         }
428 
429         LogUtil.CLog.d("Got ValueMetricDataList as following:\n");
430         for (ValueMetricData d : data) {
431             LogUtil.CLog.d("Value " + d);
432         }
433         return data;
434     }
435 
getStatsLogReport()436     protected StatsLogReport getStatsLogReport() throws Exception {
437         ConfigMetricsReport report = getConfigMetricsReport();
438         assertThat(report.hasUidMap()).isTrue();
439         assertThat(report.getMetricsCount()).isEqualTo(1);
440         return report.getMetrics(0);
441     }
442 
getConfigMetricsReport()443     protected ConfigMetricsReport getConfigMetricsReport() throws Exception {
444         ConfigMetricsReportList reportList = getReportList();
445         assertThat(reportList.getReportsCount()).isEqualTo(1);
446         return reportList.getReports(0);
447     }
448 
449     /** Gets the statsd report. Note that this also deletes that report from statsd. */
getReportList()450     protected ConfigMetricsReportList getReportList() throws Exception {
451         try {
452             ConfigMetricsReportList reportList = getDump(ConfigMetricsReportList.parser(),
453                     String.join(" ", DUMP_REPORT_CMD, String.valueOf(CONFIG_ID),
454                             "--include_current_bucket", "--proto"));
455             return reportList;
456         } catch (com.google.protobuf.InvalidProtocolBufferException e) {
457             LogUtil.CLog.e("Failed to fetch and parse the statsd output report. "
458                     + "Perhaps there is not a valid statsd config for the requested "
459                     + "uid=" + getHostUid() + ", id=" + CONFIG_ID + ".");
460             throw (e);
461         }
462     }
463 
getBatteryStatsProto()464     protected BatteryStatsProto getBatteryStatsProto() throws Exception {
465         try {
466             BatteryStatsProto batteryStatsProto = getDump(BatteryStatsServiceDumpProto.parser(),
467                     String.join(" ", DUMP_BATTERYSTATS_CMD,
468                             "--proto")).getBatterystats();
469             LogUtil.CLog.d("Got batterystats:\n " + batteryStatsProto.toString());
470             return batteryStatsProto;
471         } catch (com.google.protobuf.InvalidProtocolBufferException e) {
472             LogUtil.CLog.e("Failed to dump batterystats proto");
473             throw (e);
474         }
475     }
476 
getProcStatsProto()477     protected List<ProcessStatsProto> getProcStatsProto() throws Exception {
478         try {
479 
480             List<ProcessStatsProto> processStatsProtoList =
481                 new ArrayList<ProcessStatsProto>();
482             android.service.procstats.ProcessStatsSectionProto sectionProto = getDump(
483                     ProcessStatsServiceDumpProto.parser(),
484                     String.join(" ", DUMP_PROCSTATS_CMD,
485                             "--proto")).getProcstatsNow();
486             for (android.service.procstats.ProcessStatsProto stats :
487                     sectionProto.getProcessStatsList()) {
488                 ProcessStatsProto procStats = ProcessStatsProto.parser().parseFrom(
489                     stats.toByteArray());
490                 processStatsProtoList.add(procStats);
491             }
492             LogUtil.CLog.d("Got procstats:\n ");
493             for (ProcessStatsProto processStatsProto : processStatsProtoList) {
494                 LogUtil.CLog.d(processStatsProto.toString());
495             }
496             return processStatsProtoList;
497         } catch (com.google.protobuf.InvalidProtocolBufferException e) {
498             LogUtil.CLog.e("Failed to dump procstats proto");
499             throw (e);
500         }
501     }
502 
503     /*
504      * Get all procstats package data in proto
505      */
getAllProcStatsProto()506     protected List<ProcessStatsPackageProto> getAllProcStatsProto() throws Exception {
507         try {
508             android.service.procstats.ProcessStatsSectionProto sectionProto = getDump(
509                     ProcessStatsServiceDumpProto.parser(),
510                     String.join(" ", DUMP_PROCSTATS_CMD,
511                             "--proto")).getProcstatsOver24Hrs();
512             List<ProcessStatsPackageProto> processStatsProtoList =
513                 new ArrayList<ProcessStatsPackageProto>();
514             for (android.service.procstats.ProcessStatsPackageProto pkgStast :
515                 sectionProto.getPackageStatsList()) {
516               ProcessStatsPackageProto pkgAtom =
517                   ProcessStatsPackageProto.parser().parseFrom(pkgStast.toByteArray());
518                 processStatsProtoList.add(pkgAtom);
519             }
520             LogUtil.CLog.d("Got procstats:\n ");
521             for (ProcessStatsPackageProto processStatsProto : processStatsProtoList) {
522                 LogUtil.CLog.d(processStatsProto.toString());
523             }
524             return processStatsProtoList;
525         } catch (com.google.protobuf.InvalidProtocolBufferException e) {
526             LogUtil.CLog.e("Failed to dump procstats proto");
527             throw (e);
528         }
529     }
530 
531     /*
532      * Get all processes' procstats statsd data in proto
533      */
getAllProcStatsProtoForStatsd()534     protected List<android.service.procstats.ProcessStatsProto> getAllProcStatsProtoForStatsd()
535             throws Exception {
536         try {
537             android.service.procstats.ProcessStatsSectionProto sectionProto = getDump(
538                     android.service.procstats.ProcessStatsSectionProto.parser(),
539                     String.join(" ", DUMP_PROCSTATS_CMD,
540                             "--statsd"));
541             List<android.service.procstats.ProcessStatsProto> processStatsProtoList
542                     = sectionProto.getProcessStatsList();
543             LogUtil.CLog.d("Got procstats:\n ");
544             for (android.service.procstats.ProcessStatsProto processStatsProto
545                     : processStatsProtoList) {
546                 LogUtil.CLog.d(processStatsProto.toString());
547             }
548             return processStatsProtoList;
549         } catch (com.google.protobuf.InvalidProtocolBufferException e) {
550             LogUtil.CLog.e("Failed to dump procstats proto");
551             throw (e);
552         }
553     }
554 
hasBattery()555     protected boolean hasBattery() throws Exception {
556         try {
557             BatteryServiceDumpProto batteryProto = getDump(BatteryServiceDumpProto.parser(),
558                     String.join(" ", DUMP_BATTERY_CMD, "--proto"));
559             LogUtil.CLog.d("Got battery service dump:\n " + batteryProto.toString());
560             return batteryProto.getIsPresent();
561         } catch (com.google.protobuf.InvalidProtocolBufferException e) {
562             LogUtil.CLog.e("Failed to dump batteryservice proto");
563             throw (e);
564         }
565     }
566 
567     /** Creates a FieldValueMatcher.Builder corresponding to the given field. */
createFvm(int field)568     protected static FieldValueMatcher.Builder createFvm(int field) {
569         return FieldValueMatcher.newBuilder().setField(field);
570     }
571 
addAtomEvent(StatsdConfig.Builder conf, int atomTag)572     protected void addAtomEvent(StatsdConfig.Builder conf, int atomTag) throws Exception {
573         addAtomEvent(conf, atomTag, new ArrayList<FieldValueMatcher.Builder>());
574     }
575 
576     /**
577      * Adds an event to the config for an atom that matches the given key.
578      *
579      * @param conf    configuration
580      * @param atomTag atom tag (from atoms.proto)
581      * @param fvm     FieldValueMatcher.Builder for the relevant key
582      */
addAtomEvent(StatsdConfig.Builder conf, int atomTag, FieldValueMatcher.Builder fvm)583     protected void addAtomEvent(StatsdConfig.Builder conf, int atomTag,
584             FieldValueMatcher.Builder fvm)
585             throws Exception {
586         addAtomEvent(conf, atomTag, Arrays.asList(fvm));
587     }
588 
589     /**
590      * Adds an event to the config for an atom that matches the given keys.
591      *
592      * @param conf   configuration
593      * @param atomId atom tag (from atoms.proto)
594      * @param fvms   list of FieldValueMatcher.Builders to attach to the atom. May be null.
595      */
addAtomEvent(StatsdConfig.Builder conf, int atomId, List<FieldValueMatcher.Builder> fvms)596     protected void addAtomEvent(StatsdConfig.Builder conf, int atomId,
597             List<FieldValueMatcher.Builder> fvms) throws Exception {
598 
599         final String atomName = "Atom" + System.nanoTime();
600         final String eventName = "Event" + System.nanoTime();
601 
602         SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder().setAtomId(atomId);
603         if (fvms != null) {
604             for (FieldValueMatcher.Builder fvm : fvms) {
605                 sam.addFieldValueMatcher(fvm);
606             }
607         }
608         conf.addAtomMatcher(AtomMatcher.newBuilder()
609                 .setId(atomName.hashCode())
610                 .setSimpleAtomMatcher(sam));
611         conf.addEventMetric(EventMetric.newBuilder()
612                 .setId(eventName.hashCode())
613                 .setWhat(atomName.hashCode()));
614     }
615 
616     /**
617      * Adds an atom to a gauge metric of a config
618      *
619      * @param conf        configuration
620      * @param atomId      atom id (from atoms.proto)
621      * @param gaugeMetric the gauge metric to add
622      */
addGaugeAtom(StatsdConfig.Builder conf, int atomId, GaugeMetric.Builder gaugeMetric)623     protected void addGaugeAtom(StatsdConfig.Builder conf, int atomId,
624             GaugeMetric.Builder gaugeMetric) throws Exception {
625         final String atomName = "Atom" + System.nanoTime();
626         final String gaugeName = "Gauge" + System.nanoTime();
627         final String predicateName = "APP_BREADCRUMB";
628         SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder().setAtomId(atomId);
629         conf.addAtomMatcher(AtomMatcher.newBuilder()
630                 .setId(atomName.hashCode())
631                 .setSimpleAtomMatcher(sam));
632         final String predicateTrueName = "APP_BREADCRUMB_1";
633         final String predicateFalseName = "APP_BREADCRUMB_2";
634         conf.addAtomMatcher(AtomMatcher.newBuilder()
635                 .setId(predicateTrueName.hashCode())
636                 .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
637                         .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
638                         .addFieldValueMatcher(FieldValueMatcher.newBuilder()
639                                 .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER)
640                                 .setEqInt(1)
641                         )
642                 )
643         )
644                 // Used to trigger predicate
645                 .addAtomMatcher(AtomMatcher.newBuilder()
646                         .setId(predicateFalseName.hashCode())
647                         .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
648                                 .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
649                                 .addFieldValueMatcher(FieldValueMatcher.newBuilder()
650                                         .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER)
651                                         .setEqInt(2)
652                                 )
653                         )
654                 );
655         conf.addPredicate(Predicate.newBuilder()
656                 .setId(predicateName.hashCode())
657                 .setSimplePredicate(SimplePredicate.newBuilder()
658                         .setStart(predicateTrueName.hashCode())
659                         .setStop(predicateFalseName.hashCode())
660                         .setCountNesting(false)
661                 )
662         );
663         gaugeMetric
664                 .setId(gaugeName.hashCode())
665                 .setWhat(atomName.hashCode())
666                 .setCondition(predicateName.hashCode());
667         conf.addGaugeMetric(gaugeMetric.build());
668     }
669 
670     /**
671      * Adds an atom to a gauge metric of a config
672      *
673      * @param conf      configuration
674      * @param atomId    atom id (from atoms.proto)
675      * @param dimension dimension is needed for most pulled atoms
676      */
addGaugeAtomWithDimensions(StatsdConfig.Builder conf, int atomId, @Nullable FieldMatcher.Builder dimension)677     protected void addGaugeAtomWithDimensions(StatsdConfig.Builder conf, int atomId,
678             @Nullable FieldMatcher.Builder dimension) throws Exception {
679         GaugeMetric.Builder gaugeMetric = GaugeMetric.newBuilder()
680                 .setGaugeFieldsFilter(FieldFilter.newBuilder().setIncludeAll(true).build())
681                 .setSamplingType(GaugeMetric.SamplingType.CONDITION_CHANGE_TO_TRUE)
682                 .setMaxNumGaugeAtomsPerBucket(10000)
683                 .setBucket(TimeUnit.CTS);
684         if (dimension != null) {
685             gaugeMetric.setDimensionsInWhat(dimension.build());
686         }
687         addGaugeAtom(conf, atomId, gaugeMetric);
688     }
689 
690     /**
691      * Asserts that each set of states in stateSets occurs at least once in data.
692      * Asserts that the states in data occur in the same order as the sets in stateSets.
693      *
694      * @param stateSets        A list of set of states, where each set represents an equivalent
695      *                         state of the device for the purpose of CTS.
696      * @param data             list of EventMetricData from statsd, produced by
697      *                         getReportMetricListData()
698      * @param wait             expected duration (in ms) between state changes; asserts that the
699      *                         actual wait
700      *                         time was wait/2 <= actual_wait <= 5*wait. Use 0 to ignore this
701      *                         assertion.
702      * @param getStateFromAtom expression that takes in an Atom and returns the state it contains
703      */
assertStatesOccurred(List<Set<Integer>> stateSets, List<EventMetricData> data, int wait, Function<Atom, Integer> getStateFromAtom)704     public void assertStatesOccurred(List<Set<Integer>> stateSets, List<EventMetricData> data,
705             int wait, Function<Atom, Integer> getStateFromAtom) {
706         // Sometimes, there are more events than there are states.
707         // Eg: When the screen turns off, it may go into OFF and then DOZE immediately.
708         assertWithMessage("Too few states found").that(data.size()).isAtLeast(stateSets.size());
709         int stateSetIndex = 0; // Tracks which state set we expect the data to be in.
710         for (int dataIndex = 0; dataIndex < data.size(); dataIndex++) {
711             Atom atom = data.get(dataIndex).getAtom();
712             int state = getStateFromAtom.apply(atom);
713             // If state is in the current state set, we do not assert anything.
714             // If it is not, we expect to have transitioned to the next state set.
715             if (stateSets.get(stateSetIndex).contains(state)) {
716                 // No need to assert anything. Just log it.
717                 LogUtil.CLog.i("The following atom at dataIndex=" + dataIndex + " is "
718                         + "in stateSetIndex " + stateSetIndex + ":\n"
719                         + data.get(dataIndex).getAtom().toString());
720             } else {
721                 stateSetIndex += 1;
722                 LogUtil.CLog.i("Assert that the following atom at dataIndex=" + dataIndex + " is"
723                         + " in stateSetIndex " + stateSetIndex + ":\n"
724                         + data.get(dataIndex).getAtom().toString());
725                 assertWithMessage("Missed first state").that(dataIndex).isNotEqualTo(0);
726                 assertWithMessage("Too many states").that(stateSetIndex)
727                     .isLessThan(stateSets.size());
728                 assertWithMessage(String.format("Is in wrong state (%d)", state))
729                     .that(stateSets.get(stateSetIndex)).contains(state);
730                 if (wait > 0) {
731                     assertTimeDiffBetween(data.get(dataIndex - 1), data.get(dataIndex),
732                             wait / 2, wait * 5);
733                 }
734             }
735         }
736         assertWithMessage("Too few states").that(stateSetIndex).isEqualTo(stateSets.size() - 1);
737     }
738 
739     /**
740      * Removes all elements from data prior to the first occurrence of an element of state. After
741      * this method is called, the first element of data (if non-empty) is guaranteed to be an
742      * element in state.
743      *
744      * @param getStateFromAtom expression that takes in an Atom and returns the state it contains
745      */
popUntilFind(List<EventMetricData> data, Set<Integer> state, Function<Atom, Integer> getStateFromAtom)746     public void popUntilFind(List<EventMetricData> data, Set<Integer> state,
747             Function<Atom, Integer> getStateFromAtom) {
748         int firstStateIdx;
749         for (firstStateIdx = 0; firstStateIdx < data.size(); firstStateIdx++) {
750             Atom atom = data.get(firstStateIdx).getAtom();
751             if (state.contains(getStateFromAtom.apply(atom))) {
752                 break;
753             }
754         }
755         if (firstStateIdx == 0) {
756             // First first element already is in state, so there's nothing to do.
757             return;
758         }
759         data.subList(0, firstStateIdx).clear();
760     }
761 
762     /**
763      * Removes all elements from data after to the last occurrence of an element of state. After
764      * this method is called, the last element of data (if non-empty) is guaranteed to be an
765      * element in state.
766      *
767      * @param getStateFromAtom expression that takes in an Atom and returns the state it contains
768      */
popUntilFindFromEnd(List<EventMetricData> data, Set<Integer> state, Function<Atom, Integer> getStateFromAtom)769     public void popUntilFindFromEnd(List<EventMetricData> data, Set<Integer> state,
770         Function<Atom, Integer> getStateFromAtom) {
771         int lastStateIdx;
772         for (lastStateIdx = data.size() - 1; lastStateIdx >= 0; lastStateIdx--) {
773             Atom atom = data.get(lastStateIdx).getAtom();
774             if (state.contains(getStateFromAtom.apply(atom))) {
775                 break;
776             }
777         }
778         if (lastStateIdx == data.size()-1) {
779             // Last element already is in state, so there's nothing to do.
780             return;
781         }
782         data.subList(lastStateIdx+1, data.size()).clear();
783     }
784 
785     /** Returns the UID of the host, which should always either be SHELL (2000) or ROOT (0). */
getHostUid()786     protected int getHostUid() throws DeviceNotAvailableException {
787         String strUid = "";
788         try {
789             strUid = getDevice().executeShellCommand("id -u");
790             return Integer.parseInt(strUid.trim());
791         } catch (NumberFormatException e) {
792             LogUtil.CLog.e("Failed to get host's uid via shell command. Found " + strUid);
793             // Fall back to alternative method...
794             if (getDevice().isAdbRoot()) {
795                 return 0; // ROOT
796             } else {
797                 return 2000; // SHELL
798             }
799         }
800     }
801 
getProperty(String prop)802     protected String getProperty(String prop) throws Exception {
803         return getDevice().executeShellCommand("getprop " + prop).replace("\n", "");
804     }
805 
turnScreenOn()806     protected void turnScreenOn() throws Exception {
807         getDevice().executeShellCommand("input keyevent KEYCODE_WAKEUP");
808         getDevice().executeShellCommand("wm dismiss-keyguard");
809     }
810 
turnScreenOff()811     protected void turnScreenOff() throws Exception {
812         getDevice().executeShellCommand("input keyevent KEYCODE_SLEEP");
813     }
814 
setChargingState(int state)815     protected void setChargingState(int state) throws Exception {
816         getDevice().executeShellCommand("cmd battery set status " + state);
817     }
818 
unplugDevice()819     protected void unplugDevice() throws Exception {
820         // On batteryless devices on Android P or above, the 'unplug' command
821         // alone does not simulate the really unplugged state.
822         //
823         // This is because charging state is left as "unknown". Unless a valid
824         // state like 3 = BatteryManager.BATTERY_STATUS_DISCHARGING is set,
825         // framework does not consider the device as running on battery.
826         setChargingState(3);
827 
828         getDevice().executeShellCommand("cmd battery unplug");
829     }
830 
plugInAc()831     protected void plugInAc() throws Exception {
832         getDevice().executeShellCommand("cmd battery set ac 1");
833     }
834 
enableLooperStats()835     protected void enableLooperStats() throws Exception {
836         getDevice().executeShellCommand("cmd looper_stats enable");
837     }
838 
resetLooperStats()839     protected void resetLooperStats() throws Exception {
840         getDevice().executeShellCommand("cmd looper_stats reset");
841     }
842 
disableLooperStats()843     protected void disableLooperStats() throws Exception {
844         getDevice().executeShellCommand("cmd looper_stats disable");
845     }
846 
enableBinderStats()847     protected void enableBinderStats() throws Exception {
848         getDevice().executeShellCommand("dumpsys binder_calls_stats --enable");
849     }
850 
resetBinderStats()851     protected void resetBinderStats() throws Exception {
852         getDevice().executeShellCommand("dumpsys binder_calls_stats --reset");
853     }
854 
disableBinderStats()855     protected void disableBinderStats() throws Exception {
856         getDevice().executeShellCommand("dumpsys binder_calls_stats --disable");
857     }
858 
binderStatsNoSampling()859     protected void binderStatsNoSampling() throws Exception {
860         getDevice().executeShellCommand("dumpsys binder_calls_stats --no-sampling");
861     }
862 
setAppBreadcrumbPredicate()863     public void setAppBreadcrumbPredicate() throws Exception {
864         doAppBreadcrumbReportedStart(1);
865     }
866 
clearAppBreadcrumbPredicate()867     public void clearAppBreadcrumbPredicate() throws Exception {
868         doAppBreadcrumbReportedStart(2);
869     }
870 
doAppBreadcrumbReportedStart(int label)871     public void doAppBreadcrumbReportedStart(int label) throws Exception {
872         doAppBreadcrumbReported(label, AppBreadcrumbReported.State.START.ordinal());
873     }
874 
doAppBreadcrumbReportedStop(int label)875     public void doAppBreadcrumbReportedStop(int label) throws Exception {
876         doAppBreadcrumbReported(label, AppBreadcrumbReported.State.STOP.ordinal());
877     }
878 
doAppBreadcrumbReported(int label)879     public void doAppBreadcrumbReported(int label) throws Exception {
880         doAppBreadcrumbReported(label, AppBreadcrumbReported.State.UNSPECIFIED.ordinal());
881     }
882 
doAppBreadcrumbReported(int label, int state)883     public void doAppBreadcrumbReported(int label, int state) throws Exception {
884         getDevice().executeShellCommand(String.format(
885                 "cmd stats log-app-breadcrumb %d %d", label, state));
886     }
887 
rebootDevice()888     protected void rebootDevice() throws Exception {
889         getDevice().rebootUntilOnline();
890     }
891 
892     /**
893      * Asserts that the two events are within the specified range of each other.
894      *
895      * @param d0        the event that should occur first
896      * @param d1        the event that should occur second
897      * @param minDiffMs d0 should precede d1 by at least this amount
898      * @param maxDiffMs d0 should precede d1 by at most this amount
899      */
assertTimeDiffBetween(EventMetricData d0, EventMetricData d1, int minDiffMs, int maxDiffMs)900     public static void assertTimeDiffBetween(EventMetricData d0, EventMetricData d1,
901             int minDiffMs, int maxDiffMs) {
902         long diffMs = (d1.getElapsedTimestampNanos() - d0.getElapsedTimestampNanos()) / 1_000_000;
903         assertWithMessage("Illegal time difference")
904             .that(diffMs).isIn(Range.closed((long) minDiffMs, (long) maxDiffMs));
905     }
906 
getCurrentLogcatDate()907     protected String getCurrentLogcatDate() throws Exception {
908         // TODO: Do something more robust than this for getting logcat markers.
909         long timestampMs = getDevice().getDeviceDate();
910         return new SimpleDateFormat("MM-dd HH:mm:ss.SSS")
911                 .format(new Date(timestampMs));
912     }
913 
getLogcatSince(String date, String logcatParams)914     protected String getLogcatSince(String date, String logcatParams) throws Exception {
915         return getDevice().executeShellCommand(String.format(
916                 "logcat -v threadtime -t '%s' -d %s", date, logcatParams));
917     }
918 
919     // TODO: Remove this and migrate all usages to createConfigBuilder()
getPulledConfig()920     protected StatsdConfig.Builder getPulledConfig() {
921         return createConfigBuilder();
922     }
923     /**
924      * Determines if the device has the given feature.
925      * Prints a warning if its value differs from requiredAnswer.
926      */
hasFeature(String featureName, boolean requiredAnswer)927     protected boolean hasFeature(String featureName, boolean requiredAnswer) throws Exception {
928         final String features = getDevice().executeShellCommand("pm list features");
929         boolean hasIt = features.contains(featureName);
930         if (hasIt != requiredAnswer) {
931             LogUtil.CLog.w("Device does " + (requiredAnswer ? "not " : "") + "have feature "
932                     + featureName);
933         }
934         return hasIt == requiredAnswer;
935     }
936 
937     // Checks that a timestamp has been truncated to be a multiple of 5 min
assertTimestampIsTruncated(long timestampNs)938     protected void assertTimestampIsTruncated(long timestampNs) {
939         long fiveMinutesInNs = NS_PER_SEC * 5 * 60;
940         assertWithMessage("Timestamp is not truncated")
941                 .that(timestampNs % fiveMinutesInNs).isEqualTo(0);
942     }
943 }
944