Add tool to produce reports and graphs from a FlashApp document
Bug: 32512551
Bug: 65846824
Test: manual
Change-Id: If88f698a85590c700c0ae5d808d09eca9487ed4e
diff --git a/tools/ioanalyze/analyzer.py b/tools/ioanalyze/analyzer.py
new file mode 100755
index 0000000..7ab77b6
--- /dev/null
+++ b/tools/ioanalyze/analyzer.py
@@ -0,0 +1,266 @@
+#!/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()
+