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