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