blob: 2eb46605b28d7a9bc4d0d58171efae66a8d16e1c [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.statsd.shelltools.localdrive;
import com.android.internal.os.StatsdConfigProto.StatsdConfig;
import com.android.os.StatsLog.ConfigMetricsReport;
import com.android.os.StatsLog.ConfigMetricsReportList;
import com.android.statsd.shelltools.Utils;
import com.google.common.io.Files;
import com.google.protobuf.TextFormat;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.logging.Logger;
/**
* Tool for using statsd locally. Can upload a config and get the data. Handles
* both binary and human-readable protos.
* To make: make statsd_localdrive
* To run: statsd_localdrive (i.e. ./out/host/linux-x86/bin/statsd_localdrive)
*/
public class LocalDrive {
private static final boolean DEBUG = false;
public static final int MIN_SDK = 29;
public static final String MIN_CODENAME = "Q";
public static final long DEFAULT_CONFIG_ID = 56789;
public static final String BINARY_FLAG = "--binary";
public static final String CLEAR_DATA = "--clear";
public static final String NO_UID_MAP_FLAG = "--no-uid-map";
public static final String HELP_STRING =
"Usage:\n\n" +
"statsd_localdrive upload CONFIG_FILE [CONFIG_ID] [--binary]\n" +
" Uploads the given statsd config file (in binary or human-readable-text format).\n" +
" If a config with this id already exists, removes it first.\n" +
" CONFIG_FILE Location of config file on host.\n" +
" CONFIG_ID Long ID to associate with this config. If absent, uses "
+ DEFAULT_CONFIG_ID + ".\n" +
" --binary Config is in binary format; otherwise, assumed human-readable text.\n" +
// Similar to: adb shell cmd stats config update SHELL_UID CONFIG_ID
"\n" +
"statsd_localdrive update CONFIG_FILE [CONFIG_ID] [--binary]\n" +
" Same as upload, but does not remove the old config first (if it already exists).\n" +
// Similar to: adb shell cmd stats config update SHELL_UID CONFIG_ID
"\n" +
"statsd_localdrive get-data [CONFIG_ID] [--clear] [--binary] [--no-uid-map]\n" +
" Prints the output statslog data (in binary or human-readable-text format).\n" +
" CONFIG_ID Long ID of the config. If absent, uses " + DEFAULT_CONFIG_ID + ".\n" +
" --binary Output should be in binary, instead of default human-readable text.\n" +
" Binary output can be redirected as usual (e.g. > FILENAME).\n" +
" --no-uid-map Do not include the uid-map (the very lengthy uid<-->pkgName map).\n" +
" --clear Erase the data from statsd afterwards. Does not remove the config.\n" +
// Similar to: adb shell cmd stats dump-report SHELL_UID CONFIG_ID [--keep_data]
// --include_current_bucket --proto
"\n" +
"statsd_localdrive remove [CONFIG_ID]\n" +
" Removes the config.\n" +
" CONFIG_ID Long ID of the config. If absent, uses " + DEFAULT_CONFIG_ID + ".\n" +
// Equivalent to: adb shell cmd stats config remove SHELL_UID CONFIG_ID
"\n" +
"statsd_localdrive clear [CONFIG_ID]\n" +
" Clears the data associated with the config.\n" +
" CONFIG_ID Long ID of the config. If absent, uses " + DEFAULT_CONFIG_ID + ".\n" +
// Similar to: adb shell cmd stats dump-report SHELL_UID CONFIG_ID
// --include_current_bucket --proto
"";
private static final Logger sLogger = Logger.getLogger(LocalDrive.class.getName());
/** Usage: make statsd_localdrive && statsd_localdrive */
public static void main(String[] args) {
Utils.setUpLogger(sLogger, DEBUG);
if (!Utils.isAcceptableStatsd(sLogger, MIN_SDK, MIN_CODENAME)) {
sLogger.severe("LocalDrive only works with statsd versions for Android "
+ MIN_CODENAME + " or higher.");
return;
}
if (args.length > 0) {
switch (args[0]) {
case "clear":
cmdClear(args);
return;
case "get-data":
cmdGetData(args);
return;
case "remove":
cmdRemove(args);
return;
case "update":
cmdUpdate(args);
return;
case "upload":
cmdUpload(args);
return;
}
}
printHelp();
}
private static void printHelp() {
sLogger.info(HELP_STRING);
}
// upload CONFIG_FILE [CONFIG_ID] [--binary]
private static boolean cmdUpload(String[] args) {
return updateConfig(args, true);
}
// update CONFIG_FILE [CONFIG_ID] [--binary]
private static boolean cmdUpdate(String[] args) {
return updateConfig(args, false);
}
private static boolean updateConfig(String[] args, boolean removeOldConfig) {
int argCount = args.length - 1; // Used up one for upload/update.
// Get CONFIG_FILE
if (argCount < 1) {
sLogger.severe("No config file provided.");
printHelp();
return false;
}
final String origConfigLocation = args[1];
if (!new File(origConfigLocation).exists()) {
sLogger.severe("Error - Cannot find the provided config file: " + origConfigLocation);
return false;
}
argCount--;
// Get --binary
boolean binary = contains(args, 2, BINARY_FLAG);
if (binary) argCount --;
// Get CONFIG_ID
long configId;
try {
configId = getConfigId(argCount < 1, args, 2);
} catch (NumberFormatException e) {
sLogger.severe("Invalid config id provided.");
printHelp();
return false;
}
sLogger.fine(String.format("updateConfig with %s %d %b %b",
origConfigLocation, configId, binary, removeOldConfig));
// Remove the old config.
if (removeOldConfig) {
try {
Utils.runCommand(null, sLogger, "adb", "shell", Utils.CMD_REMOVE_CONFIG,
Utils.SHELL_UID, String.valueOf(configId));
Utils.getReportList(configId, true /* clearData */, true /* SHELL_UID */, sLogger);
} catch (InterruptedException | IOException e) {
sLogger.severe("Failed to remove config: " + e.getMessage());
return false;
}
}
// Upload the config.
String configLocation;
if (binary) {
configLocation = origConfigLocation;
} else {
StatsdConfig.Builder builder = StatsdConfig.newBuilder();
try {
TextFormat.merge(new FileReader(origConfigLocation), builder);
} catch (IOException e) {
sLogger.severe("Failed to read config file " + origConfigLocation + ": "
+ e.getMessage());
return false;
}
try {
File tempConfigFile = File.createTempFile("statsdconfig", ".config");
tempConfigFile.deleteOnExit();
Files.write(builder.build().toByteArray(), tempConfigFile);
configLocation = tempConfigFile.getAbsolutePath();
} catch (IOException e) {
sLogger.severe("Failed to write temp config file: " + e.getMessage());
return false;
}
}
String remotePath = "/data/local/tmp/statsdconfig.config";
try {
Utils.runCommand(null, sLogger, "adb", "push", configLocation, remotePath);
Utils.runCommand(null, sLogger, "adb", "shell", "cat", remotePath, "|",
Utils.CMD_UPDATE_CONFIG, Utils.SHELL_UID, String.valueOf(configId));
} catch (InterruptedException | IOException e) {
sLogger.severe("Failed to update config: " + e.getMessage());
return false;
}
return true;
}
// get-data [CONFIG_ID] [--clear] [--binary] [--no-uid-map]
private static boolean cmdGetData(String[] args) {
boolean binary = contains(args, 1, BINARY_FLAG);
boolean noUidMap = contains(args, 1, NO_UID_MAP_FLAG);
boolean clearData = contains(args, 1, CLEAR_DATA);
// Get CONFIG_ID
int argCount = args.length - 1; // Used up one for get-data.
if (binary) argCount--;
if (noUidMap) argCount--;
if (clearData) argCount--;
long configId;
try {
configId = getConfigId(argCount < 1, args, 1);
} catch (NumberFormatException e) {
sLogger.severe("Invalid config id provided.");
printHelp();
return false;
}
sLogger.fine(String.format("cmdGetData with %d %b %b %b",
configId, clearData, binary, noUidMap));
// Get the StatsLog
// Even if the args request no modifications, we still parse it to make sure it's valid.
ConfigMetricsReportList reportList;
try {
reportList = Utils.getReportList(configId, clearData, true /* SHELL_UID */, sLogger);
} catch (IOException | InterruptedException e) {
sLogger.severe("Failed to get report list: " + e.getMessage());
return false;
}
if (noUidMap) {
ConfigMetricsReportList.Builder builder
= ConfigMetricsReportList.newBuilder(reportList);
// Clear the reports, then add them back without their UidMap.
builder.clearReports();
for (ConfigMetricsReport report : reportList.getReportsList()) {
builder.addReports(ConfigMetricsReport.newBuilder(report).clearUidMap());
}
reportList = builder.build();
}
if (!binary) {
sLogger.info(reportList.toString());
} else {
try {
System.out.write(reportList.toByteArray());
} catch (IOException e) {
sLogger.severe("Failed to output binary statslog proto: "
+ e.getMessage());
return false;
}
}
return true;
}
// clear [CONFIG_ID]
private static boolean cmdClear(String[] args) {
// Get CONFIG_ID
long configId;
try {
configId = getConfigId(false, args, 1);
} catch (NumberFormatException e) {
sLogger.severe("Invalid config id provided.");
printHelp();
return false;
}
sLogger.fine(String.format("cmdClear with %d", configId));
try {
Utils.getReportList(configId, true /* clearData */, true /* SHELL_UID */, sLogger);
} catch (IOException | InterruptedException e) {
sLogger.severe("Failed to get report list: " + e.getMessage());
return false;
}
return true;
}
// remove [CONFIG_ID]
private static boolean cmdRemove(String[] args) {
// Get CONFIG_ID
long configId;
try {
configId = getConfigId(false, args, 1);
} catch (NumberFormatException e) {
sLogger.severe("Invalid config id provided.");
printHelp();
return false;
}
sLogger.fine(String.format("cmdRemove with %d", configId));
try {
Utils.runCommand(null, sLogger, "adb", "shell", Utils.CMD_REMOVE_CONFIG,
Utils.SHELL_UID, String.valueOf(configId));
} catch (InterruptedException | IOException e) {
sLogger.severe("Failed to remove config: " + e.getMessage());
return false;
}
return true;
}
/**
* Searches through the array to see if it contains (precisely) the given value, starting
* at the given firstIdx.
*/
private static boolean contains(String[] array, int firstIdx, String value) {
if (value == null) return false;
if (firstIdx < 0) return false;
for (int i = firstIdx; i < array.length; i++) {
if (value.equals(array[i])) {
return true;
}
}
return false;
}
/**
* Gets the config id from args[idx], or returns DEFAULT_CONFIG_ID if args[idx] does not exist.
* If justUseDefault, overrides and just uses DEFAULT_CONFIG_ID instead.
*/
private static long getConfigId(boolean justUseDefault, String[] args, int idx)
throws NumberFormatException {
if (justUseDefault || args.length <= idx || idx < 0) {
return DEFAULT_CONFIG_ID;
}
try {
return Long.valueOf(args[idx]);
} catch (NumberFormatException e) {
sLogger.severe("Bad config id provided: " + args[idx]);
throw e;
}
}
}