• 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.Atom;
28 import com.android.os.StatsLog;
29 import com.android.os.StatsLog.ConfigMetricsReport;
30 import com.android.os.StatsLog.ConfigMetricsReportList;
31 import com.android.os.StatsLog.StatsLogReport;
32 import com.android.statsd.shelltools.Utils;
33 
34 import com.google.common.annotations.VisibleForTesting;
35 import com.google.common.io.Files;
36 
37 import java.io.File;
38 import java.io.IOException;
39 import java.util.ArrayList;
40 import java.util.Arrays;
41 import java.util.Collections;
42 import java.util.HashSet;
43 import java.util.List;
44 import java.util.Scanner;
45 import java.util.Set;
46 import java.util.TreeSet;
47 import java.util.logging.Level;
48 import java.util.logging.Logger;
49 
50 public class TestDrive {
51 
52     private static final int METRIC_ID_BASE = 1111;
53     private static final long ATOM_MATCHER_ID_BASE = 1234567;
54     private static final long APP_BREADCRUMB_MATCHER_ID = 1111111;
55     private static final int PULL_ATOM_START = 10000;
56     private static final int MAX_PLATFORM_ATOM_TAG = 100000;
57     private static final int VENDOR_PULLED_ATOM_START_TAG = 150000;
58     private static final long CONFIG_ID = 54321;
59     private static final String[] ALLOWED_LOG_SOURCES = {
60             "AID_GRAPHICS",
61             "AID_INCIDENTD",
62             "AID_STATSD",
63             "AID_RADIO",
64             "com.android.systemui",
65             "com.android.vending",
66             "AID_SYSTEM",
67             "AID_ROOT",
68             "AID_BLUETOOTH",
69             "AID_LMKD",
70             "com.android.managedprovisioning",
71             "AID_MEDIA",
72             "AID_NETWORK_STACK",
73             "com.google.android.providers.media.module",
74             "com.android.imsserviceentitlement",
75             "com.google.android.cellbroadcastreceiver",
76             "com.google.android.apps.nexuslauncher",
77             "AID_KEYSTORE",
78             "AID_VIRTUALIZATIONSERVICE",
79     };
80     private static final String[] DEFAULT_PULL_SOURCES = {
81             "AID_KEYSTORE",
82             "AID_RADIO",
83             "AID_SYSTEM",
84     };
85     private static final Logger LOGGER = Logger.getLogger(TestDrive.class.getName());
86 
87     @VisibleForTesting
88     String mDeviceSerial = null;
89     @VisibleForTesting
90     Dumper mDumper = new BasicDumper();
91     boolean mPressToContinue = false;
92 
main(String[] args)93     public static void main(String[] args) {
94         final Configuration configuration = new Configuration();
95         final TestDrive testDrive = new TestDrive();
96         Utils.setUpLogger(LOGGER, false);
97 
98         if (!testDrive.processArgs(configuration, args,
99                 Utils.getDeviceSerials(LOGGER), Utils.getDefaultDevice(LOGGER))) {
100             return;
101         }
102 
103         final ConfigMetricsReportList reports = testDrive.testDriveAndGetReports(
104                 configuration.createConfig(), configuration.hasPulledAtoms(),
105                 configuration.hasPushedAtoms());
106         if (reports != null) {
107             configuration.dumpMetrics(reports, testDrive.mDumper);
108         }
109     }
110 
processArgs(Configuration configuration, String[] args, List<String> connectedDevices, String defaultDevice)111     boolean processArgs(Configuration configuration, String[] args, List<String> connectedDevices,
112             String defaultDevice) {
113         if (args.length < 1) {
114             LOGGER.severe("Usage: ./test_drive [-one] "
115                     + "[-p additional_allowed_package] "
116                     + "[-s DEVICE_SERIAL_NUMBER] "
117                     + "<atomId1> <atomId2> ... <atomIdN>");
118             return false;
119         }
120 
121         int first_arg = 0;
122         // Consume all flags, which must precede all atoms
123         for (; first_arg < args.length; ++first_arg) {
124             String arg = args[first_arg];
125             int remaining_args = args.length - first_arg;
126             if (remaining_args >= 2 && arg.equals("-one")) {
127                 LOGGER.info("Creating one event metric to catch all pushed atoms.");
128                 configuration.mOnePushedAtomEvent = true;
129             } else if (remaining_args >= 2 && arg.equals("-terse")) {
130                 LOGGER.info("Terse output format.");
131                 mDumper = new TerseDumper();
132             } else if (remaining_args >= 3 && arg.equals("-p")) {
133                 configuration.mAdditionalAllowedPackage = args[++first_arg];
134             } else if (remaining_args >= 3 && arg.equals("-s")) {
135                 mDeviceSerial = args[++first_arg];
136             } else if (remaining_args >= 2 && arg.equals("-e")) {
137                 mPressToContinue = true;
138             } else {
139                 break;  // Found the atom list
140             }
141         }
142 
143         mDeviceSerial = Utils.chooseDevice(mDeviceSerial, connectedDevices, defaultDevice, LOGGER);
144         if (mDeviceSerial == null) {
145             return false;
146         }
147 
148         for ( ; first_arg < args.length; ++first_arg) {
149             String atom = args[first_arg];
150             try {
151                 configuration.addAtom(Integer.valueOf(atom));
152             } catch (NumberFormatException e) {
153                 LOGGER.severe("Bad atom id provided: " + atom);
154             }
155         }
156 
157         return configuration.hasPulledAtoms() || configuration.hasPushedAtoms();
158     }
159 
testDriveAndGetReports(StatsdConfig config, boolean hasPulledAtoms, boolean hasPushedAtoms)160     private ConfigMetricsReportList testDriveAndGetReports(StatsdConfig config,
161             boolean hasPulledAtoms, boolean hasPushedAtoms) {
162         if (config == null) {
163             LOGGER.severe("Failed to create valid config.");
164             return null;
165         }
166 
167         String remoteConfigPath = null;
168         try {
169             remoteConfigPath = pushConfig(config, mDeviceSerial);
170             LOGGER.info("Pushed the following config to statsd on device '" + mDeviceSerial
171                     + "':");
172             LOGGER.info(config.toString());
173             if (hasPushedAtoms) {
174                 LOGGER.info("Now please play with the device to trigger the event.");
175             }
176             if (!hasPulledAtoms) {
177                 if (mPressToContinue) {
178                     LOGGER.info("Press enter after you finish playing with the device...");
179                     Scanner scanner = new Scanner(System.in);
180                     scanner.nextLine();
181                 } else {
182                     LOGGER.info(
183                             "All events should be dumped after 1 min ...");
184                     Thread.sleep(60_000);
185                 }
186             } else {
187                 LOGGER.info("All events should be dumped after 1.5 minutes ...");
188                 Thread.sleep(15_000);
189                 Utils.logAppBreadcrumb(0, 0, LOGGER, mDeviceSerial);
190                 Thread.sleep(75_000);
191             }
192             return Utils.getReportList(CONFIG_ID, true, false, LOGGER,
193                     mDeviceSerial);
194         } catch (Exception e) {
195             LOGGER.log(Level.SEVERE, "Failed to test drive: " + e.getMessage(), e);
196         } finally {
197             removeConfig(mDeviceSerial);
198             if (remoteConfigPath != null) {
199                 try {
200                     Utils.runCommand(null, LOGGER,
201                             "adb", "-s", mDeviceSerial, "shell", "rm",
202                             remoteConfigPath);
203                 } catch (Exception e) {
204                     LOGGER.log(Level.WARNING,
205                             "Unable to remove remote config file: " + remoteConfigPath, e);
206                 }
207             }
208         }
209         return null;
210     }
211 
212     static class Configuration {
213         boolean mOnePushedAtomEvent = false;
214         @VisibleForTesting
215         Set<Integer> mPushedAtoms = new TreeSet<>();
216         @VisibleForTesting
217         Set<Integer> mPulledAtoms = new TreeSet<>();
218         @VisibleForTesting
219         String mAdditionalAllowedPackage = null;
220         private final Set<Long> mTrackedMetrics = new HashSet<>();
221 
dumpMetrics(ConfigMetricsReportList reportList, Dumper dumper)222         private void dumpMetrics(ConfigMetricsReportList reportList, Dumper dumper) {
223             // We may get multiple reports. Take the last one.
224             ConfigMetricsReport report = reportList.getReports(reportList.getReportsCount() - 1);
225             for (StatsLogReport statsLog : report.getMetricsList()) {
226                 if (isTrackedMetric(statsLog.getMetricId())) {
227                     dumper.dump(statsLog);
228                 }
229             }
230         }
231 
isTrackedMetric(long metricId)232         boolean isTrackedMetric(long metricId) {
233             return mTrackedMetrics.contains(metricId);
234         }
235 
isPulledAtom(int atomId)236         static boolean isPulledAtom(int atomId) {
237             return atomId >= PULL_ATOM_START && atomId <= MAX_PLATFORM_ATOM_TAG
238                     || atomId >= VENDOR_PULLED_ATOM_START_TAG;
239         }
240 
addAtom(Integer atom)241         void addAtom(Integer atom) {
242             if (Atom.getDescriptor().findFieldByNumber(atom) == null) {
243                 LOGGER.severe("No such atom found: " + atom);
244                 return;
245             }
246             if (isPulledAtom(atom)) {
247                 mPulledAtoms.add(atom);
248             } else {
249                 mPushedAtoms.add(atom);
250             }
251         }
252 
hasPulledAtoms()253         private boolean hasPulledAtoms() {
254             return !mPulledAtoms.isEmpty();
255         }
256 
hasPushedAtoms()257         private boolean hasPushedAtoms() {
258             return !mPushedAtoms.isEmpty();
259         }
260 
createConfig()261         StatsdConfig createConfig() {
262             long metricId = METRIC_ID_BASE;
263             long atomMatcherId = ATOM_MATCHER_ID_BASE;
264 
265             StatsdConfig.Builder builder = baseBuilder();
266 
267             if (hasPulledAtoms()) {
268                 builder.addAtomMatcher(
269                         createAtomMatcher(
270                                 Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER,
271                                 APP_BREADCRUMB_MATCHER_ID));
272             }
273 
274             for (int atomId : mPulledAtoms) {
275                 builder.addAtomMatcher(createAtomMatcher(atomId, atomMatcherId));
276                 GaugeMetric.Builder gaugeMetricBuilder = GaugeMetric.newBuilder();
277                 gaugeMetricBuilder
278                         .setId(metricId)
279                         .setWhat(atomMatcherId)
280                         .setTriggerEvent(APP_BREADCRUMB_MATCHER_ID)
281                         .setGaugeFieldsFilter(FieldFilter.newBuilder().setIncludeAll(true).build())
282                         .setBucket(TimeUnit.ONE_MINUTE)
283                         .setSamplingType(GaugeMetric.SamplingType.FIRST_N_SAMPLES)
284                         .setMaxNumGaugeAtomsPerBucket(100);
285                 builder.addGaugeMetric(gaugeMetricBuilder.build());
286                 atomMatcherId++;
287                 mTrackedMetrics.add(metricId++);
288             }
289 
290             // A simple atom matcher for each pushed atom.
291             List<AtomMatcher> simpleAtomMatchers = new ArrayList<>();
292             for (int atomId : mPushedAtoms) {
293                 final AtomMatcher atomMatcher = createAtomMatcher(atomId, atomMatcherId++);
294                 simpleAtomMatchers.add(atomMatcher);
295                 builder.addAtomMatcher(atomMatcher);
296             }
297 
298             if (mOnePushedAtomEvent) {
299                 // Create a union event metric, using an matcher that matches all pulled atoms.
300                 AtomMatcher unionAtomMatcher = createUnionMatcher(simpleAtomMatchers,
301                         atomMatcherId);
302                 builder.addAtomMatcher(unionAtomMatcher);
303                 EventMetric.Builder eventMetricBuilder = EventMetric.newBuilder();
304                 eventMetricBuilder.setId(metricId).setWhat(unionAtomMatcher.getId());
305                 builder.addEventMetric(eventMetricBuilder.build());
306                 mTrackedMetrics.add(metricId++);
307             } else {
308                 // Create multiple event metrics, one per pulled atom.
309                 for (AtomMatcher atomMatcher : simpleAtomMatchers) {
310                     EventMetric.Builder eventMetricBuilder = EventMetric.newBuilder();
311                     eventMetricBuilder
312                             .setId(metricId)
313                             .setWhat(atomMatcher.getId());
314                     builder.addEventMetric(eventMetricBuilder.build());
315                     mTrackedMetrics.add(metricId++);
316                 }
317             }
318 
319             return builder.build();
320         }
321 
createAtomMatcher(int atomId, long matcherId)322         private static AtomMatcher createAtomMatcher(int atomId, long matcherId) {
323             AtomMatcher.Builder atomMatcherBuilder = AtomMatcher.newBuilder();
324             atomMatcherBuilder
325                     .setId(matcherId)
326                     .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder().setAtomId(atomId));
327             return atomMatcherBuilder.build();
328         }
329 
createUnionMatcher(List<AtomMatcher> simpleAtomMatchers, long atomMatcherId)330         private AtomMatcher createUnionMatcher(List<AtomMatcher> simpleAtomMatchers,
331                 long atomMatcherId) {
332             AtomMatcher.Combination.Builder combinationBuilder =
333                     AtomMatcher.Combination.newBuilder();
334             combinationBuilder.setOperation(StatsdConfigProto.LogicalOperation.OR);
335             for (AtomMatcher matcher : simpleAtomMatchers) {
336                 combinationBuilder.addMatcher(matcher.getId());
337             }
338             AtomMatcher.Builder atomMatcherBuilder = AtomMatcher.newBuilder();
339             atomMatcherBuilder.setId(atomMatcherId).setCombination(combinationBuilder.build());
340             return atomMatcherBuilder.build();
341         }
342 
baseBuilder()343         private StatsdConfig.Builder baseBuilder() {
344             ArrayList<String> allowedSources = new ArrayList<>();
345             Collections.addAll(allowedSources, ALLOWED_LOG_SOURCES);
346             if (mAdditionalAllowedPackage != null) {
347                 allowedSources.add(mAdditionalAllowedPackage);
348             }
349             return StatsdConfig.newBuilder()
350                     .addAllAllowedLogSource(allowedSources)
351                     .addAllDefaultPullPackages(Arrays.asList(DEFAULT_PULL_SOURCES))
352                     .addPullAtomPackages(PullAtomPackages.newBuilder()
353                             .setAtomId(Atom.MEDIA_DRM_ACTIVITY_INFO_FIELD_NUMBER)
354                             .addPackages("AID_MEDIA"))
355                     .addPullAtomPackages(PullAtomPackages.newBuilder()
356                             .setAtomId(Atom.GPU_STATS_GLOBAL_INFO_FIELD_NUMBER)
357                             .addPackages("AID_GPU_SERVICE"))
358                     .addPullAtomPackages(PullAtomPackages.newBuilder()
359                             .setAtomId(Atom.GPU_STATS_APP_INFO_FIELD_NUMBER)
360                             .addPackages("AID_GPU_SERVICE"))
361                     .addPullAtomPackages(PullAtomPackages.newBuilder()
362                             .setAtomId(Atom.TRAIN_INFO_FIELD_NUMBER)
363                             .addPackages("AID_STATSD"))
364                     .addPullAtomPackages(PullAtomPackages.newBuilder()
365                             .setAtomId(Atom.GENERAL_EXTERNAL_STORAGE_ACCESS_STATS_FIELD_NUMBER)
366                             .addPackages("com.google.android.providers.media.module"))
367                     .addPullAtomPackages(PullAtomPackages.newBuilder()
368                             .setAtomId(Atom.LAUNCHER_LAYOUT_SNAPSHOT_FIELD_NUMBER)
369                             .addPackages("com.google.android.apps.nexuslauncher"))
370                     .setHashStringsInMetricReport(false);
371         }
372     }
373 
374     interface Dumper {
dump(StatsLogReport report)375         void dump(StatsLogReport report);
376     }
377 
378     static class BasicDumper implements Dumper {
379         @Override
dump(StatsLogReport report)380         public void dump(StatsLogReport report) {
381             System.out.println(report.toString());
382         }
383     }
384 
385     static class TerseDumper extends BasicDumper {
386         @Override
dump(StatsLogReport report)387         public void dump(StatsLogReport report) {
388             if (report.hasGaugeMetrics()) {
389                 dumpGaugeMetrics(report);
390             }
391             if (report.hasEventMetrics()) {
392                 dumpEventMetrics(report);
393             }
394         }
dumpEventMetrics(StatsLogReport report)395         void dumpEventMetrics(StatsLogReport report) {
396             final List<StatsLog.EventMetricData> data = Utils.getEventMetricData(report);
397             if (data.isEmpty()) {
398                 return;
399             }
400             long firstTimestampNanos = data.get(0).getElapsedTimestampNanos();
401             for (StatsLog.EventMetricData event : data) {
402                 final double deltaSec = (event.getElapsedTimestampNanos() - firstTimestampNanos)
403                         / 1e9;
404                 System.out.println(
405                         String.format("+%.3fs: %s", deltaSec, event.getAtom().toString()));
406             }
407         }
dumpGaugeMetrics(StatsLogReport report)408         void dumpGaugeMetrics(StatsLogReport report) {
409             final List<StatsLog.GaugeMetricData> data = report.getGaugeMetrics().getDataList();
410             if (data.isEmpty()) {
411                 return;
412             }
413             for (StatsLog.GaugeMetricData gauge : data) {
414                 System.out.println(gauge.toString());
415             }
416         }
417     }
418 
pushConfig(StatsdConfig config, String deviceSerial)419     private static String pushConfig(StatsdConfig config, String deviceSerial)
420             throws IOException, InterruptedException {
421         File configFile = File.createTempFile("statsdconfig", ".config");
422         configFile.deleteOnExit();
423         Files.write(config.toByteArray(), configFile);
424         String remotePath = "/data/local/tmp/" + configFile.getName();
425         Utils.runCommand(null, LOGGER, "adb", "-s", deviceSerial,
426                 "push", configFile.getAbsolutePath(), remotePath);
427         Utils.runCommand(null, LOGGER, "adb", "-s", deviceSerial,
428                 "shell", "cat", remotePath, "|", Utils.CMD_UPDATE_CONFIG,
429                 String.valueOf(CONFIG_ID));
430         return remotePath;
431     }
432 
removeConfig(String deviceSerial)433     private static void removeConfig(String deviceSerial) {
434         try {
435             Utils.runCommand(null, LOGGER, "adb", "-s", deviceSerial,
436                     "shell", Utils.CMD_REMOVE_CONFIG, String.valueOf(CONFIG_ID));
437         } catch (Exception e) {
438             LOGGER.severe("Failed to remove config: " + e.getMessage());
439         }
440     }
441 }
442