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