You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
263 lines
6.8 KiB
263 lines
6.8 KiB
#!/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:
|