1 #!/usr/local/bin/python
3 # Copyright (c) 2002-2003, Jeffrey Roberson <jeff@freebsd.org>
6 # Redistribution and use in source and binary forms, with or without
7 # modification, are permitted provided that the following conditions
9 # 1. Redistributions of source code must retain the above copyright
10 # notice unmodified, this list of conditions, and the following
12 # 2. Redistributions in binary form must reproduce the above copyright
13 # notice, this list of conditions and the following disclaimer in the
14 # documentation and/or other materials provided with the distribution.
16 # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21 # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25 # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 # - Install the ports/x11-toolkits/py-tkinter package; e.g.
35 # portinstall x11-toolkits/py-tkinter package
36 # - Add KTR_SCHED to KTR_COMPILE and KTR_MASK in your KERNCONF; e.g.
38 # options KTR_ENTRIES=32768
39 # options KTR_COMPILE=(KTR_SCHED)
40 # options KTR_MASK=(KTR_SCHED)
41 # - It is encouraged to increase KTR_ENTRIES size to gather enough
42 # information for analysis; e.g.
43 # options KTR_ENTRIES=262144
44 # as 32768 entries may only correspond to a second or two of profiling
45 # data depending on your workload.
46 # - Rebuild kernel with proper changes to KERNCONF and boot new kernel.
47 # - Run your workload to be profiled.
48 # - While the workload is continuing (i.e. before it finishes), disable
49 # KTR tracing by setting 'sysctl debug.ktr.mask=0'. This is necessary
50 # to avoid a race condition while running ktrdump, i.e. the KTR ring buffer
51 # will cycle a bit while ktrdump runs, and this confuses schedgraph because
52 # the timestamps appear to go backwards at some point. Stopping KTR logging
53 # while the workload is still running is to avoid wasting log entries on
54 # "idle" time at the end.
55 # - Dump the trace to a file: 'ktrdump -ct > ktr.out'
56 # - Run the python script: 'python schedgraph.py ktr.out'
59 # 1) Add a per-thread summary display
60 # 2) Add bounding box style zoom.
62 # 4) Implement some sorting mechanism.
63 # 5) Widget to display variable-range data (e.g. q length)
64 # 6) Reorder rows, hide rows, etc.
65 # 7) "Vertical rule" to help relate data in different rows
66 # 8) Mouse-over popup of full thread/event/row lable (currently truncated)
67 # 9) More visible anchors for popup event windows
69 # BUGS: 1) Only 8 CPUs are supported, more CPUs require more choices of
70 # colours to represent them ;-)
71 # 2) Extremely short traces may cause a crash because the code
72 # assumes there is always at least one stathz entry logged, and
73 # the number of such events is used as a denominator
81 us = ticksps / 1000000
84 return (str(ticks) + "us")
87 return (str(ticks) + "ms")
89 return (str(ticks) + "s")
92 def __init__(self, master, target):
93 Frame.__init__(self, master)
94 self.scale = Scale(self, command=self.scaleset,
95 from_=1000, to_=10000000, orient=HORIZONTAL,
97 self.label = Label(self, text="Ticks per pixel")
98 self.label.pack(side=LEFT)
99 self.scale.pack(fill="both", expand=1)
101 self.scale.set(target.scaleget())
104 def scaleset(self, value):
105 self.target.scaleset(int(value))
107 def set(self, value):
108 self.scale.set(value)
111 def __init__(self, master):
112 Frame.__init__(self, master)
113 self.label = Label(self, bd=1, relief=SUNKEN, anchor=W)
114 self.label.pack(fill="both", expand=1)
118 self.label.config(text=str)
121 self.label.config(text="")
123 def startup(self, str):
127 class EventConf(Frame):
128 def __init__(self, master, name, color, enabled):
129 Frame.__init__(self, master)
131 self.color = StringVar()
132 self.color_default = color
133 self.color_current = color
134 self.color.set(color)
135 self.enabled = IntVar()
136 self.enabled_default = enabled
137 self.enabled_current = enabled
138 self.enabled.set(enabled)
142 self.label = Label(self, text=self.name, anchor=W)
143 self.sample = Canvas(self, width=24, height=24,
145 self.rect = self.sample.create_rectangle(0, 0, 24, 24,
146 fill=self.color.get())
147 self.list = OptionMenu(self, self.color,
148 "dark red", "red", "pink",
149 "dark orange", "orange",
150 "yellow", "light yellow",
151 "dark green", "green", "light green",
152 "dark blue", "blue", "light blue",
153 "dark violet", "violet", "purple",
154 "dark grey", "light grey",
156 command=self.setcolor)
157 self.checkbox = Checkbutton(self, text="enabled",
158 variable=self.enabled)
159 self.label.grid(row=0, column=0, sticky=E+W)
160 self.sample.grid(row=0, column=1)
161 self.list.grid(row=0, column=2, sticky=E+W)
162 self.checkbox.grid(row=0, column=3)
163 self.columnconfigure(0, weight=1)
164 self.columnconfigure(2, minsize=110)
166 def setcolor(self, color):
167 self.color.set(color)
168 self.sample.itemconfigure(self.rect, fill=color)
173 if (self.color_current != self.color.get()):
175 if (self.enabled_current != self.enabled.get()):
177 self.color_current = self.color.get()
178 self.enabled_current = self.enabled.get()
180 if (self.enabled_current):
181 graph.setcolor(self.name, self.color_current)
183 graph.hide(self.name)
186 graph.setcolor(self.name, self.color_current)
189 self.setcolor(self.color_current)
190 self.enabled.set(self.enabled_current)
193 self.setcolor(self.color_default)
194 self.enabled.set(self.enabled_default)
196 class EventConfigure(Toplevel):
198 Toplevel.__init__(self)
200 self.title("Event Configuration")
201 self.items = LabelFrame(self, text="Event Type")
202 self.buttons = Frame(self)
204 self.items.grid(row=0, column=0, sticky=E+W)
205 self.columnconfigure(0, weight=1)
206 self.buttons.grid(row=1, column=0, sticky=E+W)
209 for type in configtypes:
210 self.additem(type.name, type.color, type.enabled)
212 def additem(self, name, color, enabled=1):
213 item = EventConf(self.items, name, color, enabled)
214 self.types.append(item)
215 item.grid(row=self.irow, column=0, sticky=E+W)
218 def drawbuttons(self):
219 self.apply = Button(self.buttons, text="Apply",
221 self.revert = Button(self.buttons, text="Revert",
223 self.default = Button(self.buttons, text="Default",
225 self.apply.grid(row=0, column=0, sticky=E+W)
226 self.revert.grid(row=0, column=1, sticky=E+W)
227 self.default.grid(row=0, column=2, sticky=E+W)
228 self.buttons.columnconfigure(0, weight=1)
229 self.buttons.columnconfigure(1, weight=1)
230 self.buttons.columnconfigure(2, weight=1)
233 for item in self.types:
237 for item in self.types:
241 for item in self.types:
244 class EventView(Toplevel):
245 def __init__(self, event, canvas):
246 Toplevel.__init__(self)
250 self.frame = Frame(self)
251 self.frame.grid(row=0, column=0, sticky=N+S+E+W)
252 self.buttons = Frame(self)
253 self.buttons.grid(row=1, column=0, sticky=E+W)
257 event.displayref(canvas)
258 self.bind("<Destroy>", self.destroycb)
260 def destroycb(self, event):
261 self.unbind("<Destroy>")
262 if (self.event != None):
263 self.event.displayunref(self.canvas)
267 def clearlabels(self):
268 for label in self.frame.grid_slaves():
271 def drawlabels(self):
273 labels = self.event.labels()
274 while (len(labels) < 7):
275 labels.append(("", "", 0))
277 name, value, linked = label
278 l = Label(self.frame, text=name, bd=1, width=15,
279 relief=SUNKEN, anchor=W)
284 r = Label(self.frame, text=value, bd=1,
285 relief=SUNKEN, anchor=W, fg=fgcolor)
286 l.grid(row=ypos, column=0, sticky=E+W)
287 r.grid(row=ypos, column=1, sticky=E+W)
289 r.bind("<Button-1>", self.linkpress)
291 self.frame.columnconfigure(1, minsize=80)
293 def drawbuttons(self):
294 self.back = Button(self.buttons, text="<", command=self.bpress)
295 self.forw = Button(self.buttons, text=">", command=self.fpress)
296 self.new = Button(self.buttons, text="new", command=self.npress)
297 self.back.grid(row=0, column=0, sticky=E+W)
298 self.forw.grid(row=0, column=1, sticky=E+W)
299 self.new.grid(row=0, column=2, sticky=E+W)
300 self.buttons.columnconfigure(2, weight=1)
302 def newevent(self, event):
303 self.event.displayunref(self.canvas)
306 self.event.displayref(self.canvas)
310 EventView(self.event, self.canvas)
313 prev = self.event.prev()
316 while (prev.real == 0):
323 next = self.event.next()
326 while (next.real == 0):
332 def linkpress(self, wevent):
333 event = self.event.getlinked()
340 def __init__(self, source, cpu, timestamp, last=0):
343 self.timestamp = int(timestamp)
353 source.lastevent(self)
358 statstr = self.name + " " + self.source.name
359 statstr += " on: cpu" + str(self.cpu)
360 statstr += " at: " + str(self.timestamp)
361 statstr += self.stattxt()
367 def textadd(self, tuple):
369 self.entries.append(tuple)
372 return [("Source:", self.source.name, 0),
373 ("Event:", self.name, 0),
374 ("CPU:", self.cpu, 0),
375 ("Timestamp:", self.timestamp, 0),
376 ("Record: ", self.recno, 0)
378 def mouseenter(self, canvas, item):
379 self.displayref(canvas)
382 def mouseexit(self, canvas, item):
383 self.displayunref(canvas)
386 def mousepress(self, canvas, item):
387 EventView(self, canvas)
390 return self.source.eventat(self.idx + 1)
393 return self.source.eventat(self.idx - 1)
395 def displayref(self, canvas):
396 if (self.dispcnt == 0):
397 canvas.itemconfigure(self.item, width=2)
400 def displayunref(self, canvas):
402 if (self.dispcnt == 0):
403 canvas.itemconfigure(self.item, width=0)
404 canvas.tag_raise("point", "state")
407 return self.linked.findevent(self.timestamp)
409 class PointEvent(Event):
410 def __init__(self, thread, cpu, timestamp, last=0):
411 Event.__init__(self, thread, cpu, timestamp, last)
413 def draw(self, canvas, xpos, ypos):
414 l = canvas.create_oval(xpos - 6, ypos + 1, xpos + 6, ypos - 11,
415 fill=self.color, tags=("all", "point", "event")
416 + (self.name,), width=0)
417 canvas.events[l] = self
419 if (self.enabled == 0):
420 canvas.itemconfigure(l, state="hidden")
424 class StateEvent(Event):
425 def __init__(self, thread, cpu, timestamp, last=0):
426 Event.__init__(self, thread, cpu, timestamp, last)
432 def draw(self, canvas, xpos, ypos):
433 next = self.nextstate()
434 if (self.skipself == 1 or next == None):
436 while (self.skipnext):
440 next = next.nextstate()
444 self.duration = next.timestamp - self.timestamp
445 if (self.duration < 0):
447 print "Unsynchronized timestamp"
448 print self.cpu, self.timestamp
449 print next.cpu, next.timestamp
450 delta = self.duration / canvas.ratio
451 l = canvas.create_rectangle(xpos, ypos,
452 xpos + delta, ypos - 10, fill=self.color, width=0,
453 tags=("all", "state", "event") + (self.name,))
454 canvas.events[l] = self
456 if (self.enabled == 0):
457 canvas.itemconfigure(l, state="hidden")
459 return (xpos + delta)
462 return " duration: " + ticks2sec(self.duration)
466 while (next != None and next.state == 0):
471 return [("Source:", self.source.name, 0),
472 ("Event:", self.name, 0),
473 ("Timestamp:", self.timestamp, 0),
474 ("CPU:", self.cpu, 0),
475 ("Record:", self.recno, 0),
476 ("Duration:", ticks2sec(self.duration), 0)
483 def __init__(self, source, cpu, timestamp, count):
484 self.count = int(count)
485 Event.__init__(self, source, cpu, timestamp)
487 self.textadd(("count:", self.count, 0))
489 def draw(self, canvas, xpos, ypos):
491 self.duration = next.timestamp - self.timestamp
492 delta = self.duration / canvas.ratio
493 yhight = self.source.yscale() * self.count
494 l = canvas.create_rectangle(xpos, ypos - yhight,
495 xpos + delta, ypos, fill=self.color, width=0,
496 tags=("all", "count", "event") + (self.name,))
497 canvas.events[l] = self
499 if (self.enabled == 0):
500 canvas.itemconfigure(l, state="hidden")
501 return (xpos + delta)
504 return " count: " + str(self.count)
506 configtypes.append(Count)
508 class Running(StateEvent):
512 def __init__(self, thread, cpu, timestamp, prio):
513 StateEvent.__init__(self, thread, cpu, timestamp)
515 self.textadd(("prio:", self.prio, 0))
517 configtypes.append(Running)
519 class Idle(StateEvent):
523 def __init__(self, thread, cpu, timestamp, prio):
524 StateEvent.__init__(self, thread, cpu, timestamp)
526 self.textadd(("prio:", self.prio, 0))
528 configtypes.append(Idle)
530 class Yielding(StateEvent):
534 def __init__(self, thread, cpu, timestamp, prio):
535 StateEvent.__init__(self, thread, cpu, timestamp)
538 self.textadd(("prio:", self.prio, 0))
540 configtypes.append(Yielding)
542 class Swapped(StateEvent):
546 def __init__(self, thread, cpu, timestamp, prio):
547 StateEvent.__init__(self, thread, cpu, timestamp)
549 self.textadd(("prio:", self.prio, 0))
551 configtypes.append(Swapped)
553 class Suspended(StateEvent):
557 def __init__(self, thread, cpu, timestamp, prio):
558 StateEvent.__init__(self, thread, cpu, timestamp)
560 self.textadd(("prio:", self.prio, 0))
562 configtypes.append(Suspended)
564 class Iwait(StateEvent):
568 def __init__(self, thread, cpu, timestamp, prio):
569 StateEvent.__init__(self, thread, cpu, timestamp)
571 self.textadd(("prio:", self.prio, 0))
573 configtypes.append(Iwait)
575 class Preempted(StateEvent):
579 def __init__(self, thread, cpu, timestamp, prio, bythread):
580 StateEvent.__init__(self, thread, cpu, timestamp)
583 self.linked = bythread
584 self.textadd(("prio:", self.prio, 0))
585 self.textadd(("by thread:", self.linked.name, 1))
587 configtypes.append(Preempted)
589 class Sleep(StateEvent):
593 def __init__(self, thread, cpu, timestamp, prio, wmesg):
594 StateEvent.__init__(self, thread, cpu, timestamp)
597 self.textadd(("prio:", self.prio, 0))
598 self.textadd(("wmesg:", self.wmesg, 0))
601 statstr = StateEvent.stattxt(self)
602 statstr += " sleeping on: " + self.wmesg
605 configtypes.append(Sleep)
607 class Blocked(StateEvent):
611 def __init__(self, thread, cpu, timestamp, prio, lock):
612 StateEvent.__init__(self, thread, cpu, timestamp)
615 self.textadd(("prio:", self.prio, 0))
616 self.textadd(("lock:", self.lock, 0))
619 statstr = StateEvent.stattxt(self)
620 statstr += " blocked on: " + self.lock
623 configtypes.append(Blocked)
625 class KsegrpRunq(StateEvent):
629 def __init__(self, thread, cpu, timestamp, prio, bythread):
630 StateEvent.__init__(self, thread, cpu, timestamp)
632 self.linked = bythread
633 self.textadd(("prio:", self.prio, 0))
634 self.textadd(("by thread:", self.linked.name, 1))
636 configtypes.append(KsegrpRunq)
638 class Runq(StateEvent):
642 def __init__(self, thread, cpu, timestamp, prio, bythread):
643 StateEvent.__init__(self, thread, cpu, timestamp)
645 self.linked = bythread
646 self.textadd(("prio:", self.prio, 0))
647 self.textadd(("by thread:", self.linked.name, 1))
649 configtypes.append(Runq)
651 class Sched_exit_thread(StateEvent):
655 def __init__(self, thread, cpu, timestamp, prio):
656 StateEvent.__init__(self, thread, cpu, timestamp)
657 self.name = "sched_exit_thread"
659 self.textadd(("prio:", self.prio, 0))
661 configtypes.append(Sched_exit_thread)
663 class Sched_exit(StateEvent):
667 def __init__(self, thread, cpu, timestamp, prio):
668 StateEvent.__init__(self, thread, cpu, timestamp)
669 self.name = "sched_exit"
671 self.textadd(("prio:", self.prio, 0))
673 configtypes.append(Sched_exit)
675 # Events for running callout routines
677 class CalloutIdle(StateEvent):
678 name = "callwheel idle"
681 def __init__(self, wheel, cpu, timestamp):
682 StateEvent.__init__(self, wheel, cpu, timestamp)
684 configtypes.append(CalloutIdle)
686 class CalloutRunning(StateEvent):
687 name = "callout running"
690 def __init__(self, wheel, cpu, timestamp, func, arg):
691 StateEvent.__init__(self, wheel, cpu, timestamp)
692 self.textadd(("function:", func, 0))
693 self.textadd(("argument:", arg, 0))
698 statstr = StateEvent.stattxt(self)
699 statstr += " executing %s(%s)" % (self.func, self.arg)
702 configtypes.append(CalloutRunning)
706 # XXX: No support for upgrade/downgrade currently or differentiating
707 # between read/write in general.
709 # XXX: Point events for recursion perhaps?
711 class LockAcquire(StateEvent):
712 name = "lock acquire"
715 def __init__(self, lock, cpu, timestamp, file, line):
716 StateEvent.__init__(self, lock, cpu, timestamp)
717 self.textadd(("file:", file, 0))
718 self.textadd(("line:", line, 0))
720 configtypes.append(LockAcquire)
722 class LockContest(StateEvent):
723 name = "lock contest"
726 def __init__(self, lock, cpu, timestamp, file, line):
727 StateEvent.__init__(self, lock, cpu, timestamp)
728 self.textadd(("file:", file, 0))
729 self.textadd(("line:", line, 0))
731 configtypes.append(LockContest)
733 class LockFailedTry(PointEvent):
734 name = "failed lock try"
737 def __init__(self, lock, cpu, timestamp, file, line):
738 PointEvent.__init__(self, lock, cpu, timestamp)
739 self.textadd(("file:", file, 0))
740 self.textadd(("line:", line, 0))
742 configtypes.append(LockFailedTry)
744 class LockRelease(StateEvent):
745 name = "lock release"
748 def __init__(self, lock, cpu, timestamp, file, line):
749 StateEvent.__init__(self, lock, cpu, timestamp)
750 self.textadd(("file:", file, 0))
751 self.textadd(("line:", line, 0))
753 configtypes.append(LockRelease)
755 class Padevent(StateEvent):
756 def __init__(self, thread, cpu, timestamp, last=0):
757 StateEvent.__init__(self, thread, cpu, timestamp, last)
761 def draw(self, canvas, xpos, ypos):
765 self.duration = next.timestamp - self.timestamp
766 delta = self.duration / canvas.ratio
767 return (xpos + delta)
769 class Tick(PointEvent):
773 def __init__(self, thread, cpu, timestamp, prio, stathz):
774 PointEvent.__init__(self, thread, cpu, timestamp)
776 self.textadd(("prio:", self.prio, 0))
778 configtypes.append(Tick)
780 class Prio(PointEvent):
784 def __init__(self, thread, cpu, timestamp, prio, newprio, bythread):
785 PointEvent.__init__(self, thread, cpu, timestamp)
787 self.newprio = newprio
788 self.linked = bythread
789 self.textadd(("new prio:", self.newprio, 0))
790 self.textadd(("prio:", self.prio, 0))
791 if (self.linked != self.source):
792 self.textadd(("by thread:", self.linked.name, 1))
794 self.textadd(("by thread:", self.linked.name, 0))
796 configtypes.append(Prio)
798 class Lend(PointEvent):
802 def __init__(self, thread, cpu, timestamp, prio, tothread):
803 PointEvent.__init__(self, thread, cpu, timestamp)
805 self.linked = tothread
806 self.textadd(("prio:", self.prio, 0))
807 self.textadd(("to thread:", self.linked.name, 1))
809 configtypes.append(Lend)
811 class Wokeup(PointEvent):
815 def __init__(self, thread, cpu, timestamp, ranthread):
816 PointEvent.__init__(self, thread, cpu, timestamp)
817 self.linked = ranthread
818 self.textadd(("ran thread:", self.linked.name, 1))
820 configtypes.append(Wokeup)
822 (DEFAULT, LOAD, COUNT, CALLWHEEL, LOCK, THREAD) = range(6)
825 def __init__(self, name, group=DEFAULT, order=0):
833 def __cmp__(self, other):
834 if (self.group == other.group):
835 return cmp(self.order, other.order)
836 return cmp(self.group, other.group)
838 # It is much faster to append items to a list then to insert them
839 # at the beginning. As a result, we add events in reverse order
840 # and then swap the list during fixup.
842 self.events.reverse()
844 def event(self, event):
845 self.events.append(event)
847 def remove(self, event):
848 self.events.remove(event)
850 def lastevent(self, event):
851 self.events.insert(0, event)
853 def draw(self, canvas, ypos):
856 self.cpu = self.events[1].cpu
857 for i in range(0, len(self.events)):
858 self.events[i].idx = i
859 for event in self.events:
860 if (event.cpu != self.cpu and event.cpu != -1):
861 self.drawcpu(canvas, xpos, ypos)
864 xpos = event.draw(canvas, xpos, ypos)
865 self.drawcpu(canvas, xpos, ypos)
867 def drawname(self, canvas, ypos):
868 ypos = ypos - (self.ysize() / 2)
869 canvas.create_text(10, ypos, anchor="w", text=self.name)
871 def drawcpu(self, canvas, xpos, ypos):
880 color = 'light green'
882 color = 'blanched almond'
886 color = 'light slate blue'
891 l = canvas.create_rectangle(self.cpux,
892 ypos - self.ysize() - canvas.bdheight,
893 xpos, ypos + canvas.bdheight, fill=color, width=0,
894 tags=("all", "cpuinfo"))
899 def eventat(self, i):
900 if (i >= len(self.events)):
902 event = self.events[i]
905 def findevent(self, timestamp):
906 for event in self.events:
907 if (event.timestamp >= timestamp and event.real):
911 class Thread(EventSource):
913 def __init__(self, td, pcomm):
914 EventSource.__init__(self, pcomm, THREAD)
917 cnt = Thread.names[pcomm]
919 Thread.names[pcomm] = 0
921 Thread.names[pcomm] = cnt + 1
924 EventSource.fixup(self)
925 cnt = Thread.names[self.name]
929 Thread.names[self.name] = cnt
930 self.name += " td" + str(cnt)
935 class Callwheel(EventSource):
937 def __init__(self, cpu):
938 EventSource.__init__(self, "Callwheel", CALLWHEEL, cpu)
943 EventSource.fixup(self)
944 if (Callwheel.count == 1):
946 self.name += " (CPU %d)" % (self.wheel)
951 class Lock(EventSource):
952 def __init__(self, lock):
953 EventSource.__init__(self, lock, LOCK)
958 class Counter(EventSource):
960 def __init__(self, name):
961 EventSource.__init__(self, name, COUNT)
963 def event(self, event):
964 EventSource.event(self, event)
970 if (count > Counter.max):
980 return (self.ysize() / Counter.max)
982 class CPULoad(Counter):
983 def __init__(self, cpu):
984 Counter.__init__(self, "cpu" + str(cpu) + " load")
989 def __init__(self, file):
990 self.timestamp_f = None
991 self.timestamp_l = None
1004 print "first", self.timestamp_f, "last", self.timestamp_l
1005 print "time span", self.timespan()
1006 print "stathz", self.stathz
1007 ticksps = self.ticksps()
1008 print "Ticks per second", ticksps
1010 def parse(self, file):
1014 print "Can't open", file
1017 ktrhdr = "\s*\d+\s+(\d+)\s+(\d+)\s+"
1018 tdname = "(\S+)\(([^)]*)\)"
1019 crittdname = "(\S+)\s+\(\d+,\s+([^)]*)\)"
1021 # XXX doesn't handle:
1022 # 371 0 61628682318 mi_switch: 0xc075c070(swapper) prio 180 inhibit 2 wmesg ATA request done lock (null)
1023 ktrstr = "mi_switch: " + tdname
1024 ktrstr += " prio (\d+) inhibit (\d+) wmesg (\S+) lock (\S+)"
1025 switchout_re = re.compile(ktrhdr + ktrstr)
1027 ktrstr = "mi_switch: " + tdname + " prio (\d+) idle"
1028 idled_re = re.compile(ktrhdr + ktrstr)
1030 ktrstr = "mi_switch: " + tdname + " prio (\d+) preempted by "
1032 preempted_re = re.compile(ktrhdr + ktrstr)
1034 ktrstr = "mi_switch: running " + tdname + " prio (\d+)"
1035 switchin_re = re.compile(ktrhdr + ktrstr)
1037 ktrstr = "sched_add: " + tdname + " prio (\d+) by " + tdname
1038 sched_add_re = re.compile(ktrhdr + ktrstr)
1040 ktrstr = "setrunqueue: " + tdname + " prio (\d+) by " + tdname
1041 setrunqueue_re = re.compile(ktrhdr + ktrstr)
1043 ktrstr = "sched_rem: " + tdname + " prio (\d+) by " + tdname
1044 sched_rem_re = re.compile(ktrhdr + ktrstr)
1046 ktrstr = "sched_exit_thread: " + tdname + " prio (\d+)"
1047 sched_exit_thread_re = re.compile(ktrhdr + ktrstr)
1049 ktrstr = "sched_exit: " + tdname + " prio (\d+)"
1050 sched_exit_re = re.compile(ktrhdr + ktrstr)
1052 ktrstr = "statclock: " + tdname + " prio (\d+)"
1053 ktrstr += " stathz (\d+)"
1054 sched_clock_re = re.compile(ktrhdr + ktrstr)
1056 ktrstr = "sched_prio: " + tdname + " prio (\d+)"
1057 ktrstr += " newprio (\d+) by " + tdname
1058 sched_prio_re = re.compile(ktrhdr + ktrstr)
1060 cpuload_re = re.compile(ktrhdr + "load: (\d+)")
1061 cpuload2_re = re.compile(ktrhdr + "cpu (\d+) load: (\d+)")
1062 loadglobal_re = re.compile(ktrhdr + "global load: (\d+)")
1064 ktrstr = "critical_\S+ by thread " + crittdname + " to (\d+)"
1065 critsec_re = re.compile(ktrhdr + ktrstr)
1067 ktrstr = "callout 0x[a-f\d]+ "
1068 ktrstr += "func (0x[a-f\d]+) arg (0x[a-f\d]+)"
1069 callout_start_re = re.compile(ktrhdr + ktrstr)
1071 ktrstr = "callout mpsafe 0x[a-f\d]+ "
1072 ktrstr += "func (0x[a-f\d]+) arg (0x[a-f\d]+)"
1073 callout_mpsafe_re = re.compile(ktrhdr + ktrstr)
1075 ktrstr = "callout mtx 0x[a-f\d]+ "
1076 ktrstr += "func (0x[a-f\d]+) arg (0x[a-f\d]+)"
1077 callout_mtx_re = re.compile(ktrhdr + ktrstr)
1079 ktrstr = "callout 0x[a-f\d]+ finished"
1080 callout_stop_re = re.compile(ktrhdr + ktrstr)
1082 ktrstr = "TRY_([RSWX]?LOCK) \(.*\) (.*) r = ([0-9]+)"
1083 ktrstr += " at (?:\.\./)*(.*):([0-9]+)"
1084 lock_try_re = re.compile(ktrhdr + ktrstr)
1086 ktrstr = "([RSWX]?UNLOCK) \(.*\) (.*) r = ([0-9]+)"
1087 ktrstr += " at (?:\.\./)*(.*):([0-9]+)"
1088 lock_release_re = re.compile(ktrhdr + ktrstr)
1090 ktrstr = "([RSWX]?LOCK) \(.*\) (.*) r = ([0-9]+)"
1091 ktrstr += " at (?:\.\./)*(.*):([0-9]+)"
1092 lock_acquire_re = re.compile(ktrhdr + ktrstr)
1094 ktrstr = "_mtx_lock_sleep: (.*) contested \(lock=0x?[0-9a-f]*\)"
1095 ktrstr += " at (?:\.\./)*(.*):([0-9]+)"
1096 mtx_contested_re = re.compile(ktrhdr + ktrstr)
1098 # XXX: Spin lock traces don't have lock name or file/line
1100 ktrstr = "_rw_wlock_hard: (.*) contested \(lock=0x?[0-9a-f]*\)"
1101 ktrstr += " at (?:\.\./)*(.*):([0-9]+)"
1102 rw_contested_re = re.compile(ktrhdr + ktrstr)
1104 # XXX: Read lock traces for rwlocks contesting don't have
1105 # lock name or file/line
1107 parsers = [[cpuload_re, self.cpuload],
1108 [cpuload2_re, self.cpuload2],
1109 [loadglobal_re, self.loadglobal],
1110 [switchin_re, self.switchin],
1111 [switchout_re, self.switchout],
1112 [sched_add_re, self.sched_add],
1113 [setrunqueue_re, self.sched_rem],
1114 [sched_prio_re, self.sched_prio],
1115 [preempted_re, self.preempted],
1116 [sched_rem_re, self.sched_rem],
1117 [sched_exit_thread_re, self.sched_exit_thread],
1118 [sched_exit_re, self.sched_exit],
1119 [sched_clock_re, self.sched_clock],
1120 [critsec_re, self.critsec],
1121 [callout_start_re, self.callout_start],
1122 [callout_mpsafe_re, self.callout_start],
1123 [callout_mtx_re, self.callout_start],
1124 [callout_stop_re, self.callout_stop],
1125 [lock_try_re, self.lock_try],
1126 [lock_release_re, self.lock_release],
1127 [lock_acquire_re, self.lock_acquire],
1128 [mtx_contested_re, self.lock_contest],
1129 [rw_contested_re, self.lock_contest],
1130 [idled_re, self.idled]]
1134 for line in ifp.readlines():
1136 if ((lineno % 1024) == 0):
1137 status.startup("Parsing line " + str(lineno))
1139 m = p[0].match(line)
1146 def checkstamp(self, cpu, timestamp):
1147 timestamp = int(timestamp)
1148 if (self.timestamp_f == None):
1149 self.timestamp_f = timestamp;
1150 if (self.timestamp_l != None and timestamp > self.timestamp_l):
1152 self.timestamp_l = timestamp;
1156 return (self.timestamp_f - self.timestamp_l);
1159 return (self.timespan() / self.ticks[0]) * int(self.stathz)
1161 def switchout(self, cpu, timestamp, td, pcomm, prio, inhibit, wmesg, lock):
1162 TDI_SUSPENDED = 0x0001
1163 TDI_SLEEPING = 0x0002
1164 TDI_SWAPPED = 0x0004
1168 timestamp = self.checkstamp(cpu, timestamp)
1169 if (timestamp == 0):
1171 inhibit = int(inhibit)
1172 thread = self.findtd(td, pcomm)
1173 if (inhibit & TDI_SWAPPED):
1174 Swapped(thread, cpu, timestamp, prio)
1175 elif (inhibit & TDI_SLEEPING):
1176 Sleep(thread, cpu, timestamp, prio, wmesg)
1177 elif (inhibit & TDI_LOCK):
1178 Blocked(thread, cpu, timestamp, prio, lock)
1179 elif (inhibit & TDI_IWAIT):
1180 Iwait(thread, cpu, timestamp, prio)
1181 elif (inhibit & TDI_SUSPENDED):
1182 Suspended(thread, cpu, timestamp, prio)
1183 elif (inhibit == 0):
1184 Yielding(thread, cpu, timestamp, prio)
1186 print "Unknown event", inhibit
1189 def idled(self, cpu, timestamp, td, pcomm, prio):
1190 timestamp = self.checkstamp(cpu, timestamp)
1191 if (timestamp == 0):
1193 thread = self.findtd(td, pcomm)
1194 Idle(thread, cpu, timestamp, prio)
1196 def preempted(self, cpu, timestamp, td, pcomm, prio, bytd, bypcomm):
1197 timestamp = self.checkstamp(cpu, timestamp)
1198 if (timestamp == 0):
1200 thread = self.findtd(td, pcomm)
1201 Preempted(thread, cpu, timestamp, prio,
1202 self.findtd(bytd, bypcomm))
1204 def switchin(self, cpu, timestamp, td, pcomm, prio):
1205 timestamp = self.checkstamp(cpu, timestamp)
1206 if (timestamp == 0):
1208 thread = self.findtd(td, pcomm)
1209 Running(thread, cpu, timestamp, prio)
1211 def sched_add(self, cpu, timestamp, td, pcomm, prio, bytd, bypcomm):
1212 timestamp = self.checkstamp(cpu, timestamp)
1213 if (timestamp == 0):
1215 thread = self.findtd(td, pcomm)
1216 bythread = self.findtd(bytd, bypcomm)
1217 Runq(thread, cpu, timestamp, prio, bythread)
1218 Wokeup(bythread, cpu, timestamp, thread)
1220 def sched_rem(self, cpu, timestamp, td, pcomm, prio, bytd, bypcomm):
1221 timestamp = self.checkstamp(cpu, timestamp)
1222 if (timestamp == 0):
1224 thread = self.findtd(td, pcomm)
1225 KsegrpRunq(thread, cpu, timestamp, prio,
1226 self.findtd(bytd, bypcomm))
1228 def sched_exit_thread(self, cpu, timestamp, td, pcomm, prio):
1229 timestamp = self.checkstamp(cpu, timestamp)
1230 if (timestamp == 0):
1232 thread = self.findtd(td, pcomm)
1233 Sched_exit_thread(thread, cpu, timestamp, prio)
1235 def sched_exit(self, cpu, timestamp, td, pcomm, prio):
1236 timestamp = self.checkstamp(cpu, timestamp)
1237 if (timestamp == 0):
1239 thread = self.findtd(td, pcomm)
1240 Sched_exit(thread, cpu, timestamp, prio)
1242 def sched_clock(self, cpu, timestamp, td, pcomm, prio, stathz):
1243 timestamp = self.checkstamp(cpu, timestamp)
1244 if (timestamp == 0):
1246 self.stathz = stathz
1249 ticks = self.ticks[cpu]
1252 self.ticks[cpu] += 1
1253 thread = self.findtd(td, pcomm)
1254 Tick(thread, cpu, timestamp, prio, stathz)
1256 def sched_prio(self, cpu, timestamp, td, pcomm, prio, newprio, bytd, bypcomm):
1257 if (prio == newprio):
1259 timestamp = self.checkstamp(cpu, timestamp)
1260 if (timestamp == 0):
1262 thread = self.findtd(td, pcomm)
1263 bythread = self.findtd(bytd, bypcomm)
1264 Prio(thread, cpu, timestamp, prio, newprio, bythread)
1265 Lend(bythread, cpu, timestamp, newprio, thread)
1267 def cpuload(self, cpu, timestamp, count):
1268 timestamp = self.checkstamp(cpu, timestamp)
1269 if (timestamp == 0):
1273 load = self.load[cpu]
1276 self.load[cpu] = load
1277 self.sources.append(load)
1278 Count(load, cpu, timestamp, count)
1280 def cpuload2(self, cpu, timestamp, ncpu, count):
1281 timestamp = self.checkstamp(cpu, timestamp)
1282 if (timestamp == 0):
1286 load = self.load[cpu]
1289 self.load[cpu] = load
1290 self.sources.append(load)
1291 Count(load, cpu, timestamp, count)
1293 def loadglobal(self, cpu, timestamp, count):
1294 timestamp = self.checkstamp(cpu, timestamp)
1295 if (timestamp == 0):
1299 load = self.load[cpu]
1301 load = Counter("CPU load")
1302 self.load[cpu] = load
1303 self.sources.append(load)
1304 Count(load, cpu, timestamp, count)
1306 def critsec(self, cpu, timestamp, td, pcomm, to):
1307 timestamp = self.checkstamp(cpu, timestamp)
1308 if (timestamp == 0):
1312 crit = self.crit[cpu]
1314 crit = Counter("Critical Section")
1315 self.crit[cpu] = crit
1316 self.sources.append(crit)
1317 Count(crit, cpu, timestamp, to)
1319 def callout_start(self, cpu, timestamp, func, arg):
1320 timestamp = self.checkstamp(cpu, timestamp)
1321 if (timestamp == 0):
1323 wheel = self.findwheel(cpu)
1324 CalloutRunning(wheel, cpu, timestamp, func, arg)
1326 def callout_stop(self, cpu, timestamp):
1327 timestamp = self.checkstamp(cpu, timestamp)
1328 if (timestamp == 0):
1330 wheel = self.findwheel(cpu)
1331 CalloutIdle(wheel, cpu, timestamp)
1333 def lock_try(self, cpu, timestamp, op, name, result, file, line):
1334 timestamp = self.checkstamp(cpu, timestamp)
1335 if (timestamp == 0):
1337 lock = self.findlock(name)
1338 if (int(result) == 0):
1339 LockFailedTry(lock, cpu, timestamp, file, line)
1341 LockAcquire(lock, cpu, timestamp, file, line)
1343 def lock_acquire(self, cpu, timestamp, op, name, recurse, file, line):
1344 if (int(recurse) != 0):
1346 timestamp = self.checkstamp(cpu, timestamp)
1347 if (timestamp == 0):
1349 lock = self.findlock(name)
1350 LockAcquire(lock, cpu, timestamp, file, line)
1352 def lock_release(self, cpu, timestamp, op, name, recurse, file, line):
1353 if (int(recurse) != 0):
1355 timestamp = self.checkstamp(cpu, timestamp)
1356 if (timestamp == 0):
1358 lock = self.findlock(name)
1359 LockRelease(lock, cpu, timestamp, file, line)
1361 def lock_contest(self, cpu, timestamp, name, file, line):
1362 timestamp = self.checkstamp(cpu, timestamp)
1363 if (timestamp == 0):
1365 lock = self.findlock(name)
1366 LockContest(lock, cpu, timestamp, file, line)
1368 def findlock(self, name):
1370 lock = self.locks[name]
1373 self.locks[name] = lock
1374 self.sources.append(lock)
1377 def findwheel(self, cpu):
1380 wheel = self.callwheels[cpu]
1382 wheel = Callwheel(cpu)
1383 self.callwheels[cpu] = wheel
1384 self.sources.append(wheel)
1387 def findtd(self, td, pcomm):
1388 for thread in self.threads:
1389 if (thread.str == td and thread.name == pcomm):
1391 thread = Thread(td, pcomm)
1392 self.threads.append(thread)
1393 self.sources.append(thread)
1397 for source in self.sources:
1398 Padevent(source, -1, self.timestamp_l)
1399 Padevent(source, -1, self.timestamp_f, last=1)
1403 class SchedDisplay(Canvas):
1404 def __init__(self, master):
1408 self.parent = master
1412 Canvas.__init__(self, master, width=800, height=500, bg='grey',
1413 scrollregion=(0, 0, 800, 500))
1415 def setfile(self, ktrfile):
1416 self.ktrfile = ktrfile
1417 self.sources = ktrfile.sources
1419 # Compute a ratio to ensure that the file's timespan fits into
1420 # 2^31. Although python may handle larger values for X
1421 # values, the Tk internals do not.
1422 self.ratio = (ktrfile.timespan() - 1) / 2**31 + 1
1426 xsize = self.xsize()
1427 for source in self.sources:
1428 status.startup("Drawing " + source.name)
1429 self.create_line(0, ypos, xsize, ypos,
1430 width=1, fill="black", tags=("all",))
1431 ypos += self.bdheight
1432 ypos += source.ysize()
1433 source.draw(self, ypos)
1434 ypos += self.bdheight
1436 self.tag_raise("point", "state")
1437 self.tag_lower("cpuinfo", "all")
1440 self.create_line(0, ypos, xsize, ypos,
1441 width=1, fill="black", tags=("all",))
1442 self.tag_bind("event", "<Enter>", self.mouseenter)
1443 self.tag_bind("event", "<Leave>", self.mouseexit)
1444 self.tag_bind("event", "<Button-1>", self.mousepress)
1445 self.bind("<Button-4>", self.wheelup)
1446 self.bind("<Button-5>", self.wheeldown)
1448 def mouseenter(self, event):
1449 item, = self.find_withtag(CURRENT)
1450 event = self.events[item]
1451 event.mouseenter(self, item)
1453 def mouseexit(self, event):
1454 item, = self.find_withtag(CURRENT)
1455 event = self.events[item]
1456 event.mouseexit(self, item)
1458 def mousepress(self, event):
1459 item, = self.find_withtag(CURRENT)
1460 event = self.events[item]
1461 event.mousepress(self, item)
1463 def wheeldown(self, event):
1464 self.parent.display_yview("scroll", 1, "units")
1466 def wheelup(self, event):
1467 self.parent.display_yview("scroll", -1, "units")
1469 def drawnames(self, canvas):
1470 status.startup("Drawing names")
1472 canvas.configure(scrollregion=(0, 0,
1473 canvas["width"], self.ysize()))
1474 for source in self.sources:
1475 canvas.create_line(0, ypos, canvas["width"], ypos,
1476 width=1, fill="black", tags=("all",))
1477 ypos += self.bdheight
1478 ypos += source.ysize()
1479 source.drawname(canvas, ypos)
1480 ypos += self.bdheight
1481 canvas.create_line(0, ypos, canvas["width"], ypos,
1482 width=1, fill="black", tags=("all",))
1485 return ((self.ktrfile.timespan() / self.ratio) + 20)
1489 for source in self.sources:
1490 ysize += source.ysize() + (self.bdheight * 2)
1493 def scaleset(self, ratio):
1494 if (self.ktrfile == None):
1496 oldratio = self.ratio
1497 xstart, ystart = self.xview()
1498 length = (float(self["width"]) / self.xsize())
1499 middle = xstart + (length / 2)
1502 self.configure(scrollregion=(0, 0, self.xsize(), self.ysize()))
1503 self.scale("all", 0, 0, float(oldratio) / ratio, 1)
1505 length = (float(self["width"]) / self.xsize())
1506 xstart = middle - (length / 2)
1507 self.xview_moveto(xstart)
1512 def setcolor(self, tag, color):
1513 self.itemconfigure(tag, state="normal", fill=color)
1515 def hide(self, tag):
1516 self.itemconfigure(tag, state="hidden")
1518 class GraphMenu(Frame):
1519 def __init__(self, master):
1520 Frame.__init__(self, master, bd=2, relief=RAISED)
1521 self.view = Menubutton(self, text="Configure")
1522 self.viewmenu = Menu(self.view, tearoff=0)
1523 self.viewmenu.add_command(label="Events",
1525 self.view["menu"] = self.viewmenu
1526 self.view.pack(side=LEFT)
1532 class SchedGraph(Frame):
1533 def __init__(self, master):
1534 Frame.__init__(self, master)
1540 self.pack(expand=1, fill="both")
1543 self.draw(sys.argv[1])
1545 def buildwidgets(self):
1547 self.menu = GraphMenu(self)
1548 self.display = SchedDisplay(self)
1549 self.names = Canvas(self,
1550 width=120, height=self.display["height"],
1551 bg='grey', scrollregion=(0, 0, 50, 100))
1552 self.scale = Scaler(self, self.display)
1553 status = self.status = Status(self)
1554 self.scrollY = Scrollbar(self, orient="vertical",
1555 command=self.display_yview)
1556 self.display.scrollX = Scrollbar(self, orient="horizontal",
1557 command=self.display.xview)
1558 self.display["xscrollcommand"] = self.display.scrollX.set
1559 self.display["yscrollcommand"] = self.scrollY.set
1560 self.names["yscrollcommand"] = self.scrollY.set
1563 self.columnconfigure(1, weight=1)
1564 self.rowconfigure(1, weight=1)
1565 self.menu.grid(row=0, column=0, columnspan=3, sticky=E+W)
1566 self.names.grid(row=1, column=0, sticky=N+S)
1567 self.display.grid(row=1, column=1, sticky=W+E+N+S)
1568 self.scrollY.grid(row=1, column=2, sticky=N+S)
1569 self.display.scrollX.grid(row=2, column=0, columnspan=2,
1571 self.scale.grid(row=3, column=0, columnspan=3, sticky=E+W)
1572 self.status.grid(row=4, column=0, columnspan=3, sticky=E+W)
1574 def draw(self, file):
1575 self.master.update()
1576 ktrfile = KTRFile(file)
1577 self.display.setfile(ktrfile)
1578 self.display.drawnames(self.names)
1580 self.scale.set(250000)
1581 self.display.xview_moveto(0)
1583 def display_yview(self, *args):
1584 self.names.yview(*args)
1585 self.display.yview(*args)
1587 def setcolor(self, tag, color):
1588 self.display.setcolor(tag, color)
1590 def hide(self, tag):
1591 self.display.hide(tag)
1593 if (len(sys.argv) != 2):
1594 print "usage:", sys.argv[0], "<ktr file>"
1598 root.title("Scheduler Graph")
1599 graph = SchedGraph(root)