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 import static com.google.common.truth.Truth.assertThat; 21 import static com.google.common.truth.Truth.assertWithMessage; 22 23 import android.os.BatteryStatsProto; 24 import android.os.StatsDataDumpProto; 25 import android.service.battery.BatteryServiceDumpProto; 26 import android.service.batterystats.BatteryStatsServiceDumpProto; 27 import android.service.procstats.ProcessStatsServiceDumpProto; 28 29 import com.android.annotations.Nullable; 30 import com.android.internal.os.StatsdConfigProto.AtomMatcher; 31 import com.android.internal.os.StatsdConfigProto.EventMetric; 32 import com.android.internal.os.StatsdConfigProto.FieldFilter; 33 import com.android.internal.os.StatsdConfigProto.FieldMatcher; 34 import com.android.internal.os.StatsdConfigProto.FieldValueMatcher; 35 import com.android.internal.os.StatsdConfigProto.GaugeMetric; 36 import com.android.internal.os.StatsdConfigProto.Predicate; 37 import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher; 38 import com.android.internal.os.StatsdConfigProto.SimplePredicate; 39 import com.android.internal.os.StatsdConfigProto.StatsdConfig; 40 import com.android.internal.os.StatsdConfigProto.TimeUnit; 41 import com.android.os.AtomsProto.AppBreadcrumbReported; 42 import com.android.os.AtomsProto.Atom; 43 import com.android.os.AtomsProto.ProcessStatsPackageProto; 44 import com.android.os.AtomsProto.ProcessStatsProto; 45 import com.android.os.AtomsProto.ProcessStatsStateProto; 46 import com.android.os.StatsLog; 47 import com.android.os.StatsLog.ConfigMetricsReport; 48 import com.android.os.StatsLog.ConfigMetricsReportList; 49 import com.android.os.StatsLog.CountMetricData; 50 import com.android.os.StatsLog.DurationMetricData; 51 import com.android.os.StatsLog.EventMetricData; 52 import com.android.os.StatsLog.GaugeBucketInfo; 53 import com.android.os.StatsLog.GaugeMetricData; 54 import com.android.os.StatsLog.StatsLogReport; 55 import com.android.os.StatsLog.StatsLogReport.GaugeMetricDataWrapper; 56 import com.android.os.StatsLog.ValueMetricData; 57 import com.android.tradefed.device.DeviceNotAvailableException; 58 import com.android.tradefed.log.LogUtil; 59 import com.android.tradefed.util.CommandResult; 60 import com.android.tradefed.util.CommandStatus; 61 import com.android.tradefed.util.Pair; 62 63 import com.google.common.collect.Range; 64 import com.google.common.io.Files; 65 import com.google.protobuf.ByteString; 66 import java.io.File; 67 import java.text.SimpleDateFormat; 68 import java.util.ArrayList; 69 import java.util.Arrays; 70 import java.util.Collections; 71 import java.util.Comparator; 72 import java.util.Date; 73 import java.util.HashMap; 74 import java.util.LinkedList; 75 import java.util.List; 76 import java.util.Map; 77 import java.util.Queue; 78 import java.util.Random; 79 import java.util.Set; 80 import java.util.StringTokenizer; 81 import java.util.function.Function; 82 import java.util.regex.Matcher; 83 import java.util.regex.Pattern; 84 import java.util.stream.Collectors; 85 import perfetto.protos.PerfettoConfig.DataSourceConfig; 86 import perfetto.protos.PerfettoConfig.FtraceConfig; 87 import perfetto.protos.PerfettoConfig.TraceConfig; 88 89 /** 90 * Base class for testing Statsd atoms. 91 * Validates reporting of statsd logging based on different events 92 */ 93 public class AtomTestCase extends BaseTestCase { 94 95 /** 96 * Run tests that are optional; they are not valid CTS tests per se, since not all devices can 97 * be expected to pass them, but can be run, if desired, to ensure they work when appropriate. 98 */ 99 public static final boolean OPTIONAL_TESTS_ENABLED = false; 100 101 public static final String UPDATE_CONFIG_CMD = "cmd stats config update"; 102 public static final String DUMP_REPORT_CMD = "cmd stats dump-report"; 103 public static final String DUMP_BATTERY_CMD = "dumpsys battery"; 104 public static final String DUMP_BATTERYSTATS_CMD = "dumpsys batterystats"; 105 public static final String DUMPSYS_STATS_CMD = "dumpsys stats"; 106 public static final String DUMP_PROCSTATS_CMD = "dumpsys procstats"; 107 public static final String REMOVE_CONFIG_CMD = "cmd stats config remove"; 108 /** ID of the config, which evaluates to -1572883457. */ 109 public static final long CONFIG_ID = "cts_config".hashCode(); 110 111 public static final String FEATURE_AUDIO_OUTPUT = "android.hardware.audio.output"; 112 public static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive"; 113 public static final String FEATURE_BLUETOOTH = "android.hardware.bluetooth"; 114 public static final String FEATURE_BLUETOOTH_LE = "android.hardware.bluetooth_le"; 115 public static final String FEATURE_CAMERA = "android.hardware.camera"; 116 public static final String FEATURE_CAMERA_FLASH = "android.hardware.camera.flash"; 117 public static final String FEATURE_CAMERA_FRONT = "android.hardware.camera.front"; 118 public static final String FEATURE_LEANBACK_ONLY = "android.software.leanback_only"; 119 public static final String FEATURE_LOCATION_GPS = "android.hardware.location.gps"; 120 public static final String FEATURE_PC = "android.hardware.type.pc"; 121 public static final String FEATURE_PICTURE_IN_PICTURE = "android.software.picture_in_picture"; 122 public static final String FEATURE_TELEPHONY = "android.hardware.telephony"; 123 public static final String FEATURE_WATCH = "android.hardware.type.watch"; 124 public static final String FEATURE_WIFI = "android.hardware.wifi"; 125 public static final String FEATURE_INCREMENTAL_DELIVERY = 126 "android.software.incremental_delivery"; 127 128 public static final int SHELL_UID = 2000; 129 130 // Telephony phone types 131 public static final int PHONE_TYPE_GSM = 1; 132 public static final int PHONE_TYPE_CDMA = 2; 133 public static final int PHONE_TYPE_CDMA_LTE = 6; 134 135 protected static final int WAIT_TIME_SHORT = 500; 136 protected static final int WAIT_TIME_LONG = 2_000; 137 138 protected static final long SCREEN_STATE_CHANGE_TIMEOUT = 4000; 139 protected static final long SCREEN_STATE_POLLING_INTERVAL = 500; 140 141 protected static final long NS_PER_SEC = (long) 1E+9; 142 143 @Override setUp()144 protected void setUp() throws Exception { 145 super.setUp(); 146 147 // Uninstall to clear the history in case it's still on the device. 148 removeConfig(CONFIG_ID); 149 getReportList(); // Clears data. 150 } 151 152 @Override tearDown()153 protected void tearDown() throws Exception { 154 removeConfig(CONFIG_ID); 155 getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE); 156 super.tearDown(); 157 } 158 159 /** 160 * Determines whether logcat indicates that incidentd fired since the given device date. 161 */ didIncidentdFireSince(String date)162 protected boolean didIncidentdFireSince(String date) throws Exception { 163 final String INCIDENTD_TAG = "incidentd"; 164 final String INCIDENTD_STARTED_STRING = "reportIncident"; 165 // TODO: Do something more robust than this in case of delayed logging. 166 Thread.sleep(1000); 167 String log = getLogcatSince(date, String.format( 168 "-s %s -e %s", INCIDENTD_TAG, INCIDENTD_STARTED_STRING)); 169 return log.contains(INCIDENTD_STARTED_STRING); 170 } 171 checkDeviceFor(String methodName)172 protected boolean checkDeviceFor(String methodName) throws Exception { 173 try { 174 installPackage(DEVICE_SIDE_TEST_APK, true); 175 runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".Checkers", methodName); 176 // Test passes, meaning that the answer is true. 177 LogUtil.CLog.d(methodName + "() indicates true."); 178 return true; 179 } catch (AssertionError e) { 180 // Method is designed to fail if the answer is false. 181 LogUtil.CLog.d(methodName + "() indicates false."); 182 return false; 183 } 184 } 185 186 /** 187 * Returns a protobuf-encoded perfetto config that enables the kernel 188 * ftrace tracer with sched_switch for 10 seconds. 189 */ getPerfettoConfig()190 protected ByteString getPerfettoConfig() { 191 TraceConfig.Builder builder = TraceConfig.newBuilder(); 192 193 TraceConfig.BufferConfig buffer = TraceConfig.BufferConfig 194 .newBuilder() 195 .setSizeKb(128) 196 .build(); 197 builder.addBuffers(buffer); 198 199 FtraceConfig ftraceConfig = FtraceConfig.newBuilder() 200 .addFtraceEvents("sched/sched_switch") 201 .build(); 202 DataSourceConfig dataSourceConfig = DataSourceConfig.newBuilder() 203 .setName("linux.ftrace") 204 .setTargetBuffer(0) 205 .setFtraceConfig(ftraceConfig) 206 .build(); 207 TraceConfig.DataSource dataSource = TraceConfig.DataSource 208 .newBuilder() 209 .setConfig(dataSourceConfig) 210 .build(); 211 builder.addDataSources(dataSource); 212 213 builder.setDurationMs(10000); 214 builder.setAllowUserBuildTracing(true); 215 216 TraceConfig.IncidentReportConfig incident = TraceConfig.IncidentReportConfig 217 .newBuilder() 218 .setDestinationPackage("foo.bar.baz") 219 .build(); 220 builder.setIncidentReportConfig(incident); 221 222 // To avoid being hit with guardrails firing in multiple test runs back 223 // to back, we set a unique session key for each config. 224 Random random = new Random(); 225 StringBuilder sessionNameBuilder = new StringBuilder("statsd-cts-"); 226 sessionNameBuilder.append(random.nextInt() & Integer.MAX_VALUE); 227 builder.setUniqueSessionName(sessionNameBuilder.toString()); 228 229 return builder.build().toByteString(); 230 } 231 232 /** 233 * Resets the state of the Perfetto guardrails. This avoids that the test fails if it's 234 * run too close of for too many times and hits the upload limit. 235 */ resetPerfettoGuardrails()236 protected void resetPerfettoGuardrails() throws Exception { 237 final String cmd = "perfetto --reset-guardrails"; 238 CommandResult cr = getDevice().executeShellV2Command(cmd); 239 if (cr.getStatus() != CommandStatus.SUCCESS) 240 throw new Exception(String.format("Error while executing %s: %s %s", cmd, cr.getStdout(), cr.getStderr())); 241 } 242 probe(String path)243 private String probe(String path) throws Exception { 244 return getDevice().executeShellCommand("if [ -e " + path + " ] ; then" 245 + " cat " + path + " ; else echo -1 ; fi"); 246 } 247 248 /** 249 * Determines whether perfetto enabled the kernel ftrace tracer. 250 */ isSystemTracingEnabled()251 protected boolean isSystemTracingEnabled() throws Exception { 252 final String traceFsPath = "/sys/kernel/tracing/tracing_on"; 253 String tracing_on = probe(traceFsPath); 254 if (tracing_on.startsWith("0")) return false; 255 if (tracing_on.startsWith("1")) return true; 256 257 // fallback to debugfs 258 LogUtil.CLog.d("Unexpected state for %s = %s. Falling back to debugfs", traceFsPath, 259 tracing_on); 260 261 final String debugFsPath = "/sys/kernel/debug/tracing/tracing_on"; 262 tracing_on = probe(debugFsPath); 263 if (tracing_on.startsWith("0")) return false; 264 if (tracing_on.startsWith("1")) return true; 265 throw new Exception(String.format("Unexpected state for %s = %s", traceFsPath, tracing_on)); 266 } 267 createConfigBuilder()268 protected static StatsdConfig.Builder createConfigBuilder() { 269 return StatsdConfig.newBuilder() 270 .setId(CONFIG_ID) 271 .addAllowedLogSource("AID_SYSTEM") 272 .addAllowedLogSource("AID_BLUETOOTH") 273 // TODO(b/134091167): Fix bluetooth source name issue in Auto platform. 274 .addAllowedLogSource("com.android.bluetooth") 275 .addAllowedLogSource("AID_LMKD") 276 .addAllowedLogSource("AID_RADIO") 277 .addAllowedLogSource("AID_ROOT") 278 .addAllowedLogSource("AID_STATSD") 279 .addAllowedLogSource("com.android.systemui") 280 .addAllowedLogSource(DeviceAtomTestCase.DEVICE_SIDE_TEST_PACKAGE) 281 .addDefaultPullPackages("AID_RADIO") 282 .addDefaultPullPackages("AID_SYSTEM") 283 .addWhitelistedAtomIds(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER); 284 } 285 createAndUploadConfig(int atomTag)286 protected void createAndUploadConfig(int atomTag) throws Exception { 287 StatsdConfig.Builder conf = createConfigBuilder(); 288 addAtomEvent(conf, atomTag); 289 uploadConfig(conf); 290 } 291 uploadConfig(StatsdConfig.Builder config)292 protected void uploadConfig(StatsdConfig.Builder config) throws Exception { 293 uploadConfig(config.build()); 294 } 295 uploadConfig(StatsdConfig config)296 protected void uploadConfig(StatsdConfig config) throws Exception { 297 LogUtil.CLog.d("Uploading the following config:\n" + config.toString()); 298 File configFile = File.createTempFile("statsdconfig", ".config"); 299 configFile.deleteOnExit(); 300 Files.write(config.toByteArray(), configFile); 301 String remotePath = "/data/local/tmp/" + configFile.getName(); 302 getDevice().pushFile(configFile, remotePath); 303 getDevice().executeShellCommand( 304 String.join(" ", "cat", remotePath, "|", UPDATE_CONFIG_CMD, 305 String.valueOf(SHELL_UID), String.valueOf(CONFIG_ID))); 306 getDevice().executeShellCommand("rm " + remotePath); 307 } 308 removeConfig(long configId)309 protected void removeConfig(long configId) throws Exception { 310 getDevice().executeShellCommand( 311 String.join(" ", REMOVE_CONFIG_CMD, 312 String.valueOf(SHELL_UID), String.valueOf(configId))); 313 } 314 315 /** Gets the statsd report and sorts it. Note that this also deletes that report from statsd. */ getEventMetricDataList()316 protected List<EventMetricData> getEventMetricDataList() throws Exception { 317 ConfigMetricsReportList reportList = getReportList(); 318 return getEventMetricDataList(reportList); 319 } 320 321 /** 322 * Gets a List of sorted ConfigMetricsReports from ConfigMetricsReportList. 323 */ getSortedConfigMetricsReports( ConfigMetricsReportList configMetricsReportList)324 protected List<ConfigMetricsReport> getSortedConfigMetricsReports( 325 ConfigMetricsReportList configMetricsReportList) { 326 return configMetricsReportList.getReportsList().stream() 327 .sorted(Comparator.comparing(ConfigMetricsReport::getCurrentReportWallClockNanos)) 328 .collect(Collectors.toList()); 329 } 330 331 /** 332 * Extracts and sorts the EventMetricData from the given ConfigMetricsReportList (which must 333 * contain a single report). 334 */ getEventMetricDataList(ConfigMetricsReportList reportList)335 protected List<EventMetricData> getEventMetricDataList(ConfigMetricsReportList reportList) 336 throws Exception { 337 assertThat(reportList.getReportsCount()).isEqualTo(1); 338 ConfigMetricsReport report = reportList.getReports(0); 339 340 List<EventMetricData> data = new ArrayList<>(); 341 for (StatsLogReport metric : report.getMetricsList()) { 342 for (EventMetricData metricData : 343 metric.getEventMetrics().getDataList()) { 344 if (metricData.hasAtom()) { 345 data.add(metricData); 346 } else { 347 data.addAll(backfillAggregatedAtomsInEventMetric(metricData)); 348 } 349 } 350 } 351 data.sort(Comparator.comparing(EventMetricData::getElapsedTimestampNanos)); 352 353 LogUtil.CLog.d("Get EventMetricDataList as following:\n"); 354 for (EventMetricData d : data) { 355 LogUtil.CLog.d("Atom at " + d.getElapsedTimestampNanos() + ":\n" + d.getAtom().toString()); 356 } 357 return data; 358 } 359 getGaugeMetricDataList()360 protected List<Atom> getGaugeMetricDataList() throws Exception { 361 return getGaugeMetricDataList(/*checkTimestampTruncated=*/false); 362 } 363 getGaugeMetricDataList(boolean checkTimestampTruncated)364 protected List<Atom> getGaugeMetricDataList(boolean checkTimestampTruncated) throws Exception { 365 ConfigMetricsReportList reportList = getReportList(); 366 assertThat(reportList.getReportsCount()).isEqualTo(1); 367 368 // only config 369 ConfigMetricsReport report = reportList.getReports(0); 370 assertThat(report.getMetricsCount()).isEqualTo(1); 371 372 List<Atom> data = new ArrayList<>(); 373 for (GaugeMetricData gaugeMetricData : 374 report.getMetrics(0).getGaugeMetrics().getDataList()) { 375 assertThat(gaugeMetricData.getBucketInfoCount()).isEqualTo(1); 376 GaugeBucketInfo bucketInfo = gaugeMetricData.getBucketInfo(0); 377 if (bucketInfo.getAtomCount() != 0) { 378 for (Atom atom : bucketInfo.getAtomList()) { 379 data.add(atom); 380 } 381 } else { 382 backFillGaugeBucketAtoms(bucketInfo.getAggregatedAtomInfoList()); 383 } 384 if (checkTimestampTruncated) { 385 for (long timestampNs : bucketInfo.getElapsedTimestampNanosList()) { 386 assertTimestampIsTruncated(timestampNs); 387 } 388 } 389 } 390 391 LogUtil.CLog.d("Get GaugeMetricDataList as following:\n"); 392 for (Atom d : data) { 393 LogUtil.CLog.d("Atom:\n" + d.toString()); 394 } 395 return data; 396 } 397 backFillGaugeBucketAtoms( List<StatsLog.AggregatedAtomInfo> atomInfoList)398 private List<Atom> backFillGaugeBucketAtoms( 399 List<StatsLog.AggregatedAtomInfo> atomInfoList) { 400 List<Pair<Atom, Long>> atomTimestamp = new ArrayList<>(); 401 for (StatsLog.AggregatedAtomInfo atomInfo : atomInfoList) { 402 for (long timestampNs : atomInfo.getElapsedTimestampNanosList()) { 403 atomTimestamp.add(Pair.create(atomInfo.getAtom(), timestampNs)); 404 } 405 } 406 atomTimestamp.sort(Comparator.comparing(o -> o.second)); 407 return atomTimestamp.stream().map(p -> p.first).collect(Collectors.toList()); 408 } 409 backfillGaugeMetricData(GaugeMetricDataWrapper dataWrapper)410 protected GaugeMetricDataWrapper backfillGaugeMetricData(GaugeMetricDataWrapper dataWrapper) { 411 GaugeMetricDataWrapper.Builder dataWrapperBuilder = dataWrapper.toBuilder(); 412 List<GaugeMetricData> backfilledMetricData = new ArrayList<>(); 413 for (GaugeMetricData gaugeMetricData : dataWrapperBuilder.getDataList()) { 414 GaugeMetricData.Builder gaugeMetricDataBuilder = gaugeMetricData.toBuilder(); 415 List<GaugeBucketInfo> backfilledBuckets = new ArrayList<>(); 416 for (GaugeBucketInfo bucketInfo : gaugeMetricData.getBucketInfoList()) { 417 backfilledBuckets.add(backfillGaugeBucket(bucketInfo.toBuilder())); 418 } 419 gaugeMetricDataBuilder.clearBucketInfo(); 420 gaugeMetricDataBuilder.addAllBucketInfo(backfilledBuckets); 421 backfilledMetricData.add(gaugeMetricDataBuilder.build()); 422 } 423 dataWrapperBuilder.clearData(); 424 dataWrapperBuilder.addAllData(backfilledMetricData); 425 return dataWrapperBuilder.build(); 426 } 427 backfillGaugeBucket(GaugeBucketInfo.Builder bucketInfoBuilder)428 private GaugeBucketInfo backfillGaugeBucket(GaugeBucketInfo.Builder bucketInfoBuilder) { 429 if (bucketInfoBuilder.getAtomCount() != 0) { 430 return bucketInfoBuilder.build(); 431 } 432 List<Pair<Atom, Long>> atomTimestampData = new ArrayList<>(); 433 for (StatsLog.AggregatedAtomInfo atomInfo : bucketInfoBuilder.getAggregatedAtomInfoList()) { 434 for (long timestampNs : atomInfo.getElapsedTimestampNanosList()) { 435 atomTimestampData.add(Pair.create(atomInfo.getAtom(), timestampNs)); 436 } 437 } 438 atomTimestampData.sort(Comparator.comparing(o -> o.second)); 439 bucketInfoBuilder.clearAggregatedAtomInfo(); 440 for (Pair<Atom, Long> atomTimestamp : atomTimestampData) { 441 bucketInfoBuilder.addAtom(atomTimestamp.first); 442 bucketInfoBuilder.addElapsedTimestampNanos(atomTimestamp.second); 443 } 444 return bucketInfoBuilder.build(); 445 } 446 447 /** 448 * Gets the statsd report and extract duration metric data. 449 * Note that this also deletes that report from statsd. 450 */ getDurationMetricDataList()451 protected List<DurationMetricData> getDurationMetricDataList() throws Exception { 452 ConfigMetricsReportList reportList = getReportList(); 453 assertThat(reportList.getReportsCount()).isEqualTo(1); 454 ConfigMetricsReport report = reportList.getReports(0); 455 456 List<DurationMetricData> data = new ArrayList<>(); 457 for (StatsLogReport metric : report.getMetricsList()) { 458 data.addAll(metric.getDurationMetrics().getDataList()); 459 } 460 461 LogUtil.CLog.d("Got DurationMetricDataList as following:\n"); 462 for (DurationMetricData d : data) { 463 LogUtil.CLog.d("Duration " + d); 464 } 465 return data; 466 } 467 468 /** 469 * Gets the statsd report and extract count metric data. 470 * Note that this also deletes that report from statsd. 471 */ getCountMetricDataList()472 protected List<CountMetricData> getCountMetricDataList() throws Exception { 473 ConfigMetricsReportList reportList = getReportList(); 474 assertThat(reportList.getReportsCount()).isEqualTo(1); 475 ConfigMetricsReport report = reportList.getReports(0); 476 477 List<CountMetricData> data = new ArrayList<>(); 478 for (StatsLogReport metric : report.getMetricsList()) { 479 data.addAll(metric.getCountMetrics().getDataList()); 480 } 481 482 LogUtil.CLog.d("Got CountMetricDataList as following:\n"); 483 for (CountMetricData d : data) { 484 LogUtil.CLog.d("Count " + d); 485 } 486 return data; 487 } 488 489 /** 490 * Gets the statsd report and extract value metric data. 491 * Note that this also deletes that report from statsd. 492 */ getValueMetricDataList()493 protected List<ValueMetricData> getValueMetricDataList() throws Exception { 494 ConfigMetricsReportList reportList = getReportList(); 495 assertThat(reportList.getReportsCount()).isEqualTo(1); 496 ConfigMetricsReport report = reportList.getReports(0); 497 498 List<ValueMetricData> data = new ArrayList<>(); 499 for (StatsLogReport metric : report.getMetricsList()) { 500 data.addAll(metric.getValueMetrics().getDataList()); 501 } 502 503 LogUtil.CLog.d("Got ValueMetricDataList as following:\n"); 504 for (ValueMetricData d : data) { 505 LogUtil.CLog.d("Value " + d); 506 } 507 return data; 508 } 509 getStatsLogReport()510 protected StatsLogReport getStatsLogReport() throws Exception { 511 ConfigMetricsReport report = getConfigMetricsReport(); 512 assertThat(report.hasUidMap()).isTrue(); 513 assertThat(report.getMetricsCount()).isEqualTo(1); 514 return report.getMetrics(0); 515 } 516 getConfigMetricsReport()517 protected ConfigMetricsReport getConfigMetricsReport() throws Exception { 518 ConfigMetricsReportList reportList = getReportList(); 519 assertThat(reportList.getReportsCount()).isEqualTo(1); 520 return reportList.getReports(0); 521 } 522 523 /** Gets the statsd report. Note that this also deletes that report from statsd. */ getReportList()524 protected ConfigMetricsReportList getReportList() throws Exception { 525 try { 526 ConfigMetricsReportList reportList = getDump(ConfigMetricsReportList.parser(), 527 String.join(" ", DUMP_REPORT_CMD, String.valueOf(SHELL_UID), 528 String.valueOf(CONFIG_ID), "--include_current_bucket", "--proto")); 529 return reportList; 530 } catch (com.google.protobuf.InvalidProtocolBufferException e) { 531 LogUtil.CLog.e("Failed to fetch and parse the statsd output report. " 532 + "Perhaps there is not a valid statsd config for the requested " 533 + "uid=" + getHostUid() + ", id=" + CONFIG_ID + "."); 534 throw (e); 535 } 536 } 537 getBatteryStatsProto()538 protected BatteryStatsProto getBatteryStatsProto() throws Exception { 539 try { 540 BatteryStatsProto batteryStatsProto = getDump(BatteryStatsServiceDumpProto.parser(), 541 String.join(" ", DUMP_BATTERYSTATS_CMD, 542 "--proto")).getBatterystats(); 543 LogUtil.CLog.d("Got batterystats:\n " + batteryStatsProto.toString()); 544 return batteryStatsProto; 545 } catch (com.google.protobuf.InvalidProtocolBufferException e) { 546 LogUtil.CLog.e("Failed to dump batterystats proto"); 547 throw (e); 548 } 549 } 550 551 /** Gets reports from the statsd data incident section from the stats dumpsys. */ getReportsFromStatsDataDumpProto()552 protected List<ConfigMetricsReportList> getReportsFromStatsDataDumpProto() throws Exception { 553 try { 554 StatsDataDumpProto statsProto = getDump(StatsDataDumpProto.parser(), 555 String.join(" ", DUMPSYS_STATS_CMD, "--proto")); 556 // statsProto holds repeated bytes, which we must parse into ConfigMetricsReportLists. 557 List<ConfigMetricsReportList> reports 558 = new ArrayList<>(statsProto.getConfigMetricsReportListCount()); 559 for (ByteString reportListBytes : statsProto.getConfigMetricsReportListList()) { 560 reports.add(ConfigMetricsReportList.parseFrom(reportListBytes)); 561 } 562 LogUtil.CLog.d("Got dumpsys stats output:\n " + reports.toString()); 563 return reports; 564 } catch (com.google.protobuf.InvalidProtocolBufferException e) { 565 LogUtil.CLog.e("Failed to dumpsys stats proto"); 566 throw (e); 567 } 568 } 569 getProcStatsProto()570 protected List<ProcessStatsProto> getProcStatsProto() throws Exception { 571 try { 572 573 List<ProcessStatsProto> processStatsProtoList = 574 new ArrayList<ProcessStatsProto>(); 575 android.service.procstats.ProcessStatsSectionProto sectionProto = getDump( 576 ProcessStatsServiceDumpProto.parser(), 577 String.join(" ", DUMP_PROCSTATS_CMD, 578 "--proto")).getProcstatsNow(); 579 for (android.service.procstats.ProcessStatsProto stats : 580 sectionProto.getProcessStatsList()) { 581 ProcessStatsProto procStats = ProcessStatsProto.parser().parseFrom( 582 stats.toByteArray()); 583 processStatsProtoList.add(procStats); 584 } 585 LogUtil.CLog.d("Got procstats:\n "); 586 for (ProcessStatsProto processStatsProto : processStatsProtoList) { 587 LogUtil.CLog.d(processStatsProto.toString()); 588 } 589 return processStatsProtoList; 590 } catch (com.google.protobuf.InvalidProtocolBufferException e) { 591 LogUtil.CLog.e("Failed to dump procstats proto"); 592 throw (e); 593 } 594 } 595 596 /* 597 * Get all procstats package data in proto 598 */ getAllProcStatsProto()599 protected List<ProcessStatsPackageProto> getAllProcStatsProto() throws Exception { 600 try { 601 android.service.procstats.ProcessStatsSectionProto sectionProto = getDump( 602 ProcessStatsServiceDumpProto.parser(), 603 String.join(" ", DUMP_PROCSTATS_CMD, 604 "--proto")).getProcstatsOver24Hrs(); 605 List<ProcessStatsPackageProto> processStatsProtoList = 606 new ArrayList<ProcessStatsPackageProto>(); 607 for (android.service.procstats.ProcessStatsPackageProto pkgStast : 608 sectionProto.getPackageStatsList()) { 609 ProcessStatsPackageProto pkgAtom = 610 ProcessStatsPackageProto.parser().parseFrom(pkgStast.toByteArray()); 611 processStatsProtoList.add(pkgAtom); 612 } 613 LogUtil.CLog.d("Got procstats:\n "); 614 for (ProcessStatsPackageProto processStatsProto : processStatsProtoList) { 615 LogUtil.CLog.d(processStatsProto.toString()); 616 } 617 return processStatsProtoList; 618 } catch (com.google.protobuf.InvalidProtocolBufferException e) { 619 LogUtil.CLog.e("Failed to dump procstats proto"); 620 throw (e); 621 } 622 } 623 624 /* 625 * Get all processes' procstats statsd data in proto 626 */ getAllProcStatsProtoForStatsd()627 protected List<android.service.procstats.ProcessStatsProto> getAllProcStatsProtoForStatsd() 628 throws Exception { 629 try { 630 android.service.procstats.ProcessStatsSectionProto sectionProto = getDump( 631 android.service.procstats.ProcessStatsSectionProto.parser(), 632 String.join(" ", DUMP_PROCSTATS_CMD, 633 "--statsd")); 634 List<android.service.procstats.ProcessStatsProto> processStatsProtoList 635 = sectionProto.getProcessStatsList(); 636 LogUtil.CLog.d("Got procstats:\n "); 637 for (android.service.procstats.ProcessStatsProto processStatsProto 638 : processStatsProtoList) { 639 LogUtil.CLog.d(processStatsProto.toString()); 640 } 641 return processStatsProtoList; 642 } catch (com.google.protobuf.InvalidProtocolBufferException e) { 643 LogUtil.CLog.e("Failed to dump procstats proto"); 644 throw (e); 645 } 646 } 647 hasBattery()648 protected boolean hasBattery() throws Exception { 649 try { 650 BatteryServiceDumpProto batteryProto = getDump(BatteryServiceDumpProto.parser(), 651 String.join(" ", DUMP_BATTERY_CMD, "--proto")); 652 LogUtil.CLog.d("Got battery service dump:\n " + batteryProto.toString()); 653 return batteryProto.getIsPresent(); 654 } catch (com.google.protobuf.InvalidProtocolBufferException e) { 655 LogUtil.CLog.e("Failed to dump batteryservice proto"); 656 throw (e); 657 } 658 } 659 660 /** Creates a FieldValueMatcher.Builder corresponding to the given field. */ createFvm(int field)661 protected static FieldValueMatcher.Builder createFvm(int field) { 662 return FieldValueMatcher.newBuilder().setField(field); 663 } 664 addAtomEvent(StatsdConfig.Builder conf, int atomTag)665 protected void addAtomEvent(StatsdConfig.Builder conf, int atomTag) throws Exception { 666 addAtomEvent(conf, atomTag, new ArrayList<FieldValueMatcher.Builder>()); 667 } 668 669 /** 670 * Adds an event to the config for an atom that matches the given key. 671 * 672 * @param conf configuration 673 * @param atomTag atom tag (from atoms.proto) 674 * @param fvm FieldValueMatcher.Builder for the relevant key 675 */ addAtomEvent(StatsdConfig.Builder conf, int atomTag, FieldValueMatcher.Builder fvm)676 protected void addAtomEvent(StatsdConfig.Builder conf, int atomTag, 677 FieldValueMatcher.Builder fvm) 678 throws Exception { 679 addAtomEvent(conf, atomTag, Arrays.asList(fvm)); 680 } 681 682 /** 683 * Adds an event to the config for an atom that matches the given keys. 684 * 685 * @param conf configuration 686 * @param atomId atom tag (from atoms.proto) 687 * @param fvms list of FieldValueMatcher.Builders to attach to the atom. May be null. 688 */ addAtomEvent(StatsdConfig.Builder conf, int atomId, List<FieldValueMatcher.Builder> fvms)689 protected void addAtomEvent(StatsdConfig.Builder conf, int atomId, 690 List<FieldValueMatcher.Builder> fvms) throws Exception { 691 692 final String atomName = "Atom" + System.nanoTime(); 693 final String eventName = "Event" + System.nanoTime(); 694 695 SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder().setAtomId(atomId); 696 if (fvms != null) { 697 for (FieldValueMatcher.Builder fvm : fvms) { 698 sam.addFieldValueMatcher(fvm); 699 } 700 } 701 conf.addAtomMatcher(AtomMatcher.newBuilder() 702 .setId(atomName.hashCode()) 703 .setSimpleAtomMatcher(sam)); 704 conf.addEventMetric(EventMetric.newBuilder() 705 .setId(eventName.hashCode()) 706 .setWhat(atomName.hashCode())); 707 } 708 709 /** 710 * Adds an atom to a gauge metric of a config 711 * 712 * @param conf configuration 713 * @param atomId atom id (from atoms.proto) 714 * @param gaugeMetric the gauge metric to add 715 */ addGaugeAtom(StatsdConfig.Builder conf, int atomId, GaugeMetric.Builder gaugeMetric)716 protected void addGaugeAtom(StatsdConfig.Builder conf, int atomId, 717 GaugeMetric.Builder gaugeMetric) throws Exception { 718 final String atomName = "Atom" + System.nanoTime(); 719 final String gaugeName = "Gauge" + System.nanoTime(); 720 final String predicateName = "APP_BREADCRUMB"; 721 SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder().setAtomId(atomId); 722 conf.addAtomMatcher(AtomMatcher.newBuilder() 723 .setId(atomName.hashCode()) 724 .setSimpleAtomMatcher(sam)); 725 final String predicateTrueName = "APP_BREADCRUMB_1"; 726 final String predicateFalseName = "APP_BREADCRUMB_2"; 727 conf.addAtomMatcher(AtomMatcher.newBuilder() 728 .setId(predicateTrueName.hashCode()) 729 .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder() 730 .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER) 731 .addFieldValueMatcher(FieldValueMatcher.newBuilder() 732 .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER) 733 .setEqInt(1) 734 ) 735 ) 736 ) 737 // Used to trigger predicate 738 .addAtomMatcher(AtomMatcher.newBuilder() 739 .setId(predicateFalseName.hashCode()) 740 .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder() 741 .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER) 742 .addFieldValueMatcher(FieldValueMatcher.newBuilder() 743 .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER) 744 .setEqInt(2) 745 ) 746 ) 747 ); 748 conf.addPredicate(Predicate.newBuilder() 749 .setId(predicateName.hashCode()) 750 .setSimplePredicate(SimplePredicate.newBuilder() 751 .setStart(predicateTrueName.hashCode()) 752 .setStop(predicateFalseName.hashCode()) 753 .setCountNesting(false) 754 ) 755 ); 756 gaugeMetric 757 .setId(gaugeName.hashCode()) 758 .setWhat(atomName.hashCode()) 759 .setCondition(predicateName.hashCode()); 760 conf.addGaugeMetric(gaugeMetric.build()); 761 } 762 763 /** 764 * Adds an atom to a gauge metric of a config 765 * 766 * @param conf configuration 767 * @param atomId atom id (from atoms.proto) 768 * @param dimension dimension is needed for most pulled atoms 769 */ addGaugeAtomWithDimensions(StatsdConfig.Builder conf, int atomId, @Nullable FieldMatcher.Builder dimension)770 protected void addGaugeAtomWithDimensions(StatsdConfig.Builder conf, int atomId, 771 @Nullable FieldMatcher.Builder dimension) throws Exception { 772 GaugeMetric.Builder gaugeMetric = GaugeMetric.newBuilder() 773 .setGaugeFieldsFilter(FieldFilter.newBuilder().setIncludeAll(true).build()) 774 .setSamplingType(GaugeMetric.SamplingType.CONDITION_CHANGE_TO_TRUE) 775 .setMaxNumGaugeAtomsPerBucket(10000) 776 .setBucket(TimeUnit.CTS); 777 if (dimension != null) { 778 gaugeMetric.setDimensionsInWhat(dimension.build()); 779 } 780 addGaugeAtom(conf, atomId, gaugeMetric); 781 } 782 783 /** 784 * Asserts that each set of states in stateSets occurs at least once in data. 785 * Asserts that the states in data occur in the same order as the sets in stateSets. 786 * 787 * @param stateSets A list of set of states, where each set represents an equivalent 788 * state of the device for the purpose of CTS. 789 * @param data list of EventMetricData from statsd, produced by 790 * getReportMetricListData() 791 * @param wait expected duration (in ms) between state changes; asserts that the 792 * actual wait 793 * time was wait/2 <= actual_wait <= 5*wait. Use 0 to ignore this 794 * assertion. 795 * @param getStateFromAtom expression that takes in an Atom and returns the state it contains 796 */ assertStatesOccurred(List<Set<Integer>> stateSets, List<EventMetricData> data, int wait, Function<Atom, Integer> getStateFromAtom)797 public void assertStatesOccurred(List<Set<Integer>> stateSets, List<EventMetricData> data, 798 int wait, Function<Atom, Integer> getStateFromAtom) { 799 // Sometimes, there are more events than there are states. 800 // Eg: When the screen turns off, it may go into OFF and then DOZE immediately. 801 assertWithMessage("Too few states found").that(data.size()).isAtLeast(stateSets.size()); 802 int stateSetIndex = 0; // Tracks which state set we expect the data to be in. 803 for (int dataIndex = 0; dataIndex < data.size(); dataIndex++) { 804 Atom atom = data.get(dataIndex).getAtom(); 805 int state = getStateFromAtom.apply(atom); 806 // If state is in the current state set, we do not assert anything. 807 // If it is not, we expect to have transitioned to the next state set. 808 if (stateSets.get(stateSetIndex).contains(state)) { 809 // No need to assert anything. Just log it. 810 LogUtil.CLog.i("The following atom at dataIndex=" + dataIndex + " is " 811 + "in stateSetIndex " + stateSetIndex + ":\n" 812 + data.get(dataIndex).getAtom().toString()); 813 } else { 814 stateSetIndex += 1; 815 LogUtil.CLog.i("Assert that the following atom at dataIndex=" + dataIndex + " is" 816 + " in stateSetIndex " + stateSetIndex + ":\n" 817 + data.get(dataIndex).getAtom().toString()); 818 assertWithMessage("Missed first state").that(dataIndex).isNotEqualTo(0); 819 assertWithMessage("Too many states").that(stateSetIndex) 820 .isLessThan(stateSets.size()); 821 assertWithMessage(String.format("Is in wrong state (%d)", state)) 822 .that(stateSets.get(stateSetIndex)).contains(state); 823 if (wait > 0) { 824 assertTimeDiffBetween(data.get(dataIndex - 1), data.get(dataIndex), 825 wait / 2, wait * 5); 826 } 827 } 828 } 829 assertWithMessage("Too few states").that(stateSetIndex).isEqualTo(stateSets.size() - 1); 830 } 831 832 /** 833 * Removes all elements from data prior to the first occurrence of an element of state. After 834 * this method is called, the first element of data (if non-empty) is guaranteed to be an 835 * element in state. 836 * 837 * @param getStateFromAtom expression that takes in an Atom and returns the state it contains 838 */ popUntilFind(List<EventMetricData> data, Set<Integer> state, Function<Atom, Integer> getStateFromAtom)839 public void popUntilFind(List<EventMetricData> data, Set<Integer> state, 840 Function<Atom, Integer> getStateFromAtom) { 841 int firstStateIdx; 842 for (firstStateIdx = 0; firstStateIdx < data.size(); firstStateIdx++) { 843 Atom atom = data.get(firstStateIdx).getAtom(); 844 if (state.contains(getStateFromAtom.apply(atom))) { 845 break; 846 } 847 } 848 if (firstStateIdx == 0) { 849 // First first element already is in state, so there's nothing to do. 850 return; 851 } 852 data.subList(0, firstStateIdx).clear(); 853 } 854 855 /** 856 * Removes all elements from data after to the last occurrence of an element of state. After 857 * this method is called, the last element of data (if non-empty) is guaranteed to be an 858 * element in state. 859 * 860 * @param getStateFromAtom expression that takes in an Atom and returns the state it contains 861 */ popUntilFindFromEnd(List<EventMetricData> data, Set<Integer> state, Function<Atom, Integer> getStateFromAtom)862 public void popUntilFindFromEnd(List<EventMetricData> data, Set<Integer> state, 863 Function<Atom, Integer> getStateFromAtom) { 864 int lastStateIdx; 865 for (lastStateIdx = data.size() - 1; lastStateIdx >= 0; lastStateIdx--) { 866 Atom atom = data.get(lastStateIdx).getAtom(); 867 if (state.contains(getStateFromAtom.apply(atom))) { 868 break; 869 } 870 } 871 if (lastStateIdx == data.size()-1) { 872 // Last element already is in state, so there's nothing to do. 873 return; 874 } 875 data.subList(lastStateIdx+1, data.size()).clear(); 876 } 877 878 /** Returns the UID of the host, which should always either be SHELL (2000). */ getHostUid()879 protected int getHostUid() throws DeviceNotAvailableException { 880 return SHELL_UID; 881 } 882 getProperty(String prop)883 protected String getProperty(String prop) throws Exception { 884 return getDevice().executeShellCommand("getprop " + prop).replace("\n", ""); 885 } 886 turnScreenOn()887 protected void turnScreenOn() throws Exception { 888 getDevice().executeShellCommand("input keyevent KEYCODE_WAKEUP"); 889 getDevice().executeShellCommand("wm dismiss-keyguard"); 890 } 891 turnScreenOff()892 protected void turnScreenOff() throws Exception { 893 getDevice().executeShellCommand("input keyevent KEYCODE_SLEEP"); 894 } 895 setChargingState(int state)896 protected void setChargingState(int state) throws Exception { 897 getDevice().executeShellCommand("cmd battery set status " + state); 898 } 899 unplugDevice()900 protected void unplugDevice() throws Exception { 901 // On batteryless devices on Android P or above, the 'unplug' command 902 // alone does not simulate the really unplugged state. 903 // 904 // This is because charging state is left as "unknown". Unless a valid 905 // state like 3 = BatteryManager.BATTERY_STATUS_DISCHARGING is set, 906 // framework does not consider the device as running on battery. 907 setChargingState(3); 908 909 getDevice().executeShellCommand("cmd battery unplug"); 910 } 911 plugInAc()912 protected void plugInAc() throws Exception { 913 getDevice().executeShellCommand("cmd battery set ac 1"); 914 } 915 plugInUsb()916 protected void plugInUsb() throws Exception { 917 getDevice().executeShellCommand("cmd battery set usb 1"); 918 } 919 plugInWireless()920 protected void plugInWireless() throws Exception { 921 getDevice().executeShellCommand("cmd battery set wireless 1"); 922 } 923 enableLooperStats()924 protected void enableLooperStats() throws Exception { 925 getDevice().executeShellCommand("cmd looper_stats enable"); 926 } 927 resetLooperStats()928 protected void resetLooperStats() throws Exception { 929 getDevice().executeShellCommand("cmd looper_stats reset"); 930 } 931 disableLooperStats()932 protected void disableLooperStats() throws Exception { 933 getDevice().executeShellCommand("cmd looper_stats disable"); 934 } 935 enableBinderStats()936 protected void enableBinderStats() throws Exception { 937 getDevice().executeShellCommand("dumpsys binder_calls_stats --enable"); 938 } 939 resetBinderStats()940 protected void resetBinderStats() throws Exception { 941 getDevice().executeShellCommand("dumpsys binder_calls_stats --reset"); 942 } 943 disableBinderStats()944 protected void disableBinderStats() throws Exception { 945 getDevice().executeShellCommand("dumpsys binder_calls_stats --disable"); 946 } 947 binderStatsNoSampling()948 protected void binderStatsNoSampling() throws Exception { 949 getDevice().executeShellCommand("dumpsys binder_calls_stats --no-sampling"); 950 } 951 setUpLooperStats()952 protected void setUpLooperStats() throws Exception { 953 getDevice().executeShellCommand("cmd looper_stats enable"); 954 getDevice().executeShellCommand("cmd looper_stats sampling_interval 1"); 955 getDevice().executeShellCommand("cmd looper_stats reset"); 956 } 957 cleanUpLooperStats()958 protected void cleanUpLooperStats() throws Exception { 959 getDevice().executeShellCommand("cmd looper_stats disable"); 960 } 961 setAppBreadcrumbPredicate()962 public void setAppBreadcrumbPredicate() throws Exception { 963 doAppBreadcrumbReportedStart(1); 964 } 965 clearAppBreadcrumbPredicate()966 public void clearAppBreadcrumbPredicate() throws Exception { 967 doAppBreadcrumbReportedStart(2); 968 } 969 doAppBreadcrumbReportedStart(int label)970 public void doAppBreadcrumbReportedStart(int label) throws Exception { 971 doAppBreadcrumbReported(label, AppBreadcrumbReported.State.START.ordinal()); 972 } 973 doAppBreadcrumbReportedStop(int label)974 public void doAppBreadcrumbReportedStop(int label) throws Exception { 975 doAppBreadcrumbReported(label, AppBreadcrumbReported.State.STOP.ordinal()); 976 } 977 doAppBreadcrumbReported(int label)978 public void doAppBreadcrumbReported(int label) throws Exception { 979 doAppBreadcrumbReported(label, AppBreadcrumbReported.State.UNSPECIFIED.ordinal()); 980 } 981 doAppBreadcrumbReported(int label, int state)982 public void doAppBreadcrumbReported(int label, int state) throws Exception { 983 getDevice().executeShellCommand(String.format( 984 "cmd stats log-app-breadcrumb %d %d %d", SHELL_UID, label, state)); 985 } 986 setBatteryLevel(int level)987 protected void setBatteryLevel(int level) throws Exception { 988 getDevice().executeShellCommand("cmd battery set level " + level); 989 } 990 resetBatteryStatus()991 protected void resetBatteryStatus() throws Exception { 992 getDevice().executeShellCommand("cmd battery reset"); 993 } 994 getScreenBrightness()995 protected int getScreenBrightness() throws Exception { 996 return Integer.parseInt( 997 getDevice().executeShellCommand("settings get system screen_brightness").trim()); 998 } 999 setScreenBrightness(int brightness)1000 protected void setScreenBrightness(int brightness) throws Exception { 1001 getDevice().executeShellCommand("settings put system screen_brightness " + brightness); 1002 } 1003 1004 // Gets whether "Always on Display" setting is enabled. 1005 // In rare cases, this is different from whether the device can enter SCREEN_STATE_DOZE. getAodState()1006 protected String getAodState() throws Exception { 1007 return getDevice().executeShellCommand("settings get secure doze_always_on"); 1008 } 1009 setAodState(String state)1010 protected void setAodState(String state) throws Exception { 1011 getDevice().executeShellCommand("settings put secure doze_always_on " + state); 1012 } 1013 isScreenBrightnessModeManual()1014 protected boolean isScreenBrightnessModeManual() throws Exception { 1015 String mode = getDevice().executeShellCommand("settings get system screen_brightness_mode"); 1016 return Integer.parseInt(mode.trim()) == 0; 1017 } 1018 setScreenBrightnessMode(boolean manual)1019 protected void setScreenBrightnessMode(boolean manual) throws Exception { 1020 getDevice().executeShellCommand( 1021 "settings put system screen_brightness_mode " + (manual ? 0 : 1)); 1022 } 1023 enterDozeModeLight()1024 protected void enterDozeModeLight() throws Exception { 1025 getDevice().executeShellCommand("dumpsys deviceidle force-idle light"); 1026 } 1027 enterDozeModeDeep()1028 protected void enterDozeModeDeep() throws Exception { 1029 getDevice().executeShellCommand("dumpsys deviceidle force-idle deep"); 1030 } 1031 leaveDozeMode()1032 protected void leaveDozeMode() throws Exception { 1033 getDevice().executeShellCommand("dumpsys deviceidle unforce"); 1034 getDevice().executeShellCommand("dumpsys deviceidle disable"); 1035 getDevice().executeShellCommand("dumpsys deviceidle enable"); 1036 } 1037 turnBatterySaverOn()1038 protected void turnBatterySaverOn() throws Exception { 1039 unplugDevice(); 1040 getDevice().executeShellCommand("settings put global low_power 1"); 1041 } 1042 turnBatterySaverOff()1043 protected void turnBatterySaverOff() throws Exception { 1044 getDevice().executeShellCommand("settings put global low_power 0"); 1045 getDevice().executeShellCommand("cmd battery reset"); 1046 } 1047 turnBatteryStatsAutoResetOn()1048 protected void turnBatteryStatsAutoResetOn() throws Exception { 1049 getDevice().executeShellCommand("dumpsys batterystats enable no-auto-reset"); 1050 } 1051 turnBatteryStatsAutoResetOff()1052 protected void turnBatteryStatsAutoResetOff() throws Exception { 1053 getDevice().executeShellCommand("dumpsys batterystats enable no-auto-reset"); 1054 } 1055 flushBatteryStatsHandlers()1056 protected void flushBatteryStatsHandlers() throws Exception { 1057 // Dumping batterystats will flush everything in the batterystats handler threads. 1058 getDevice().executeShellCommand(DUMP_BATTERYSTATS_CMD); 1059 } 1060 rebootDevice()1061 protected void rebootDevice() throws Exception { 1062 getDevice().rebootUntilOnline(); 1063 } 1064 1065 /** 1066 * Asserts that the two events are within the specified range of each other. 1067 * 1068 * @param d0 the event that should occur first 1069 * @param d1 the event that should occur second 1070 * @param minDiffMs d0 should precede d1 by at least this amount 1071 * @param maxDiffMs d0 should precede d1 by at most this amount 1072 */ assertTimeDiffBetween(EventMetricData d0, EventMetricData d1, int minDiffMs, int maxDiffMs)1073 public static void assertTimeDiffBetween(EventMetricData d0, EventMetricData d1, 1074 int minDiffMs, int maxDiffMs) { 1075 long diffMs = (d1.getElapsedTimestampNanos() - d0.getElapsedTimestampNanos()) / 1_000_000; 1076 assertWithMessage("Illegal time difference") 1077 .that(diffMs).isIn(Range.closed((long) minDiffMs, (long) maxDiffMs)); 1078 } 1079 getCurrentLogcatDate()1080 protected String getCurrentLogcatDate() throws Exception { 1081 // TODO: Do something more robust than this for getting logcat markers. 1082 long timestampMs = getDevice().getDeviceDate(); 1083 return new SimpleDateFormat("MM-dd HH:mm:ss.SSS") 1084 .format(new Date(timestampMs)); 1085 } 1086 getLogcatSince(String date, String logcatParams)1087 protected String getLogcatSince(String date, String logcatParams) throws Exception { 1088 return getDevice().executeShellCommand(String.format( 1089 "logcat -v threadtime -t '%s' -d %s", date, logcatParams)); 1090 } 1091 1092 // TODO: Remove this and migrate all usages to createConfigBuilder() getPulledConfig()1093 protected StatsdConfig.Builder getPulledConfig() { 1094 return createConfigBuilder(); 1095 } 1096 /** 1097 * Determines if the device has the given feature. 1098 * Prints a warning if its value differs from requiredAnswer. 1099 */ hasFeature(String featureName, boolean requiredAnswer)1100 protected boolean hasFeature(String featureName, boolean requiredAnswer) throws Exception { 1101 final String features = getDevice().executeShellCommand("pm list features"); 1102 StringTokenizer featureToken = new StringTokenizer(features, "\n"); 1103 boolean hasIt = false; 1104 1105 while (featureToken.hasMoreTokens()) { 1106 if (("feature:" + featureName).equals(featureToken.nextToken())) { 1107 hasIt = true; 1108 break; 1109 } 1110 } 1111 1112 if (hasIt != requiredAnswer) { 1113 LogUtil.CLog.w("Device does " + (requiredAnswer ? "not " : "") + "have feature " 1114 + featureName); 1115 } 1116 return hasIt == requiredAnswer; 1117 } 1118 1119 /** 1120 * Determines if the device has |file|. 1121 */ doesFileExist(String file)1122 protected boolean doesFileExist(String file) throws Exception { 1123 return getDevice().doesFileExist(file); 1124 } 1125 turnOnAirplaneMode()1126 protected void turnOnAirplaneMode() throws Exception { 1127 getDevice().executeShellCommand("cmd connectivity airplane-mode enable"); 1128 } 1129 turnOffAirplaneMode()1130 protected void turnOffAirplaneMode() throws Exception { 1131 getDevice().executeShellCommand("cmd connectivity airplane-mode disable"); 1132 } 1133 1134 /** 1135 * Returns a list of fields and values for {@code className} from {@link TelephonyDebugService} 1136 * output. 1137 * 1138 * <p>Telephony dumpsys output does not support proto at the moment. This method provides 1139 * limited support for parsing its output. Specifically, it does not support arrays or 1140 * multi-line values. 1141 */ getTelephonyDumpEntries(String className)1142 private List<Map<String, String>> getTelephonyDumpEntries(String className) throws Exception { 1143 // Matches any line with indentation, except for lines with only spaces 1144 Pattern indentPattern = Pattern.compile("^(\\s*)[^ ].*$"); 1145 // Matches pattern for class, e.g. " Phone:" 1146 Pattern classNamePattern = Pattern.compile("^(\\s*)" + Pattern.quote(className) + ":.*$"); 1147 // Matches pattern for key-value pairs, e.g. " mPhoneId=1" 1148 Pattern keyValuePattern = Pattern.compile("^(\\s*)([a-zA-Z]+[a-zA-Z0-9_]*)\\=(.+)$"); 1149 String response = 1150 getDevice().executeShellCommand("dumpsys activity service TelephonyDebugService"); 1151 Queue<String> responseLines = new LinkedList<>(Arrays.asList(response.split("[\\r\\n]+"))); 1152 1153 List<Map<String, String>> results = new ArrayList<>(); 1154 while (responseLines.peek() != null) { 1155 Matcher matcher = classNamePattern.matcher(responseLines.poll()); 1156 if (matcher.matches()) { 1157 final int classIndentLevel = matcher.group(1).length(); 1158 final Map<String, String> instanceEntries = new HashMap<>(); 1159 while (responseLines.peek() != null) { 1160 // Skip blank lines 1161 matcher = indentPattern.matcher(responseLines.peek()); 1162 if (responseLines.peek().length() == 0 || !matcher.matches()) { 1163 responseLines.poll(); 1164 continue; 1165 } 1166 // Finish (without consuming the line) if already parsed past this instance 1167 final int indentLevel = matcher.group(1).length(); 1168 if (indentLevel <= classIndentLevel) { 1169 break; 1170 } 1171 // Parse key-value pair if it belongs to the instance directly 1172 matcher = keyValuePattern.matcher(responseLines.poll()); 1173 if (indentLevel == classIndentLevel + 1 && matcher.matches()) { 1174 instanceEntries.put(matcher.group(2), matcher.group(3)); 1175 } 1176 } 1177 results.add(instanceEntries); 1178 } 1179 } 1180 return results; 1181 } 1182 getActiveSimSlotCount()1183 protected int getActiveSimSlotCount() throws Exception { 1184 List<Map<String, String>> slots = getTelephonyDumpEntries("UiccSlot"); 1185 long count = slots.stream().filter(slot -> "true".equals(slot.get("mActive"))).count(); 1186 return Math.toIntExact(count); 1187 } 1188 1189 /** 1190 * Returns the upper bound of active SIM profile count. 1191 * 1192 * <p>The value is an upper bound as eSIMs without profiles are also counted in. 1193 */ getActiveSimCountUpperBound()1194 protected int getActiveSimCountUpperBound() throws Exception { 1195 List<Map<String, String>> slots = getTelephonyDumpEntries("UiccSlot"); 1196 long count = slots.stream().filter(slot -> 1197 "true".equals(slot.get("mActive")) 1198 && "CARDSTATE_PRESENT".equals(slot.get("mCardState"))).count(); 1199 return Math.toIntExact(count); 1200 } 1201 1202 /** 1203 * Returns the upper bound of active eSIM profile count. 1204 * 1205 * <p>The value is an upper bound as eSIMs without profiles are also counted in. 1206 */ getActiveEsimCountUpperBound()1207 protected int getActiveEsimCountUpperBound() throws Exception { 1208 List<Map<String, String>> slots = getTelephonyDumpEntries("UiccSlot"); 1209 long count = slots.stream().filter(slot -> 1210 "true".equals(slot.get("mActive")) 1211 && "CARDSTATE_PRESENT".equals(slot.get("mCardState")) 1212 && "true".equals(slot.get("mIsEuicc"))).count(); 1213 return Math.toIntExact(count); 1214 } 1215 hasGsmPhone()1216 protected boolean hasGsmPhone() throws Exception { 1217 // Not using log entries or ServiceState in the dump since they may or may not be present, 1218 // which can make the test flaky 1219 return getTelephonyDumpEntries("Phone").stream() 1220 .anyMatch(phone -> 1221 String.format("%d", PHONE_TYPE_GSM).equals(phone.get("getPhoneType()"))); 1222 } 1223 hasCdmaPhone()1224 protected boolean hasCdmaPhone() throws Exception { 1225 // Not using log entries or ServiceState in the dump due to the same reason as hasGsmPhone() 1226 return getTelephonyDumpEntries("Phone").stream() 1227 .anyMatch(phone -> 1228 String.format("%d", PHONE_TYPE_CDMA).equals(phone.get("getPhoneType()")) 1229 || String.format("%d", PHONE_TYPE_CDMA_LTE) 1230 .equals(phone.get("getPhoneType()"))); 1231 } 1232 1233 // Checks that a timestamp has been truncated to be a multiple of 5 min assertTimestampIsTruncated(long timestampNs)1234 protected void assertTimestampIsTruncated(long timestampNs) { 1235 long fiveMinutesInNs = NS_PER_SEC * 5 * 60; 1236 assertWithMessage("Timestamp is not truncated") 1237 .that(timestampNs % fiveMinutesInNs).isEqualTo(0); 1238 } 1239 backfillAggregatedAtomsInEventMetric( EventMetricData metricData)1240 protected List<EventMetricData> backfillAggregatedAtomsInEventMetric( 1241 EventMetricData metricData) { 1242 if (!metricData.hasAggregatedAtomInfo()) { 1243 return Collections.singletonList(metricData); 1244 } 1245 List<EventMetricData> data = new ArrayList<>(); 1246 StatsLog.AggregatedAtomInfo atomInfo = metricData.getAggregatedAtomInfo(); 1247 for (long timestamp : atomInfo.getElapsedTimestampNanosList()) { 1248 data.add(EventMetricData.newBuilder() 1249 .setAtom(atomInfo.getAtom()) 1250 .setElapsedTimestampNanos(timestamp) 1251 .build()); 1252 } 1253 return data; 1254 } 1255 } 1256