1 /* 2 * Copyright (C) 2018 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 com.android.statsd.shelltools.testdrive; 17 18 import com.android.internal.os.StatsdConfigProto; 19 import com.android.internal.os.StatsdConfigProto.AtomMatcher; 20 import com.android.internal.os.StatsdConfigProto.EventMetric; 21 import com.android.internal.os.StatsdConfigProto.FieldFilter; 22 import com.android.internal.os.StatsdConfigProto.GaugeMetric; 23 import com.android.internal.os.StatsdConfigProto.PullAtomPackages; 24 import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher; 25 import com.android.internal.os.StatsdConfigProto.StatsdConfig; 26 import com.android.internal.os.StatsdConfigProto.TimeUnit; 27 import com.android.os.AtomsProto; 28 import com.android.os.AtomsProto.Atom; 29 import com.android.os.StatsLog; 30 import com.android.os.StatsLog.ConfigMetricsReport; 31 import com.android.os.StatsLog.ConfigMetricsReportList; 32 import com.android.os.StatsLog.StatsLogReport; 33 import com.android.os.telephony.qns.QnsExtensionAtoms; 34 import com.android.statsd.shelltools.Utils; 35 36 import com.google.common.annotations.VisibleForTesting; 37 import com.google.common.io.Files; 38 import com.google.protobuf.CodedInputStream; 39 import com.google.protobuf.CodedOutputStream; 40 import com.google.protobuf.DescriptorProtos; 41 import com.google.protobuf.Descriptors; 42 import com.google.protobuf.DynamicMessage; 43 44 import java.io.ByteArrayInputStream; 45 import java.io.ByteArrayOutputStream; 46 import java.io.File; 47 import java.io.FileInputStream; 48 import java.io.IOException; 49 import java.io.InputStream; 50 import java.nio.file.Path; 51 import java.nio.file.Paths; 52 import java.util.ArrayList; 53 import java.util.Arrays; 54 import java.util.Collections; 55 import java.util.HashSet; 56 import java.util.List; 57 import java.util.Scanner; 58 import java.util.Set; 59 import java.util.TreeSet; 60 import java.util.logging.Level; 61 import java.util.logging.Logger; 62 63 public class TestDrive { 64 65 private static final int METRIC_ID_BASE = 1111; 66 private static final long ATOM_MATCHER_ID_BASE = 1234567; 67 private static final long APP_BREADCRUMB_MATCHER_ID = 1111111; 68 private static final int PULL_ATOM_START = 10000; 69 private static final int MAX_PLATFORM_ATOM_TAG = 100000; 70 private static final int VENDOR_PULLED_ATOM_START_TAG = 150000; 71 private static final long CONFIG_ID = 54321; 72 private static final String[] ALLOWED_LOG_SOURCES = { 73 "AID_GRAPHICS", 74 "AID_INCIDENTD", 75 "AID_STATSD", 76 "AID_RADIO", 77 "com.android.systemui", 78 "com.android.vending", 79 "AID_SYSTEM", 80 "AID_ROOT", 81 "AID_BLUETOOTH", 82 "AID_LMKD", 83 "com.android.managedprovisioning", 84 "AID_MEDIA", 85 "AID_NETWORK_STACK", 86 "com.google.android.providers.media.module", 87 "com.android.imsserviceentitlement", 88 "com.google.android.cellbroadcastreceiver", 89 "com.google.android.apps.nexuslauncher", 90 "com.google.android.markup", 91 "com.android.art", 92 "com.google.android.art", 93 "AID_KEYSTORE", 94 "AID_VIRTUALIZATIONSERVICE", 95 "com.google.android.permissioncontroller", 96 "AID_NFC", 97 "AID_SECURE_ELEMENT", 98 "com.google.android.wearable.media.routing", 99 "com.google.android.healthconnect.controller", 100 "com.android.telephony.qns", 101 }; 102 private static final String[] DEFAULT_PULL_SOURCES = { 103 "AID_KEYSTORE", "AID_RADIO", "AID_SYSTEM", 104 }; 105 private static final Logger LOGGER = Logger.getLogger(TestDrive.class.getName()); 106 private static final String HW_ATOMS_PROTO_FILEPATH = 107 "hardware/google/pixel/pixelstats/pixelatoms.proto"; 108 109 @VisibleForTesting 110 String mDeviceSerial = null; 111 @VisibleForTesting 112 Dumper mDumper = new BasicDumper(); 113 boolean mPressToContinue = false; 114 Integer mReportCollectionDelayMillis = 60_000; 115 List<String> mProtoIncludes = new ArrayList<>(); 116 main(String[] args)117 public static void main(String[] args) { 118 final Configuration configuration = new Configuration(); 119 final TestDrive testDrive = new TestDrive(); 120 Utils.setUpLogger(LOGGER, false); 121 122 if (!testDrive.processArgs( 123 configuration, args, Utils.getDeviceSerials(LOGGER), 124 Utils.getDefaultDevice(LOGGER))) { 125 return; 126 } 127 128 final ConfigMetricsReportList reports = 129 testDrive.testDriveAndGetReports( 130 configuration.createConfig(), 131 configuration.hasPulledAtoms(), 132 configuration.hasPushedAtoms()); 133 if (reports != null) { 134 configuration.dumpMetrics(reports, testDrive.mDumper); 135 } 136 } 137 printUsageMessage()138 static void printUsageMessage() { 139 LOGGER.severe("Usage: ./test_drive [options] <atomId1> <atomId2> ... <atomIdN>"); 140 LOGGER.severe("OPTIONS"); 141 LOGGER.severe("-h, --help"); 142 LOGGER.severe("\tPrint this message"); 143 LOGGER.severe("-one"); 144 LOGGER.severe("\tCreating one event metric to catch all pushed atoms"); 145 LOGGER.severe("-i"); 146 LOGGER.severe("\tPath to proto file to include (pixelatoms.proto, etc.)"); 147 LOGGER.severe("\tPath is absolute or relative to current dir or to ANDROID_BUILD_TOP"); 148 LOGGER.severe("-terse"); 149 LOGGER.severe("\tTerse output format."); 150 LOGGER.severe("-p additional_allowed_packages_csv"); 151 LOGGER.severe("\tAllows collection atoms from an additional packages"); 152 LOGGER.severe("-s DEVICE_SERIAL_NUMBER"); 153 LOGGER.severe("\tDevice serial number to use for adb communication"); 154 LOGGER.severe("-e"); 155 LOGGER.severe("\tWait for Enter key press before collecting report"); 156 LOGGER.severe("-d delay_ms"); 157 LOGGER.severe("\tWait for delay_ms before collecting report, default is 60000 ms"); 158 LOGGER.severe("-v"); 159 LOGGER.severe("\tDebug logging level"); 160 } 161 processArgs( Configuration configuration, String[] args, List<String> connectedDevices, String defaultDevice)162 boolean processArgs( 163 Configuration configuration, 164 String[] args, 165 List<String> connectedDevices, 166 String defaultDevice) { 167 if (args.length < 1) { 168 printUsageMessage(); 169 return false; 170 } 171 172 int first_arg = 0; 173 // Consume all flags, which must precede all atoms 174 for (; first_arg < args.length; ++first_arg) { 175 String arg = args[first_arg]; 176 int remaining_args = args.length - first_arg; 177 if (remaining_args >= 2 && arg.equals("-one")) { 178 LOGGER.info("Creating one event metric to catch all pushed atoms."); 179 configuration.mOnePushedAtomEvent = true; 180 } else if (remaining_args >= 2 && arg.equals("-terse")) { 181 LOGGER.info("Terse output format."); 182 mDumper = new TerseDumper(); 183 } else if (remaining_args >= 3 && arg.equals("-p")) { 184 Collections.addAll(configuration.mAdditionalAllowedPackages, 185 args[++first_arg].split(",")); 186 } else if (remaining_args >= 3 && arg.equals("-i")) { 187 mProtoIncludes.add(args[++first_arg]); 188 } else if (remaining_args >= 3 && arg.equals("-s")) { 189 mDeviceSerial = args[++first_arg]; 190 } else if (remaining_args >= 2 && arg.equals("-e")) { 191 mPressToContinue = true; 192 } else if (remaining_args >= 2 && arg.equals("-v")) { 193 Utils.setUpLogger(LOGGER, true); 194 } else if (remaining_args >= 2 && arg.equals("-d")) { 195 mPressToContinue = false; 196 mReportCollectionDelayMillis = Integer.parseInt(args[++first_arg]); 197 } else if (arg.equals("-h") || arg.equals("--help")) { 198 printUsageMessage(); 199 return false; 200 } else { 201 break; // Found the atom list 202 } 203 } 204 205 if (mProtoIncludes.size() == 0) { 206 mProtoIncludes.add(HW_ATOMS_PROTO_FILEPATH); 207 } 208 209 for (; first_arg < args.length; ++first_arg) { 210 String atom = args[first_arg]; 211 try { 212 configuration.addAtom(Integer.valueOf(atom), mProtoIncludes); 213 } catch (NumberFormatException e) { 214 LOGGER.severe("Bad atom id provided: " + atom); 215 } 216 } 217 218 mDeviceSerial = Utils.chooseDevice(mDeviceSerial, connectedDevices, defaultDevice, LOGGER); 219 if (mDeviceSerial == null) { 220 return false; 221 } 222 223 return configuration.hasPulledAtoms() || configuration.hasPushedAtoms(); 224 } 225 testDriveAndGetReports( StatsdConfig config, boolean hasPulledAtoms, boolean hasPushedAtoms)226 private ConfigMetricsReportList testDriveAndGetReports( 227 StatsdConfig config, boolean hasPulledAtoms, boolean hasPushedAtoms) { 228 if (config == null) { 229 LOGGER.severe("Failed to create valid config."); 230 return null; 231 } 232 233 String remoteConfigPath = null; 234 try { 235 remoteConfigPath = pushConfig(config, mDeviceSerial); 236 LOGGER.info("Pushed the following config to statsd on device '" + mDeviceSerial + "':"); 237 LOGGER.info(config.toString()); 238 if (hasPushedAtoms) { 239 LOGGER.info("Now please play with the device to trigger the event."); 240 } 241 if (!hasPulledAtoms) { 242 if (mPressToContinue) { 243 LOGGER.info("Press Enter after you finish playing with the device..."); 244 Scanner scanner = new Scanner(System.in); 245 scanner.nextLine(); 246 } else { 247 LOGGER.info( 248 String.format( 249 "All events should be dumped after %d ms ...", 250 mReportCollectionDelayMillis)); 251 Thread.sleep(mReportCollectionDelayMillis); 252 } 253 } else { 254 LOGGER.info("All events should be dumped after 1.5 minutes ..."); 255 Thread.sleep(15_000); 256 Utils.logAppBreadcrumb(0, 0, LOGGER, mDeviceSerial); 257 Thread.sleep(75_000); 258 } 259 return Utils.getReportList(CONFIG_ID, true, false, LOGGER, mDeviceSerial); 260 } catch (Exception e) { 261 LOGGER.log(Level.SEVERE, "Failed to test drive: " + e.getMessage(), e); 262 } finally { 263 removeConfig(mDeviceSerial); 264 if (remoteConfigPath != null) { 265 try { 266 Utils.runCommand( 267 null, LOGGER, "adb", "-s", mDeviceSerial, "shell", "rm", 268 remoteConfigPath); 269 } catch (Exception e) { 270 LOGGER.log(Level.WARNING, 271 "Unable to remove remote config file: " + remoteConfigPath, e); 272 } 273 } 274 } 275 return null; 276 } 277 278 static class Configuration { 279 boolean mOnePushedAtomEvent = false; 280 @VisibleForTesting 281 Set<Integer> mPushedAtoms = new TreeSet<>(); 282 @VisibleForTesting 283 Set<Integer> mPulledAtoms = new TreeSet<>(); 284 @VisibleForTesting 285 ArrayList<String> mAdditionalAllowedPackages = new ArrayList<>(); 286 private final Set<Long> mTrackedMetrics = new HashSet<>(); 287 private final String mAndroidBuildTop = System.getenv("ANDROID_BUILD_TOP"); 288 289 private Descriptors.Descriptor externalDescriptor = null; 290 dumpMetrics(ConfigMetricsReportList reportList, Dumper dumper)291 private void dumpMetrics(ConfigMetricsReportList reportList, Dumper dumper) { 292 // We may get multiple reports. Take the last one. 293 ConfigMetricsReport report = reportList.getReports(reportList.getReportsCount() - 1); 294 for (StatsLogReport statsLog : report.getMetricsList()) { 295 if (isTrackedMetric(statsLog.getMetricId())) { 296 dumper.dump(statsLog, externalDescriptor); 297 } 298 } 299 } 300 isTrackedMetric(long metricId)301 boolean isTrackedMetric(long metricId) { 302 return mTrackedMetrics.contains(metricId); 303 } 304 isPulledAtom(int atomId)305 static boolean isPulledAtom(int atomId) { 306 return atomId >= PULL_ATOM_START && atomId <= MAX_PLATFORM_ATOM_TAG 307 || atomId >= VENDOR_PULLED_ATOM_START_TAG; 308 } 309 addAtom(Integer atom, List<String> protoIncludes)310 void addAtom(Integer atom, List<String> protoIncludes) { 311 if (Atom.getDescriptor().findFieldByNumber(atom) == null && 312 Atom.getDescriptor().isExtensionNumber(atom) == false) { 313 // try to look in alternative locations 314 if (protoIncludes != null) { 315 boolean isAtomDefined = false; 316 for (int i = 0; i < protoIncludes.size(); i++) { 317 isAtomDefined = isAtomDefinedInFile(protoIncludes.get(i), atom); 318 if (isAtomDefined) { 319 break; 320 } 321 } 322 if (!isAtomDefined) { 323 LOGGER.severe("No such atom found: " + atom); 324 return; 325 } 326 } 327 } 328 if (isPulledAtom(atom)) { 329 mPulledAtoms.add(atom); 330 } else { 331 mPushedAtoms.add(atom); 332 } 333 } 334 compileProtoFileIntoDescriptorSet(String protoFileName)335 private String compileProtoFileIntoDescriptorSet(String protoFileName) { 336 final String protoCompilerBinary = "aprotoc"; 337 final String descSetFlag = "--descriptor_set_out"; 338 final String includeImportsFlag = "--include_imports"; 339 final String includeSourceInfoFlag = "--include_source_info"; 340 final String dsFileName = generateDescriptorSetFileName(protoFileName); 341 342 if (dsFileName == null) return null; 343 344 LOGGER.log(Level.FINE, "Target DescriptorSet File " + dsFileName); 345 346 try { 347 List<String> cmdArgs = new ArrayList<>(); 348 cmdArgs.add(protoCompilerBinary); 349 cmdArgs.add(descSetFlag); 350 cmdArgs.add(dsFileName); 351 cmdArgs.add(includeImportsFlag); 352 cmdArgs.add(includeSourceInfoFlag); 353 354 // populate the proto_path argument 355 if (mAndroidBuildTop != null) { 356 cmdArgs.add("-I"); 357 cmdArgs.add(mAndroidBuildTop); 358 359 Path protoBufSrcPath = Paths.get(mAndroidBuildTop, "external/protobuf/src"); 360 cmdArgs.add("-I"); 361 cmdArgs.add(protoBufSrcPath.toString()); 362 } 363 364 Path protoPath = Paths.get(protoFileName); 365 while (protoPath.getParent() != null) { 366 LOGGER.log(Level.FINE, "Including " + protoPath.getParent().toString()); 367 cmdArgs.add("-I"); 368 cmdArgs.add(protoPath.getParent().toString()); 369 protoPath = protoPath.getParent(); 370 } 371 cmdArgs.add(protoFileName); 372 373 String[] commands = new String[cmdArgs.size()]; 374 commands = cmdArgs.toArray(commands); 375 Utils.runCommand(null, LOGGER, commands); 376 return dsFileName; 377 } catch (InterruptedException | IOException e) { 378 LOGGER.severe("Error while performing proto compilation: " + e.getMessage()); 379 } 380 return null; 381 } 382 validateIncludeProtoPath(String protoFileName)383 private String validateIncludeProtoPath(String protoFileName) { 384 try { 385 File protoFile = new File(protoFileName); 386 if (!protoFile.exists()) { 387 protoFileName = Paths.get(mAndroidBuildTop).resolve( 388 protoFileName).toRealPath().toString(); 389 } 390 391 // file will be generated in the current work dir 392 return Paths.get(protoFileName).toRealPath().toString(); 393 } catch (IOException e) { 394 LOGGER.log(Level.INFO, "Could not find file " + protoFileName); 395 } 396 return null; 397 } 398 generateDescriptorSetFileName(String protoFileName)399 private String generateDescriptorSetFileName(String protoFileName) { 400 try { 401 // file will be generated in the current work dir 402 final Path protoPath = Paths.get(protoFileName).toRealPath(); 403 LOGGER.log(Level.FINE, "Absolute proto file " + protoPath.toString()); 404 Path dsPath = Paths.get(System.getProperty("user.dir")); 405 dsPath = dsPath.resolve(protoPath.getFileName().toString() + ".ds.tmp"); 406 return dsPath.toString(); 407 } catch (IOException e) { 408 LOGGER.severe("Could not find file " + protoFileName); 409 } 410 return null; 411 } 412 isAtomDefinedInFile(String fileName, Integer atom)413 private boolean isAtomDefinedInFile(String fileName, Integer atom) { 414 final String fullProtoFilePath = validateIncludeProtoPath(fileName); 415 if (fullProtoFilePath == null) return false; 416 417 final String dsFileName = compileProtoFileIntoDescriptorSet(fullProtoFilePath); 418 if (dsFileName == null) return false; 419 420 try (InputStream input = new FileInputStream(dsFileName)) { 421 DescriptorProtos.FileDescriptorSet fileDescriptorSet = 422 DescriptorProtos.FileDescriptorSet.parseFrom(input); 423 Descriptors.FileDescriptor fieldOptionsDesc = 424 DescriptorProtos.FieldOptions.getDescriptor().getFile(); 425 426 LOGGER.fine("Files count is " + fileDescriptorSet.getFileCount()); 427 428 // preparing dependencies list 429 List<Descriptors.FileDescriptor> dependencies = 430 new ArrayList<Descriptors.FileDescriptor>(); 431 for (int fileIndex = 0; fileIndex < fileDescriptorSet.getFileCount(); fileIndex++) { 432 LOGGER.fine("Processing file " + fileIndex); 433 try { 434 Descriptors.FileDescriptor dep = Descriptors.FileDescriptor.buildFrom( 435 fileDescriptorSet.getFile(fileIndex), 436 new Descriptors.FileDescriptor[0]); 437 dependencies.add(dep); 438 } catch (Descriptors.DescriptorValidationException e) { 439 LOGGER.fine("Unable to parse atoms proto file: " + fileName + ". Error: " 440 + e.getDescription()); 441 } 442 } 443 444 Descriptors.FileDescriptor[] fileDescriptorDeps = 445 new Descriptors.FileDescriptor[dependencies.size()]; 446 fileDescriptorDeps = dependencies.toArray(fileDescriptorDeps); 447 448 // looking for a file with an Atom definition 449 for (int fileIndex = 0; fileIndex < fileDescriptorSet.getFileCount(); fileIndex++) { 450 LOGGER.fine("Processing file " + fileIndex); 451 Descriptors.Descriptor atomMsgDesc = null; 452 try { 453 atomMsgDesc = Descriptors.FileDescriptor.buildFrom( 454 fileDescriptorSet.getFile(fileIndex), fileDescriptorDeps, 455 true) 456 .findMessageTypeByName("Atom"); 457 } catch (Descriptors.DescriptorValidationException e) { 458 LOGGER.severe("Unable to parse atoms proto file: " + fileName + ". Error: " 459 + e.getDescription()); 460 } 461 462 if (atomMsgDesc != null) { 463 LOGGER.fine("Atom message is located"); 464 } 465 466 if (atomMsgDesc != null && atomMsgDesc.findFieldByNumber(atom) != null) { 467 externalDescriptor = atomMsgDesc; 468 return true; 469 } 470 } 471 } catch (IOException e) { 472 LOGGER.log(Level.WARNING, "Unable to parse atoms proto file: " + fileName, e); 473 } finally { 474 File dsFile = new File(dsFileName); 475 dsFile.delete(); 476 } 477 return false; 478 } 479 hasPulledAtoms()480 private boolean hasPulledAtoms() { 481 return !mPulledAtoms.isEmpty(); 482 } 483 hasPushedAtoms()484 private boolean hasPushedAtoms() { 485 return !mPushedAtoms.isEmpty(); 486 } 487 createConfig()488 StatsdConfig createConfig() { 489 long metricId = METRIC_ID_BASE; 490 long atomMatcherId = ATOM_MATCHER_ID_BASE; 491 492 StatsdConfig.Builder builder = baseBuilder(); 493 494 if (hasPulledAtoms()) { 495 builder.addAtomMatcher( 496 createAtomMatcher( 497 Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER, 498 APP_BREADCRUMB_MATCHER_ID)); 499 } 500 501 for (int atomId : mPulledAtoms) { 502 builder.addAtomMatcher(createAtomMatcher(atomId, atomMatcherId)); 503 GaugeMetric.Builder gaugeMetricBuilder = GaugeMetric.newBuilder(); 504 gaugeMetricBuilder 505 .setId(metricId) 506 .setWhat(atomMatcherId) 507 .setTriggerEvent(APP_BREADCRUMB_MATCHER_ID) 508 .setGaugeFieldsFilter(FieldFilter.newBuilder().setIncludeAll(true).build()) 509 .setBucket(TimeUnit.ONE_MINUTE) 510 .setSamplingType(GaugeMetric.SamplingType.FIRST_N_SAMPLES) 511 .setMaxNumGaugeAtomsPerBucket(100); 512 builder.addGaugeMetric(gaugeMetricBuilder.build()); 513 atomMatcherId++; 514 mTrackedMetrics.add(metricId++); 515 } 516 517 // A simple atom matcher for each pushed atom. 518 List<AtomMatcher> simpleAtomMatchers = new ArrayList<>(); 519 for (int atomId : mPushedAtoms) { 520 final AtomMatcher atomMatcher = createAtomMatcher(atomId, atomMatcherId++); 521 simpleAtomMatchers.add(atomMatcher); 522 builder.addAtomMatcher(atomMatcher); 523 } 524 525 if (mOnePushedAtomEvent) { 526 // Create a union event metric, using a matcher that matches all pushed atoms. 527 AtomMatcher unionAtomMatcher = createUnionMatcher(simpleAtomMatchers, 528 atomMatcherId); 529 builder.addAtomMatcher(unionAtomMatcher); 530 EventMetric.Builder eventMetricBuilder = EventMetric.newBuilder(); 531 eventMetricBuilder.setId(metricId).setWhat(unionAtomMatcher.getId()); 532 builder.addEventMetric(eventMetricBuilder.build()); 533 mTrackedMetrics.add(metricId++); 534 } else { 535 // Create multiple event metrics, one per pushed atom. 536 for (AtomMatcher atomMatcher : simpleAtomMatchers) { 537 EventMetric.Builder eventMetricBuilder = EventMetric.newBuilder(); 538 eventMetricBuilder.setId(metricId).setWhat(atomMatcher.getId()); 539 builder.addEventMetric(eventMetricBuilder.build()); 540 mTrackedMetrics.add(metricId++); 541 } 542 } 543 544 return builder.build(); 545 } 546 createAtomMatcher(int atomId, long matcherId)547 private static AtomMatcher createAtomMatcher(int atomId, long matcherId) { 548 AtomMatcher.Builder atomMatcherBuilder = AtomMatcher.newBuilder(); 549 atomMatcherBuilder 550 .setId(matcherId) 551 .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder().setAtomId(atomId)); 552 return atomMatcherBuilder.build(); 553 } 554 createUnionMatcher( List<AtomMatcher> simpleAtomMatchers, long atomMatcherId)555 private AtomMatcher createUnionMatcher( 556 List<AtomMatcher> simpleAtomMatchers, long atomMatcherId) { 557 AtomMatcher.Combination.Builder combinationBuilder = 558 AtomMatcher.Combination.newBuilder(); 559 combinationBuilder.setOperation(StatsdConfigProto.LogicalOperation.OR); 560 for (AtomMatcher matcher : simpleAtomMatchers) { 561 combinationBuilder.addMatcher(matcher.getId()); 562 } 563 AtomMatcher.Builder atomMatcherBuilder = AtomMatcher.newBuilder(); 564 atomMatcherBuilder.setId(atomMatcherId).setCombination(combinationBuilder.build()); 565 return atomMatcherBuilder.build(); 566 } 567 baseBuilder()568 private StatsdConfig.Builder baseBuilder() { 569 ArrayList<String> allowedSources = new ArrayList<>(); 570 Collections.addAll(allowedSources, ALLOWED_LOG_SOURCES); 571 allowedSources.addAll(mAdditionalAllowedPackages); 572 return StatsdConfig.newBuilder() 573 .addAllAllowedLogSource(allowedSources) 574 .addAllDefaultPullPackages(Arrays.asList(DEFAULT_PULL_SOURCES)) 575 .addPullAtomPackages( 576 PullAtomPackages.newBuilder() 577 .setAtomId(Atom.MEDIA_DRM_ACTIVITY_INFO_FIELD_NUMBER) 578 .addPackages("AID_MEDIA")) 579 .addPullAtomPackages( 580 PullAtomPackages.newBuilder() 581 .setAtomId(Atom.GPU_STATS_GLOBAL_INFO_FIELD_NUMBER) 582 .addPackages("AID_GPU_SERVICE")) 583 .addPullAtomPackages( 584 PullAtomPackages.newBuilder() 585 .setAtomId(Atom.GPU_STATS_APP_INFO_FIELD_NUMBER) 586 .addPackages("AID_GPU_SERVICE")) 587 .addPullAtomPackages( 588 PullAtomPackages.newBuilder() 589 .setAtomId(Atom.TRAIN_INFO_FIELD_NUMBER) 590 .addPackages("AID_STATSD")) 591 .addPullAtomPackages( 592 PullAtomPackages.newBuilder() 593 .setAtomId( 594 Atom.GENERAL_EXTERNAL_STORAGE_ACCESS_STATS_FIELD_NUMBER) 595 .addPackages("com.google.android.providers.media.module")) 596 .addPullAtomPackages( 597 PullAtomPackages.newBuilder() 598 .setAtomId(Atom.LAUNCHER_LAYOUT_SNAPSHOT_FIELD_NUMBER) 599 .addPackages("com.google.android.apps.nexuslauncher")) 600 .addPullAtomPackages( 601 PullAtomPackages.newBuilder() 602 .setAtomId(QnsExtensionAtoms 603 .QNS_RAT_PREFERENCE_MISMATCH_INFO_FIELD_NUMBER) 604 .addPackages("com.android.telephony.qns")) 605 .addPullAtomPackages( 606 PullAtomPackages.newBuilder() 607 .setAtomId(QnsExtensionAtoms 608 .QNS_HANDOVER_TIME_MILLIS_FIELD_NUMBER) 609 .addPackages("com.android.telephony.qns")) 610 .addPullAtomPackages( 611 PullAtomPackages.newBuilder() 612 .setAtomId(QnsExtensionAtoms 613 .QNS_HANDOVER_PINGPONG_FIELD_NUMBER) 614 .addPackages("com.android.telephony.qns")) 615 .setHashStringsInMetricReport(false); 616 } 617 } 618 619 interface Dumper { dump(StatsLogReport report, Descriptors.Descriptor externalDescriptor)620 void dump(StatsLogReport report, Descriptors.Descriptor externalDescriptor); 621 } 622 623 static class BasicDumper implements Dumper { 624 @Override dump(StatsLogReport report, Descriptors.Descriptor externalDescriptor)625 public void dump(StatsLogReport report, Descriptors.Descriptor externalDescriptor) { 626 System.out.println(report.toString()); 627 } 628 } 629 630 static class TerseDumper extends BasicDumper { 631 @Override dump(StatsLogReport report, Descriptors.Descriptor externalDescriptor)632 public void dump(StatsLogReport report, Descriptors.Descriptor externalDescriptor) { 633 if (report.hasGaugeMetrics()) { 634 dumpGaugeMetrics(report); 635 } 636 if (report.hasEventMetrics()) { 637 dumpEventMetrics(report, externalDescriptor); 638 } 639 } 640 dumpEventMetrics(StatsLogReport report, Descriptors.Descriptor externalDescriptor)641 void dumpEventMetrics(StatsLogReport report, 642 Descriptors.Descriptor externalDescriptor) { 643 final List<StatsLog.EventMetricData> data = Utils.getEventMetricData(report); 644 if (data.isEmpty()) { 645 return; 646 } 647 long firstTimestampNanos = data.get(0).getElapsedTimestampNanos(); 648 for (StatsLog.EventMetricData event : data) { 649 final double deltaSec = 650 (event.getElapsedTimestampNanos() - firstTimestampNanos) / 1e9; 651 System.out.println(String.format("+%.3fs: %s", deltaSec, 652 dumpAtom(event.getAtom(), externalDescriptor))); 653 } 654 } 655 dumpGaugeMetrics(StatsLogReport report)656 void dumpGaugeMetrics(StatsLogReport report) { 657 final List<StatsLog.GaugeMetricData> data = report.getGaugeMetrics().getDataList(); 658 if (data.isEmpty()) { 659 return; 660 } 661 for (StatsLog.GaugeMetricData gauge : data) { 662 System.out.println(gauge.toString()); 663 } 664 } 665 } 666 dumpAtom(AtomsProto.Atom atom, Descriptors.Descriptor externalDescriptor)667 private static String dumpAtom(AtomsProto.Atom atom, 668 Descriptors.Descriptor externalDescriptor) { 669 if (atom.getPushedCase().getNumber() != 0 || atom.getPulledCase().getNumber() != 0) { 670 return atom.toString(); 671 } else { 672 try { 673 return convertToExternalAtom(atom, externalDescriptor).toString(); 674 } catch (Exception e) { 675 LOGGER.severe("Failed to parse an atom: " + e.getMessage()); 676 return ""; 677 } 678 } 679 } 680 convertToExternalAtom(AtomsProto.Atom atom, Descriptors.Descriptor externalDescriptor)681 private static DynamicMessage convertToExternalAtom(AtomsProto.Atom atom, 682 Descriptors.Descriptor externalDescriptor) throws Exception { 683 ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 684 CodedOutputStream cos = CodedOutputStream.newInstance(outputStream); 685 atom.writeTo(cos); 686 cos.flush(); 687 ByteArrayInputStream inputStream = new ByteArrayInputStream( 688 outputStream.toByteArray()); 689 CodedInputStream cis = CodedInputStream.newInstance(inputStream); 690 return DynamicMessage.parseFrom(externalDescriptor, cis); 691 } 692 693 pushConfig(StatsdConfig config, String deviceSerial)694 private static String pushConfig(StatsdConfig config, String deviceSerial) 695 throws IOException, InterruptedException { 696 File configFile = File.createTempFile("statsdconfig", ".config"); 697 configFile.deleteOnExit(); 698 Files.write(config.toByteArray(), configFile); 699 String remotePath = "/data/local/tmp/" + configFile.getName(); 700 Utils.runCommand( 701 null, LOGGER, "adb", "-s", deviceSerial, "push", configFile.getAbsolutePath(), 702 remotePath); 703 Utils.runCommand( 704 null, 705 LOGGER, 706 "adb", 707 "-s", 708 deviceSerial, 709 "shell", 710 "cat", 711 remotePath, 712 "|", 713 Utils.CMD_UPDATE_CONFIG, 714 String.valueOf(CONFIG_ID)); 715 return remotePath; 716 } 717 removeConfig(String deviceSerial)718 private static void removeConfig(String deviceSerial) { 719 try { 720 Utils.runCommand( 721 null, 722 LOGGER, 723 "adb", 724 "-s", 725 deviceSerial, 726 "shell", 727 Utils.CMD_REMOVE_CONFIG, 728 String.valueOf(CONFIG_ID)); 729 } catch (Exception e) { 730 LOGGER.severe("Failed to remove config: " + e.getMessage()); 731 } 732 } 733 } 734