| #!/usr/bin/env python3 |
| |
| import argparse |
| import datetime |
| import json |
| import matplotlib.pyplot as plt |
| import sys |
| |
| class UidSnapshot(object): |
| def __init__(self, activity): |
| self.uid = activity['uid'] |
| self.foregroundWrittenBytes = activity['foregroundWrittenBytes'] |
| self.foregroundFsyncCalls = activity['foregroundFsyncCalls'] |
| self.backgroundFsyncCalls = activity['backgroundFsyncCalls'] |
| self.backgroundWrittenBytes = activity['backgroundWrittenBytes'] |
| self.appPackages = activity['appPackages'] |
| self.runtimeMs = activity['runtimeMs'] |
| self.totalWrittenBytes = self.foregroundWrittenBytes + self.backgroundWrittenBytes |
| self.totalFsyncCalls = self.backgroundFsyncCalls + self.foregroundFsyncCalls |
| if self.appPackages is None: self.appPackages = [] |
| |
| class Snapshot(object): |
| def __init__(self, activity, uptime): |
| self.uptime = uptime |
| self.uids = {} |
| self.foregroundWrittenBytes = 0 |
| self.foregroundFsyncCalls = 0 |
| self.backgroundFsyncCalls = 0 |
| self.backgroundWrittenBytes = 0 |
| self.totalWrittenBytes = 0 |
| self.totalFsyncCalls = 0 |
| for entry in activity: |
| uid = entry['uid'] |
| snapshot = UidSnapshot(entry) |
| self.foregroundWrittenBytes += snapshot.foregroundWrittenBytes |
| self.foregroundFsyncCalls += snapshot.foregroundFsyncCalls |
| self.backgroundFsyncCalls += snapshot.backgroundFsyncCalls |
| self.backgroundWrittenBytes += snapshot.backgroundWrittenBytes |
| self.totalWrittenBytes += snapshot.totalWrittenBytes |
| self.totalFsyncCalls += snapshot.totalFsyncCalls |
| self.uids[uid] = snapshot |
| |
| class Document(object): |
| def __init__(self, f): |
| self.snapshots = [] |
| uptimes = [0, 0] |
| for line in f: |
| line = json.loads(line) |
| if line['type'] != 'snapshot': continue |
| activity = line['activity'] |
| uptime = line['uptime'] |
| if uptime < uptimes[0]: uptimes[0] = uptime |
| if uptime > uptimes[1]: uptimes[1] = uptime |
| self.snapshots.append(Snapshot(activity, uptime)) |
| self.runtime = datetime.timedelta(milliseconds=uptimes[1]-uptimes[0]) |
| |
| def merge(l1, l2): |
| s1 = set(l1) |
| s2 = set(l2) |
| return list(s1 | s2) |
| |
| |
| thresholds = [ |
| (1024 * 1024 * 1024 * 1024, "TB"), |
| (1024 * 1024 * 1024, "GB"), |
| (1024 * 1024, "MB"), |
| (1024, "KB"), |
| (1, "bytes") |
| ] |
| def prettyPrintBytes(n): |
| for t in thresholds: |
| if n >= t[0]: |
| return "%.1f %s" % (n / (t[0] + 0.0), t[1]) |
| return "0 bytes" |
| |
| # knowledge extracted from android_filesystem_config.h |
| wellKnownUids = { |
| 0 : ["linux kernel"], |
| 1010 : ["wifi"], |
| 1013 : ["mediaserver"], |
| 1017 : ["keystore"], |
| 1019 : ["DRM server"], |
| 1021 : ["GPS"], |
| 1023 : ["media storage write access"], |
| 1036 : ["logd"], |
| 1040 : ["mediaextractor"], |
| 1041 : ["audioserver"], |
| 1046 : ["mediacodec"], |
| 1047 : ["cameraserver"], |
| 1053 : ["webview zygote"], |
| 1054 : ["vehicle hal"], |
| 1058 : ["tombstoned"], |
| 1066 : ["statsd"], |
| 1067 : ["incidentd"], |
| 9999 : ["nobody"], |
| } |
| |
| class UserActivity(object): |
| def __init__(self, uid): |
| self.uid = uid |
| self.snapshots = [] |
| self.appPackages = wellKnownUids.get(uid, []) |
| self.foregroundWrittenBytes = 0 |
| self.foregroundFsyncCalls = 0 |
| self.backgroundFsyncCalls = 0 |
| self.backgroundWrittenBytes = 0 |
| self.totalWrittenBytes = 0 |
| self.totalFsyncCalls = 0 |
| |
| def addSnapshot(self, snapshot): |
| assert snapshot.uid == self.uid |
| self.snapshots.append(snapshot) |
| self.foregroundWrittenBytes += snapshot.foregroundWrittenBytes |
| self.foregroundFsyncCalls += snapshot.foregroundFsyncCalls |
| self.backgroundFsyncCalls += snapshot.backgroundFsyncCalls |
| self.backgroundWrittenBytes += snapshot.backgroundWrittenBytes |
| self.totalWrittenBytes += snapshot.totalWrittenBytes |
| self.totalFsyncCalls += snapshot.totalFsyncCalls |
| self.appPackages = merge(self.appPackages, snapshot.appPackages) |
| |
| def plot(self, foreground=True, background=True, total=True): |
| plt.figure() |
| plt.title("I/O activity for UID %s" % (self.uid)) |
| X = range(0,len(self.snapshots)) |
| minY = 0 |
| maxY = 0 |
| if foreground: |
| Y = [s.foregroundWrittenBytes for s in self.snapshots] |
| if any([y > 0 for y in Y]): |
| plt.plot(X, Y, 'b-') |
| minY = min(minY, min(Y)) |
| maxY = max(maxY, max(Y)) |
| if background: |
| Y = [s.backgroundWrittenBytes for s in self.snapshots] |
| if any([y > 0 for y in Y]): |
| plt.plot(X, Y, 'g-') |
| minY = min(minY, min(Y)) |
| maxY = max(maxY, max(Y)) |
| if total: |
| Y = [s.totalWrittenBytes for s in self.snapshots] |
| if any([y > 0 for y in Y]): |
| plt.plot(X, Y, 'r-') |
| minY = min(minY, min(Y)) |
| maxY = max(maxY, max(Y)) |
| |
| i = int((maxY - minY) / 5) |
| Yt = list(range(minY, maxY, i)) |
| Yl = [prettyPrintBytes(y) for y in Yt] |
| plt.yticks(Yt, Yl) |
| Xt = list(range(0, len(X))) |
| plt.xticks(Xt) |
| |
| class SystemActivity(object): |
| def __init__(self): |
| self.uids = {} |
| self.snapshots = [] |
| self.foregroundWrittenBytes = 0 |
| self.foregroundFsyncCalls = 0 |
| self.backgroundFsyncCalls = 0 |
| self.backgroundWrittenBytes = 0 |
| self.totalWrittenBytes = 0 |
| self.totalFsyncCalls = 0 |
| |
| def addSnapshot(self, snapshot): |
| self.snapshots.append(snapshot) |
| self.foregroundWrittenBytes += snapshot.foregroundWrittenBytes |
| self.foregroundFsyncCalls += snapshot.foregroundFsyncCalls |
| self.backgroundFsyncCalls += snapshot.backgroundFsyncCalls |
| self.backgroundWrittenBytes += snapshot.backgroundWrittenBytes |
| self.totalWrittenBytes += snapshot.totalWrittenBytes |
| self.totalFsyncCalls += snapshot.totalFsyncCalls |
| for uid in snapshot.uids: |
| if uid not in self.uids: self.uids[uid] = UserActivity(uid) |
| self.uids[uid].addSnapshot(snapshot.uids[uid]) |
| |
| def loadDocument(self, doc): |
| for snapshot in doc.snapshots: |
| self.addSnapshot(snapshot) |
| |
| def sorted(self, f): |
| return sorted(self.uids.values(), key=f, reverse=True) |
| |
| def pie(self): |
| plt.figure() |
| plt.title("Total disk writes per UID") |
| A = [(K, self.uids[K].totalWrittenBytes) for K in self.uids] |
| A = filter(lambda i: i[1] > 0, A) |
| A = list(sorted(A, key=lambda i: i[1], reverse=True)) |
| X = [i[1] for i in A] |
| L = [i[0] for i in A] |
| plt.pie(X, labels=L, counterclock=False, startangle=90) |
| |
| parser = argparse.ArgumentParser("Process FlashApp logs into reports") |
| parser.add_argument("filename") |
| parser.add_argument("--reportuid", action="append", default=[]) |
| parser.add_argument("--plotuid", action="append", default=[]) |
| parser.add_argument("--totalpie", action="store_true", default=False) |
| |
| args = parser.parse_args() |
| |
| class UidFilter(object): |
| def __call__(self, uid): |
| return False |
| |
| class UidFilterAcceptAll(UidFilter): |
| def __call__(self, uid): |
| return True |
| |
| class UidFilterAcceptSome(UidFilter): |
| def __init__(self, uids): |
| self.uids = uids |
| |
| def __call__(self, uid): |
| return uid in self.uids |
| |
| uidset = set() |
| plotfilter = None |
| for uid in args.plotuid: |
| if uid == "all": |
| plotfilter = UidFilterAcceptAll() |
| break |
| else: |
| uidset.add(int(uid)) |
| if plotfilter is None: plotfilter = UidFilterAcceptSome(uidset) |
| |
| uidset = set() |
| reportfilter = None |
| for uid in args.reportuid: |
| if uid == "all": |
| reportfilter = UidFilterAcceptAll() |
| break |
| else: |
| uidset.add(int(uid)) |
| if reportfilter is None: |
| if len(uidset) == 0: |
| reportfilter = UidFilterAcceptAll() |
| else: |
| reportfilter = UidFilterAcceptSome(uidset) |
| |
| document = Document(open(args.filename)) |
| print("System runtime: %s\n" % (document.runtime)) |
| system = SystemActivity() |
| system.loadDocument(document) |
| |
| print("Total bytes written: %s (of which %s in foreground and %s in background)\n" % ( |
| prettyPrintBytes(system.totalWrittenBytes), |
| prettyPrintBytes(system.foregroundWrittenBytes), |
| prettyPrintBytes(system.backgroundWrittenBytes))) |
| |
| writemost = filter(lambda ua: ua.totalWrittenBytes > 0, system.sorted(lambda ua: ua.totalWrittenBytes)) |
| for entry in writemost: |
| if reportfilter(entry.uid): |
| print("user id %d (%s) wrote %s (of which %s in foreground and %s in background)" % ( |
| entry.uid, |
| ','.join(entry.appPackages), |
| prettyPrintBytes(entry.totalWrittenBytes), |
| prettyPrintBytes(entry.foregroundWrittenBytes), |
| prettyPrintBytes(entry.backgroundWrittenBytes))) |
| if plotfilter(entry.uid): |
| entry.plot() |
| plt.show() |
| |
| if args.totalpie: |
| system.pie() |
| plt.show() |
| |