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