1#!/usr/bin/env python3 2 3import argparse 4import datetime 5import json 6import matplotlib.pyplot as plt 7import sys 8 9class UidSnapshot(object): 10 def __init__(self, activity): 11 self.uid = activity['uid'] 12 self.foregroundWrittenBytes = activity['foregroundWrittenBytes'] 13 self.foregroundFsyncCalls = activity['foregroundFsyncCalls'] 14 self.backgroundFsyncCalls = activity['backgroundFsyncCalls'] 15 self.backgroundWrittenBytes = activity['backgroundWrittenBytes'] 16 self.appPackages = activity['appPackages'] 17 self.runtimeMs = activity['runtimeMs'] 18 self.totalWrittenBytes = self.foregroundWrittenBytes + self.backgroundWrittenBytes 19 self.totalFsyncCalls = self.backgroundFsyncCalls + self.foregroundFsyncCalls 20 if self.appPackages is None: self.appPackages = [] 21 22class Snapshot(object): 23 def __init__(self, activity, uptime): 24 self.uptime = uptime 25 self.uids = {} 26 self.foregroundWrittenBytes = 0 27 self.foregroundFsyncCalls = 0 28 self.backgroundFsyncCalls = 0 29 self.backgroundWrittenBytes = 0 30 self.totalWrittenBytes = 0 31 self.totalFsyncCalls = 0 32 for entry in activity: 33 uid = entry['uid'] 34 snapshot = UidSnapshot(entry) 35 self.foregroundWrittenBytes += snapshot.foregroundWrittenBytes 36 self.foregroundFsyncCalls += snapshot.foregroundFsyncCalls 37 self.backgroundFsyncCalls += snapshot.backgroundFsyncCalls 38 self.backgroundWrittenBytes += snapshot.backgroundWrittenBytes 39 self.totalWrittenBytes += snapshot.totalWrittenBytes 40 self.totalFsyncCalls += snapshot.totalFsyncCalls 41 self.uids[uid] = snapshot 42 43class Document(object): 44 def __init__(self, f): 45 self.snapshots = [] 46 uptimes = [0, 0] 47 for line in f: 48 line = json.loads(line) 49 if line['type'] != 'snapshot': continue 50 activity = line['activity'] 51 uptime = line['uptime'] 52 if uptime < uptimes[0]: uptimes[0] = uptime 53 if uptime > uptimes[1]: uptimes[1] = uptime 54 self.snapshots.append(Snapshot(activity, uptime)) 55 self.runtime = datetime.timedelta(milliseconds=uptimes[1]-uptimes[0]) 56 57def merge(l1, l2): 58 s1 = set(l1) 59 s2 = set(l2) 60 return list(s1 | s2) 61 62 63thresholds = [ 64 (1024 * 1024 * 1024 * 1024, "TB"), 65 (1024 * 1024 * 1024, "GB"), 66 (1024 * 1024, "MB"), 67 (1024, "KB"), 68 (1, "bytes") 69] 70def prettyPrintBytes(n): 71 for t in thresholds: 72 if n >= t[0]: 73 return "%.1f %s" % (n / (t[0] + 0.0), t[1]) 74 return "0 bytes" 75 76# knowledge extracted from android_filesystem_config.h 77wellKnownUids = { 78 0 : ["linux kernel"], 79 1010 : ["wifi"], 80 1013 : ["mediaserver"], 81 1017 : ["keystore"], 82 1019 : ["DRM server"], 83 1021 : ["GPS"], 84 1023 : ["media storage write access"], 85 1036 : ["logd"], 86 1040 : ["mediaextractor"], 87 1041 : ["audioserver"], 88 1046 : ["mediacodec"], 89 1047 : ["cameraserver"], 90 1053 : ["webview zygote"], 91 1054 : ["vehicle hal"], 92 1058 : ["tombstoned"], 93 1066 : ["statsd"], 94 1067 : ["incidentd"], 95 9999 : ["nobody"], 96} 97 98class UserActivity(object): 99 def __init__(self, uid): 100 self.uid = uid 101 self.snapshots = [] 102 self.appPackages = wellKnownUids.get(uid, []) 103 self.foregroundWrittenBytes = 0 104 self.foregroundFsyncCalls = 0 105 self.backgroundFsyncCalls = 0 106 self.backgroundWrittenBytes = 0 107 self.totalWrittenBytes = 0 108 self.totalFsyncCalls = 0 109 110 def addSnapshot(self, snapshot): 111 assert snapshot.uid == self.uid 112 self.snapshots.append(snapshot) 113 self.foregroundWrittenBytes += snapshot.foregroundWrittenBytes 114 self.foregroundFsyncCalls += snapshot.foregroundFsyncCalls 115 self.backgroundFsyncCalls += snapshot.backgroundFsyncCalls 116 self.backgroundWrittenBytes += snapshot.backgroundWrittenBytes 117 self.totalWrittenBytes += snapshot.totalWrittenBytes 118 self.totalFsyncCalls += snapshot.totalFsyncCalls 119 self.appPackages = merge(self.appPackages, snapshot.appPackages) 120 121 def plot(self, foreground=True, background=True, total=True): 122 plt.figure() 123 plt.title("I/O activity for UID %s" % (self.uid)) 124 X = range(0,len(self.snapshots)) 125 minY = 0 126 maxY = 0 127 if foreground: 128 Y = [s.foregroundWrittenBytes for s in self.snapshots] 129 if any([y > 0 for y in Y]): 130 plt.plot(X, Y, 'b-') 131 minY = min(minY, min(Y)) 132 maxY = max(maxY, max(Y)) 133 if background: 134 Y = [s.backgroundWrittenBytes for s in self.snapshots] 135 if any([y > 0 for y in Y]): 136 plt.plot(X, Y, 'g-') 137 minY = min(minY, min(Y)) 138 maxY = max(maxY, max(Y)) 139 if total: 140 Y = [s.totalWrittenBytes for s in self.snapshots] 141 if any([y > 0 for y in Y]): 142 plt.plot(X, Y, 'r-') 143 minY = min(minY, min(Y)) 144 maxY = max(maxY, max(Y)) 145 146 i = int((maxY - minY) / 5) 147 Yt = list(range(minY, maxY, i)) 148 Yl = [prettyPrintBytes(y) for y in Yt] 149 plt.yticks(Yt, Yl) 150 Xt = list(range(0, len(X))) 151 plt.xticks(Xt) 152 153class SystemActivity(object): 154 def __init__(self): 155 self.uids = {} 156 self.snapshots = [] 157 self.foregroundWrittenBytes = 0 158 self.foregroundFsyncCalls = 0 159 self.backgroundFsyncCalls = 0 160 self.backgroundWrittenBytes = 0 161 self.totalWrittenBytes = 0 162 self.totalFsyncCalls = 0 163 164 def addSnapshot(self, snapshot): 165 self.snapshots.append(snapshot) 166 self.foregroundWrittenBytes += snapshot.foregroundWrittenBytes 167 self.foregroundFsyncCalls += snapshot.foregroundFsyncCalls 168 self.backgroundFsyncCalls += snapshot.backgroundFsyncCalls 169 self.backgroundWrittenBytes += snapshot.backgroundWrittenBytes 170 self.totalWrittenBytes += snapshot.totalWrittenBytes 171 self.totalFsyncCalls += snapshot.totalFsyncCalls 172 for uid in snapshot.uids: 173 if uid not in self.uids: self.uids[uid] = UserActivity(uid) 174 self.uids[uid].addSnapshot(snapshot.uids[uid]) 175 176 def loadDocument(self, doc): 177 for snapshot in doc.snapshots: 178 self.addSnapshot(snapshot) 179 180 def sorted(self, f): 181 return sorted(self.uids.values(), key=f, reverse=True) 182 183 def pie(self): 184 plt.figure() 185 plt.title("Total disk writes per UID") 186 A = [(K, self.uids[K].totalWrittenBytes) for K in self.uids] 187 A = filter(lambda i: i[1] > 0, A) 188 A = list(sorted(A, key=lambda i: i[1], reverse=True)) 189 X = [i[1] for i in A] 190 L = [i[0] for i in A] 191 plt.pie(X, labels=L, counterclock=False, startangle=90) 192 193parser = argparse.ArgumentParser("Process FlashApp logs into reports") 194parser.add_argument("filename") 195parser.add_argument("--reportuid", action="append", default=[]) 196parser.add_argument("--plotuid", action="append", default=[]) 197parser.add_argument("--totalpie", action="store_true", default=False) 198 199args = parser.parse_args() 200 201class UidFilter(object): 202 def __call__(self, uid): 203 return False 204 205class UidFilterAcceptAll(UidFilter): 206 def __call__(self, uid): 207 return True 208 209class UidFilterAcceptSome(UidFilter): 210 def __init__(self, uids): 211 self.uids = uids 212 213 def __call__(self, uid): 214 return uid in self.uids 215 216uidset = set() 217plotfilter = None 218for uid in args.plotuid: 219 if uid == "all": 220 plotfilter = UidFilterAcceptAll() 221 break 222 else: 223 uidset.add(int(uid)) 224if plotfilter is None: plotfilter = UidFilterAcceptSome(uidset) 225 226uidset = set() 227reportfilter = None 228for uid in args.reportuid: 229 if uid == "all": 230 reportfilter = UidFilterAcceptAll() 231 break 232 else: 233 uidset.add(int(uid)) 234if reportfilter is None: 235 if len(uidset) == 0: 236 reportfilter = UidFilterAcceptAll() 237 else: 238 reportfilter = UidFilterAcceptSome(uidset) 239 240document = Document(open(args.filename)) 241print("System runtime: %s\n" % (document.runtime)) 242system = SystemActivity() 243system.loadDocument(document) 244 245print("Total bytes written: %s (of which %s in foreground and %s in background)\n" % ( 246 prettyPrintBytes(system.totalWrittenBytes), 247 prettyPrintBytes(system.foregroundWrittenBytes), 248 prettyPrintBytes(system.backgroundWrittenBytes))) 249 250writemost = filter(lambda ua: ua.totalWrittenBytes > 0, system.sorted(lambda ua: ua.totalWrittenBytes)) 251for entry in writemost: 252 if reportfilter(entry.uid): 253 print("user id %d (%s) wrote %s (of which %s in foreground and %s in background)" % ( 254 entry.uid, 255 ','.join(entry.appPackages), 256 prettyPrintBytes(entry.totalWrittenBytes), 257 prettyPrintBytes(entry.foregroundWrittenBytes), 258 prettyPrintBytes(entry.backgroundWrittenBytes))) 259 if plotfilter(entry.uid): 260 entry.plot() 261 plt.show() 262 263if args.totalpie: 264 system.pie() 265 plt.show() 266 267