1#!/usr/bin/env python2.7 2 3import argparse 4import datetime 5import re 6import subprocess 7import sys 8 9import logs 10import ps 11 12DURATION_RE = re.compile("((\\d+)w)?((\\d+)d)?((\\d+)h)?((\\d+)m)?((\\d+)s)?") 13 14class Bucket(object): 15 """Bucket of stats for a particular key managed by the Stats object.""" 16 def __init__(self): 17 self.count = 0 18 self.memory = 0 19 self.lines = [] 20 21 def __str__(self): 22 return "(%s,%s)" % (self.count, self.memory) 23 24 25class Stats(object): 26 """A group of stats with a particular key, where both memory and count are tracked.""" 27 def __init__(self): 28 self._data = dict() 29 30 def add(self, key, logLine): 31 bucket = self._data.get(key) 32 if not bucket: 33 bucket = Bucket() 34 self._data[key] = bucket 35 bucket.count += 1 36 bucket.memory += logLine.memory() 37 bucket.lines.append(logLine) 38 39 def __iter__(self): 40 return self._data.iteritems() 41 42 def data(self): 43 return [(key, bucket) for key, bucket in self._data.iteritems()] 44 45 def byCount(self): 46 result = self.data() 47 result.sort(lambda a, b: -cmp(a[1].count, b[1].count)) 48 return result 49 50 def byMemory(self): 51 result = self.data() 52 result.sort(lambda a, b: -cmp(a[1].memory, b[1].memory)) 53 return result 54 55 56def ParseDuration(s): 57 """Parse a date of the format .w.d.h.m.s into the number of seconds.""" 58 def make_int(index): 59 val = m.group(index) 60 if val: 61 return int(val) 62 else: 63 return 0 64 m = DURATION_RE.match(s) 65 if m: 66 weeks = make_int(2) 67 days = make_int(4) 68 hours = make_int(6) 69 minutes = make_int(8) 70 seconds = make_int(10) 71 return (weeks * 604800) + (days * 86400) + (hours * 3600) + (minutes * 60) + seconds 72 return 0 73 74def FormatMemory(n): 75 """Prettify the number of bytes into gb, mb, etc.""" 76 if n >= 1024 * 1024 * 1024: 77 return "%10d gb" % (n / (1024 * 1024 * 1024)) 78 elif n >= 1024 * 1024: 79 return "%10d mb" % (n / (1024 * 1024)) 80 elif n >= 1024: 81 return "%10d kb" % (n / 1024) 82 else: 83 return "%10d b " % n 84 85def FormateTimeDelta(td): 86 """Format a time duration into the same format we accept on the commandline.""" 87 seconds = (td.days * 86400) + (td.seconds) + int(td.microseconds / 1000000) 88 if seconds == 0: 89 return "0s" 90 result = "" 91 if seconds >= 604800: 92 weeks = int(seconds / 604800) 93 seconds -= weeks * 604800 94 result += "%dw" % weeks 95 if seconds >= 86400: 96 days = int(seconds / 86400) 97 seconds -= days * 86400 98 result += "%dd" % days 99 if seconds >= 3600: 100 hours = int(seconds / 3600) 101 seconds -= hours * 3600 102 result += "%dh" % hours 103 if seconds >= 60: 104 minutes = int(seconds / 60) 105 seconds -= minutes * 60 106 result += "%dm" % minutes 107 if seconds > 0: 108 result += "%ds" % seconds 109 return result 110 111 112def WriteResult(totalCount, totalMemory, bucket, text): 113 """Write a bucket in the normalized format.""" 114 print "%7d (%2d%%) %s (%2d%%) %s" % (bucket.count, (100 * bucket.count / totalCount), 115 FormatMemory(bucket.memory), (100 * bucket.memory / totalMemory), text) 116 117 118def ParseArgs(argv): 119 parser = argparse.ArgumentParser(description="Process some integers.") 120 parser.add_argument("input", type=str, nargs="?", 121 help="the logs file to read") 122 parser.add_argument("--clear", action="store_true", 123 help="clear the log buffer before running logcat") 124 parser.add_argument("--duration", type=str, nargs=1, 125 help="how long to run for (XdXhXmXs)") 126 parser.add_argument("--rawlogs", type=str, nargs=1, 127 help="file to put the rawlogs into") 128 129 args = parser.parse_args() 130 131 args.durationSec = ParseDuration(args.duration[0]) if args.duration else 0 132 133 return args 134 135 136def main(argv): 137 args = ParseArgs(argv) 138 139 processes = ps.ProcessSet() 140 141 if args.rawlogs: 142 rawlogs = file(args.rawlogs[0], "w") 143 else: 144 rawlogs = None 145 146 # Choose the input 147 if args.input: 148 # From a file of raw logs 149 try: 150 infile = file(args.input, "r") 151 except IOError: 152 sys.stderr.write("Error opening file for read: %s\n" % args.input[0]) 153 sys.exit(1) 154 else: 155 # From running adb logcat on an attached device 156 if args.clear: 157 subprocess.check_call(["adb", "logcat", "-c"]) 158 cmd = ["adb", "logcat", "-v", "long", "-D", "-v", "uid"] 159 if not args.durationSec: 160 cmd.append("-d") 161 logcat = subprocess.Popen(cmd, stdout=subprocess.PIPE) 162 infile = logcat.stdout 163 164 # Do one update because we know we'll need it, but then don't do it again 165 # if we're not streaming them. 166 processes.Update(True) 167 if args.durationSec: 168 processes.doUpdates = True 169 170 totalCount = 0 171 totalMemory = 0 172 byTag = Stats() 173 byPid = Stats() 174 byText = Stats() 175 176 startTime = datetime.datetime.now() 177 178 # Read the log lines from the parser and build a big mapping of everything 179 for logLine in logs.ParseLogcat(infile, processes, args.durationSec): 180 if rawlogs: 181 rawlogs.write("%-10s %s %-6s %-6s %-6s %s/%s: %s\n" %(logLine.buf, logLine.timestamp, 182 logLine.uid, logLine.pid, logLine.tid, logLine.level, logLine.tag, logLine.text)) 183 184 totalCount += 1 185 totalMemory += logLine.memory() 186 byTag.add(logLine.tag, logLine) 187 byPid.add(logLine.pid, logLine) 188 byText.add(logLine.text, logLine) 189 190 endTime = datetime.datetime.now() 191 192 # Print the log analysis 193 194 # At this point, everything is loaded, don't bother looking 195 # for new processes 196 processes.doUpdates = False 197 198 print "Top tags by count" 199 print "-----------------" 200 i = 0 201 for k,v in byTag.byCount(): 202 WriteResult(totalCount, totalMemory, v, k) 203 if i >= 10: 204 break 205 i += 1 206 207 print 208 print "Top tags by memory" 209 print "------------------" 210 i = 0 211 for k,v in byTag.byMemory(): 212 WriteResult(totalCount, totalMemory, v, k) 213 if i >= 10: 214 break 215 i += 1 216 217 print 218 print "Top Processes by memory" 219 print "-----------------------" 220 i = 0 221 for k,v in byPid.byMemory(): 222 WriteResult(totalCount, totalMemory, v, 223 "%-8s %s" % (k, processes.FindPid(k).DisplayName())) 224 if i >= 10: 225 break 226 i += 1 227 228 print 229 print "Top Duplicates by count" 230 print "-----------------------" 231 i = 0 232 for k,v in byText.byCount(): 233 logLine = v.lines[0] 234 WriteResult(totalCount, totalMemory, v, 235 "%s/%s: %s" % (logLine.level, logLine.tag, logLine.text)) 236 if i >= 10: 237 break 238 i += 1 239 240 print 241 print "Top Duplicates by memory" 242 print "-----------------------" 243 i = 0 244 for k,v in byText.byCount(): 245 logLine = v.lines[0] 246 WriteResult(totalCount, totalMemory, v, 247 "%s/%s: %s" % (logLine.level, logLine.tag, logLine.text)) 248 if i >= 10: 249 break 250 i += 1 251 252 print 253 print "Totals" 254 print "------" 255 print "%7d %s" % (totalCount, FormatMemory(totalMemory)) 256 257 print "Actual duration: %s" % FormateTimeDelta(endTime-startTime) 258 259if __name__ == "__main__": 260 main(sys.argv) 261 262# vim: set ts=2 sw=2 sts=2 tw=100 nocindent autoindent smartindent expandtab: 263