You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
267 lines
9.0 KiB
267 lines
9.0 KiB
4 months ago
|
#!/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()
|
||
|
|