#!/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: