| /* |
| * 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); |
| } |
| } |