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