diff options
author | Darren Hart <dvhltc@us.ibm.com> | 2009-12-29 01:25:13 -0500 |
---|---|---|
committer | Darren Hart <dvhltc@us.ibm.com> | 2009-12-30 12:22:41 -0500 |
commit | 5f85740ef7a9e7e61fdbd2583a1ff2b1001c4418 (patch) | |
tree | e405a2e62a5d5e567b001dd3a5789012998b5681 | |
parent | 9703124e36ad78a1484138dbe010d587aed5a60b (diff) |
trace-cmd: Python EventStore and record memory management
Add a python EventStore instead of a generic gtk.ListStore. Load time is
reduced 50% although still 15x slower than the C version.
Add the proper freeing of the record to the Event destructor rather than
setting ownership of the pointer to Python. The latter causes swig to
call free() on the record rather than free_record() which does some
intelligent things regarding the mmap.
Signed-off-by: Darren Hart <dvhltc@us.ibm.com>
-rw-r--r-- | Makefile | 2 | ||||
-rwxr-xr-x | event-viewer.py | 172 | ||||
-rw-r--r-- | tracecmd.py | 11 |
3 files changed, 160 insertions, 25 deletions
@@ -87,8 +87,10 @@ plugin_mac80211.so: plugin_mac80211.o | |||
87 | .PHONY: python | 87 | .PHONY: python |
88 | python: $(TCMD_LIB_OBJS) | 88 | python: $(TCMD_LIB_OBJS) |
89 | swig -Wall -python -noproxy ctracecmd.i | 89 | swig -Wall -python -noproxy ctracecmd.i |
90 | #swig -Wall -python ctracecmd.i | ||
90 | gcc -fpic -c `python-config --includes` ctracecmd_wrap.c | 91 | gcc -fpic -c `python-config --includes` ctracecmd_wrap.c |
91 | $(CC) --shared $^ ctracecmd_wrap.o -o ctracecmd.so | 92 | $(CC) --shared $^ ctracecmd_wrap.o -o ctracecmd.so |
93 | #$(CC) --shared $^ ctracecmd_wrap.o -o _ctracecmd.so | ||
92 | 94 | ||
93 | 95 | ||
94 | .PHONY: force | 96 | .PHONY: force |
diff --git a/event-viewer.py b/event-viewer.py index a8ccd5b..6f35642 100755 --- a/event-viewer.py +++ b/event-viewer.py | |||
@@ -1,8 +1,10 @@ | |||
1 | #!/usr/bin/env python | 1 | #!/usr/bin/env python |
2 | 2 | ||
3 | import getopt | ||
3 | from gobject import * | 4 | from gobject import * |
4 | import gtk | 5 | import gtk |
5 | from tracecmd import * | 6 | from tracecmd import * |
7 | import time | ||
6 | 8 | ||
7 | app = None | 9 | app = None |
8 | data_func_cnt = 0 | 10 | data_func_cnt = 0 |
@@ -15,21 +17,136 @@ EVENT_COL_W = 150 | |||
15 | PID_COL_W = 75 | 17 | PID_COL_W = 75 |
16 | COMM_COL_W = 250 | 18 | COMM_COL_W = 250 |
17 | 19 | ||
18 | class EventStore(gtk.ListStore): | 20 | |
21 | def timing(func): | ||
22 | def wrapper(*arg): | ||
23 | start = time.time() | ||
24 | ret = func(*arg) | ||
25 | end = time.time() | ||
26 | print '@%s took %0.3f s' % (func.func_name, (end-start)) | ||
27 | return ret | ||
28 | return wrapper | ||
29 | |||
30 | |||
31 | class EventStore(gtk.GenericTreeModel): | ||
32 | class EventRef(object): | ||
33 | '''Inner class to build the trace event index''' | ||
34 | def __init__(self, index, timestamp, offset, cpu): | ||
35 | self.index = index | ||
36 | self.offset = offset | ||
37 | self.ts = timestamp | ||
38 | self.cpu = cpu | ||
39 | |||
40 | def __cmp__(self, other): | ||
41 | if self.ts < other.ts: | ||
42 | return -1 | ||
43 | if self.ts > other.ts: | ||
44 | return 1 | ||
45 | if self.offset < other.offset: | ||
46 | return -1 | ||
47 | if self.offset > other.offset: | ||
48 | return 1 | ||
49 | return 0 | ||
50 | |||
51 | # The store only returns the record offset into the trace | ||
52 | # The view is responsible for looking up the Event with the offset | ||
53 | column_types = (long,) | ||
54 | |||
55 | @timing | ||
19 | def __init__(self, trace): | 56 | def __init__(self, trace): |
20 | gtk.ListStore.__init__(self, gobject.TYPE_PYOBJECT) | 57 | gtk.GenericTreeModel.__init__(self) |
21 | self.trace = trace | 58 | self.trace = trace |
59 | self.refs = [] | ||
60 | self._load_trace() | ||
61 | self._sort() | ||
62 | self._reindex() | ||
63 | |||
64 | @timing | ||
65 | def _load_trace(self): | ||
66 | print "Building trace index..." | ||
67 | index = 0 | ||
22 | for cpu in range(0, trace.cpus): | 68 | for cpu in range(0, trace.cpus): |
23 | ev = trace.read_event(cpu) | 69 | rec = tracecmd_read_data(self.trace.handle, cpu) |
24 | while ev: | 70 | while rec: |
25 | # store the record offset into the trace file | 71 | offset = record_offset_get(rec) |
26 | self.append([record_offset_get(ev.rec)]) | 72 | ts = record_ts_get(rec) |
27 | ev = trace.read_event(cpu) | 73 | self.refs.append(self.EventRef(index, ts, offset, cpu)) |
28 | print "Loaded %d events across %d cpus" % (len(self), trace.cpus) | 74 | index = index + 1 |
75 | rec = tracecmd_read_data(self.trace.handle, cpu) | ||
76 | print "Loaded %d events from trace" % (index) | ||
77 | |||
78 | @timing | ||
79 | def _sort(self): | ||
80 | self.refs.sort() | ||
81 | |||
82 | @timing | ||
83 | def _reindex(self): | ||
84 | for i in range(0, len(self.refs)): | ||
85 | self.refs[i].index = i | ||
86 | |||
87 | def on_get_flags(self): | ||
88 | return gtk.TREE_MODEL_LIST_ONLY | gtk.TREE_MODEL_ITERS_PERSIST | ||
89 | |||
90 | def on_get_n_columns(self): | ||
91 | return len(self.column_types) | ||
92 | |||
93 | def on_get_column_type(self, col): | ||
94 | return self.column_types[col] | ||
95 | |||
96 | def on_get_iter(self, path): | ||
97 | return self.refs[path[0]] | ||
98 | |||
99 | def on_get_path(self, ref): | ||
100 | return ref.index | ||
101 | |||
102 | def on_get_value(self, ref, col): | ||
103 | ''' | ||
104 | The Event record was getting deleted when passed back via this | ||
105 | method, now it just returns the ref itself. Use get_event() instead. | ||
106 | ''' | ||
107 | if col == 0: | ||
108 | #return self.trace.read_event_at(ref.offset) | ||
109 | return ref | ||
110 | return None | ||
111 | |||
112 | def on_iter_next(self, ref): | ||
113 | try: | ||
114 | return self.refs[ref.index+1] | ||
115 | except IndexError: | ||
116 | return None | ||
117 | |||
118 | def on_iter_children(self, ref): | ||
119 | if ref: | ||
120 | return None | ||
121 | return self.refs[0] | ||
122 | |||
123 | def on_iter_has_child(self, ref): | ||
124 | return False | ||
125 | |||
126 | def on_iter_n_children(self, ref): | ||
127 | if ref: | ||
128 | return 0 | ||
129 | return len(self.refs) | ||
130 | |||
131 | def on_iter_nth_child(self, ref, n): | ||
132 | if ref: | ||
133 | return None | ||
134 | try: | ||
135 | return self.refs[n] | ||
136 | except IndexError: | ||
137 | return None | ||
138 | |||
139 | def on_iter_parent(self, child): | ||
140 | return None | ||
29 | 141 | ||
30 | def get_event(self, iter): | 142 | def get_event(self, iter): |
31 | offset = self.get_value(iter, 0) | 143 | '''This allocates a record which must be freed by the caller''' |
32 | return self.trace.read_event_at(offset) | 144 | try: |
145 | ref = self.refs[self.get_path(iter)[0]] | ||
146 | ev = self.trace.read_event_at(ref.offset) | ||
147 | return ev | ||
148 | except IndexError: | ||
149 | return None | ||
33 | 150 | ||
34 | 151 | ||
35 | class EventView(gtk.TreeView): | 152 | class EventView(gtk.TreeView): |
@@ -81,29 +198,29 @@ class EventView(gtk.TreeView): | |||
81 | global app, data_func_cnt | 198 | global app, data_func_cnt |
82 | 199 | ||
83 | ev = model.get_event(iter) | 200 | ev = model.get_event(iter) |
201 | #ev = model.get_value(iter, 0) | ||
84 | if not ev: | 202 | if not ev: |
85 | return False | 203 | return False |
204 | |||
86 | if data == "ts": | 205 | if data == "ts": |
87 | cell.set_property("markup", "%d.%d" % (ev.ts/1000000000, | 206 | cell.set_property("markup", "%d.%d" % (ev.ts/1000000000, |
88 | ev.ts%1000000000)) | 207 | ev.ts%1000000000)) |
89 | data_func_cnt = data_func_cnt + 1 | 208 | data_func_cnt = data_func_cnt + 1 |
90 | if app: | 209 | if app: |
91 | app.inc_data_func() | 210 | app.inc_data_func() |
92 | return True | 211 | elif data == "cpu": |
93 | if data == "cpu": | ||
94 | cell.set_property("markup", ev.cpu) | 212 | cell.set_property("markup", ev.cpu) |
95 | return True | 213 | elif data == "event": |
96 | if data == "event": | ||
97 | cell.set_property("markup", ev.name) | 214 | cell.set_property("markup", ev.name) |
98 | return True | 215 | elif data == "pid": |
99 | if data == "pid": | ||
100 | cell.set_property("markup", ev.pid) | 216 | cell.set_property("markup", ev.pid) |
101 | return True | 217 | elif data == "comm": |
102 | if data == "comm": | ||
103 | cell.set_property("markup", ev.comm) | 218 | cell.set_property("markup", ev.comm) |
104 | return True | 219 | else: |
220 | print "Unknown Column:", data | ||
221 | return False | ||
105 | 222 | ||
106 | return False | 223 | return True |
107 | 224 | ||
108 | 225 | ||
109 | class EventViewerApp(gtk.Window): | 226 | class EventViewerApp(gtk.Window): |
@@ -116,9 +233,8 @@ class EventViewerApp(gtk.Window): | |||
116 | self.connect("destroy", gtk.main_quit) | 233 | self.connect("destroy", gtk.main_quit) |
117 | self.set_title("Event Viewer") | 234 | self.set_title("Event Viewer") |
118 | 235 | ||
119 | 236 | store = EventStore(trace) | |
120 | es = EventStore(trace) | 237 | view = EventView(store) |
121 | view = EventView(es) | ||
122 | 238 | ||
123 | sw = gtk.ScrolledWindow() | 239 | sw = gtk.ScrolledWindow() |
124 | sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS) | 240 | sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS) |
@@ -143,6 +259,14 @@ class EventViewerApp(gtk.Window): | |||
143 | 259 | ||
144 | 260 | ||
145 | if __name__ == "__main__": | 261 | if __name__ == "__main__": |
146 | trace = Trace("trace.dat") | 262 | if len(sys.argv) >=2: |
263 | filename = sys.argv[1] | ||
264 | else: | ||
265 | filename = "trace.dat" | ||
266 | |||
267 | print "Initializing trace..." | ||
268 | trace = Trace(filename) | ||
269 | print "Initializing app..." | ||
147 | app = EventViewerApp(trace) | 270 | app = EventViewerApp(trace) |
271 | print "Go!" | ||
148 | gtk.main() | 272 | gtk.main() |
diff --git a/tracecmd.py b/tracecmd.py index fdcda76..e7ea5f6 100644 --- a/tracecmd.py +++ b/tracecmd.py | |||
@@ -44,6 +44,9 @@ class Event(object): | |||
44 | (self.ts/1000000000, self.ts%1000000000, self.cpu, self.name, | 44 | (self.ts/1000000000, self.ts%1000000000, self.cpu, self.name, |
45 | self.num_field("common_pid"), self.comm, self.type) | 45 | self.num_field("common_pid"), self.comm, self.type) |
46 | 46 | ||
47 | def __del__(self): | ||
48 | free_record(self.rec); | ||
49 | |||
47 | 50 | ||
48 | # TODO: consider caching the results of the properties | 51 | # TODO: consider caching the results of the properties |
49 | @property | 52 | @property |
@@ -100,6 +103,8 @@ class Trace(object): | |||
100 | def read_event(self, cpu): | 103 | def read_event(self, cpu): |
101 | rec = tracecmd_read_data(self.handle, cpu) | 104 | rec = tracecmd_read_data(self.handle, cpu) |
102 | if rec: | 105 | if rec: |
106 | #rec.acquire() | ||
107 | #rec.thisown = 1 | ||
103 | return Event(self, rec, cpu) | 108 | return Event(self, rec, cpu) |
104 | return None | 109 | return None |
105 | 110 | ||
@@ -108,7 +113,11 @@ class Trace(object): | |||
108 | # SWIG only returns the CPU if the record is None for some reason | 113 | # SWIG only returns the CPU if the record is None for some reason |
109 | if isinstance(res, int): | 114 | if isinstance(res, int): |
110 | return None | 115 | return None |
111 | return Event(self, res[0], res[1]) | 116 | rec,cpu = res |
117 | #rec.acquire() | ||
118 | #rec.thisown = 1 | ||
119 | ev = Event(self, rec, cpu) | ||
120 | return ev | ||
112 | 121 | ||
113 | def peek_event(self, cpu): | 122 | def peek_event(self, cpu): |
114 | pass | 123 | pass |