xref: /freebsd/tools/sched/schedgraph.py (revision f7c4bd95ba735bd6a5454b4953945a99cefbb80c)
1#!/usr/local/bin/python
2
3# Copyright (c) 2002-2003, Jeffrey Roberson <jeff@freebsd.org>
4# All rights reserved.
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions
8# are met:
9# 1. Redistributions of source code must retain the above copyright
10#    notice unmodified, this list of conditions, and the following
11#    disclaimer.
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.
15#
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.
26#
27# $FreeBSD$
28
29import sys
30import re
31from Tkinter import *
32
33# To use:
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.
37#	options 	KTR
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'
57#
58# To do:
59# 1)  Add a per-thread summary display
60# 2)  Add bounding box style zoom.
61# 3)  Click to center.
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
68#
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
74
75ticksps = None
76status = None
77configtypes = []
78lineno = -1
79
80def ticks2sec(ticks):
81	us = ticksps / 1000000
82	ticks /= us
83	if (ticks < 1000):
84		return (str(ticks) + "us")
85	ticks /= 1000
86	if (ticks < 1000):
87		return (str(ticks) + "ms")
88	ticks /= 1000
89	return (str(ticks) + "s")
90
91class Scaler(Frame):
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,
96		    resolution=1000)
97		self.label = Label(self, text="Ticks per pixel")
98		self.label.pack(side=LEFT)
99		self.scale.pack(fill="both", expand=1)
100		self.target = target
101		self.scale.set(target.scaleget())
102		self.initialized = 1
103
104	def scaleset(self, value):
105		self.target.scaleset(int(value))
106
107	def set(self, value):
108		self.scale.set(value)
109
110class Status(Frame):
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)
115		self.clear()
116
117	def set(self, str):
118		self.label.config(text=str)
119
120	def clear(self):
121		self.label.config(text="")
122
123	def startup(self, str):
124		self.set(str)
125		root.update()
126
127class EventConf(Frame):
128	def __init__(self, master, name, color, enabled):
129		Frame.__init__(self, master)
130		self.name = name
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)
139		self.draw()
140
141	def draw(self):
142		self.label = Label(self, text=self.name, anchor=W)
143		self.sample = Canvas(self, width=24, height=24,
144		    bg='grey')
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",
155		    "white", "black",
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)
165
166	def setcolor(self, color):
167		self.color.set(color)
168		self.sample.itemconfigure(self.rect, fill=color)
169
170	def apply(self):
171		cchange = 0
172		echange = 0
173		if (self.color_current != self.color.get()):
174			cchange = 1
175		if (self.enabled_current != self.enabled.get()):
176			echange = 1
177		self.color_current = self.color.get()
178		self.enabled_current = self.enabled.get()
179		if (echange != 0):
180			if (self.enabled_current):
181				graph.setcolor(self.name, self.color_current)
182			else:
183				graph.hide(self.name)
184			return
185		if (cchange != 0):
186			graph.setcolor(self.name, self.color_current)
187
188	def revert(self):
189		self.setcolor(self.color_current)
190		self.enabled.set(self.enabled_current)
191
192	def default(self):
193		self.setcolor(self.color_default)
194		self.enabled.set(self.enabled_default)
195
196class EventConfigure(Toplevel):
197	def __init__(self):
198		Toplevel.__init__(self)
199		self.resizable(0, 0)
200		self.title("Event Configuration")
201		self.items = LabelFrame(self, text="Event Type")
202		self.buttons = Frame(self)
203		self.drawbuttons()
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)
207		self.types = []
208		self.irow = 0
209		for type in configtypes:
210			self.additem(type.name, type.color, type.enabled)
211
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)
216		self.irow += 1
217
218	def drawbuttons(self):
219		self.apply = Button(self.buttons, text="Apply",
220		    command=self.apress)
221		self.revert = Button(self.buttons, text="Revert",
222		    command=self.rpress)
223		self.default = Button(self.buttons, text="Default",
224		    command=self.dpress)
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)
231
232	def apress(self):
233		for item in self.types:
234			item.apply()
235
236	def rpress(self):
237		for item in self.types:
238			item.revert()
239
240	def dpress(self):
241		for item in self.types:
242			item.default()
243
244class EventView(Toplevel):
245	def __init__(self, event, canvas):
246		Toplevel.__init__(self)
247		self.resizable(0, 0)
248		self.title("Event")
249		self.event = event
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)
254		self.canvas = canvas
255		self.drawlabels()
256		self.drawbuttons()
257		event.displayref(canvas)
258		self.bind("<Destroy>", self.destroycb)
259
260	def destroycb(self, event):
261		self.unbind("<Destroy>")
262		if (self.event != None):
263			self.event.displayunref(self.canvas)
264			self.event = None
265		self.destroy()
266
267	def clearlabels(self):
268		for label in self.frame.grid_slaves():
269			label.grid_remove()
270
271	def drawlabels(self):
272		ypos = 0
273		labels = self.event.labels()
274		while (len(labels) < 7):
275			labels.append(("", "", 0))
276		for label in labels:
277			name, value, linked = label
278			l = Label(self.frame, text=name, bd=1, width=15,
279			    relief=SUNKEN, anchor=W)
280			if (linked):
281				fgcolor = "blue"
282			else:
283				fgcolor = "black"
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)
288			if (linked):
289				r.bind("<Button-1>", self.linkpress)
290			ypos += 1
291		self.frame.columnconfigure(1, minsize=80)
292
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)
301
302	def newevent(self, event):
303		self.event.displayunref(self.canvas)
304		self.clearlabels()
305		self.event = event
306		self.event.displayref(self.canvas)
307		self.drawlabels()
308
309	def npress(self):
310		EventView(self.event, self.canvas)
311
312	def bpress(self):
313		prev = self.event.prev()
314		if (prev == None):
315			return
316		while (prev.real == 0):
317			prev = prev.prev()
318			if (prev == None):
319				return
320		self.newevent(prev)
321
322	def fpress(self):
323		next = self.event.next()
324		if (next == None):
325			return
326		while (next.real == 0):
327			next = next.next()
328			if (next == None):
329				return
330		self.newevent(next)
331
332	def linkpress(self, wevent):
333		event = self.event.getlinked()
334		if (event != None):
335			self.newevent(event)
336
337class Event:
338	name = "none"
339	color = "grey"
340	def __init__(self, source, cpu, timestamp, last=0):
341		self.source = source
342		self.cpu = cpu
343		self.timestamp = int(timestamp)
344		self.entries = []
345		self.real = 1
346		self.idx = None
347		self.state = 0
348		self.item = None
349		self.dispcnt = 0
350		self.linked = None
351		self.recno = lineno
352		if (last):
353			source.lastevent(self)
354		else:
355			source.event(self)
356
357	def status(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()
362		status.set(statstr)
363
364	def stattxt(self):
365		return ""
366
367	def textadd(self, tuple):
368		pass
369		self.entries.append(tuple)
370
371	def labels(self):
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)
377		] + self.entries
378	def mouseenter(self, canvas, item):
379		self.displayref(canvas)
380		self.status()
381
382	def mouseexit(self, canvas, item):
383		self.displayunref(canvas)
384		status.clear()
385
386	def mousepress(self, canvas, item):
387		EventView(self, canvas)
388
389	def next(self):
390		return self.source.eventat(self.idx + 1)
391
392	def prev(self):
393		return self.source.eventat(self.idx - 1)
394
395	def displayref(self, canvas):
396		if (self.dispcnt == 0):
397			canvas.itemconfigure(self.item, width=2)
398		self.dispcnt += 1
399
400	def displayunref(self, canvas):
401		self.dispcnt -= 1
402		if (self.dispcnt == 0):
403			canvas.itemconfigure(self.item, width=0)
404			canvas.tag_raise("point", "state")
405
406	def getlinked(self):
407		return self.linked.findevent(self.timestamp)
408
409class PointEvent(Event):
410	def __init__(self, thread, cpu, timestamp, last=0):
411		Event.__init__(self, thread, cpu, timestamp, last)
412
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
418		self.item = l
419		if (self.enabled == 0):
420			canvas.itemconfigure(l, state="hidden")
421
422		return (xpos)
423
424class StateEvent(Event):
425	def __init__(self, thread, cpu, timestamp, last=0):
426		Event.__init__(self, thread, cpu, timestamp, last)
427		self.duration = 0
428		self.skipnext = 0
429		self.skipself = 0
430		self.state = 1
431
432	def draw(self, canvas, xpos, ypos):
433		next = self.nextstate()
434		if (self.skipself == 1 or next == None):
435			return (xpos)
436		while (self.skipnext):
437			skipped = next
438			next.skipself = 1
439			next.real = 0
440			next = next.nextstate()
441			if (next == None):
442				next = skipped
443			self.skipnext -= 1
444		self.duration = next.timestamp - self.timestamp
445		if (self.duration < 0):
446			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
455		self.item = l
456		if (self.enabled == 0):
457			canvas.itemconfigure(l, state="hidden")
458
459		return (xpos + delta)
460
461	def stattxt(self):
462		return " duration: " + ticks2sec(self.duration)
463
464	def nextstate(self):
465		next = self.next()
466		while (next != None and next.state == 0):
467			next = next.next()
468		return (next)
469
470	def labels(self):
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)
477		] + self.entries
478
479class Count(Event):
480	name = "Count"
481	color = "red"
482	enabled = 1
483	def __init__(self, source, cpu, timestamp, count):
484		self.count = int(count)
485		Event.__init__(self, source, cpu, timestamp)
486		self.duration = 0
487		self.textadd(("count:", self.count, 0))
488
489	def draw(self, canvas, xpos, ypos):
490		next = self.next()
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
498		self.item = l
499		if (self.enabled == 0):
500			canvas.itemconfigure(l, state="hidden")
501		return (xpos + delta)
502
503	def stattxt(self):
504		return " count: " + str(self.count)
505
506configtypes.append(Count)
507
508class Running(StateEvent):
509	name = "running"
510	color = "green"
511	enabled = 1
512	def __init__(self, thread, cpu, timestamp, prio):
513		StateEvent.__init__(self, thread, cpu, timestamp)
514		self.prio = prio
515		self.textadd(("prio:", self.prio, 0))
516
517configtypes.append(Running)
518
519class Idle(StateEvent):
520	name = "idle"
521	color = "grey"
522	enabled = 0
523	def __init__(self, thread, cpu, timestamp, prio):
524		StateEvent.__init__(self, thread, cpu, timestamp)
525		self.prio = prio
526		self.textadd(("prio:", self.prio, 0))
527
528configtypes.append(Idle)
529
530class Yielding(StateEvent):
531	name = "yielding"
532	color = "yellow"
533	enabled = 1
534	def __init__(self, thread, cpu, timestamp, prio):
535		StateEvent.__init__(self, thread, cpu, timestamp)
536		self.skipnext = 0
537		self.prio = prio
538		self.textadd(("prio:", self.prio, 0))
539
540configtypes.append(Yielding)
541
542class Swapped(StateEvent):
543	name = "swapped"
544	color = "violet"
545	enabled = 1
546	def __init__(self, thread, cpu, timestamp, prio):
547		StateEvent.__init__(self, thread, cpu, timestamp)
548		self.prio = prio
549		self.textadd(("prio:", self.prio, 0))
550
551configtypes.append(Swapped)
552
553class Suspended(StateEvent):
554	name = "suspended"
555	color = "purple"
556	enabled = 1
557	def __init__(self, thread, cpu, timestamp, prio):
558		StateEvent.__init__(self, thread, cpu, timestamp)
559		self.prio = prio
560		self.textadd(("prio:", self.prio, 0))
561
562configtypes.append(Suspended)
563
564class Iwait(StateEvent):
565	name = "iwait"
566	color = "grey"
567	enabled = 0
568	def __init__(self, thread, cpu, timestamp, prio):
569		StateEvent.__init__(self, thread, cpu, timestamp)
570		self.prio = prio
571		self.textadd(("prio:", self.prio, 0))
572
573configtypes.append(Iwait)
574
575class Preempted(StateEvent):
576	name = "preempted"
577	color = "red"
578	enabled = 1
579	def __init__(self, thread, cpu, timestamp, prio, bythread):
580		StateEvent.__init__(self, thread, cpu, timestamp)
581		self.skipnext = 1
582		self.prio = prio
583		self.linked = bythread
584		self.textadd(("prio:", self.prio, 0))
585		self.textadd(("by thread:", self.linked.name, 1))
586
587configtypes.append(Preempted)
588
589class Sleep(StateEvent):
590	name = "sleep"
591	color = "blue"
592	enabled = 1
593	def __init__(self, thread, cpu, timestamp, prio, wmesg):
594		StateEvent.__init__(self, thread, cpu, timestamp)
595		self.prio = prio
596		self.wmesg = wmesg
597		self.textadd(("prio:", self.prio, 0))
598		self.textadd(("wmesg:", self.wmesg, 0))
599
600	def stattxt(self):
601		statstr = StateEvent.stattxt(self)
602		statstr += " sleeping on: " + self.wmesg
603		return (statstr)
604
605configtypes.append(Sleep)
606
607class Blocked(StateEvent):
608	name = "blocked"
609	color = "dark red"
610	enabled = 1
611	def __init__(self, thread, cpu, timestamp, prio, lock):
612		StateEvent.__init__(self, thread, cpu, timestamp)
613		self.prio = prio
614		self.lock = lock
615		self.textadd(("prio:", self.prio, 0))
616		self.textadd(("lock:", self.lock, 0))
617
618	def stattxt(self):
619		statstr = StateEvent.stattxt(self)
620		statstr += " blocked on: " + self.lock
621		return (statstr)
622
623configtypes.append(Blocked)
624
625class KsegrpRunq(StateEvent):
626	name = "KsegrpRunq"
627	color = "orange"
628	enabled = 1
629	def __init__(self, thread, cpu, timestamp, prio, bythread):
630		StateEvent.__init__(self, thread, cpu, timestamp)
631		self.prio = prio
632		self.linked = bythread
633		self.textadd(("prio:", self.prio, 0))
634		self.textadd(("by thread:", self.linked.name, 1))
635
636configtypes.append(KsegrpRunq)
637
638class Runq(StateEvent):
639	name = "Runq"
640	color = "yellow"
641	enabled = 1
642	def __init__(self, thread, cpu, timestamp, prio, bythread):
643		StateEvent.__init__(self, thread, cpu, timestamp)
644		self.prio = prio
645		self.linked = bythread
646		self.textadd(("prio:", self.prio, 0))
647		self.textadd(("by thread:", self.linked.name, 1))
648
649configtypes.append(Runq)
650
651class Sched_exit_thread(StateEvent):
652	name = "exit_thread"
653	color = "grey"
654	enabled = 0
655	def __init__(self, thread, cpu, timestamp, prio):
656		StateEvent.__init__(self, thread, cpu, timestamp)
657		self.name = "sched_exit_thread"
658		self.prio = prio
659		self.textadd(("prio:", self.prio, 0))
660
661configtypes.append(Sched_exit_thread)
662
663class Sched_exit(StateEvent):
664	name = "exit"
665	color = "grey"
666	enabled = 0
667	def __init__(self, thread, cpu, timestamp, prio):
668		StateEvent.__init__(self, thread, cpu, timestamp)
669		self.name = "sched_exit"
670		self.prio = prio
671		self.textadd(("prio:", self.prio, 0))
672
673configtypes.append(Sched_exit)
674
675class Padevent(StateEvent):
676	def __init__(self, thread, cpu, timestamp, last=0):
677		StateEvent.__init__(self, thread, cpu, timestamp, last)
678		self.name = "pad"
679		self.real = 0
680
681	def draw(self, canvas, xpos, ypos):
682		next = self.next()
683		if (next == None):
684			return (xpos)
685		self.duration = next.timestamp - self.timestamp
686		delta = self.duration / canvas.ratio
687		return (xpos + delta)
688
689class Tick(PointEvent):
690	name = "tick"
691	color = "black"
692	enabled = 0
693	def __init__(self, thread, cpu, timestamp, prio, stathz):
694		PointEvent.__init__(self, thread, cpu, timestamp)
695		self.prio = prio
696		self.textadd(("prio:", self.prio, 0))
697
698configtypes.append(Tick)
699
700class Prio(PointEvent):
701	name = "prio"
702	color = "black"
703	enabled = 0
704	def __init__(self, thread, cpu, timestamp, prio, newprio, bythread):
705		PointEvent.__init__(self, thread, cpu, timestamp)
706		self.prio = prio
707		self.newprio = newprio
708		self.linked = bythread
709		self.textadd(("new prio:", self.newprio, 0))
710		self.textadd(("prio:", self.prio, 0))
711		if (self.linked != self.source):
712			self.textadd(("by thread:", self.linked.name, 1))
713		else:
714			self.textadd(("by thread:", self.linked.name, 0))
715
716configtypes.append(Prio)
717
718class Lend(PointEvent):
719	name = "lend"
720	color = "black"
721	enabled = 0
722	def __init__(self, thread, cpu, timestamp, prio, tothread):
723		PointEvent.__init__(self, thread, cpu, timestamp)
724		self.prio = prio
725		self.linked = tothread
726		self.textadd(("prio:", self.prio, 0))
727		self.textadd(("to thread:", self.linked.name, 1))
728
729configtypes.append(Lend)
730
731class Wokeup(PointEvent):
732	name = "wokeup"
733	color = "black"
734	enabled = 0
735	def __init__(self, thread, cpu, timestamp, ranthread):
736		PointEvent.__init__(self, thread, cpu, timestamp)
737		self.linked = ranthread
738		self.textadd(("ran thread:", self.linked.name, 1))
739
740configtypes.append(Wokeup)
741
742class EventSource:
743	def __init__(self, name):
744		self.name = name
745		self.events = []
746		self.cpu = 0
747		self.cpux = 0
748
749	def fixup(self):
750		pass
751
752	def event(self, event):
753		self.events.insert(0, event)
754
755	def remove(self, event):
756		self.events.remove(event)
757
758	def lastevent(self, event):
759		self.events.append(event)
760
761	def draw(self, canvas, ypos):
762		xpos = 10
763		self.cpux = 10
764		self.cpu = self.events[1].cpu
765		for i in range(0, len(self.events)):
766			self.events[i].idx = i
767		for event in self.events:
768			if (event.cpu != self.cpu and event.cpu != -1):
769				self.drawcpu(canvas, xpos, ypos)
770				self.cpux = xpos
771				self.cpu = event.cpu
772			xpos = event.draw(canvas, xpos, ypos)
773		self.drawcpu(canvas, xpos, ypos)
774
775	def drawname(self, canvas, ypos):
776		ypos = ypos - (self.ysize() / 2)
777		canvas.create_text(10, ypos, anchor="w", text=self.name)
778
779	def drawcpu(self, canvas, xpos, ypos):
780		cpu = int(self.cpu)
781		if (cpu == 0):
782			color = 'light grey'
783		elif (cpu == 1):
784			color = 'dark grey'
785		elif (cpu == 2):
786			color = 'light blue'
787		elif (cpu == 3):
788			color = 'light green'
789		elif (cpu == 4):
790			color = 'blanched almond'
791		elif (cpu == 5):
792			color = 'slate grey'
793		elif (cpu == 6):
794			color = 'light slate blue'
795		elif (cpu == 7):
796			color = 'thistle'
797		else:
798			color = "white"
799		l = canvas.create_rectangle(self.cpux,
800		    ypos - self.ysize() - canvas.bdheight,
801		    xpos, ypos + canvas.bdheight, fill=color, width=0,
802		    tags=("all", "cpuinfo"))
803
804	def ysize(self):
805		return (None)
806
807	def eventat(self, i):
808		if (i >= len(self.events)):
809			return (None)
810		event = self.events[i]
811		return (event)
812
813	def findevent(self, timestamp):
814		for event in self.events:
815			if (event.timestamp >= timestamp and event.real):
816				return (event)
817		return (None)
818
819class Thread(EventSource):
820	names = {}
821	def __init__(self, td, pcomm):
822		EventSource.__init__(self, pcomm)
823		self.str = td
824		try:
825			cnt = Thread.names[pcomm]
826		except:
827			Thread.names[pcomm] = 0
828			return
829		Thread.names[pcomm] = cnt + 1
830
831	def fixup(self):
832		cnt = Thread.names[self.name]
833		if (cnt == 0):
834			return
835		cnt -= 1
836		Thread.names[self.name] = cnt
837		self.name += " td" + str(cnt)
838
839	def ysize(self):
840		return (10)
841
842class Counter(EventSource):
843	max = 0
844	def __init__(self, name):
845		EventSource.__init__(self, name)
846
847	def event(self, event):
848		EventSource.event(self, event)
849		try:
850			count = event.count
851		except:
852			return
853		count = int(count)
854		if (count > Counter.max):
855			Counter.max = count
856
857	def ymax(self):
858		return (Counter.max)
859
860	def ysize(self):
861		return (80)
862
863	def yscale(self):
864		return (self.ysize() / Counter.max)
865
866
867class KTRFile:
868	def __init__(self, file):
869		self.timestamp_f = None
870		self.timestamp_l = None
871		self.threads = []
872		self.sources = []
873		self.ticks = {}
874		self.load = {}
875		self.crit = {}
876		self.stathz = 0
877
878		self.parse(file)
879		self.fixup()
880		global ticksps
881		print "first", self.timestamp_f, "last", self.timestamp_l
882		print "time span", self.timespan()
883		print "stathz", self.stathz
884		ticksps = self.ticksps()
885		print "Ticks per second", ticksps
886
887	def parse(self, file):
888		try:
889			ifp = open(file)
890		except:
891			print "Can't open", file
892			sys.exit(1)
893
894		ktrhdr = "\s*\d+\s+(\d+)\s+(\d+)\s+"
895		tdname = "(\S+)\(([^)]*)\)"
896		crittdname = "(\S+)\s+\(\d+,\s+([^)]*)\)"
897
898# XXX doesn't handle:
899#   371   0      61628682318 mi_switch: 0xc075c070(swapper) prio 180 inhibit 2 wmesg ATA request done lock (null)
900		ktrstr = "mi_switch: " + tdname
901		ktrstr += " prio (\d+) inhibit (\d+) wmesg (\S+) lock (\S+)"
902		switchout_re = re.compile(ktrhdr + ktrstr)
903
904		ktrstr = "mi_switch: " + tdname + " prio (\d+) idle"
905		idled_re = re.compile(ktrhdr + ktrstr)
906
907		ktrstr = "mi_switch: " + tdname + " prio (\d+) preempted by "
908		ktrstr += tdname
909		preempted_re = re.compile(ktrhdr + ktrstr)
910
911		ktrstr = "mi_switch: running " + tdname + " prio (\d+)"
912		switchin_re = re.compile(ktrhdr + ktrstr)
913
914		ktrstr = "sched_add: " + tdname + " prio (\d+) by " + tdname
915		sched_add_re = re.compile(ktrhdr + ktrstr)
916
917		ktrstr = "setrunqueue: " + tdname + " prio (\d+) by " + tdname
918		setrunqueue_re = re.compile(ktrhdr + ktrstr)
919
920		ktrstr = "sched_rem: " + tdname + " prio (\d+) by " + tdname
921		sched_rem_re = re.compile(ktrhdr + ktrstr)
922
923		ktrstr = "sched_exit_thread: " + tdname + " prio (\d+)"
924		sched_exit_thread_re = re.compile(ktrhdr + ktrstr)
925
926		ktrstr = "sched_exit: " + tdname + " prio (\d+)"
927		sched_exit_re = re.compile(ktrhdr + ktrstr)
928
929		ktrstr = "statclock: " + tdname + " prio (\d+)"
930		ktrstr += " stathz (\d+)"
931		sched_clock_re = re.compile(ktrhdr + ktrstr)
932
933		ktrstr = "sched_prio: " + tdname + " prio (\d+)"
934		ktrstr += " newprio (\d+) by " + tdname
935		sched_prio_re = re.compile(ktrhdr + ktrstr)
936
937		cpuload_re = re.compile(ktrhdr + "load: (\d+)")
938		cpuload2_re = re.compile(ktrhdr + "cpu (\d+) load: (\d+)")
939		loadglobal_re = re.compile(ktrhdr + "global load: (\d+)")
940
941		ktrstr = "critical_\S+ by thread " + crittdname + " to (\d+)"
942		critsec_re = re.compile(ktrhdr + ktrstr)
943
944		parsers = [[cpuload_re, self.cpuload],
945			   [cpuload2_re, self.cpuload2],
946			   [loadglobal_re, self.loadglobal],
947			   [switchin_re, self.switchin],
948			   [switchout_re, self.switchout],
949			   [sched_add_re, self.sched_add],
950			   [setrunqueue_re, self.sched_rem],
951			   [sched_prio_re, self.sched_prio],
952			   [preempted_re, self.preempted],
953			   [sched_rem_re, self.sched_rem],
954			   [sched_exit_thread_re, self.sched_exit_thread],
955			   [sched_exit_re, self.sched_exit],
956			   [sched_clock_re, self.sched_clock],
957			   [critsec_re, self.critsec],
958			   [idled_re, self.idled]]
959
960		global lineno
961		lineno = 0
962		for line in ifp.readlines():
963			lineno += 1
964			if ((lineno % 1024) == 0):
965				status.startup("Parsing line " + str(lineno))
966			for p in parsers:
967				m = p[0].match(line)
968				if (m != None):
969					p[1](*m.groups())
970					break
971			if (m == None):
972				print line,
973
974	def checkstamp(self, cpu, timestamp):
975		timestamp = int(timestamp)
976		if (self.timestamp_f == None):
977			self.timestamp_f = timestamp;
978		if (self.timestamp_l != None and timestamp > self.timestamp_l):
979			return (0)
980		self.timestamp_l = timestamp;
981		return (timestamp)
982
983	def timespan(self):
984		return (self.timestamp_f - self.timestamp_l);
985
986	def ticksps(self):
987		return (self.timespan() / self.ticks[0]) * int(self.stathz)
988
989	def switchout(self, cpu, timestamp, td, pcomm, prio, inhibit, wmesg, lock):
990		TDI_SUSPENDED = 0x0001
991		TDI_SLEEPING = 0x0002
992		TDI_SWAPPED = 0x0004
993		TDI_LOCK = 0x0008
994		TDI_IWAIT = 0x0010
995
996		timestamp = self.checkstamp(cpu, timestamp)
997		if (timestamp == 0):
998			return
999		inhibit = int(inhibit)
1000		thread = self.findtd(td, pcomm)
1001		if (inhibit & TDI_SWAPPED):
1002			Swapped(thread, cpu, timestamp, prio)
1003		elif (inhibit & TDI_SLEEPING):
1004			Sleep(thread, cpu, timestamp, prio, wmesg)
1005		elif (inhibit & TDI_LOCK):
1006			Blocked(thread, cpu, timestamp, prio, lock)
1007		elif (inhibit & TDI_IWAIT):
1008			Iwait(thread, cpu, timestamp, prio)
1009		elif (inhibit & TDI_SUSPENDED):
1010			Suspended(thread, cpu, timestamp, prio)
1011		elif (inhibit == 0):
1012			Yielding(thread, cpu, timestamp, prio)
1013		else:
1014			print "Unknown event", inhibit
1015			sys.exit(1)
1016
1017	def idled(self, cpu, timestamp, td, pcomm, prio):
1018		timestamp = self.checkstamp(cpu, timestamp)
1019		if (timestamp == 0):
1020			return
1021		thread = self.findtd(td, pcomm)
1022		Idle(thread, cpu, timestamp, prio)
1023
1024	def preempted(self, cpu, timestamp, td, pcomm, prio, bytd, bypcomm):
1025		timestamp = self.checkstamp(cpu, timestamp)
1026		if (timestamp == 0):
1027			return
1028		thread = self.findtd(td, pcomm)
1029		Preempted(thread, cpu, timestamp, prio,
1030		    self.findtd(bytd, bypcomm))
1031
1032	def switchin(self, cpu, timestamp, td, pcomm, prio):
1033		timestamp = self.checkstamp(cpu, timestamp)
1034		if (timestamp == 0):
1035			return
1036		thread = self.findtd(td, pcomm)
1037		Running(thread, cpu, timestamp, prio)
1038
1039	def sched_add(self, cpu, timestamp, td, pcomm, prio, bytd, bypcomm):
1040		timestamp = self.checkstamp(cpu, timestamp)
1041		if (timestamp == 0):
1042			return
1043		thread = self.findtd(td, pcomm)
1044		bythread = self.findtd(bytd, bypcomm)
1045		Runq(thread, cpu, timestamp, prio, bythread)
1046		Wokeup(bythread, cpu, timestamp, thread)
1047
1048	def sched_rem(self, cpu, timestamp, td, pcomm, prio, bytd, bypcomm):
1049		timestamp = self.checkstamp(cpu, timestamp)
1050		if (timestamp == 0):
1051			return
1052		thread = self.findtd(td, pcomm)
1053		KsegrpRunq(thread, cpu, timestamp, prio,
1054		    self.findtd(bytd, bypcomm))
1055
1056	def sched_exit_thread(self, cpu, timestamp, td, pcomm, prio):
1057		timestamp = self.checkstamp(cpu, timestamp)
1058		if (timestamp == 0):
1059			return
1060		thread = self.findtd(td, pcomm)
1061		Sched_exit_thread(thread, cpu, timestamp, prio)
1062
1063	def sched_exit(self, cpu, timestamp, td, pcomm, prio):
1064		timestamp = self.checkstamp(cpu, timestamp)
1065		if (timestamp == 0):
1066			return
1067		thread = self.findtd(td, pcomm)
1068		Sched_exit(thread, cpu, timestamp, prio)
1069
1070	def sched_clock(self, cpu, timestamp, td, pcomm, prio, stathz):
1071		timestamp = self.checkstamp(cpu, timestamp)
1072		if (timestamp == 0):
1073			return
1074		self.stathz = stathz
1075		cpu = int(cpu)
1076		try:
1077			ticks = self.ticks[cpu]
1078		except:
1079			self.ticks[cpu] = 0
1080		self.ticks[cpu] += 1
1081		thread = self.findtd(td, pcomm)
1082		Tick(thread, cpu, timestamp, prio, stathz)
1083
1084	def sched_prio(self, cpu, timestamp, td, pcomm, prio, newprio, bytd, bypcomm):
1085		if (prio == newprio):
1086			return
1087		timestamp = self.checkstamp(cpu, timestamp)
1088		if (timestamp == 0):
1089			return
1090		thread = self.findtd(td, pcomm)
1091		bythread = self.findtd(bytd, bypcomm)
1092		Prio(thread, cpu, timestamp, prio, newprio, bythread)
1093		Lend(bythread, cpu, timestamp, newprio, thread)
1094
1095	def cpuload(self, cpu, timestamp, count):
1096		timestamp = self.checkstamp(cpu, timestamp)
1097		if (timestamp == 0):
1098			return
1099		cpu = int(cpu)
1100		try:
1101			load = self.load[cpu]
1102		except:
1103			load = Counter("cpu" + str(cpu) + " load")
1104			self.load[cpu] = load
1105			self.sources.insert(0, load)
1106		Count(load, cpu, timestamp, count)
1107
1108	def cpuload2(self, cpu, timestamp, ncpu, count):
1109		timestamp = self.checkstamp(cpu, timestamp)
1110		if (timestamp == 0):
1111			return
1112		cpu = int(ncpu)
1113		try:
1114			load = self.load[cpu]
1115		except:
1116			load = Counter("cpu" + str(cpu) + " load")
1117			self.load[cpu] = load
1118			self.sources.insert(0, load)
1119		Count(load, cpu, timestamp, count)
1120
1121	def loadglobal(self, cpu, timestamp, count):
1122		timestamp = self.checkstamp(cpu, timestamp)
1123		if (timestamp == 0):
1124			return
1125		cpu = 0
1126		try:
1127			load = self.load[cpu]
1128		except:
1129			load = Counter("CPU load")
1130			self.load[cpu] = load
1131			self.sources.insert(0, load)
1132		Count(load, cpu, timestamp, count)
1133
1134	def critsec(self, cpu, timestamp, td, pcomm, to):
1135		timestamp = self.checkstamp(cpu, timestamp)
1136		if (timestamp == 0):
1137			return
1138		cpu = int(cpu)
1139		try:
1140			crit = self.crit[cpu]
1141		except:
1142			crit = Counter("Critical Section")
1143			self.crit[cpu] = crit
1144			self.sources.insert(0, crit)
1145		Count(crit, cpu, timestamp, to)
1146
1147	def findtd(self, td, pcomm):
1148		for thread in self.threads:
1149			if (thread.str == td and thread.name == pcomm):
1150				return thread
1151		thread = Thread(td, pcomm)
1152		self.threads.append(thread)
1153		self.sources.append(thread)
1154		return (thread)
1155
1156	def fixup(self):
1157		for source in self.sources:
1158			Padevent(source, -1, self.timestamp_l)
1159			Padevent(source, -1, self.timestamp_f, last=1)
1160			source.fixup()
1161
1162class SchedDisplay(Canvas):
1163	def __init__(self, master):
1164		self.ratio = 10
1165		self.ktrfile = None
1166		self.sources = None
1167		self.bdheight = 10
1168		self.events = {}
1169
1170		Canvas.__init__(self, master, width=800, height=500, bg='grey',
1171		     scrollregion=(0, 0, 800, 500))
1172
1173	def setfile(self, ktrfile):
1174		self.ktrfile = ktrfile
1175		self.sources = ktrfile.sources
1176
1177	def draw(self):
1178		ypos = 0
1179		xsize = self.xsize()
1180		for source in self.sources:
1181			status.startup("Drawing " + source.name)
1182			self.create_line(0, ypos, xsize, ypos,
1183			    width=1, fill="black", tags=("all",))
1184			ypos += self.bdheight
1185			ypos += source.ysize()
1186			source.draw(self, ypos)
1187			ypos += self.bdheight
1188			try:
1189				self.tag_raise("point", "state")
1190				self.tag_lower("cpuinfo", "all")
1191			except:
1192				pass
1193		self.create_line(0, ypos, xsize, ypos,
1194		    width=1, fill="black", tags=("all",))
1195		self.tag_bind("event", "<Enter>", self.mouseenter)
1196		self.tag_bind("event", "<Leave>", self.mouseexit)
1197		self.tag_bind("event", "<Button-1>", self.mousepress)
1198
1199	def mouseenter(self, event):
1200		item, = self.find_withtag(CURRENT)
1201		event = self.events[item]
1202		event.mouseenter(self, item)
1203
1204	def mouseexit(self, event):
1205		item, = self.find_withtag(CURRENT)
1206		event = self.events[item]
1207		event.mouseexit(self, item)
1208
1209	def mousepress(self, event):
1210		item, = self.find_withtag(CURRENT)
1211		event = self.events[item]
1212		event.mousepress(self, item)
1213
1214	def drawnames(self, canvas):
1215		status.startup("Drawing names")
1216		ypos = 0
1217		canvas.configure(scrollregion=(0, 0,
1218		    canvas["width"], self.ysize()))
1219		for source in self.sources:
1220			canvas.create_line(0, ypos, canvas["width"], ypos,
1221			    width=1, fill="black", tags=("all",))
1222			ypos += self.bdheight
1223			ypos += source.ysize()
1224			source.drawname(canvas, ypos)
1225			ypos += self.bdheight
1226		canvas.create_line(0, ypos, canvas["width"], ypos,
1227		    width=1, fill="black", tags=("all",))
1228
1229	def xsize(self):
1230		return ((self.ktrfile.timespan() / self.ratio) + 20)
1231
1232	def ysize(self):
1233		ysize = 0
1234		for source in self.sources:
1235			ysize += source.ysize() + (self.bdheight * 2)
1236		return (ysize)
1237
1238	def scaleset(self, ratio):
1239		if (self.ktrfile == None):
1240			return
1241		oldratio = self.ratio
1242		xstart, ystart = self.xview()
1243		length = (float(self["width"]) / self.xsize())
1244		middle = xstart + (length / 2)
1245
1246		self.ratio = ratio
1247		self.configure(scrollregion=(0, 0, self.xsize(), self.ysize()))
1248		self.scale("all", 0, 0, float(oldratio) / ratio, 1)
1249
1250		length = (float(self["width"]) / self.xsize())
1251		xstart = middle - (length / 2)
1252		self.xview_moveto(xstart)
1253
1254	def scaleget(self):
1255		return self.ratio
1256
1257	def setcolor(self, tag, color):
1258		self.itemconfigure(tag, state="normal", fill=color)
1259
1260	def hide(self, tag):
1261		self.itemconfigure(tag, state="hidden")
1262
1263class GraphMenu(Frame):
1264	def __init__(self, master):
1265		Frame.__init__(self, master, bd=2, relief=RAISED)
1266		self.view = Menubutton(self, text="Configure")
1267		self.viewmenu = Menu(self.view, tearoff=0)
1268		self.viewmenu.add_command(label="Events",
1269		    command=self.econf)
1270		self.view["menu"] = self.viewmenu
1271		self.view.pack(side=LEFT)
1272
1273	def econf(self):
1274		EventConfigure()
1275
1276
1277class SchedGraph(Frame):
1278	def __init__(self, master):
1279		Frame.__init__(self, master)
1280		self.menu = None
1281		self.names = None
1282		self.display = None
1283		self.scale = None
1284		self.status = None
1285		self.pack(expand=1, fill="both")
1286		self.buildwidgets()
1287		self.layout()
1288		self.draw(sys.argv[1])
1289
1290	def buildwidgets(self):
1291		global status
1292		self.menu = GraphMenu(self)
1293		self.display = SchedDisplay(self)
1294		self.names = Canvas(self,
1295		    width=100, height=self.display["height"],
1296		    bg='grey', scrollregion=(0, 0, 50, 100))
1297		self.scale = Scaler(self, self.display)
1298		status = self.status = Status(self)
1299		self.scrollY = Scrollbar(self, orient="vertical",
1300		    command=self.display_yview)
1301		self.display.scrollX = Scrollbar(self, orient="horizontal",
1302		    command=self.display.xview)
1303		self.display["xscrollcommand"] = self.display.scrollX.set
1304		self.display["yscrollcommand"] = self.scrollY.set
1305		self.names["yscrollcommand"] = self.scrollY.set
1306
1307	def layout(self):
1308		self.columnconfigure(1, weight=1)
1309		self.rowconfigure(1, weight=1)
1310		self.menu.grid(row=0, column=0, columnspan=3, sticky=E+W)
1311		self.names.grid(row=1, column=0, sticky=N+S)
1312		self.display.grid(row=1, column=1, sticky=W+E+N+S)
1313		self.scrollY.grid(row=1, column=2, sticky=N+S)
1314		self.display.scrollX.grid(row=2, column=0, columnspan=2,
1315		    sticky=E+W)
1316		self.scale.grid(row=3, column=0, columnspan=3, sticky=E+W)
1317		self.status.grid(row=4, column=0, columnspan=3, sticky=E+W)
1318
1319	def draw(self, file):
1320		self.master.update()
1321		ktrfile = KTRFile(file)
1322		self.display.setfile(ktrfile)
1323		self.display.drawnames(self.names)
1324		self.display.draw()
1325		self.scale.set(250000)
1326		self.display.xview_moveto(0)
1327
1328	def display_yview(self, *args):
1329		self.names.yview(*args)
1330		self.display.yview(*args)
1331
1332	def setcolor(self, tag, color):
1333		self.display.setcolor(tag, color)
1334
1335	def hide(self, tag):
1336		self.display.hide(tag)
1337
1338if (len(sys.argv) != 2):
1339	print "usage:", sys.argv[0], "<ktr file>"
1340	sys.exit(1)
1341
1342root = Tk()
1343root.title("Scheduler Graph")
1344graph = SchedGraph(root)
1345root.mainloop()
1346