• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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