• 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.localdrive;
17 
18 import com.android.internal.os.StatsdConfigProto.StatsdConfig;
19 import com.android.os.StatsLog.ConfigMetricsReport;
20 import com.android.os.StatsLog.ConfigMetricsReportList;
21 import com.android.statsd.shelltools.Utils;
22 
23 import com.google.common.io.Files;
24 import com.google.protobuf.TextFormat;
25 
26 import java.io.File;
27 import java.io.FileReader;
28 import java.io.IOException;
29 import java.util.List;
30 import java.util.logging.Logger;
31 
32 /**
33  * Tool for using statsd locally. Can upload a config and get the data. Handles
34  * both binary and human-readable protos.
35  * To make: make statsd_localdrive
36  * To run: statsd_localdrive     (i.e.  ./out/host/linux-x86/bin/statsd_localdrive)
37  */
38 public class LocalDrive {
39     private static final boolean DEBUG = false;
40 
41     public static final int MIN_SDK = 29;
42     public static final String MIN_CODENAME = "Q";
43 
44     public static final long DEFAULT_CONFIG_ID = 56789;
45 
46     public static final String BINARY_FLAG = "--binary";
47     public static final String CLEAR_DATA = "--clear";
48     public static final String NO_UID_MAP_FLAG = "--no-uid-map";
49 
50     public static final String HELP_STRING =
51         "Usage:\n\n" +
52 
53         "statsd_localdrive [-s DEVICE_SERIAL] upload CONFIG_FILE [CONFIG_ID] [--binary]\n" +
54         "  Uploads the given statsd config file (in binary or human-readable-text format).\n" +
55         "  If a config with this id already exists, removes it first.\n" +
56         "    CONFIG_FILE    Location of config file on host.\n" +
57         "    CONFIG_ID      Long ID to associate with this config. If absent, uses "
58                                                                 + DEFAULT_CONFIG_ID + ".\n" +
59         "    --binary       Config is in binary format; otherwise, assumed human-readable text.\n" +
60         // Similar to: adb shell cmd stats config update SHELL_UID CONFIG_ID
61         "\n" +
62 
63         "statsd_localdrive [-s DEVICE_SERIAL] update CONFIG_FILE [CONFIG_ID] [--binary]\n" +
64         "  Same as upload, but does not remove the old config first (if it already exists).\n" +
65         // Similar to: adb shell cmd stats config update SHELL_UID CONFIG_ID
66         "\n" +
67 
68         "statsd_localdrive [-s DEVICE_SERIAL] get-data [CONFIG_ID] [--clear] [--binary] [--no-uid-map]\n" +
69         "  Prints the output statslog data (in binary or human-readable-text format).\n" +
70         "    CONFIG_ID      Long ID of the config. If absent, uses " + DEFAULT_CONFIG_ID + ".\n" +
71         "    --binary       Output should be in binary, instead of default human-readable text.\n" +
72         "                       Binary output can be redirected as usual (e.g. > FILENAME).\n" +
73         "    --no-uid-map   Do not include the uid-map (the very lengthy uid<-->pkgName map).\n" +
74         "    --clear        Erase the data from statsd afterwards. Does not remove the config.\n" +
75         // Similar to: adb shell cmd stats dump-report SHELL_UID CONFIG_ID [--keep_data]
76         //                                                      --include_current_bucket --proto
77         "\n" +
78 
79         "statsd_localdrive [-s DEVICE_SERIAL] remove [CONFIG_ID]\n" +
80         "  Removes the config.\n" +
81         "    CONFIG_ID      Long ID of the config. If absent, uses " + DEFAULT_CONFIG_ID + ".\n" +
82         // Equivalent to: adb shell cmd stats config remove SHELL_UID CONFIG_ID
83         "\n" +
84 
85         "statsd_localdrive [-s DEVICE_SERIAL] clear [CONFIG_ID]\n" +
86         "  Clears the data associated with the config.\n" +
87         "    CONFIG_ID      Long ID of the config. If absent, uses " + DEFAULT_CONFIG_ID + ".\n" +
88         // Similar to: adb shell cmd stats dump-report SHELL_UID CONFIG_ID
89         //                                                      --include_current_bucket --proto
90         "";
91 
92 
93     private static final Logger sLogger = Logger.getLogger(LocalDrive.class.getName());
94 
95     /** Usage: make statsd_localdrive && statsd_localdrive */
main(String[] args)96     public static void main(String[] args) {
97         Utils.setUpLogger(sLogger, DEBUG);
98         if (args.length == 0) {
99             printHelp();
100             return;
101         }
102 
103         int remainingArgsLength = args.length;
104         String deviceSerial = null;
105         if (args[0].equals("-s")) {
106             if (args.length == 1) {
107                 printHelp();
108             }
109             deviceSerial = args[1];
110             remainingArgsLength -= 2;
111         }
112 
113         List<String> connectedDevices = Utils.getDeviceSerials(sLogger);
114         deviceSerial = Utils.chooseDevice(deviceSerial, connectedDevices,
115                 Utils.getDefaultDevice(sLogger), sLogger);
116         if (deviceSerial == null) {
117             return;
118         }
119 
120         if (!Utils.isAcceptableStatsd(sLogger, MIN_SDK, MIN_CODENAME, deviceSerial)) {
121             sLogger.severe("LocalDrive only works with statsd versions for Android "
122                     + MIN_CODENAME + " or higher.");
123             return;
124         }
125 
126         int idx = args.length - remainingArgsLength;
127         if (remainingArgsLength > 0) {
128             switch (args[idx]) {
129                 case "clear":
130                     cmdClear(args, idx, deviceSerial);
131                     return;
132                 case "get-data":
133                     cmdGetData(args, idx, deviceSerial);
134                     return;
135                 case "remove":
136                     cmdRemove(args, idx);
137                     return;
138                 case "update":
139                     cmdUpdate(args, idx, deviceSerial);
140                     return;
141                 case "upload":
142                     cmdUpload(args, idx, deviceSerial);
143                     return;
144             }
145         }
146         printHelp();
147     }
148 
printHelp()149     private static void printHelp() {
150         sLogger.info(HELP_STRING);
151     }
152 
153     // upload CONFIG_FILE [CONFIG_ID] [--binary]
cmdUpload(String[] args, int idx, String deviceSerial)154     private static boolean cmdUpload(String[] args, int idx, String deviceSerial) {
155         return updateConfig(args, idx, true, deviceSerial);
156     }
157 
158     // update CONFIG_FILE [CONFIG_ID] [--binary]
cmdUpdate(String[] args, int idx, String deviceSerial)159     private static boolean cmdUpdate(String[] args, int idx, String deviceSerial) {
160         return updateConfig(args, idx, false, deviceSerial);
161     }
162 
updateConfig(String[] args, int idx, boolean removeOldConfig, String deviceSerial)163     private static boolean updateConfig(String[] args, int idx, boolean removeOldConfig,
164             String deviceSerial) {
165         int argCount = args.length - 1 - idx; // Used up one for upload/update.
166 
167         // Get CONFIG_FILE
168         if (argCount < 1) {
169             sLogger.severe("No config file provided.");
170             printHelp();
171             return false;
172         }
173         final String origConfigLocation = args[idx + 1];
174         if (!new File(origConfigLocation).exists()) {
175             sLogger.severe("Error - Cannot find the provided config file: " + origConfigLocation);
176             return false;
177         }
178         argCount--;
179 
180         // Get --binary
181         boolean binary = contains(args, idx + 2, BINARY_FLAG);
182         if (binary) argCount --;
183 
184         // Get CONFIG_ID
185         long configId;
186         try {
187             configId = getConfigId(argCount < 1, args, idx + 2);
188         } catch (NumberFormatException e) {
189             sLogger.severe("Invalid config id provided.");
190             printHelp();
191             return false;
192         }
193         sLogger.fine(String.format("updateConfig with %s %d %b %b",
194                 origConfigLocation, configId, binary, removeOldConfig));
195 
196         // Remove the old config.
197         if (removeOldConfig) {
198             try {
199                 Utils.runCommand(null, sLogger, "adb", "shell", Utils.CMD_REMOVE_CONFIG,
200                         Utils.SHELL_UID, String.valueOf(configId));
201                 Utils.getReportList(configId, true /* clearData */, true /* SHELL_UID */, sLogger,
202                         deviceSerial);
203             } catch (InterruptedException | IOException e) {
204                 sLogger.severe("Failed to remove config: " + e.getMessage());
205                 return false;
206             }
207         }
208 
209         // Upload the config.
210         String configLocation;
211         if (binary) {
212             configLocation = origConfigLocation;
213         } else {
214             StatsdConfig.Builder builder = StatsdConfig.newBuilder();
215             try {
216                 TextFormat.merge(new FileReader(origConfigLocation), builder);
217             } catch (IOException e) {
218                 sLogger.severe("Failed to read config file " + origConfigLocation + ": "
219                         + e.getMessage());
220                 return false;
221             }
222 
223             try {
224                 File tempConfigFile = File.createTempFile("statsdconfig", ".config");
225                 tempConfigFile.deleteOnExit();
226                 Files.write(builder.build().toByteArray(), tempConfigFile);
227                 configLocation = tempConfigFile.getAbsolutePath();
228             } catch (IOException e) {
229                 sLogger.severe("Failed to write temp config file: " + e.getMessage());
230                 return false;
231             }
232         }
233         String remotePath = "/data/local/tmp/statsdconfig.config";
234         try {
235             Utils.runCommand(null, sLogger, "adb", "push", configLocation, remotePath);
236             Utils.runCommand(null, sLogger, "adb", "shell", "cat", remotePath, "|",
237                     Utils.CMD_UPDATE_CONFIG, Utils.SHELL_UID, String.valueOf(configId));
238         } catch (InterruptedException | IOException e) {
239             sLogger.severe("Failed to update config: " + e.getMessage());
240             return false;
241         }
242         return true;
243     }
244 
245     // get-data [CONFIG_ID] [--clear] [--binary] [--no-uid-map]
cmdGetData(String[] args, int idx, String deviceSerial)246     private static boolean cmdGetData(String[] args, int idx, String deviceSerial) {
247         boolean binary = contains(args, idx + 1, BINARY_FLAG);
248         boolean noUidMap = contains(args, idx + 1, NO_UID_MAP_FLAG);
249         boolean clearData = contains(args, idx + 1, CLEAR_DATA);
250 
251         // Get CONFIG_ID
252         int argCount = args.length - 1 - idx; // Used up one for get-data.
253         if (binary) argCount--;
254         if (noUidMap) argCount--;
255         if (clearData) argCount--;
256         long configId;
257         try {
258             configId = getConfigId(argCount < 1, args, idx + 1);
259         } catch (NumberFormatException e) {
260             sLogger.severe("Invalid config id provided.");
261             printHelp();
262             return false;
263         }
264         sLogger.fine(String.format("cmdGetData with %d %b %b %b",
265                 configId, clearData, binary, noUidMap));
266 
267         // Get the StatsLog
268         // Even if the args request no modifications, we still parse it to make sure it's valid.
269         ConfigMetricsReportList reportList;
270         try {
271             reportList = Utils.getReportList(configId, clearData, true /* SHELL_UID */, sLogger,
272                     deviceSerial);
273         } catch (IOException | InterruptedException e) {
274             sLogger.severe("Failed to get report list: " + e.getMessage());
275             return false;
276         }
277         if (noUidMap) {
278             ConfigMetricsReportList.Builder builder
279                     = ConfigMetricsReportList.newBuilder(reportList);
280             // Clear the reports, then add them back without their UidMap.
281             builder.clearReports();
282             for (ConfigMetricsReport report : reportList.getReportsList()) {
283                 builder.addReports(ConfigMetricsReport.newBuilder(report).clearUidMap());
284             }
285             reportList = builder.build();
286         }
287 
288         if (!binary) {
289             sLogger.info(reportList.toString());
290         } else {
291             try {
292                 System.out.write(reportList.toByteArray());
293             } catch (IOException e) {
294                 sLogger.severe("Failed to output binary statslog proto: "
295                         + e.getMessage());
296                 return false;
297             }
298         }
299         return true;
300     }
301 
302     // clear [CONFIG_ID]
cmdClear(String[] args, int idx, String deviceSerial)303     private static boolean cmdClear(String[] args, int idx, String deviceSerial) {
304         // Get CONFIG_ID
305         long configId;
306         try {
307             configId = getConfigId(false, args, idx + 1);
308         } catch (NumberFormatException e) {
309             sLogger.severe("Invalid config id provided.");
310             printHelp();
311             return false;
312         }
313         sLogger.fine(String.format("cmdClear with %d", configId));
314 
315         try {
316             Utils.getReportList(configId, true /* clearData */, true /* SHELL_UID */, sLogger,
317                     deviceSerial);
318         } catch (IOException | InterruptedException e) {
319             sLogger.severe("Failed to get report list: " + e.getMessage());
320             return false;
321         }
322         return true;
323     }
324 
325     // remove [CONFIG_ID]
cmdRemove(String[] args, int idx)326     private static boolean cmdRemove(String[] args, int idx) {
327         // Get CONFIG_ID
328         long configId;
329         try {
330             configId = getConfigId(false, args, idx + 1);
331         } catch (NumberFormatException e) {
332             sLogger.severe("Invalid config id provided.");
333             printHelp();
334             return false;
335         }
336         sLogger.fine(String.format("cmdRemove with %d", configId));
337 
338         try {
339             Utils.runCommand(null, sLogger, "adb", "shell", Utils.CMD_REMOVE_CONFIG,
340                     Utils.SHELL_UID, String.valueOf(configId));
341         } catch (InterruptedException | IOException e) {
342             sLogger.severe("Failed to remove config: " + e.getMessage());
343             return false;
344         }
345         return true;
346     }
347 
348     /**
349      * Searches through the array to see if it contains (precisely) the given value, starting
350      * at the given firstIdx.
351      */
contains(String[] array, int firstIdx, String value)352     private static boolean contains(String[] array, int firstIdx, String value) {
353         if (value == null) return false;
354         if (firstIdx < 0) return false;
355         for (int i = firstIdx; i < array.length; i++) {
356             if (value.equals(array[i])) {
357                 return true;
358             }
359         }
360         return false;
361     }
362 
363     /**
364      * Gets the config id from args[idx], or returns DEFAULT_CONFIG_ID if args[idx] does not exist.
365      * If justUseDefault, overrides and just uses DEFAULT_CONFIG_ID instead.
366      */
getConfigId(boolean justUseDefault, String[] args, int idx)367     private static long getConfigId(boolean justUseDefault, String[] args, int idx)
368             throws NumberFormatException {
369         if (justUseDefault || args.length <= idx || idx < 0) {
370             return DEFAULT_CONFIG_ID;
371         }
372         try {
373             return Long.valueOf(args[idx]);
374         } catch (NumberFormatException e) {
375             sLogger.severe("Bad config id provided: " + args[idx]);
376             throw e;
377         }
378     }
379 }
380