]> bbs.cooldavid.org Git - net-next-2.6.git/blob - tools/perf/scripts/python/sched-migration.py
perf, sched migration: Make the GUI class client agnostic
[net-next-2.6.git] / tools / perf / scripts / python / sched-migration.py
1 #!/usr/bin/python
2 #
3 # Cpu task migration overview toy
4 #
5 # Copyright (C) 2010 Frederic Weisbecker <fweisbec@gmail.com>
6 #
7 # perf trace event handlers have been generated by perf trace -g python
8 #
9 # The whole is licensed under the terms of the GNU GPL License version 2
10
11
12 try:
13         import wx
14 except ImportError:
15         raise ImportError, "You need to install the wxpython lib for this script"
16
17 import os
18 import sys
19
20 from collections import defaultdict
21 from UserList import UserList
22
23 sys.path.append(os.environ['PERF_EXEC_PATH'] + \
24         '/scripts/python/Perf-Trace-Util/lib/Perf/Trace')
25
26 from perf_trace_context import *
27 from Core import *
28
29 class RootFrame(wx.Frame):
30         Y_OFFSET = 100
31         RECT_HEIGHT = 100
32         RECT_SPACE = 50
33         EVENT_MARKING_WIDTH = 5
34
35         def __init__(self, sched_tracer, title, parent = None, id = -1):
36                 wx.Frame.__init__(self, parent, id, title)
37
38                 (self.screen_width, self.screen_height) = wx.GetDisplaySize()
39                 self.screen_width -= 10
40                 self.screen_height -= 10
41                 self.zoom = 0.5
42                 self.scroll_scale = 20
43                 self.sched_tracer = sched_tracer
44                 self.sched_tracer.set_root_win(self)
45                 (self.ts_start, self.ts_end) = sched_tracer.interval()
46                 self.update_width_virtual()
47                 self.nr_rects = sched_tracer.nr_rectangles() + 1
48                 self.height_virtual = RootFrame.Y_OFFSET + (self.nr_rects * (RootFrame.RECT_HEIGHT + RootFrame.RECT_SPACE))
49
50                 # whole window panel
51                 self.panel = wx.Panel(self, size=(self.screen_width, self.screen_height))
52
53                 # scrollable container
54                 self.scroll = wx.ScrolledWindow(self.panel)
55                 self.scroll.SetScrollbars(self.scroll_scale, self.scroll_scale, self.width_virtual / self.scroll_scale, self.height_virtual / self.scroll_scale)
56                 self.scroll.EnableScrolling(True, True)
57                 self.scroll.SetFocus()
58
59                 # scrollable drawing area
60                 self.scroll_panel = wx.Panel(self.scroll, size=(self.screen_width - 15, self.screen_height / 2))
61                 self.scroll_panel.Bind(wx.EVT_PAINT, self.on_paint)
62                 self.scroll_panel.Bind(wx.EVT_KEY_DOWN, self.on_key_press)
63                 self.scroll_panel.Bind(wx.EVT_LEFT_DOWN, self.on_mouse_down)
64                 self.scroll.Bind(wx.EVT_PAINT, self.on_paint)
65                 self.scroll.Bind(wx.EVT_KEY_DOWN, self.on_key_press)
66                 self.scroll.Bind(wx.EVT_LEFT_DOWN, self.on_mouse_down)
67
68                 self.scroll.Fit()
69                 self.Fit()
70
71                 self.scroll_panel.SetDimensions(-1, -1, self.width_virtual, self.height_virtual, wx.SIZE_USE_EXISTING)
72
73                 self.txt = None
74
75                 self.Show(True)
76
77         def us_to_px(self, val):
78                 return val / (10 ** 3) * self.zoom
79
80         def px_to_us(self, val):
81                 return (val / self.zoom) * (10 ** 3)
82
83         def scroll_start(self):
84                 (x, y) = self.scroll.GetViewStart()
85                 return (x * self.scroll_scale, y * self.scroll_scale)
86
87         def scroll_start_us(self):
88                 (x, y) = self.scroll_start()
89                 return self.px_to_us(x)
90
91         def paint_rectangle_zone(self, nr, color, top_color, start, end):
92                 offset_px = self.us_to_px(start - self.ts_start)
93                 width_px = self.us_to_px(end - self.ts_start)
94
95                 offset_py = RootFrame.Y_OFFSET + (nr * (RootFrame.RECT_HEIGHT + RootFrame.RECT_SPACE))
96                 width_py = RootFrame.RECT_HEIGHT
97
98                 dc = self.dc
99
100                 if top_color is not None:
101                         (r, g, b) = top_color
102                         top_color = wx.Colour(r, g, b)
103                         brush = wx.Brush(top_color, wx.SOLID)
104                         dc.SetBrush(brush)
105                         dc.DrawRectangle(offset_px, offset_py, width_px, RootFrame.EVENT_MARKING_WIDTH)
106                         width_py -= RootFrame.EVENT_MARKING_WIDTH
107                         offset_py += RootFrame.EVENT_MARKING_WIDTH
108
109                 (r ,g, b) = color
110                 color = wx.Colour(r, g, b)
111                 brush = wx.Brush(color, wx.SOLID)
112                 dc.SetBrush(brush)
113                 dc.DrawRectangle(offset_px, offset_py, width_px, width_py)
114
115         def update_rectangles(self, dc, start, end):
116                 start += self.ts_start
117                 end += self.ts_start
118                 self.sched_tracer.fill_zone(start, end)
119
120         def on_paint(self, event):
121                 dc = wx.PaintDC(self.scroll_panel)
122                 self.dc = dc
123
124                 width = min(self.width_virtual, self.screen_width)
125                 (x, y) = self.scroll_start()
126                 start = self.px_to_us(x)
127                 end = self.px_to_us(x + width)
128                 self.update_rectangles(dc, start, end)
129
130         def rect_from_ypixel(self, y):
131                 y -= RootFrame.Y_OFFSET
132                 rect = y / (RootFrame.RECT_HEIGHT + RootFrame.RECT_SPACE)
133                 height = y % (RootFrame.RECT_HEIGHT + RootFrame.RECT_SPACE)
134
135                 if rect < 0 or rect > self.nr_rects - 1 or height > RootFrame.RECT_HEIGHT:
136                         return -1
137
138                 return rect
139
140         def update_summary(self, txt):
141                 if self.txt:
142                         self.txt.Destroy()
143                 self.txt = wx.StaticText(self.panel, -1, txt, (0, (self.screen_height / 2) + 50))
144
145
146         def on_mouse_down(self, event):
147                 (x, y) = event.GetPositionTuple()
148                 rect = self.rect_from_ypixel(y)
149                 if rect == -1:
150                         return
151
152                 t = self.px_to_us(x) + self.ts_start
153
154                 self.sched_tracer.mouse_down(rect, t)
155
156
157         def update_width_virtual(self):
158                 self.width_virtual = self.us_to_px(self.ts_end - self.ts_start)
159
160         def __zoom(self, x):
161                 self.update_width_virtual()
162                 (xpos, ypos) = self.scroll.GetViewStart()
163                 xpos = self.us_to_px(x) / self.scroll_scale
164                 self.scroll.SetScrollbars(self.scroll_scale, self.scroll_scale, self.width_virtual / self.scroll_scale, self.height_virtual / self.scroll_scale, xpos, ypos)
165                 self.Refresh()
166
167         def zoom_in(self):
168                 x = self.scroll_start_us()
169                 self.zoom *= 2
170                 self.__zoom(x)
171
172         def zoom_out(self):
173                 x = self.scroll_start_us()
174                 self.zoom /= 2
175                 self.__zoom(x)
176
177
178         def on_key_press(self, event):
179                 key = event.GetRawKeyCode()
180                 if key == ord("+"):
181                         self.zoom_in()
182                         return
183                 if key == ord("-"):
184                         self.zoom_out()
185                         return
186
187                 key = event.GetKeyCode()
188                 (x, y) = self.scroll.GetViewStart()
189                 if key == wx.WXK_RIGHT:
190                         self.scroll.Scroll(x + 1, y)
191                 elif key == wx.WXK_LEFT:
192                         self.scroll.Scroll(x - 1, y)
193                 elif key == wx.WXK_DOWN:
194                         self.scroll.Scroll(x, y + 1)
195                 elif key == wx.WXK_UP:
196                         self.scroll.Scroll(x, y - 1)
197
198
199 threads = { 0 : "idle"}
200
201 def thread_name(pid):
202         return "%s:%d" % (threads[pid], pid)
203
204 class EventHeaders:
205         def __init__(self, common_cpu, common_secs, common_nsecs,
206                      common_pid, common_comm):
207                 self.cpu = common_cpu
208                 self.secs = common_secs
209                 self.nsecs = common_nsecs
210                 self.pid = common_pid
211                 self.comm = common_comm
212
213         def ts(self):
214                 return (self.secs * (10 ** 9)) + self.nsecs
215
216         def ts_format(self):
217                 return "%d.%d" % (self.secs, int(self.nsecs / 1000))
218
219
220 def taskState(state):
221         states = {
222                 0 : "R",
223                 1 : "S",
224                 2 : "D",
225                 64: "DEAD"
226         }
227
228         if state not in states:
229                 return "Unknown"
230
231         return states[state]
232
233
234 class RunqueueEventUnknown:
235         @staticmethod
236         def color():
237                 return None
238
239         def __repr__(self):
240                 return "unknown"
241
242 class RunqueueEventSleep:
243         @staticmethod
244         def color():
245                 return (0, 0, 0xff)
246
247         def __init__(self, sleeper):
248                 self.sleeper = sleeper
249
250         def __repr__(self):
251                 return "%s gone to sleep" % thread_name(self.sleeper)
252
253 class RunqueueEventWakeup:
254         @staticmethod
255         def color():
256                 return (0xff, 0xff, 0)
257
258         def __init__(self, wakee):
259                 self.wakee = wakee
260
261         def __repr__(self):
262                 return "%s woke up" % thread_name(self.wakee)
263
264 class RunqueueEventFork:
265         @staticmethod
266         def color():
267                 return (0, 0xff, 0)
268
269         def __init__(self, child):
270                 self.child = child
271
272         def __repr__(self):
273                 return "new forked task %s" % thread_name(self.child)
274
275 class RunqueueMigrateIn:
276         @staticmethod
277         def color():
278                 return (0, 0xf0, 0xff)
279
280         def __init__(self, new):
281                 self.new = new
282
283         def __repr__(self):
284                 return "task migrated in %s" % thread_name(self.new)
285
286 class RunqueueMigrateOut:
287         @staticmethod
288         def color():
289                 return (0xff, 0, 0xff)
290
291         def __init__(self, old):
292                 self.old = old
293
294         def __repr__(self):
295                 return "task migrated out %s" % thread_name(self.old)
296
297 class RunqueueSnapshot:
298         def __init__(self, tasks = [0], event = RunqueueEventUnknown()):
299                 self.tasks = tuple(tasks)
300                 self.event = event
301
302         def sched_switch(self, prev, prev_state, next):
303                 event = RunqueueEventUnknown()
304
305                 if taskState(prev_state) == "R" and next in self.tasks \
306                         and prev in self.tasks:
307                         return self
308
309                 if taskState(prev_state) != "R":
310                         event = RunqueueEventSleep(prev)
311
312                 next_tasks = list(self.tasks[:])
313                 if prev in self.tasks:
314                         if taskState(prev_state) != "R":
315                                 next_tasks.remove(prev)
316                 elif taskState(prev_state) == "R":
317                         next_tasks.append(prev)
318
319                 if next not in next_tasks:
320                         next_tasks.append(next)
321
322                 return RunqueueSnapshot(next_tasks, event)
323
324         def migrate_out(self, old):
325                 if old not in self.tasks:
326                         return self
327                 next_tasks = [task for task in self.tasks if task != old]
328
329                 return RunqueueSnapshot(next_tasks, RunqueueMigrateOut(old))
330
331         def __migrate_in(self, new, event):
332                 if new in self.tasks:
333                         self.event = event
334                         return self
335                 next_tasks = self.tasks[:] + tuple([new])
336
337                 return RunqueueSnapshot(next_tasks, event)
338
339         def migrate_in(self, new):
340                 return self.__migrate_in(new, RunqueueMigrateIn(new))
341
342         def wake_up(self, new):
343                 return self.__migrate_in(new, RunqueueEventWakeup(new))
344
345         def wake_up_new(self, new):
346                 return self.__migrate_in(new, RunqueueEventFork(new))
347
348         def load(self):
349                 """ Provide the number of tasks on the runqueue.
350                     Don't count idle"""
351                 return len(self.tasks) - 1
352
353         def __repr__(self):
354                 ret = self.tasks.__repr__()
355                 ret += self.origin_tostring()
356
357                 return ret
358
359 class TimeSlice:
360         def __init__(self, start, prev):
361                 self.start = start
362                 self.prev = prev
363                 self.end = start
364                 # cpus that triggered the event
365                 self.event_cpus = []
366                 if prev is not None:
367                         self.total_load = prev.total_load
368                         self.rqs = prev.rqs.copy()
369                 else:
370                         self.rqs = defaultdict(RunqueueSnapshot)
371                         self.total_load = 0
372
373         def __update_total_load(self, old_rq, new_rq):
374                 diff = new_rq.load() - old_rq.load()
375                 self.total_load += diff
376
377         def sched_switch(self, ts_list, prev, prev_state, next, cpu):
378                 old_rq = self.prev.rqs[cpu]
379                 new_rq = old_rq.sched_switch(prev, prev_state, next)
380
381                 if old_rq is new_rq:
382                         return
383
384                 self.rqs[cpu] = new_rq
385                 self.__update_total_load(old_rq, new_rq)
386                 ts_list.append(self)
387                 self.event_cpus = [cpu]
388
389         def migrate(self, ts_list, new, old_cpu, new_cpu):
390                 if old_cpu == new_cpu:
391                         return
392                 old_rq = self.prev.rqs[old_cpu]
393                 out_rq = old_rq.migrate_out(new)
394                 self.rqs[old_cpu] = out_rq
395                 self.__update_total_load(old_rq, out_rq)
396
397                 new_rq = self.prev.rqs[new_cpu]
398                 in_rq = new_rq.migrate_in(new)
399                 self.rqs[new_cpu] = in_rq
400                 self.__update_total_load(new_rq, in_rq)
401
402                 ts_list.append(self)
403
404                 if old_rq is not out_rq:
405                         self.event_cpus.append(old_cpu)
406                 self.event_cpus.append(new_cpu)
407
408         def wake_up(self, ts_list, pid, cpu, fork):
409                 old_rq = self.prev.rqs[cpu]
410                 if fork:
411                         new_rq = old_rq.wake_up_new(pid)
412                 else:
413                         new_rq = old_rq.wake_up(pid)
414
415                 if new_rq is old_rq:
416                         return
417                 self.rqs[cpu] = new_rq
418                 self.__update_total_load(old_rq, new_rq)
419                 ts_list.append(self)
420                 self.event_cpus = [cpu]
421
422         def next(self, t):
423                 self.end = t
424                 return TimeSlice(t, self)
425
426 class TimeSliceList(UserList):
427         def __init__(self, arg = []):
428                 self.data = arg
429
430         def get_time_slice(self, ts):
431                 if len(self.data) == 0:
432                         slice = TimeSlice(ts, TimeSlice(-1, None))
433                 else:
434                         slice = self.data[-1].next(ts)
435                 return slice
436
437         def find_time_slice(self, ts):
438                 start = 0
439                 end = len(self.data)
440                 found = -1
441                 searching = True
442                 while searching:
443                         if start == end or start == end - 1:
444                                 searching = False
445
446                         i = (end + start) / 2
447                         if self.data[i].start <= ts and self.data[i].end >= ts:
448                                 found = i
449                                 end = i
450                                 continue
451
452                         if self.data[i].end < ts:
453                                 start = i
454
455                         elif self.data[i].start > ts:
456                                 end = i
457
458                 return found
459
460         def set_root_win(self, win):
461                 self.root_win = win
462
463         def mouse_down(self, cpu, t):
464                 idx = self.find_time_slice(t)
465                 if idx == -1:
466                         return
467
468                 ts = self[idx]
469                 rq = ts.rqs[cpu]
470                 raw = "CPU: %d\n" % cpu
471                 raw += "Last event : %s\n" % rq.event.__repr__()
472                 raw += "Timestamp : %d.%06d\n" % (ts.start / (10 ** 9), (ts.start % (10 ** 9)) / 1000)
473                 raw += "Duration : %6d us\n" % ((ts.end - ts.start) / (10 ** 6))
474                 raw += "Load = %d\n" % rq.load()
475                 for t in rq.tasks:
476                         raw += "%s \n" % thread_name(t)
477
478                 self.root_win.update_summary(raw)
479
480         def update_rectangle_cpu(self, slice, cpu):
481                 rq = slice.rqs[cpu]
482
483                 if slice.total_load != 0:
484                         load_rate = rq.load() / float(slice.total_load)
485                 else:
486                         load_rate = 0
487
488                 red_power = int(0xff - (0xff * load_rate))
489                 color = (0xff, red_power, red_power)
490
491                 top_color = None
492
493                 if cpu in slice.event_cpus:
494                         top_color = rq.event.color()
495
496                 self.root_win.paint_rectangle_zone(cpu, color, top_color, slice.start, slice.end)
497
498         def fill_zone(self, start, end):
499                 i = self.find_time_slice(start)
500                 if i == -1:
501                         return
502
503                 for i in xrange(i, len(self.data)):
504                         timeslice = self.data[i]
505                         if timeslice.start > end:
506                                 return
507
508                         for cpu in timeslice.rqs:
509                                 self.update_rectangle_cpu(timeslice, cpu)
510
511         def interval(self):
512                 if len(self.data) == 0:
513                         return (0, 0)
514
515                 return (self.data[0].start, self.data[-1].end)
516
517         def nr_rectangles(self):
518                 last_ts = self.data[-1]
519                 max_cpu = 0
520                 for cpu in last_ts.rqs:
521                         if cpu > max_cpu:
522                                 max_cpu = cpu
523                 return max_cpu
524
525
526 class SchedEventProxy:
527         def __init__(self):
528                 self.current_tsk = defaultdict(lambda : -1)
529                 self.timeslices = TimeSliceList()
530
531         def sched_switch(self, headers, prev_comm, prev_pid, prev_prio, prev_state,
532                          next_comm, next_pid, next_prio):
533                 """ Ensure the task we sched out this cpu is really the one
534                     we logged. Otherwise we may have missed traces """
535
536                 on_cpu_task = self.current_tsk[headers.cpu]
537
538                 if on_cpu_task != -1 and on_cpu_task != prev_pid:
539                         print "Sched switch event rejected ts: %s cpu: %d prev: %s(%d) next: %s(%d)" % \
540                                 (headers.ts_format(), headers.cpu, prev_comm, prev_pid, next_comm, next_pid)
541
542                 threads[prev_pid] = prev_comm
543                 threads[next_pid] = next_comm
544                 self.current_tsk[headers.cpu] = next_pid
545
546                 ts = self.timeslices.get_time_slice(headers.ts())
547                 ts.sched_switch(self.timeslices, prev_pid, prev_state, next_pid, headers.cpu)
548
549         def migrate(self, headers, pid, prio, orig_cpu, dest_cpu):
550                 ts = self.timeslices.get_time_slice(headers.ts())
551                 ts.migrate(self.timeslices, pid, orig_cpu, dest_cpu)
552
553         def wake_up(self, headers, comm, pid, success, target_cpu, fork):
554                 if success == 0:
555                         return
556                 ts = self.timeslices.get_time_slice(headers.ts())
557                 ts.wake_up(self.timeslices, pid, target_cpu, fork)
558
559
560 def trace_begin():
561         global parser
562         parser = SchedEventProxy()
563
564 def trace_end():
565         app = wx.App(False)
566         timeslices = parser.timeslices
567         frame = RootFrame(timeslices, "Migration")
568         app.MainLoop()
569
570 def sched__sched_stat_runtime(event_name, context, common_cpu,
571         common_secs, common_nsecs, common_pid, common_comm,
572         comm, pid, runtime, vruntime):
573         pass
574
575 def sched__sched_stat_iowait(event_name, context, common_cpu,
576         common_secs, common_nsecs, common_pid, common_comm,
577         comm, pid, delay):
578         pass
579
580 def sched__sched_stat_sleep(event_name, context, common_cpu,
581         common_secs, common_nsecs, common_pid, common_comm,
582         comm, pid, delay):
583         pass
584
585 def sched__sched_stat_wait(event_name, context, common_cpu,
586         common_secs, common_nsecs, common_pid, common_comm,
587         comm, pid, delay):
588         pass
589
590 def sched__sched_process_fork(event_name, context, common_cpu,
591         common_secs, common_nsecs, common_pid, common_comm,
592         parent_comm, parent_pid, child_comm, child_pid):
593         pass
594
595 def sched__sched_process_wait(event_name, context, common_cpu,
596         common_secs, common_nsecs, common_pid, common_comm,
597         comm, pid, prio):
598         pass
599
600 def sched__sched_process_exit(event_name, context, common_cpu,
601         common_secs, common_nsecs, common_pid, common_comm,
602         comm, pid, prio):
603         pass
604
605 def sched__sched_process_free(event_name, context, common_cpu,
606         common_secs, common_nsecs, common_pid, common_comm,
607         comm, pid, prio):
608         pass
609
610 def sched__sched_migrate_task(event_name, context, common_cpu,
611         common_secs, common_nsecs, common_pid, common_comm,
612         comm, pid, prio, orig_cpu,
613         dest_cpu):
614         headers = EventHeaders(common_cpu, common_secs, common_nsecs,
615                                 common_pid, common_comm)
616         parser.migrate(headers, pid, prio, orig_cpu, dest_cpu)
617
618 def sched__sched_switch(event_name, context, common_cpu,
619         common_secs, common_nsecs, common_pid, common_comm,
620         prev_comm, prev_pid, prev_prio, prev_state,
621         next_comm, next_pid, next_prio):
622
623         headers = EventHeaders(common_cpu, common_secs, common_nsecs,
624                                 common_pid, common_comm)
625         parser.sched_switch(headers, prev_comm, prev_pid, prev_prio, prev_state,
626                          next_comm, next_pid, next_prio)
627
628 def sched__sched_wakeup_new(event_name, context, common_cpu,
629         common_secs, common_nsecs, common_pid, common_comm,
630         comm, pid, prio, success,
631         target_cpu):
632         headers = EventHeaders(common_cpu, common_secs, common_nsecs,
633                                 common_pid, common_comm)
634         parser.wake_up(headers, comm, pid, success, target_cpu, 1)
635
636 def sched__sched_wakeup(event_name, context, common_cpu,
637         common_secs, common_nsecs, common_pid, common_comm,
638         comm, pid, prio, success,
639         target_cpu):
640         headers = EventHeaders(common_cpu, common_secs, common_nsecs,
641                                 common_pid, common_comm)
642         parser.wake_up(headers, comm, pid, success, target_cpu, 0)
643
644 def sched__sched_wait_task(event_name, context, common_cpu,
645         common_secs, common_nsecs, common_pid, common_comm,
646         comm, pid, prio):
647         pass
648
649 def sched__sched_kthread_stop_ret(event_name, context, common_cpu,
650         common_secs, common_nsecs, common_pid, common_comm,
651         ret):
652         pass
653
654 def sched__sched_kthread_stop(event_name, context, common_cpu,
655         common_secs, common_nsecs, common_pid, common_comm,
656         comm, pid):
657         pass
658
659 def trace_unhandled(event_name, context, common_cpu, common_secs, common_nsecs,
660                 common_pid, common_comm):
661         pass