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