#!/usr/bin/env python2.7 import argparse import datetime import re import subprocess import sys import logs import ps DURATION_RE = re.compile("((\\d+)w)?((\\d+)d)?((\\d+)h)?((\\d+)m)?((\\d+)s)?") class Bucket(object): """Bucket of stats for a particular key managed by the Stats object.""" def __init__(self): self.count = 0 self.memory = 0 self.lines = [] def __str__(self): return "(%s,%s)" % (self.count, self.memory) class Stats(object): """A group of stats with a particular key, where both memory and count are tracked.""" def __init__(self): self._data = dict() def add(self, key, logLine): bucket = self._data.get(key) if not bucket: bucket = Bucket() self._data[key] = bucket bucket.count += 1 bucket.memory += logLine.memory() bucket.lines.append(logLine) def __iter__(self): return self._data.iteritems() def data(self): return [(key, bucket) for key, bucket in self._data.iteritems()] def byCount(self): result = self.data() result.sort(lambda a, b: -cmp(a[1].count, b[1].count)) return result def byMemory(self): result = self.data() result.sort(lambda a, b: -cmp(a[1].memory, b[1].memory)) return result def ParseDuration(s): """Parse a date of the format .w.d.h.m.s into the number of seconds.""" def make_int(index): val = m.group(index) if val: return int(val) else: return 0 m = DURATION_RE.match(s) if m: weeks = make_int(2) days = make_int(4) hours = make_int(6) minutes = make_int(8) seconds = make_int(10) return (weeks * 604800) + (days * 86400) + (hours * 3600) + (minutes * 60) + seconds return 0 def FormatMemory(n): """Prettify the number of bytes into gb, mb, etc.""" if n >= 1024 * 1024 * 1024: return "%10d gb" % (n / (1024 * 1024 * 1024)) elif n >= 1024 * 1024: return "%10d mb" % (n / (1024 * 1024)) elif n >= 1024: return "%10d kb" % (n / 1024) else: return "%10d b " % n def FormateTimeDelta(td): """Format a time duration into the same format we accept on the commandline.""" seconds = (td.days * 86400) + (td.seconds) + int(td.microseconds / 1000000) if seconds == 0: return "0s" result = "" if seconds >= 604800: weeks = int(seconds / 604800) seconds -= weeks * 604800 result += "%dw" % weeks if seconds >= 86400: days = int(seconds / 86400) seconds -= days * 86400 result += "%dd" % days if seconds >= 3600: hours = int(seconds / 3600) seconds -= hours * 3600 result += "%dh" % hours if seconds >= 60: minutes = int(seconds / 60) seconds -= minutes * 60 result += "%dm" % minutes if seconds > 0: result += "%ds" % seconds return result def WriteResult(totalCount, totalMemory, bucket, text): """Write a bucket in the normalized format.""" print "%7d (%2d%%) %s (%2d%%) %s" % (bucket.count, (100 * bucket.count / totalCount), FormatMemory(bucket.memory), (100 * bucket.memory / totalMemory), text) def ParseArgs(argv): parser = argparse.ArgumentParser(description="Process some integers.") parser.add_argument("input", type=str, nargs="?", help="the logs file to read") parser.add_argument("--clear", action="store_true", help="clear the log buffer before running logcat") parser.add_argument("--duration", type=str, nargs=1, help="how long to run for (XdXhXmXs)") parser.add_argument("--rawlogs", type=str, nargs=1, help="file to put the rawlogs into") args = parser.parse_args() args.durationSec = ParseDuration(args.duration[0]) if args.duration else 0 return args def main(argv): args = ParseArgs(argv) processes = ps.ProcessSet() if args.rawlogs: rawlogs = file(args.rawlogs[0], "w") else: rawlogs = None # Choose the input if args.input: # From a file of raw logs try: infile = file(args.input, "r") except IOError: sys.stderr.write("Error opening file for read: %s\n" % args.input[0]) sys.exit(1) else: # From running adb logcat on an attached device if args.clear: subprocess.check_call(["adb", "logcat", "-c"]) cmd = ["adb", "logcat", "-v", "long", "-D", "-v", "uid"] if not args.durationSec: cmd.append("-d") logcat = subprocess.Popen(cmd, stdout=subprocess.PIPE) infile = logcat.stdout # Do one update because we know we'll need it, but then don't do it again # if we're not streaming them. processes.Update(True) if args.durationSec: processes.doUpdates = True totalCount = 0 totalMemory = 0 byTag = Stats() byPid = Stats() byText = Stats() startTime = datetime.datetime.now() # Read the log lines from the parser and build a big mapping of everything for logLine in logs.ParseLogcat(infile, processes, args.durationSec): if rawlogs: rawlogs.write("%-10s %s %-6s %-6s %-6s %s/%s: %s\n" %(logLine.buf, logLine.timestamp, logLine.uid, logLine.pid, logLine.tid, logLine.level, logLine.tag, logLine.text)) totalCount += 1 totalMemory += logLine.memory() byTag.add(logLine.tag, logLine) byPid.add(logLine.pid, logLine) byText.add(logLine.text, logLine) endTime = datetime.datetime.now() # Print the log analysis # At this point, everything is loaded, don't bother looking # for new processes processes.doUpdates = False print "Top tags by count" print "-----------------" i = 0 for k,v in byTag.byCount(): WriteResult(totalCount, totalMemory, v, k) if i >= 10: break i += 1 print print "Top tags by memory" print "------------------" i = 0 for k,v in byTag.byMemory(): WriteResult(totalCount, totalMemory, v, k) if i >= 10: break i += 1 print print "Top Processes by memory" print "-----------------------" i = 0 for k,v in byPid.byMemory(): WriteResult(totalCount, totalMemory, v, "%-8s %s" % (k, processes.FindPid(k).DisplayName())) if i >= 10: break i += 1 print print "Top Duplicates by count" print "-----------------------" i = 0 for k,v in byText.byCount(): logLine = v.lines[0] WriteResult(totalCount, totalMemory, v, "%s/%s: %s" % (logLine.level, logLine.tag, logLine.text)) if i >= 10: break i += 1 print print "Top Duplicates by memory" print "-----------------------" i = 0 for k,v in byText.byCount(): logLine = v.lines[0] WriteResult(totalCount, totalMemory, v, "%s/%s: %s" % (logLine.level, logLine.tag, logLine.text)) if i >= 10: break i += 1 print print "Totals" print "------" print "%7d %s" % (totalCount, FormatMemory(totalMemory)) print "Actual duration: %s" % FormateTimeDelta(endTime-startTime) if __name__ == "__main__": main(sys.argv) # vim: set ts=2 sw=2 sts=2 tw=100 nocindent autoindent smartindent expandtab: