summaryrefslogtreecommitdiff
path: root/scripts/tracing/dma-api/trace.py
blob: c4507b6c4d026a228c8f3134a563196414f79c55 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
"""Main program and stuff"""

#from pprint import pprint
from sys import stdin
import os.path
import re
from argparse import ArgumentParser
import cPickle as pickle
from collections import namedtuple
from plotting import plotseries, disp_pic
import smmu

class TracelineParser(object):
    """Parse the needed information out of an ftrace line"""
    #            <...>-6     [000] d..2     5.287079: dmadebug_iommu_map_page: device=sdhci-tegra.3, addr=0x01048000, size=4096 page=c13e7214 archdata=ed504640
    def __init__(self):
        self.pattern = re.compile("device=(?P<dev>.*), addr=(?P<addr>.*), size=(?P<size>.*) page=(?P<page>.*) archdata=(?P<archdata>.*)")
    def parse(self, args):
        args = self.pattern.match(args)
        return (args.group("dev"), int(args.group("addr"), 16),
                int(args.group("size")), int(args.group("page"), 16),
                int(args.group("archdata"), 16))

def biggest_indices(items, n):
    """Return list of indices of n biggest elements in items"""
    with_indices = [(x, i) for i, x in enumerate(items)]
    ordered = sorted(with_indices)
    return [i for x, i in ordered[-n:]]

def by_indices(xs, ids):
    """Get elements from the list xs by their indices"""
    return [xs[i] for i in ids]

"""Event represents one input line"""
Event = namedtuple("Event", ["time", "dev", "data", "delta"])

class Trace(object):
    def __init__(self, args):
        smmu.VERBOSITY = args.verbosity
        self._args = args
        self.devlist = []
        self.events = []
        self.metrics = {
                "max_peak": self._usage_peak,
                "activity_rate": self._usage_activity,
                "average_mem": self._usage_avg
        }
        self.traceliner = TracelineParser()

    @staticmethod
    def get_metrics():
        """What filter metrics to get max users"""
        return ["max_peak", "activity_rate", "average_mem"]

    def show(self):
        """Shuffle events around, build plots, and show them"""
        if self._args.max_plots:
            evs = self.merge_events()
        else:
            evs = self.events
        series, devlist = self.unload(evs)
        if not self._args.no_plots:
            self.plot(series, devlist)

    def _get_usage(self, evs):
        """Return a metric of how active the events in evs are"""
        return self.metrics[self._args.max_metric](evs)

    def _usage_peak(self, evs):
        """Return the biggest peak"""
        return max(e.data for e in evs)

    def _usage_activity(self, evs):
        """Return the activity count: simply the length of the event list"""
        return len(evs)

    def _usage_avg(self, evs):
        """Return the average over all points"""
        # FIXME: the data points are not uniform in time, so this might be
        # somewhat off.
        return float(sum(e.data for e in evs)) / len(e)

    def merge_events(self):
        """Find out biggest users, keep them and flatten others to a single user"""
        sizes = []
        dev_evs = []
        for i, dev in enumerate(self.devlist):
            dev_evs.append([e for e in self.events if e.dev == dev])
            sizes.append(self._get_usage(dev_evs[i]))

        # indices of the devices
        biggestix = biggest_indices(sizes, self._args.max_plots)
        print biggestix
        is_big = {}
        for i, dev in enumerate(self.devlist):
            is_big[dev] = i in biggestix

        evs = []
        for e in self.events:
            if not is_big[e.dev]:
                e = Event(e.time, "others", e.data, e.delta)
            evs.append(e)

        self.devlist.append("others")
        return evs

    def unload(self, events):
        """Prepare the event list for plotting

        series ends up as [([time0], [data0]), ([time1], [data1]), ...]
        """
        # ([x], [y]) for matplotlib
        series = [([], []) for x in self.devlist]
        devidx = dict([(d, i) for i, d in enumerate(self.devlist)])

        for event in events:
            devid = devidx[event.dev]
            series[devid][0].append(event.time)
            series[devid][1].append(event.data) # self.dev_data(event.dev))

        series_out = []
        devlist_out = []

        for ser, dev in zip(series, self.devlist):
            if len(ser[0]) > 0:
                series_out.append(ser)
                devlist_out.append(dev)

        return series_out, devlist_out

    def plot(self, series, devlist):
        """Display the plots"""
        #series, devlist = flatten_axes(self.series, self.devlist,
        #        self._args.max_plots)
        devinfo = (series, map(str, devlist))
        allocfreeinfo = (self.allocsfrees, ["allocd", "freed", "current"])
        plotseries(devinfo, allocfreeinfo)
        #plotseries(devinfo)

    def dev_data(self, dev):
        """what data to plot against time"""
        return dev._cur_alloc

    def _cache_hash(self, filename):
        """The trace files are probably not of the same size"""
        return str(os.path.getsize(filename))

    def load_cache(self):
        """Get the trace data from a database file, if one exists"""
        has = self._cache_hash(self._args.filename)
        try:
            cache = open("trace." + has)
        except IOError:
            pass
        else:
            self._load_cache(pickle.load(cache))
            return True
        return False

    def save_cache(self):
        """Store the raw trace data to a database"""
        data = self._save_cache()
        fh = open("trace." + self._cache_hash(self._args.filename), "w")
        pickle.dump(data, fh)

    def _save_cache(self):
        """Return the internal data that is needed to be pickled"""
        return self.events, self.devlist, self.allocsfrees

    def _load_cache(self, data):
        """Get the data from an unpickled object"""
        self.events, self.devlist, self.allocsfrees = data

    def load_events(self):
        """Get the internal data from a trace file or cache"""
        if self._args.filename:
            if self._args.cache and self.load_cache():
                return
            fh = open(self._args.filename)
        else:
            fh = stdin

        self.parse(fh)

        if self._args.cache and self._args.filename:
            self.save_cache()

    def parse(self, fh):
        """Parse the trace file in fh, store data to self"""
        mems = {}
        dev_by_name = {}
        devlist = []
        buf_owners = {}
        events = []
        allocsfrees = [([], []), ([], []), ([], [])] # allocs, frees, current
        allocs = 0
        frees = 0
        curbufs = 0

        mem_bytes = 1024 * 1024 * 1024
        npages = mem_bytes / 4096
        ncols = 512
        le_pic = [0] * npages
        lastupd = 0

        for lineidx, line in enumerate(fh):
            # no comments
            if line.startswith("#"):
                continue

            taskpid, cpu, flags, timestamp, func, args = line.strip().split(None, 5)
            func = func[:-len(":")]
            # unneeded events may be there too
            if not func.startswith("dmadebug"):
                continue

            if self._args.verbosity >= 3:
                print line.rstrip()

            timestamp = float(timestamp[:-1])
            if timestamp < self._args.start:
                continue
            if timestamp >= self._args.end:
                break

            devname, addr, size, page, archdata = self.traceliner.parse(args)
            if self._args.processes:
                devname = taskpid.split("-")[0]
            mapping = archdata

            try:
                memmap = mems[mapping]
            except KeyError:
                memmap = mem(mapping)
                mems[mapping] = memmap

            try:
                dev = dev_by_name[devname]
            except KeyError:
                dev = smmu.Device(devname, memmap)
                dev_by_name[devname] = dev
                devlist.append(dev)

            allocfuncs = ["dmadebug_map_page", "dmadebug_map_sg", "dmadebug_alloc_coherent"]
            freefuncs = ["dmadebug_unmap_page", "dmadebug_unmap_sg", "dmadebug_free_coherent"]
            ignfuncs = []

            if timestamp-lastupd > 0.1:
                # just some debug prints for now
                lastupd = timestamp
                print lineidx,timestamp
                le_pic2 = [le_pic[i:i+ncols] for i in range(0, npages, ncols)]
                #disp_pic(le_pic2)

            # animating the bitmap would be cool
            #for row in le_pic:
            #    for i, a in enumerate(row):
            #        pass
                    #row[i] = 0.09 * a

            if func in allocfuncs:
                pages = dev_by_name[devname].alloc(addr, size)
                for p in pages:
                    le_pic[p] = 1
                buf_owners[addr] = dev_by_name[devname]
                allocs += 1
                curbufs += 1
                allocsfrees[0][0].append(timestamp)
                allocsfrees[0][1].append(allocs)
            elif func in freefuncs:
                if addr not in buf_owners:
                    if self._args.verbosity >= 1:
                        print "warning: %s unmapping unmapped %s" % (dev, addr)
                    buf_owners[addr] = dev
                # fixme: move this to bitmap handling
                # get to know the owners of bits
                # allocs/frees calls should be traced separately from maps?
                # map_pages is traced per page :(
                if buf_owners[addr] != dev and self._args.verbosity >= 2:
                    print "note: %s unmapping [%d,%d) mapped by %s" % (
                            dev, addr, addr+size, buf_owners[addr])
                pages = buf_owners[addr].free(addr, size)
                for p in pages:
                    le_pic[p] = 0
                frees -= 1
                curbufs -= 1
                allocsfrees[1][0].append(timestamp)
                allocsfrees[1][1].append(frees)
            elif func not in ignfuncs:
                raise ValueError("unhandled %s" % func)

            allocsfrees[2][0].append(timestamp)
            allocsfrees[2][1].append(curbufs)

            events.append(Event(timestamp, dev, self.dev_data(dev), size))

        self.events = events
        self.devlist = devlist
        self.allocsfrees = allocsfrees

        le_pic2 = [le_pic[i:i+ncols] for i in range(0, npages, ncols)]
        # FIXME: not quite ready yet
        disp_pic(le_pic2)

        return

def mem(asid):
    """Create a new memory object for the given asid space"""
    SZ_2G = 2 * 1024 * 1024 * 1024
    SZ_1M = 1 * 1024 * 1024
    # arch/arm/mach-tegra/include/mach/iomap.h TEGRA_SMMU_(BASE|SIZE)
    base = 0x80000000
    size = SZ_2G - SZ_1M
    return smmu.Memory(base, size, asid)

def get_args():
    """Eat command line arguments, return argparse namespace for settings"""
    parser = ArgumentParser()
    parser.add_argument("filename", nargs="?",
            help="trace file dump, stdin if not given")
    parser.add_argument("-s", "--start", type=float, default=0,
            help="start timestamp")
    parser.add_argument("-e", "--end", type=float, default=1e9,
            help="end timestamp")
    parser.add_argument("-v", "--verbosity", action="count", default=0,
            help="amount of extra information: once for warns (dup addrs), "
            "twice for notices (different client in map/unmap), "
            "three for echoing all back")
    parser.add_argument("-p", "--processes", action="store_true",
            help="use processes as memory clients instead of devices")
    parser.add_argument("-n", "--no-plots", action="store_true",
            help="Don't draw the plots, only read the trace")
    parser.add_argument("-c", "--cache", action="store_true",
            help="Pickle the data and make a cache file for fast reloading")
    parser.add_argument("-m", "--max-plots", type=int,
            help="Maximum number of clients to show; show biggest and sum others")
    parser.add_argument("-M", "--max-metric", choices=Trace.get_metrics(),
            default=Trace.get_metrics()[0],
            help="Metric to use when choosing clients in --max-plots")
    return parser.parse_args()

def main():
    args = get_args()
    trace = Trace(args)
    trace.load_events()
    trace.show()

if __name__ == "__main__":
    main()