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