blob: 7ab77b6afb5a2f364d551a97d29891e41abf29de [file] [log] [blame]
Enrico Granataea17dea2018-02-21 14:57:10 -08001#!/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