blob: 691bf0c8d4facd082b7124fc26c66d45d998019b [file] [log] [blame]
/*
* Copyright (C) 2008 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.eventanalyzer;
import com.android.ddmlib.AdbCommandRejectedException;
import com.android.ddmlib.AndroidDebugBridge;
import com.android.ddmlib.IDevice;
import com.android.ddmlib.Log;
import com.android.ddmlib.TimeoutException;
import com.android.ddmlib.Log.ILogOutput;
import com.android.ddmlib.Log.LogLevel;
import com.android.ddmlib.log.EventContainer;
import com.android.ddmlib.log.EventLogParser;
import com.android.ddmlib.log.InvalidTypeException;
import com.android.ddmlib.log.LogReceiver;
import com.android.ddmlib.log.LogReceiver.ILogListener;
import com.android.ddmlib.log.LogReceiver.LogEntry;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Set;
import java.util.TreeMap;
/**
* Connects to a device using ddmlib and analyze its event log.
*/
public class EventAnalyzer implements ILogListener {
private final static int TAG_ACTIVITY_LAUNCH_TIME = 30009;
private final static char DATA_SEPARATOR = ',';
private final static String CVS_EXT = ".csv";
private final static String TAG_FILE_EXT = ".tag"; //$NON-NLS-1$
private EventLogParser mParser;
private TreeMap<String, ArrayList<Long>> mLaunchMap = new TreeMap<String, ArrayList<Long>>();
String mInputTextFile = null;
String mInputBinaryFile = null;
String mInputDevice = null;
String mInputFolder = null;
String mAlternateTagFile = null;
String mOutputFile = null;
public static void main(String[] args) {
new EventAnalyzer().run(args);
}
private void run(String[] args) {
if (args.length == 0) {
printUsageAndQuit();
}
int index = 0;
do {
String argument = args[index++];
if ("-s".equals(argument)) {
checkInputValidity("-s");
if (index == args.length) {
printUsageAndQuit();
}
mInputDevice = args[index++];
} else if ("-fb".equals(argument)) {
checkInputValidity("-fb");
if (index == args.length) {
printUsageAndQuit();
}
mInputBinaryFile = args[index++];
} else if ("-ft".equals(argument)) {
checkInputValidity("-ft");
if (index == args.length) {
printUsageAndQuit();
}
mInputTextFile = args[index++];
} else if ("-F".equals(argument)) {
checkInputValidity("-F");
if (index == args.length) {
printUsageAndQuit();
}
mInputFolder = args[index++];
} else if ("-t".equals(argument)) {
if (index == args.length) {
printUsageAndQuit();
}
mAlternateTagFile = args[index++];
} else {
// get the filepath and break.
mOutputFile = argument;
// should not be any other device.
if (index < args.length) {
printAndExit("Too many arguments!", false /* terminate */);
}
}
} while (index < args.length);
if ((mInputTextFile == null && mInputBinaryFile == null && mInputFolder == null &&
mInputDevice == null)) {
printUsageAndQuit();
}
File outputParent = new File(mOutputFile).getParentFile();
if (outputParent == null || outputParent.isDirectory() == false) {
printAndExit(String.format("%1$s is not a valid ouput file", mOutputFile),
false /* terminate */);
}
// redirect the log output to /dev/null
Log.setLogOutput(new ILogOutput() {
public void printAndPromptLog(LogLevel logLevel, String tag, String message) {
// pass
}
public void printLog(LogLevel logLevel, String tag, String message) {
// pass
}
});
try {
if (mInputBinaryFile != null) {
parseBinaryLogFile();
} else if (mInputTextFile != null) {
parseTextLogFile(mInputTextFile);
} else if (mInputFolder != null) {
parseFolder(mInputFolder);
} else if (mInputDevice != null) {
parseLogFromDevice();
}
// analyze the data gathered by the parser methods
analyzeData();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Parses a binary event log file located at {@link #mInputBinaryFile}.
* @throws IOException
*/
private void parseBinaryLogFile() throws IOException {
mParser = new EventLogParser();
String tagFile = mInputBinaryFile + TAG_FILE_EXT;
if (mParser.init(tagFile) == false) {
// if we have an alternate location
if (mAlternateTagFile != null) {
if (mParser.init(mAlternateTagFile) == false) {
printAndExit("Failed to get event tags from " + mAlternateTagFile,
false /* terminate*/);
}
} else {
printAndExit("Failed to get event tags from " + tagFile, false /* terminate*/);
}
}
LogReceiver receiver = new LogReceiver(this);
byte[] buffer = new byte[256];
FileInputStream fis = new FileInputStream(mInputBinaryFile);
int count;
while ((count = fis.read(buffer)) != -1) {
receiver.parseNewData(buffer, 0, count);
}
}
/**
* Parse a text Log file.
* @param filePath the location of the file.
* @throws IOException
*/
private void parseTextLogFile(String filePath) throws IOException {
mParser = new EventLogParser();
String tagFile = filePath + TAG_FILE_EXT;
if (mParser.init(tagFile) == false) {
// if we have an alternate location
if (mAlternateTagFile != null) {
if (mParser.init(mAlternateTagFile) == false) {
printAndExit("Failed to get event tags from " + mAlternateTagFile,
false /* terminate*/);
}
} else {
printAndExit("Failed to get event tags from " + tagFile, false /* terminate*/);
}
}
// read the lines from the file and process them.
BufferedReader reader = new BufferedReader(
new InputStreamReader(new FileInputStream(filePath)));
String line;
while ((line = reader.readLine()) != null) {
processEvent(mParser.parse(line));
}
}
private void parseLogFromDevice() throws IOException, TimeoutException,
AdbCommandRejectedException {
// init the lib
AndroidDebugBridge.init(false /* debugger support */);
try {
AndroidDebugBridge bridge = AndroidDebugBridge.createBridge();
// we can't just ask for the device list right away, as the internal thread getting
// them from ADB may not be done getting the first list.
// Since we don't really want getDevices() to be blocking, we wait here manually.
int count = 0;
while (bridge.hasInitialDeviceList() == false) {
try {
Thread.sleep(100);
count++;
} catch (InterruptedException e) {
// pass
}
// let's not wait > 10 sec.
if (count > 100) {
printAndExit("Timeout getting device list!", true /* terminate*/);
}
}
// now get the devices
IDevice[] devices = bridge.getDevices();
for (IDevice device : devices) {
if (device.getSerialNumber().equals(mInputDevice)) {
grabLogFrom(device);
return;
}
}
System.err.println("Could not find " + mInputDevice);
} finally {
AndroidDebugBridge.terminate();
}
}
/**
* Parses the log files located in the folder, and its sub-folders.
* @param folderPath the path to the folder.
*/
private void parseFolder(String folderPath) {
File f = new File(folderPath);
if (f.isDirectory() == false) {
printAndExit(String.format("%1$s is not a valid folder", folderPath),
false /* terminate */);
}
String[] files = f.list(new FilenameFilter() {
public boolean accept(File dir, String name) {
name = name.toLowerCase();
return name.endsWith(".tag") == false;
}
});
for (String file : files) {
try {
f = new File(folderPath + File.separator + file);
if (f.isDirectory()) {
parseFolder(f.getAbsolutePath());
} else {
parseTextLogFile(f.getAbsolutePath());
}
} catch (IOException e) {
// ignore this file.
}
}
}
private void grabLogFrom(IDevice device) throws IOException, TimeoutException,
AdbCommandRejectedException {
mParser = new EventLogParser();
if (mParser.init(device) == false) {
printAndExit("Failed to get event-log-tags from " + device.getSerialNumber(),
true /* terminate*/);
}
LogReceiver receiver = new LogReceiver(this);
device.runEventLogService(receiver);
}
/**
* Analyze the data and writes it to {@link #mOutputFile}
* @throws IOException
*/
private void analyzeData() throws IOException {
BufferedWriter writer = null;
try {
// make sure the file name has the proper extension.
if (mOutputFile.toLowerCase().endsWith(CVS_EXT) == false) {
mOutputFile = mOutputFile + CVS_EXT;
}
writer = new BufferedWriter(new FileWriter(mOutputFile));
StringBuilder builder = new StringBuilder();
// write the list of launch start. One column per activity.
Set<String> activities = mLaunchMap.keySet();
// write the column headers.
for (String activity : activities) {
builder.append(activity).append(DATA_SEPARATOR);
}
writer.write(builder.append('\n').toString());
// loop on the activities and write their values.
boolean moreValues = true;
int index = 0;
while (moreValues) {
moreValues = false;
builder.setLength(0);
for (String activity : activities) {
// get the activity list.
ArrayList<Long> list = mLaunchMap.get(activity);
if (index < list.size()) {
moreValues = true;
builder.append(list.get(index).longValue()).append(DATA_SEPARATOR);
} else {
builder.append(DATA_SEPARATOR);
}
}
// write the line.
if (moreValues) {
writer.write(builder.append('\n').toString());
}
index++;
}
// write per-activity stats.
for (String activity : activities) {
builder.setLength(0);
builder.append(activity).append(DATA_SEPARATOR);
// get the activity list.
ArrayList<Long> list = mLaunchMap.get(activity);
// sort the list
Collections.sort(list);
// write min/max
builder.append(list.get(0).longValue()).append(DATA_SEPARATOR);
builder.append(list.get(list.size()-1).longValue()).append(DATA_SEPARATOR);
// write median value
builder.append(list.get(list.size()/2).longValue()).append(DATA_SEPARATOR);
// compute and write average
long total = 0; // despite being encoded on a long, the values are low enough that
// a Long should be enough to compute the total
for (Long value : list) {
total += value.longValue();
}
builder.append(total / list.size()).append(DATA_SEPARATOR);
// finally write the data.
writer.write(builder.append('\n').toString());
}
} finally {
writer.close();
}
}
/*
* (non-Javadoc)
* @see com.android.ddmlib.log.LogReceiver.ILogListener#newData(byte[], int, int)
*/
public void newData(byte[] data, int offset, int length) {
// we ignore raw data. New entries are processed in #newEntry(LogEntry)
}
/*
* (non-Javadoc)
* @see com.android.ddmlib.log.LogReceiver.ILogListener#newEntry(com.android.ddmlib.log.LogReceiver.LogEntry)
*/
public void newEntry(LogEntry entry) {
// parse and process the entry data.
processEvent(mParser.parse(entry));
}
private void processEvent(EventContainer event) {
if (event != null && event.mTag == TAG_ACTIVITY_LAUNCH_TIME) {
// get the activity name
try {
String name = event.getValueAsString(0);
// get the launch time
Object value = event.getValue(1);
if (value instanceof Long) {
addLaunchTime(name, (Long)value);
}
} catch (InvalidTypeException e) {
// Couldn't get the name as a string...
// Ignore this event.
}
}
}
private void addLaunchTime(String name, Long value) {
ArrayList<Long> list = mLaunchMap.get(name);
if (list == null) {
list = new ArrayList<Long>();
mLaunchMap.put(name, list);
}
list.add(value);
}
private void checkInputValidity(String option) {
if (mInputTextFile != null || mInputBinaryFile != null) {
printAndExit(String.format("ERROR: %1$s cannot be used with an input file.", option),
false /* terminate */);
} else if (mInputFolder != null) {
printAndExit(String.format("ERROR: %1$s cannot be used with an input file.", option),
false /* terminate */);
} else if (mInputDevice != null) {
printAndExit(String.format("ERROR: %1$s cannot be used with an input device serial number.",
option), false /* terminate */);
}
}
private static void printUsageAndQuit() {
// 80 cols marker: 01234567890123456789012345678901234567890123456789012345678901234567890123456789
System.out.println("Usage:");
System.out.println(" eventanalyzer [-t <TAG_FILE>] <SOURCE> <OUTPUT>");
System.out.println("");
System.out.println("Possible sources:");
System.out.println(" -fb <file> The path to a binary event log, gathered by dumpeventlog");
System.out.println(" -ft <file> The path to a text event log, gathered by adb logcat -b events");
System.out.println(" -F <folder> The path to a folder containing multiple text log files.");
System.out.println(" -s <serial> The serial number of the Device to grab the event log from.");
System.out.println("Options:");
System.out.println(" -t <file> The path to tag file to use in case the one associated with");
System.out.println(" the source is missing");
System.exit(1);
}
private static void printAndExit(String message, boolean terminate) {
System.out.println(message);
if (terminate) {
AndroidDebugBridge.terminate();
}
System.exit(1);
}
}