Commit | Line | Data |
---|---|---|
880d22f2 FW |
1 | # Cpu task migration overview toy |
2 | # | |
3 | # Copyright (C) 2010 Frederic Weisbecker <fweisbec@gmail.com> | |
4 | # | |
765532c8 | 5 | # perf script event handlers have been generated by perf script -g python |
880d22f2 | 6 | # |
df92b408 FW |
7 | # This software is distributed under the terms of the GNU General |
8 | # Public License ("GPL") version 2 as published by the Free Software | |
9 | # Foundation. | |
8c1c1ab2 | 10 | from __future__ import print_function |
880d22f2 | 11 | |
880d22f2 FW |
12 | import os |
13 | import sys | |
14 | ||
15 | from collections import defaultdict | |
8c1c1ab2 | 16 | try: |
b504d7f6 | 17 | from UserList import UserList |
8c1c1ab2 | 18 | except ImportError: |
b504d7f6 TJ |
19 | # Python 3: UserList moved to the collections package |
20 | from collections import UserList | |
880d22f2 FW |
21 | |
22 | sys.path.append(os.environ['PERF_EXEC_PATH'] + \ | |
23 | '/scripts/python/Perf-Trace-Util/lib/Perf/Trace') | |
df92b408 | 24 | sys.path.append('scripts/python/Perf-Trace-Util/lib/Perf/Trace') |
880d22f2 FW |
25 | |
26 | from perf_trace_context import * | |
27 | from Core import * | |
df92b408 | 28 | from SchedGui import * |
880d22f2 FW |
29 | |
30 | ||
31 | threads = { 0 : "idle"} | |
32 | ||
33 | def thread_name(pid): | |
34 | return "%s:%d" % (threads[pid], pid) | |
35 | ||
880d22f2 FW |
36 | class RunqueueEventUnknown: |
37 | @staticmethod | |
38 | def color(): | |
39 | return None | |
40 | ||
41 | def __repr__(self): | |
42 | return "unknown" | |
43 | ||
44 | class RunqueueEventSleep: | |
45 | @staticmethod | |
46 | def color(): | |
47 | return (0, 0, 0xff) | |
48 | ||
49 | def __init__(self, sleeper): | |
50 | self.sleeper = sleeper | |
51 | ||
52 | def __repr__(self): | |
53 | return "%s gone to sleep" % thread_name(self.sleeper) | |
54 | ||
55 | class RunqueueEventWakeup: | |
56 | @staticmethod | |
57 | def color(): | |
58 | return (0xff, 0xff, 0) | |
59 | ||
60 | def __init__(self, wakee): | |
61 | self.wakee = wakee | |
62 | ||
63 | def __repr__(self): | |
64 | return "%s woke up" % thread_name(self.wakee) | |
65 | ||
66 | class RunqueueEventFork: | |
67 | @staticmethod | |
68 | def color(): | |
69 | return (0, 0xff, 0) | |
70 | ||
71 | def __init__(self, child): | |
72 | self.child = child | |
73 | ||
74 | def __repr__(self): | |
75 | return "new forked task %s" % thread_name(self.child) | |
76 | ||
77 | class RunqueueMigrateIn: | |
78 | @staticmethod | |
79 | def color(): | |
80 | return (0, 0xf0, 0xff) | |
81 | ||
82 | def __init__(self, new): | |
83 | self.new = new | |
84 | ||
85 | def __repr__(self): | |
86 | return "task migrated in %s" % thread_name(self.new) | |
87 | ||
88 | class RunqueueMigrateOut: | |
89 | @staticmethod | |
90 | def color(): | |
91 | return (0xff, 0, 0xff) | |
92 | ||
93 | def __init__(self, old): | |
94 | self.old = old | |
95 | ||
96 | def __repr__(self): | |
97 | return "task migrated out %s" % thread_name(self.old) | |
98 | ||
99 | class RunqueueSnapshot: | |
100 | def __init__(self, tasks = [0], event = RunqueueEventUnknown()): | |
101 | self.tasks = tuple(tasks) | |
102 | self.event = event | |
103 | ||
104 | def sched_switch(self, prev, prev_state, next): | |
105 | event = RunqueueEventUnknown() | |
106 | ||
107 | if taskState(prev_state) == "R" and next in self.tasks \ | |
108 | and prev in self.tasks: | |
109 | return self | |
110 | ||
111 | if taskState(prev_state) != "R": | |
112 | event = RunqueueEventSleep(prev) | |
113 | ||
114 | next_tasks = list(self.tasks[:]) | |
115 | if prev in self.tasks: | |
116 | if taskState(prev_state) != "R": | |
117 | next_tasks.remove(prev) | |
118 | elif taskState(prev_state) == "R": | |
119 | next_tasks.append(prev) | |
120 | ||
121 | if next not in next_tasks: | |
122 | next_tasks.append(next) | |
123 | ||
124 | return RunqueueSnapshot(next_tasks, event) | |
125 | ||
126 | def migrate_out(self, old): | |
127 | if old not in self.tasks: | |
128 | return self | |
129 | next_tasks = [task for task in self.tasks if task != old] | |
130 | ||
131 | return RunqueueSnapshot(next_tasks, RunqueueMigrateOut(old)) | |
132 | ||
133 | def __migrate_in(self, new, event): | |
134 | if new in self.tasks: | |
135 | self.event = event | |
136 | return self | |
137 | next_tasks = self.tasks[:] + tuple([new]) | |
138 | ||
139 | return RunqueueSnapshot(next_tasks, event) | |
140 | ||
141 | def migrate_in(self, new): | |
142 | return self.__migrate_in(new, RunqueueMigrateIn(new)) | |
143 | ||
144 | def wake_up(self, new): | |
145 | return self.__migrate_in(new, RunqueueEventWakeup(new)) | |
146 | ||
147 | def wake_up_new(self, new): | |
148 | return self.__migrate_in(new, RunqueueEventFork(new)) | |
149 | ||
150 | def load(self): | |
151 | """ Provide the number of tasks on the runqueue. | |
152 | Don't count idle""" | |
153 | return len(self.tasks) - 1 | |
154 | ||
155 | def __repr__(self): | |
156 | ret = self.tasks.__repr__() | |
157 | ret += self.origin_tostring() | |
158 | ||
159 | return ret | |
160 | ||
161 | class TimeSlice: | |
162 | def __init__(self, start, prev): | |
163 | self.start = start | |
164 | self.prev = prev | |
165 | self.end = start | |
166 | # cpus that triggered the event | |
167 | self.event_cpus = [] | |
168 | if prev is not None: | |
169 | self.total_load = prev.total_load | |
170 | self.rqs = prev.rqs.copy() | |
171 | else: | |
172 | self.rqs = defaultdict(RunqueueSnapshot) | |
173 | self.total_load = 0 | |
174 | ||
175 | def __update_total_load(self, old_rq, new_rq): | |
176 | diff = new_rq.load() - old_rq.load() | |
177 | self.total_load += diff | |
178 | ||
179 | def sched_switch(self, ts_list, prev, prev_state, next, cpu): | |
180 | old_rq = self.prev.rqs[cpu] | |
181 | new_rq = old_rq.sched_switch(prev, prev_state, next) | |
182 | ||
183 | if old_rq is new_rq: | |
184 | return | |
185 | ||
186 | self.rqs[cpu] = new_rq | |
187 | self.__update_total_load(old_rq, new_rq) | |
188 | ts_list.append(self) | |
189 | self.event_cpus = [cpu] | |
190 | ||
191 | def migrate(self, ts_list, new, old_cpu, new_cpu): | |
192 | if old_cpu == new_cpu: | |
193 | return | |
194 | old_rq = self.prev.rqs[old_cpu] | |
195 | out_rq = old_rq.migrate_out(new) | |
196 | self.rqs[old_cpu] = out_rq | |
197 | self.__update_total_load(old_rq, out_rq) | |
198 | ||
199 | new_rq = self.prev.rqs[new_cpu] | |
200 | in_rq = new_rq.migrate_in(new) | |
201 | self.rqs[new_cpu] = in_rq | |
202 | self.__update_total_load(new_rq, in_rq) | |
203 | ||
204 | ts_list.append(self) | |
749e5074 FW |
205 | |
206 | if old_rq is not out_rq: | |
207 | self.event_cpus.append(old_cpu) | |
208 | self.event_cpus.append(new_cpu) | |
880d22f2 FW |
209 | |
210 | def wake_up(self, ts_list, pid, cpu, fork): | |
211 | old_rq = self.prev.rqs[cpu] | |
212 | if fork: | |
213 | new_rq = old_rq.wake_up_new(pid) | |
214 | else: | |
215 | new_rq = old_rq.wake_up(pid) | |
216 | ||
217 | if new_rq is old_rq: | |
218 | return | |
219 | self.rqs[cpu] = new_rq | |
220 | self.__update_total_load(old_rq, new_rq) | |
221 | ts_list.append(self) | |
222 | self.event_cpus = [cpu] | |
223 | ||
224 | def next(self, t): | |
225 | self.end = t | |
226 | return TimeSlice(t, self) | |
227 | ||
228 | class TimeSliceList(UserList): | |
229 | def __init__(self, arg = []): | |
230 | self.data = arg | |
231 | ||
232 | def get_time_slice(self, ts): | |
233 | if len(self.data) == 0: | |
234 | slice = TimeSlice(ts, TimeSlice(-1, None)) | |
235 | else: | |
236 | slice = self.data[-1].next(ts) | |
237 | return slice | |
238 | ||
239 | def find_time_slice(self, ts): | |
240 | start = 0 | |
241 | end = len(self.data) | |
242 | found = -1 | |
243 | searching = True | |
244 | while searching: | |
245 | if start == end or start == end - 1: | |
246 | searching = False | |
247 | ||
248 | i = (end + start) / 2 | |
249 | if self.data[i].start <= ts and self.data[i].end >= ts: | |
250 | found = i | |
251 | end = i | |
252 | continue | |
253 | ||
254 | if self.data[i].end < ts: | |
255 | start = i | |
256 | ||
257 | elif self.data[i].start > ts: | |
258 | end = i | |
259 | ||
260 | return found | |
261 | ||
699b6d92 FW |
262 | def set_root_win(self, win): |
263 | self.root_win = win | |
264 | ||
265 | def mouse_down(self, cpu, t): | |
266 | idx = self.find_time_slice(t) | |
267 | if idx == -1: | |
268 | return | |
269 | ||
270 | ts = self[idx] | |
271 | rq = ts.rqs[cpu] | |
272 | raw = "CPU: %d\n" % cpu | |
273 | raw += "Last event : %s\n" % rq.event.__repr__() | |
274 | raw += "Timestamp : %d.%06d\n" % (ts.start / (10 ** 9), (ts.start % (10 ** 9)) / 1000) | |
275 | raw += "Duration : %6d us\n" % ((ts.end - ts.start) / (10 ** 6)) | |
276 | raw += "Load = %d\n" % rq.load() | |
277 | for t in rq.tasks: | |
278 | raw += "%s \n" % thread_name(t) | |
279 | ||
280 | self.root_win.update_summary(raw) | |
281 | ||
282 | def update_rectangle_cpu(self, slice, cpu): | |
283 | rq = slice.rqs[cpu] | |
284 | ||
285 | if slice.total_load != 0: | |
286 | load_rate = rq.load() / float(slice.total_load) | |
287 | else: | |
288 | load_rate = 0 | |
289 | ||
290 | red_power = int(0xff - (0xff * load_rate)) | |
291 | color = (0xff, red_power, red_power) | |
292 | ||
293 | top_color = None | |
294 | ||
295 | if cpu in slice.event_cpus: | |
296 | top_color = rq.event.color() | |
297 | ||
298 | self.root_win.paint_rectangle_zone(cpu, color, top_color, slice.start, slice.end) | |
299 | ||
300 | def fill_zone(self, start, end): | |
301 | i = self.find_time_slice(start) | |
302 | if i == -1: | |
303 | return | |
304 | ||
8c1c1ab2 | 305 | for i in range(i, len(self.data)): |
699b6d92 FW |
306 | timeslice = self.data[i] |
307 | if timeslice.start > end: | |
308 | return | |
309 | ||
310 | for cpu in timeslice.rqs: | |
311 | self.update_rectangle_cpu(timeslice, cpu) | |
312 | ||
880d22f2 FW |
313 | def interval(self): |
314 | if len(self.data) == 0: | |
315 | return (0, 0) | |
316 | ||
317 | return (self.data[0].start, self.data[-1].end) | |
318 | ||
699b6d92 | 319 | def nr_rectangles(self): |
70d815a3 FW |
320 | last_ts = self.data[-1] |
321 | max_cpu = 0 | |
322 | for cpu in last_ts.rqs: | |
323 | if cpu > max_cpu: | |
324 | max_cpu = cpu | |
325 | return max_cpu | |
326 | ||
880d22f2 FW |
327 | |
328 | class SchedEventProxy: | |
329 | def __init__(self): | |
330 | self.current_tsk = defaultdict(lambda : -1) | |
331 | self.timeslices = TimeSliceList() | |
332 | ||
333 | def sched_switch(self, headers, prev_comm, prev_pid, prev_prio, prev_state, | |
334 | next_comm, next_pid, next_prio): | |
335 | """ Ensure the task we sched out this cpu is really the one | |
336 | we logged. Otherwise we may have missed traces """ | |
337 | ||
338 | on_cpu_task = self.current_tsk[headers.cpu] | |
339 | ||
340 | if on_cpu_task != -1 and on_cpu_task != prev_pid: | |
8c1c1ab2 JC |
341 | print("Sched switch event rejected ts: %s cpu: %d prev: %s(%d) next: %s(%d)" % \ |
342 | headers.ts_format(), headers.cpu, prev_comm, prev_pid, next_comm, next_pid) | |
880d22f2 FW |
343 | |
344 | threads[prev_pid] = prev_comm | |
345 | threads[next_pid] = next_comm | |
346 | self.current_tsk[headers.cpu] = next_pid | |
347 | ||
348 | ts = self.timeslices.get_time_slice(headers.ts()) | |
349 | ts.sched_switch(self.timeslices, prev_pid, prev_state, next_pid, headers.cpu) | |
350 | ||
351 | def migrate(self, headers, pid, prio, orig_cpu, dest_cpu): | |
352 | ts = self.timeslices.get_time_slice(headers.ts()) | |
353 | ts.migrate(self.timeslices, pid, orig_cpu, dest_cpu) | |
354 | ||
355 | def wake_up(self, headers, comm, pid, success, target_cpu, fork): | |
356 | if success == 0: | |
357 | return | |
358 | ts = self.timeslices.get_time_slice(headers.ts()) | |
359 | ts.wake_up(self.timeslices, pid, target_cpu, fork) | |
360 | ||
361 | ||
362 | def trace_begin(): | |
363 | global parser | |
364 | parser = SchedEventProxy() | |
365 | ||
366 | def trace_end(): | |
367 | app = wx.App(False) | |
368 | timeslices = parser.timeslices | |
699b6d92 | 369 | frame = RootFrame(timeslices, "Migration") |
880d22f2 FW |
370 | app.MainLoop() |
371 | ||
372 | def sched__sched_stat_runtime(event_name, context, common_cpu, | |
373 | common_secs, common_nsecs, common_pid, common_comm, | |
0f5f5bcd | 374 | common_callchain, comm, pid, runtime, vruntime): |
880d22f2 FW |
375 | pass |
376 | ||
377 | def sched__sched_stat_iowait(event_name, context, common_cpu, | |
378 | common_secs, common_nsecs, common_pid, common_comm, | |
0f5f5bcd | 379 | common_callchain, comm, pid, delay): |
880d22f2 FW |
380 | pass |
381 | ||
382 | def sched__sched_stat_sleep(event_name, context, common_cpu, | |
383 | common_secs, common_nsecs, common_pid, common_comm, | |
0f5f5bcd | 384 | common_callchain, comm, pid, delay): |
880d22f2 FW |
385 | pass |
386 | ||
387 | def sched__sched_stat_wait(event_name, context, common_cpu, | |
388 | common_secs, common_nsecs, common_pid, common_comm, | |
0f5f5bcd | 389 | common_callchain, comm, pid, delay): |
880d22f2 FW |
390 | pass |
391 | ||
392 | def sched__sched_process_fork(event_name, context, common_cpu, | |
393 | common_secs, common_nsecs, common_pid, common_comm, | |
0f5f5bcd | 394 | common_callchain, parent_comm, parent_pid, child_comm, child_pid): |
880d22f2 FW |
395 | pass |
396 | ||
397 | def sched__sched_process_wait(event_name, context, common_cpu, | |
398 | common_secs, common_nsecs, common_pid, common_comm, | |
0f5f5bcd | 399 | common_callchain, comm, pid, prio): |
880d22f2 FW |
400 | pass |
401 | ||
402 | def sched__sched_process_exit(event_name, context, common_cpu, | |
403 | common_secs, common_nsecs, common_pid, common_comm, | |
0f5f5bcd | 404 | common_callchain, comm, pid, prio): |
880d22f2 FW |
405 | pass |
406 | ||
407 | def sched__sched_process_free(event_name, context, common_cpu, | |
408 | common_secs, common_nsecs, common_pid, common_comm, | |
0f5f5bcd | 409 | common_callchain, comm, pid, prio): |
880d22f2 FW |
410 | pass |
411 | ||
412 | def sched__sched_migrate_task(event_name, context, common_cpu, | |
413 | common_secs, common_nsecs, common_pid, common_comm, | |
0f5f5bcd | 414 | common_callchain, comm, pid, prio, orig_cpu, |
880d22f2 FW |
415 | dest_cpu): |
416 | headers = EventHeaders(common_cpu, common_secs, common_nsecs, | |
0f5f5bcd | 417 | common_pid, common_comm, common_callchain) |
880d22f2 FW |
418 | parser.migrate(headers, pid, prio, orig_cpu, dest_cpu) |
419 | ||
420 | def sched__sched_switch(event_name, context, common_cpu, | |
0f5f5bcd | 421 | common_secs, common_nsecs, common_pid, common_comm, common_callchain, |
880d22f2 FW |
422 | prev_comm, prev_pid, prev_prio, prev_state, |
423 | next_comm, next_pid, next_prio): | |
424 | ||
425 | headers = EventHeaders(common_cpu, common_secs, common_nsecs, | |
0f5f5bcd | 426 | common_pid, common_comm, common_callchain) |
880d22f2 FW |
427 | parser.sched_switch(headers, prev_comm, prev_pid, prev_prio, prev_state, |
428 | next_comm, next_pid, next_prio) | |
429 | ||
430 | def sched__sched_wakeup_new(event_name, context, common_cpu, | |
431 | common_secs, common_nsecs, common_pid, common_comm, | |
0f5f5bcd | 432 | common_callchain, comm, pid, prio, success, |
880d22f2 FW |
433 | target_cpu): |
434 | headers = EventHeaders(common_cpu, common_secs, common_nsecs, | |
0f5f5bcd | 435 | common_pid, common_comm, common_callchain) |
880d22f2 FW |
436 | parser.wake_up(headers, comm, pid, success, target_cpu, 1) |
437 | ||
438 | def sched__sched_wakeup(event_name, context, common_cpu, | |
439 | common_secs, common_nsecs, common_pid, common_comm, | |
0f5f5bcd | 440 | common_callchain, comm, pid, prio, success, |
880d22f2 FW |
441 | target_cpu): |
442 | headers = EventHeaders(common_cpu, common_secs, common_nsecs, | |
0f5f5bcd | 443 | common_pid, common_comm, common_callchain) |
880d22f2 FW |
444 | parser.wake_up(headers, comm, pid, success, target_cpu, 0) |
445 | ||
446 | def sched__sched_wait_task(event_name, context, common_cpu, | |
447 | common_secs, common_nsecs, common_pid, common_comm, | |
0f5f5bcd | 448 | common_callchain, comm, pid, prio): |
880d22f2 FW |
449 | pass |
450 | ||
451 | def sched__sched_kthread_stop_ret(event_name, context, common_cpu, | |
452 | common_secs, common_nsecs, common_pid, common_comm, | |
0f5f5bcd | 453 | common_callchain, ret): |
880d22f2 FW |
454 | pass |
455 | ||
456 | def sched__sched_kthread_stop(event_name, context, common_cpu, | |
457 | common_secs, common_nsecs, common_pid, common_comm, | |
0f5f5bcd | 458 | common_callchain, comm, pid): |
880d22f2 FW |
459 | pass |
460 | ||
0f5f5bcd | 461 | def trace_unhandled(event_name, context, event_fields_dict): |
880d22f2 | 462 | pass |