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