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