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