Enrico Granata | ea17dea | 2018-02-21 14:57:10 -0800 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
| 2 | |
| 3 | import argparse |
| 4 | import datetime |
| 5 | import json |
| 6 | import matplotlib.pyplot as plt |
| 7 | import sys |
| 8 | |
| 9 | class 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 | |
| 22 | class 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 | |
| 43 | class 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 | |
| 57 | def merge(l1, l2): |
| 58 | s1 = set(l1) |
| 59 | s2 = set(l2) |
| 60 | return list(s1 | s2) |
| 61 | |
| 62 | |
| 63 | thresholds = [ |
| 64 | (1024 * 1024 * 1024 * 1024, "TB"), |
| 65 | (1024 * 1024 * 1024, "GB"), |
| 66 | (1024 * 1024, "MB"), |
| 67 | (1024, "KB"), |
| 68 | (1, "bytes") |
| 69 | ] |
| 70 | def 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 |
| 77 | wellKnownUids = { |
| 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 | |
| 98 | class 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 | |
| 153 | class 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 | |
| 193 | parser = argparse.ArgumentParser("Process FlashApp logs into reports") |
| 194 | parser.add_argument("filename") |
| 195 | parser.add_argument("--reportuid", action="append", default=[]) |
| 196 | parser.add_argument("--plotuid", action="append", default=[]) |
| 197 | parser.add_argument("--totalpie", action="store_true", default=False) |
| 198 | |
| 199 | args = parser.parse_args() |
| 200 | |
| 201 | class UidFilter(object): |
| 202 | def __call__(self, uid): |
| 203 | return False |
| 204 | |
| 205 | class UidFilterAcceptAll(UidFilter): |
| 206 | def __call__(self, uid): |
| 207 | return True |
| 208 | |
| 209 | class UidFilterAcceptSome(UidFilter): |
| 210 | def __init__(self, uids): |
| 211 | self.uids = uids |
| 212 | |
| 213 | def __call__(self, uid): |
| 214 | return uid in self.uids |
| 215 | |
| 216 | uidset = set() |
| 217 | plotfilter = None |
| 218 | for uid in args.plotuid: |
| 219 | if uid == "all": |
| 220 | plotfilter = UidFilterAcceptAll() |
| 221 | break |
| 222 | else: |
| 223 | uidset.add(int(uid)) |
| 224 | if plotfilter is None: plotfilter = UidFilterAcceptSome(uidset) |
| 225 | |
| 226 | uidset = set() |
| 227 | reportfilter = None |
| 228 | for uid in args.reportuid: |
| 229 | if uid == "all": |
| 230 | reportfilter = UidFilterAcceptAll() |
| 231 | break |
| 232 | else: |
| 233 | uidset.add(int(uid)) |
| 234 | if reportfilter is None: |
| 235 | if len(uidset) == 0: |
| 236 | reportfilter = UidFilterAcceptAll() |
| 237 | else: |
| 238 | reportfilter = UidFilterAcceptSome(uidset) |
| 239 | |
| 240 | document = Document(open(args.filename)) |
| 241 | print("System runtime: %s\n" % (document.runtime)) |
| 242 | system = SystemActivity() |
| 243 | system.loadDocument(document) |
| 244 | |
| 245 | print("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 | |
| 250 | writemost = filter(lambda ua: ua.totalWrittenBytes > 0, system.sorted(lambda ua: ua.totalWrittenBytes)) |
| 251 | for 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 | |
| 263 | if args.totalpie: |
| 264 | system.pie() |
| 265 | plt.show() |
| 266 | |