Home | History | Annotate | Download | only in ioanalyze
      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 
    267