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