xref: /freebsd/tools/sched/schedgraph.py (revision 698e6141)
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.
35# - Add KTR_SCHED to KTR_COMPILE and KTR_MASK in your KERNCONF
36# - It is encouraged to increase KTR_ENTRIES size to 32768 to gather
37#    enough information for analysis.
38# - Rebuild kernel with proper changes to KERNCONF.
39# - Run ktrace(1) with whatever workload you wish to use.
40# - Dump the trace to a file: 'ktrdump -ct > ktr.out'
41# - Run the python script: 'python schedgraph.py ktr.out'
42#
43# To do:
44# 1)  Add a per-thread summary display
45# 2)  Add bounding box style zoom.
46# 3)  Click to center.
47# 4)  Implement some sorting mechanism.
48
49ticksps = None
50status = None
51configtypes = []
52
53def ticks2sec(ticks):
54	ns = ticksps / 1000000000
55	ticks /= ns
56	if (ticks < 1000):
57		return (str(ticks) + "ns")
58	ticks /= 1000
59	if (ticks < 1000):
60		return (str(ticks) + "us")
61	ticks /= 1000
62	if (ticks < 1000):
63		return (str(ticks) + "ms")
64	ticks /= 1000
65	return (str(ticks) + "s")
66
67class Scaler(Frame):
68	def __init__(self, master, target):
69		Frame.__init__(self, master)
70		self.scale = Scale(self, command=self.scaleset,
71		    from_=1000, to_=1000000, orient=HORIZONTAL, resolution=1000)
72		self.label = Label(self, text="Ticks per pixel")
73		self.label.pack(side=LEFT)
74		self.scale.pack(fill="both", expand=1)
75		self.target = target
76		self.scale.set(target.scaleget())
77		self.initialized = 1
78
79	def scaleset(self, value):
80		self.target.scaleset(int(value))
81
82	def set(self, value):
83		self.scale.set(value)
84
85class Status(Frame):
86	def __init__(self, master):
87		Frame.__init__(self, master)
88		self.label = Label(self, bd=1, relief=SUNKEN, anchor=W)
89		self.label.pack(fill="both", expand=1)
90		self.clear()
91
92	def set(self, str):
93		self.label.config(text=str)
94
95	def clear(self):
96		self.label.config(text="")
97
98	def startup(self, str):
99		self.set(str)
100		root.update()
101
102class EventConf(Frame):
103	def __init__(self, master, name, color, enabled):
104		Frame.__init__(self, master)
105		self.name = name
106		self.color = StringVar()
107		self.color_default = color
108		self.color_current = color
109		self.color.set(color)
110		self.enabled = IntVar()
111		self.enabled_default = enabled
112		self.enabled_current = enabled
113		self.enabled.set(enabled)
114		self.draw()
115
116	def draw(self):
117		self.label = Label(self, text=self.name, anchor=W)
118		self.sample = Canvas(self, width=24, height=24,
119		    bg='grey')
120		self.rect = self.sample.create_rectangle(0, 0, 24, 24,
121		    fill=self.color.get())
122		self.list = OptionMenu(self, self.color,
123		    "dark red", "red", "pink",
124		    "dark orange", "orange",
125		    "yellow", "light yellow",
126		    "dark green", "green", "light green",
127		    "dark blue", "blue", "light blue",
128		    "dark violet", "violet", "purple",
129		    "dark grey", "light grey",
130		    "white", "black",
131		    command=self.setcolor)
132		self.checkbox = Checkbutton(self, text="enabled",
133		    variable=self.enabled)
134		self.label.grid(row=0, column=0, sticky=E+W)
135		self.sample.grid(row=0, column=1)
136		self.list.grid(row=0, column=2, sticky=E+W)
137		self.checkbox.grid(row=0, column=3)
138		self.columnconfigure(0, weight=1)
139		self.columnconfigure(2, minsize=110)
140
141	def setcolor(self, color):
142		self.color.set(color)
143		self.sample.itemconfigure(self.rect, fill=color)
144
145	def apply(self):
146		cchange = 0
147		echange = 0
148		if (self.color_current != self.color.get()):
149			cchange = 1
150		if (self.enabled_current != self.enabled.get()):
151			echange = 1
152		self.color_current = self.color.get()
153		self.enabled_current = self.enabled.get()
154		if (echange != 0):
155			if (self.enabled_current):
156				graph.setcolor(self.name, self.color_current)
157			else:
158				graph.hide(self.name)
159			return
160		if (cchange != 0):
161			graph.setcolor(self.name, self.color_current)
162
163	def revert(self):
164		self.setcolor(self.color_current)
165		self.enabled.set(self.enabled_current)
166
167	def default(self):
168		self.setcolor(self.color_default)
169		self.enabled.set(self.enabled_default)
170
171class EventConfigure(Toplevel):
172	def __init__(self):
173		Toplevel.__init__(self)
174		self.resizable(0, 0)
175		self.title("Event Configuration")
176		self.items = LabelFrame(self, text="Event Type")
177		self.buttons = Frame(self)
178		self.drawbuttons()
179		self.items.grid(row=0, column=0, sticky=E+W)
180		self.columnconfigure(0, weight=1)
181		self.buttons.grid(row=1, column=0, sticky=E+W)
182		self.types = []
183		self.irow = 0
184		for type in configtypes:
185			self.additem(type.name, type.color, type.enabled)
186
187	def additem(self, name, color, enabled=1):
188		item = EventConf(self.items, name, color, enabled)
189		self.types.append(item)
190		item.grid(row=self.irow, column=0, sticky=E+W)
191		self.irow += 1
192
193	def drawbuttons(self):
194		self.apply = Button(self.buttons, text="Apply",
195		    command=self.apress)
196		self.revert = Button(self.buttons, text="Revert",
197		    command=self.rpress)
198		self.default = Button(self.buttons, text="Default",
199		    command=self.dpress)
200		self.apply.grid(row=0, column=0, sticky=E+W)
201		self.revert.grid(row=0, column=1, sticky=E+W)
202		self.default.grid(row=0, column=2, sticky=E+W)
203		self.buttons.columnconfigure(0, weight=1)
204		self.buttons.columnconfigure(1, weight=1)
205		self.buttons.columnconfigure(2, weight=1)
206
207	def apress(self):
208		for item in self.types:
209			item.apply()
210
211	def rpress(self):
212		for item in self.types:
213			item.revert()
214
215	def dpress(self):
216		for item in self.types:
217			item.default()
218
219class EventView(Toplevel):
220	def __init__(self, event, canvas):
221		Toplevel.__init__(self)
222		self.resizable(0, 0)
223		self.title("Event")
224		self.event = event
225		self.frame = Frame(self)
226		self.frame.grid(row=0, column=0, sticky=N+S+E+W)
227		self.buttons = Frame(self)
228		self.buttons.grid(row=1, column=0, sticky=E+W)
229		self.canvas = canvas
230		self.drawlabels()
231		self.drawbuttons()
232		event.displayref(canvas)
233		self.bind("<Destroy>", self.destroycb)
234
235	def destroycb(self, event):
236		self.unbind("<Destroy>")
237		if (self.event != None):
238			self.event.displayunref(self.canvas)
239			self.event = None
240		self.destroy()
241
242	def clearlabels(self):
243		for label in self.frame.grid_slaves():
244			label.grid_remove()
245
246	def drawlabels(self):
247		ypos = 0
248		labels = self.event.labels()
249		while (len(labels) < 7):
250			labels.append(("", "", 0))
251		for label in labels:
252			name, value, linked = label
253			l = Label(self.frame, text=name, bd=1, width=15,
254			    relief=SUNKEN, anchor=W)
255			if (linked):
256				fgcolor = "blue"
257			else:
258				fgcolor = "black"
259			r = Label(self.frame, text=value, bd=1,
260			    relief=SUNKEN, anchor=W, fg=fgcolor)
261			l.grid(row=ypos, column=0, sticky=E+W)
262			r.grid(row=ypos, column=1, sticky=E+W)
263			if (linked):
264				r.bind("<Button-1>", self.linkpress)
265			ypos += 1
266		self.frame.columnconfigure(1, minsize=80)
267
268	def drawbuttons(self):
269		self.back = Button(self.buttons, text="<", command=self.bpress)
270		self.forw = Button(self.buttons, text=">", command=self.fpress)
271		self.new = Button(self.buttons, text="new", command=self.npress)
272		self.back.grid(row=0, column=0, sticky=E+W)
273		self.forw.grid(row=0, column=1, sticky=E+W)
274		self.new.grid(row=0, column=2, sticky=E+W)
275		self.buttons.columnconfigure(2, weight=1)
276
277	def newevent(self, event):
278		self.event.displayunref(self.canvas)
279		self.clearlabels()
280		self.event = event
281		self.event.displayref(self.canvas)
282		self.drawlabels()
283
284	def npress(self):
285		EventView(self.event, self.canvas)
286
287	def bpress(self):
288		prev = self.event.prev()
289		if (prev == None):
290			return
291		while (prev.real == 0):
292			prev = prev.prev()
293			if (prev == None):
294				return
295		self.newevent(prev)
296
297	def fpress(self):
298		next = self.event.next()
299		if (next == None):
300			return
301		while (next.real == 0):
302			next = next.next()
303			if (next == None):
304				return
305		self.newevent(next)
306
307	def linkpress(self, wevent):
308		event = self.event.getlinked()
309		if (event != None):
310			self.newevent(event)
311
312class Event:
313	name = "none"
314	color = "grey"
315	def __init__(self, source, cpu, timestamp, last=0):
316		self.source = source
317		self.cpu = cpu
318		self.timestamp = int(timestamp)
319		self.entries = []
320		self.real = 1
321		self.idx = None
322		self.state = 0
323		self.item = None
324		self.dispcnt = 0
325		self.linked = None
326		if (last):
327			source.lastevent(self)
328		else:
329			source.event(self)
330
331	def status(self):
332		statstr = self.name + " " + self.source.name
333		statstr += " on: cpu" + str(self.cpu)
334		statstr += " at: " + str(self.timestamp)
335		statstr += self.stattxt()
336		status.set(statstr)
337
338	def stattxt(self):
339		return ""
340
341	def textadd(self, tuple):
342		pass
343		self.entries.append(tuple)
344
345	def labels(self):
346		return [("Source:", self.source.name, 0),
347				("Event:", self.name, 0),
348				("CPU:", self.cpu, 0),
349				("Timestamp:", self.timestamp, 0)] + self.entries
350	def mouseenter(self, canvas, item):
351		self.displayref(canvas)
352		self.status()
353
354	def mouseexit(self, canvas, item):
355		self.displayunref(canvas)
356		status.clear()
357
358	def mousepress(self, canvas, item):
359		EventView(self, canvas)
360
361	def next(self):
362		return self.source.eventat(self.idx + 1)
363
364	def prev(self):
365		return self.source.eventat(self.idx - 1)
366
367	def displayref(self, canvas):
368		if (self.dispcnt == 0):
369			canvas.itemconfigure(self.item, width=2)
370		self.dispcnt += 1
371
372	def displayunref(self, canvas):
373		self.dispcnt -= 1
374		if (self.dispcnt == 0):
375			canvas.itemconfigure(self.item, width=0)
376			canvas.tag_raise("point", "state")
377
378	def getlinked(self):
379		return self.linked.findevent(self.timestamp)
380
381class PointEvent(Event):
382	def __init__(self, thread, cpu, timestamp, last=0):
383		Event.__init__(self, thread, cpu, timestamp, last)
384
385	def draw(self, canvas, xpos, ypos):
386		l = canvas.create_oval(xpos - 6, ypos + 1, xpos + 6, ypos - 11,
387		    fill=self.color, tags=("all", "point", "event")
388		    + (self.name,), width=0)
389		canvas.events[l] = self
390		self.item = l
391		if (self.enabled == 0):
392			canvas.itemconfigure(l, state="hidden")
393
394		return (xpos)
395
396class StateEvent(Event):
397	def __init__(self, thread, cpu, timestamp, last=0):
398		Event.__init__(self, thread, cpu, timestamp, last)
399		self.duration = 0
400		self.skipnext = 0
401		self.skipself = 0
402		self.state = 1
403
404	def draw(self, canvas, xpos, ypos):
405		next = self.nextstate()
406		if (self.skipself == 1 or next == None):
407			return (xpos)
408		while (self.skipnext):
409			skipped = next
410			next.skipself = 1
411			next.real = 0
412			next = next.nextstate()
413			if (next == None):
414				next = skipped
415			self.skipnext -= 1
416		self.duration = next.timestamp - self.timestamp
417		delta = self.duration / canvas.ratio
418		l = canvas.create_rectangle(xpos, ypos,
419		    xpos + delta, ypos - 10, fill=self.color, width=0,
420		    tags=("all", "state", "event") + (self.name,))
421		canvas.events[l] = self
422		self.item = l
423		if (self.enabled == 0):
424			canvas.itemconfigure(l, state="hidden")
425
426		return (xpos + delta)
427
428	def stattxt(self):
429		return " duration: " + ticks2sec(self.duration)
430
431	def nextstate(self):
432		next = self.next()
433		while (next != None and next.state == 0):
434			next = next.next()
435		return (next)
436
437	def labels(self):
438		return [("Source:", self.source.name, 0),
439				("Event:", self.name, 0),
440				("Timestamp:", self.timestamp, 0),
441				("CPU:", self.cpu, 0),
442				("Duration:", ticks2sec(self.duration), 0)] \
443				 + self.entries
444
445class Count(Event):
446	name = "Count"
447	color = "red"
448	enabled = 1
449	def __init__(self, source, cpu, timestamp, count):
450		self.count = int(count)
451		Event.__init__(self, source, cpu, timestamp)
452		self.duration = 0
453		self.textadd(("count:", self.count, 0))
454
455	def draw(self, canvas, xpos, ypos):
456		next = self.next()
457		self.duration = next.timestamp - self.timestamp
458		delta = self.duration / canvas.ratio
459		yhight = self.source.yscale() * self.count
460		l = canvas.create_rectangle(xpos, ypos - yhight,
461		    xpos + delta, ypos, fill=self.color, width=0,
462		    tags=("all", "count", "event") + (self.name,))
463		canvas.events[l] = self
464		self.item = l
465		if (self.enabled == 0):
466			canvas.itemconfigure(l, state="hidden")
467		return (xpos + delta)
468
469	def stattxt(self):
470		return " count: " + str(self.count)
471
472configtypes.append(Count)
473
474class Running(StateEvent):
475	name = "running"
476	color = "green"
477	enabled = 1
478	def __init__(self, thread, cpu, timestamp, prio):
479		StateEvent.__init__(self, thread, cpu, timestamp)
480		self.prio = prio
481		self.textadd(("prio:", self.prio, 0))
482
483configtypes.append(Running)
484
485class Idle(StateEvent):
486	name = "idle"
487	color = "grey"
488	enabled = 0
489	def __init__(self, thread, cpu, timestamp, prio):
490		StateEvent.__init__(self, thread, cpu, timestamp)
491		self.prio = prio
492		self.textadd(("prio:", self.prio, 0))
493
494configtypes.append(Idle)
495
496class Yielding(StateEvent):
497	name = "yielding"
498	color = "yellow"
499	enabled = 1
500	def __init__(self, thread, cpu, timestamp, prio):
501		StateEvent.__init__(self, thread, cpu, timestamp)
502		self.skipnext = 2
503		self.prio = prio
504		self.textadd(("prio:", self.prio, 0))
505
506configtypes.append(Yielding)
507
508class Swapped(StateEvent):
509	name = "swapped"
510	color = "violet"
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(Swapped)
518
519class Suspended(StateEvent):
520	name = "suspended"
521	color = "purple"
522	enabled = 1
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(Suspended)
529
530class Iwait(StateEvent):
531	name = "iwait"
532	color = "grey"
533	enabled = 0
534	def __init__(self, thread, cpu, timestamp, prio):
535		StateEvent.__init__(self, thread, cpu, timestamp)
536		self.prio = prio
537		self.textadd(("prio:", self.prio, 0))
538
539configtypes.append(Iwait)
540
541class Preempted(StateEvent):
542	name = "preempted"
543	color = "red"
544	enabled = 1
545	def __init__(self, thread, cpu, timestamp, prio, bythread):
546		StateEvent.__init__(self, thread, cpu, timestamp)
547		self.skipnext = 2
548		self.prio = prio
549		self.linked = bythread
550		self.textadd(("prio:", self.prio, 0))
551		self.textadd(("by thread:", self.linked.name, 1))
552
553configtypes.append(Preempted)
554
555class Sleep(StateEvent):
556	name = "sleep"
557	color = "blue"
558	enabled = 1
559	def __init__(self, thread, cpu, timestamp, prio, wmesg):
560		StateEvent.__init__(self, thread, cpu, timestamp)
561		self.prio = prio
562		self.wmesg = wmesg
563		self.textadd(("prio:", self.prio, 0))
564		self.textadd(("wmesg:", self.wmesg, 0))
565
566	def stattxt(self):
567		statstr = StateEvent.stattxt(self)
568		statstr += " sleeping on: " + self.wmesg
569		return (statstr)
570
571configtypes.append(Sleep)
572
573class Blocked(StateEvent):
574	name = "blocked"
575	color = "dark red"
576	enabled = 1
577	def __init__(self, thread, cpu, timestamp, prio, lock):
578		StateEvent.__init__(self, thread, cpu, timestamp)
579		self.prio = prio
580		self.lock = lock
581		self.textadd(("prio:", self.prio, 0))
582		self.textadd(("lock:", self.lock, 0))
583
584	def stattxt(self):
585		statstr = StateEvent.stattxt(self)
586		statstr += " blocked on: " + self.lock
587		return (statstr)
588
589configtypes.append(Blocked)
590
591class KsegrpRunq(StateEvent):
592	name = "KsegrpRunq"
593	color = "orange"
594	enabled = 1
595	def __init__(self, thread, cpu, timestamp, prio, bythread):
596		StateEvent.__init__(self, thread, cpu, timestamp)
597		self.prio = prio
598		self.linked = bythread
599		self.textadd(("prio:", self.prio, 0))
600		self.textadd(("by thread:", self.linked.name, 1))
601
602configtypes.append(KsegrpRunq)
603
604class Runq(StateEvent):
605	name = "Runq"
606	color = "yellow"
607	enabled = 1
608	def __init__(self, thread, cpu, timestamp, prio, bythread):
609		StateEvent.__init__(self, thread, cpu, timestamp)
610		self.prio = prio
611		self.linked = bythread
612		self.textadd(("prio:", self.prio, 0))
613		self.textadd(("by thread:", self.linked.name, 1))
614
615configtypes.append(Runq)
616
617class Sched_exit(StateEvent):
618	name = "exit"
619	color = "grey"
620	enabled = 0
621	def __init__(self, thread, cpu, timestamp, prio):
622		StateEvent.__init__(self, thread, cpu, timestamp)
623		self.name = "sched_exit"
624		self.prio = prio
625		self.textadd(("prio:", self.prio, 0))
626
627configtypes.append(Sched_exit)
628
629class Padevent(StateEvent):
630	def __init__(self, thread, cpu, timestamp, last=0):
631		StateEvent.__init__(self, thread, cpu, timestamp, last)
632		self.name = "pad"
633		self.real = 0
634
635	def draw(self, canvas, xpos, ypos):
636		next = self.next()
637		if (next == None):
638			return (xpos)
639		self.duration = next.timestamp - self.timestamp
640		delta = self.duration / canvas.ratio
641		return (xpos + delta)
642
643class Tick(PointEvent):
644	name = "tick"
645	color = "black"
646	enabled = 0
647	def __init__(self, thread, cpu, timestamp, prio, stathz):
648		PointEvent.__init__(self, thread, cpu, timestamp)
649		self.prio = prio
650		self.textadd(("prio:", self.prio, 0))
651
652configtypes.append(Tick)
653
654class Prio(PointEvent):
655	name = "prio"
656	color = "black"
657	enabled = 0
658	def __init__(self, thread, cpu, timestamp, prio, newprio, bythread):
659		PointEvent.__init__(self, thread, cpu, timestamp)
660		self.prio = prio
661		self.newprio = newprio
662		self.linked = bythread
663		self.textadd(("new prio:", self.newprio, 0))
664		self.textadd(("prio:", self.prio, 0))
665		if (self.linked != self.source):
666			self.textadd(("by thread:", self.linked.name, 1))
667		else:
668			self.textadd(("by thread:", self.linked.name, 0))
669
670configtypes.append(Prio)
671
672class Lend(PointEvent):
673	name = "lend"
674	color = "black"
675	enabled = 0
676	def __init__(self, thread, cpu, timestamp, prio, tothread):
677		PointEvent.__init__(self, thread, cpu, timestamp)
678		self.prio = prio
679		self.linked = tothread
680		self.textadd(("prio:", self.prio, 0))
681		self.textadd(("to thread:", self.linked.name, 1))
682
683configtypes.append(Lend)
684
685class Wokeup(PointEvent):
686	name = "wokeup"
687	color = "black"
688	enabled = 0
689	def __init__(self, thread, cpu, timestamp, ranthread):
690		PointEvent.__init__(self, thread, cpu, timestamp)
691		self.linked = ranthread
692		self.textadd(("ran thread:", self.linked.name, 1))
693
694configtypes.append(Wokeup)
695
696class EventSource:
697	def __init__(self, name):
698		self.name = name
699		self.events = []
700		self.cpu = 0
701		self.cpux = 0
702
703	def fixup(self):
704		pass
705
706	def event(self, event):
707		self.events.insert(0, event)
708
709	def remove(self, event):
710		self.events.remove(event)
711
712	def lastevent(self, event):
713		self.events.append(event)
714
715	def draw(self, canvas, ypos):
716		xpos = 10
717		self.cpux = 10
718		self.cpu = self.events[1].cpu
719		for i in range(0, len(self.events)):
720			self.events[i].idx = i
721		for event in self.events:
722			if (event.cpu != self.cpu and event.cpu != -1):
723				self.drawcpu(canvas, xpos, ypos)
724				self.cpux = xpos
725				self.cpu = event.cpu
726			xpos = event.draw(canvas, xpos, ypos)
727		self.drawcpu(canvas, xpos, ypos)
728
729	def drawname(self, canvas, ypos):
730		ypos = ypos - (self.ysize() / 2)
731		canvas.create_text(10, ypos, anchor="w", text=self.name)
732
733	def drawcpu(self, canvas, xpos, ypos):
734		cpu = int(self.cpu)
735		if (cpu == 0):
736			color = 'light grey'
737		elif (cpu == 1):
738			color = 'dark grey'
739		elif (cpu == 2):
740			color = 'light blue'
741		elif (cpu == 3):
742			color == 'light green'
743		else:
744			color == "white"
745		l = canvas.create_rectangle(self.cpux,
746		    ypos - self.ysize() - canvas.bdheight,
747		    xpos, ypos + canvas.bdheight, fill=color, width=0,
748		    tags=("all", "cpuinfo"))
749
750	def ysize(self):
751		return (None)
752
753	def eventat(self, i):
754		if (i >= len(self.events)):
755			return (None)
756		event = self.events[i]
757		return (event)
758
759	def findevent(self, timestamp):
760		for event in self.events:
761			if (event.timestamp >= timestamp and event.real):
762				return (event)
763		return (None)
764
765class Thread(EventSource):
766	names = {}
767	def __init__(self, td, pcomm):
768		EventSource.__init__(self, pcomm)
769		self.str = td
770		try:
771			cnt = Thread.names[pcomm]
772		except:
773			Thread.names[pcomm] = 0
774			return
775		Thread.names[pcomm] = cnt + 1
776
777	def fixup(self):
778		cnt = Thread.names[self.name]
779		if (cnt == 0):
780			return
781		cnt -= 1
782		Thread.names[self.name] = cnt
783		self.name += " td" + str(cnt)
784
785	def ysize(self):
786		return (10)
787
788class Counter(EventSource):
789	max = 0
790	def __init__(self, name):
791		EventSource.__init__(self, name)
792
793	def event(self, event):
794		EventSource.event(self, event)
795		try:
796			count = event.count
797		except:
798			return
799		count = int(count)
800		if (count > Counter.max):
801			Counter.max = count
802
803	def ysize(self):
804		return (80)
805
806	def yscale(self):
807		return (self.ysize() / Counter.max)
808
809
810class KTRFile:
811	def __init__(self, file):
812		self.timestamp_first = None
813		self.timestamp_last = None
814		self.lineno = -1
815		self.threads = []
816		self.sources = []
817		self.ticks = {}
818		self.load = {}
819
820		self.parse(file)
821		self.fixup()
822		global ticksps
823		ticksps = self.ticksps()
824
825	def parse(self, file):
826		try:
827			ifp = open(file)
828		except:
829			print "Can't open", file
830			sys.exit(1)
831
832		ktrhdr = "\s+\d+\s+(\d+)\s+(\d+)\s+"
833		tdname = "(\S+)\(([^)]*)\)"
834
835		ktrstr = "mi_switch: " + tdname
836		ktrstr += " prio (\d+) inhibit (\d+) wmesg (\S+) lock (\S+)"
837		switchout_re = re.compile(ktrhdr + ktrstr)
838
839		ktrstr = "mi_switch: " + tdname + " prio (\d+) idle"
840		idled_re = re.compile(ktrhdr + ktrstr)
841
842		ktrstr = "mi_switch: " + tdname + " prio (\d+) preempted by "
843		ktrstr += tdname
844		preempted_re = re.compile(ktrhdr + ktrstr)
845
846		ktrstr = "mi_switch: running " + tdname + " prio (\d+)"
847		switchin_re = re.compile(ktrhdr + ktrstr)
848
849		ktrstr = "sched_add: " + tdname + " prio (\d+) by " + tdname
850		sched_add_re = re.compile(ktrhdr + ktrstr)
851
852		ktrstr = "setrunqueue: " + tdname + " prio (\d+) by " + tdname
853		setrunqueue_re = re.compile(ktrhdr + ktrstr)
854
855		ktrstr = "sched_rem: " + tdname + " prio (\d+) by " + tdname
856		sched_rem_re = re.compile(ktrhdr + ktrstr)
857
858		ktrstr = "sched_exit_thread: " + tdname + " prio (\d+)"
859		sched_exit_re = re.compile(ktrhdr + ktrstr)
860
861		ktrstr = "statclock: " + tdname + " prio (\d+)"
862		ktrstr += " stathz (\d+)"
863		sched_clock_re = re.compile(ktrhdr + ktrstr)
864
865		ktrstr = "sched_prio: " + tdname + " prio (\d+)"
866		ktrstr += " newprio (\d+) by " + tdname
867		sched_prio_re = re.compile(ktrhdr + ktrstr)
868
869		cpuload_re = re.compile(ktrhdr + "load: (\d+)")
870		loadglobal_re = re.compile(ktrhdr + "global load: (\d+)")
871
872		parsers = [[cpuload_re, self.cpuload],
873			   [loadglobal_re, self.loadglobal],
874			   [switchin_re, self.switchin],
875			   [switchout_re, self.switchout],
876			   [sched_add_re, self.sched_add],
877			   [setrunqueue_re, self.sched_rem],
878			   [sched_prio_re, self.sched_prio],
879			   [preempted_re, self.preempted],
880			   [sched_rem_re, self.sched_rem],
881			   [sched_exit_re, self.sched_exit],
882			   [sched_clock_re, self.sched_clock],
883			   [idled_re, self.idled]]
884
885		for line in ifp.readlines():
886			self.lineno += 1
887			if ((self.lineno % 1024) == 0):
888				status.startup("Parsing line " +
889				    str(self.lineno))
890			for p in parsers:
891				m = p[0].match(line)
892				if (m != None):
893					p[1](*m.groups())
894					break
895			# if (m == None):
896			# 	print line,
897
898	def checkstamp(self, timestamp):
899		timestamp = int(timestamp)
900		if (self.timestamp_first == None):
901			self.timestamp_first = timestamp
902		if (timestamp > self.timestamp_first):
903			print "Bad timestamp on line ", self.lineno
904			return (0)
905		self.timestamp_last = timestamp
906		return (1)
907
908	def timespan(self):
909		return (self.timestamp_first - self.timestamp_last);
910
911	def ticksps(self):
912		return (self.timespan() / self.ticks[0]) * int(self.stathz)
913
914	def switchout(self, cpu, timestamp, td, pcomm, prio, inhibit, wmesg, lock):
915		TDI_SUSPENDED = 0x0001
916		TDI_SLEEPING = 0x0002
917		TDI_SWAPPED = 0x0004
918		TDI_LOCK = 0x0008
919		TDI_IWAIT = 0x0010
920
921		if (self.checkstamp(timestamp) == 0):
922			return
923		inhibit = int(inhibit)
924		thread = self.findtd(td, pcomm)
925		if (inhibit & TDI_SWAPPED):
926			Swapped(thread, cpu, timestamp, prio)
927		elif (inhibit & TDI_SLEEPING):
928			Sleep(thread, cpu, timestamp, prio, wmesg)
929		elif (inhibit & TDI_LOCK):
930			Blocked(thread, cpu, timestamp, prio, lock)
931		elif (inhibit & TDI_IWAIT):
932			Iwait(thread, cpu, timestamp, prio)
933		elif (inhibit & TDI_SUSPENDED):
934			Suspended(thread, cpu, timestamp, prio)
935		elif (inhibit == 0):
936			Yielding(thread, cpu, timestamp, prio)
937		else:
938			print "Unknown event", inhibit
939			sys.exit(1)
940
941	def idled(self, cpu, timestamp, td, pcomm, prio):
942		if (self.checkstamp(timestamp) == 0):
943			return
944		thread = self.findtd(td, pcomm)
945		Idle(thread, cpu, timestamp, prio)
946
947	def preempted(self, cpu, timestamp, td, pcomm, prio, bytd, bypcomm):
948		if (self.checkstamp(timestamp) == 0):
949			return
950		thread = self.findtd(td, pcomm)
951		Preempted(thread, cpu, timestamp, prio,
952		    self.findtd(bytd, bypcomm))
953
954	def switchin(self, cpu, timestamp, td, pcomm, prio):
955		if (self.checkstamp(timestamp) == 0):
956			return
957		thread = self.findtd(td, pcomm)
958		Running(thread, cpu, timestamp, prio)
959
960	def sched_add(self, cpu, timestamp, td, pcomm, prio, bytd, bypcomm):
961		if (self.checkstamp(timestamp) == 0):
962			return
963		thread = self.findtd(td, pcomm)
964		bythread = self.findtd(bytd, bypcomm)
965		Runq(thread, cpu, timestamp, prio, bythread)
966		Wokeup(bythread, cpu, timestamp, thread)
967
968	def sched_rem(self, cpu, timestamp, td, pcomm, prio, bytd, bypcomm):
969		if (self.checkstamp(timestamp) == 0):
970			return
971		thread = self.findtd(td, pcomm)
972		KsegrpRunq(thread, cpu, timestamp, prio,
973		    self.findtd(bytd, bypcomm))
974
975	def sched_exit(self, cpu, timestamp, td, pcomm, prio):
976		if (self.checkstamp(timestamp) == 0):
977			return
978		thread = self.findtd(td, pcomm)
979		Sched_exit(thread, cpu, timestamp, prio)
980
981	def sched_clock(self, cpu, timestamp, td, pcomm, prio, stathz):
982		if (self.checkstamp(timestamp) == 0):
983			return
984		self.stathz = stathz
985		cpu = int(cpu)
986		try:
987			ticks = self.ticks[cpu]
988		except:
989			self.ticks[cpu] = 0
990		self.ticks[cpu] += 1
991		thread = self.findtd(td, pcomm)
992		Tick(thread, cpu, timestamp, prio, stathz)
993
994	def sched_prio(self, cpu, timestamp, td, pcomm, prio, newprio, bytd, bypcomm):
995		if (prio == newprio):
996			return
997		if (self.checkstamp(timestamp) == 0):
998			return
999		thread = self.findtd(td, pcomm)
1000		bythread = self.findtd(bytd, bypcomm)
1001		Prio(thread, cpu, timestamp, prio, newprio, bythread)
1002		Lend(bythread, cpu, timestamp, newprio, thread)
1003
1004	def cpuload(self, cpu, timestamp, count):
1005		if (self.checkstamp(timestamp) == 0):
1006			return
1007		cpu = int(cpu)
1008		try:
1009			load = self.load[cpu]
1010		except:
1011			load = Counter("cpu" + str(cpu) + " load")
1012			self.load[cpu] = load
1013			self.sources.insert(0, load)
1014		Count(load, cpu, timestamp, count)
1015
1016	def loadglobal(self, cpu, timestamp, count):
1017		if (self.checkstamp(timestamp) == 0):
1018			return
1019		cpu = 0
1020		try:
1021			load = self.load[cpu]
1022		except:
1023			load = Counter("CPU load")
1024			self.load[cpu] = load
1025			self.sources.insert(0, load)
1026		Count(load, cpu, timestamp, count)
1027
1028	def findtd(self, td, pcomm):
1029		for thread in self.threads:
1030			if (thread.str == td and thread.name == pcomm):
1031				return thread
1032		thread = Thread(td, pcomm)
1033		self.threads.append(thread)
1034		self.sources.append(thread)
1035		return (thread)
1036
1037	def fixup(self):
1038		for source in self.sources:
1039			Padevent(source, -1, self.timestamp_last)
1040			Padevent(source, -1, self.timestamp_first, last=1)
1041			source.fixup()
1042
1043class SchedDisplay(Canvas):
1044	def __init__(self, master):
1045		self.ratio = 1
1046		self.ktrfile = None
1047		self.sources = None
1048		self.bdheight = 10
1049		self.events = {}
1050
1051		Canvas.__init__(self, master, width=800, height=500, bg='grey',
1052		     scrollregion=(0, 0, 800, 500))
1053
1054	def setfile(self, ktrfile):
1055		self.ktrfile = ktrfile
1056		self.sources = ktrfile.sources
1057
1058	def draw(self):
1059		ypos = 0
1060		xsize = self.xsize()
1061		for source in self.sources:
1062			status.startup("Drawing " + source.name)
1063			self.create_line(0, ypos, xsize, ypos,
1064			    width=1, fill="black", tags=("all",))
1065			ypos += self.bdheight
1066			ypos += source.ysize()
1067			source.draw(self, ypos)
1068			ypos += self.bdheight
1069			try:
1070				self.tag_raise("point", "state")
1071				self.tag_lower("cpuinfo", "all")
1072			except:
1073				pass
1074		self.create_line(0, ypos, xsize, ypos,
1075		    width=1, fill="black", tags=("all",))
1076		self.tag_bind("event", "<Enter>", self.mouseenter)
1077		self.tag_bind("event", "<Leave>", self.mouseexit)
1078		self.tag_bind("event", "<Button-1>", self.mousepress)
1079
1080	def mouseenter(self, event):
1081		item, = self.find_withtag(CURRENT)
1082		event = self.events[item]
1083		event.mouseenter(self, item)
1084
1085	def mouseexit(self, event):
1086		item, = self.find_withtag(CURRENT)
1087		event = self.events[item]
1088		event.mouseexit(self, item)
1089
1090	def mousepress(self, event):
1091		item, = self.find_withtag(CURRENT)
1092		event = self.events[item]
1093		event.mousepress(self, item)
1094
1095	def drawnames(self, canvas):
1096		status.startup("Drawing names")
1097		ypos = 0
1098		canvas.configure(scrollregion=(0, 0,
1099		    canvas["width"], self.ysize()))
1100		for source in self.sources:
1101			canvas.create_line(0, ypos, canvas["width"], ypos,
1102			    width=1, fill="black", tags=("all",))
1103			ypos += self.bdheight
1104			ypos += source.ysize()
1105			source.drawname(canvas, ypos)
1106			ypos += self.bdheight
1107		canvas.create_line(0, ypos, canvas["width"], ypos,
1108		    width=1, fill="black", tags=("all",))
1109
1110	def xsize(self):
1111		return ((self.ktrfile.timespan() / self.ratio) + 20)
1112
1113	def ysize(self):
1114		ysize = 0
1115		for source in self.sources:
1116			ysize += source.ysize() + (self.bdheight * 2)
1117		return (ysize)
1118
1119	def scaleset(self, ratio):
1120		if (self.ktrfile == None):
1121			return
1122		oldratio = self.ratio
1123		xstart, ystart = self.xview()
1124		length = (float(self["width"]) / self.xsize())
1125		middle = xstart + (length / 2)
1126
1127		self.ratio = ratio
1128		self.configure(scrollregion=(0, 0, self.xsize(), self.ysize()))
1129		self.scale("all", 0, 0, float(oldratio) / ratio, 1)
1130
1131		length = (float(self["width"]) / self.xsize())
1132		xstart = middle - (length / 2)
1133		self.xview_moveto(xstart)
1134
1135	def scaleget(self):
1136		return self.ratio
1137
1138	def setcolor(self, tag, color):
1139		self.itemconfigure(tag, state="normal", fill=color)
1140
1141	def hide(self, tag):
1142		self.itemconfigure(tag, state="hidden")
1143
1144class GraphMenu(Frame):
1145	def __init__(self, master):
1146		Frame.__init__(self, master, bd=2, relief=RAISED)
1147		self.view = Menubutton(self, text="Configure")
1148		self.viewmenu = Menu(self.view, tearoff=0)
1149		self.viewmenu.add_command(label="Events",
1150		    command=self.econf)
1151		self.view["menu"] = self.viewmenu
1152		self.view.pack(side=LEFT)
1153
1154	def econf(self):
1155		EventConfigure()
1156
1157
1158class SchedGraph(Frame):
1159	def __init__(self, master):
1160		Frame.__init__(self, master)
1161		self.menu = None
1162		self.names = None
1163		self.display = None
1164		self.scale = None
1165		self.status = None
1166		self.pack(expand=1, fill="both")
1167		self.buildwidgets()
1168		self.layout()
1169		self.draw(sys.argv[1])
1170
1171	def buildwidgets(self):
1172		global status
1173		self.menu = GraphMenu(self)
1174		self.display = SchedDisplay(self)
1175		self.names = Canvas(self,
1176		    width=100, height=self.display["height"],
1177		    bg='grey', scrollregion=(0, 0, 50, 100))
1178		self.scale = Scaler(self, self.display)
1179		status = self.status = Status(self)
1180		self.scrollY = Scrollbar(self, orient="vertical",
1181		    command=self.display_yview)
1182		self.display.scrollX = Scrollbar(self, orient="horizontal",
1183		    command=self.display.xview)
1184		self.display["xscrollcommand"] = self.display.scrollX.set
1185		self.display["yscrollcommand"] = self.scrollY.set
1186		self.names["yscrollcommand"] = self.scrollY.set
1187
1188	def layout(self):
1189		self.columnconfigure(1, weight=1)
1190		self.rowconfigure(1, weight=1)
1191		self.menu.grid(row=0, column=0, columnspan=3, sticky=E+W)
1192		self.names.grid(row=1, column=0, sticky=N+S)
1193		self.display.grid(row=1, column=1, sticky=W+E+N+S)
1194		self.scrollY.grid(row=1, column=2, sticky=N+S)
1195		self.display.scrollX.grid(row=2, column=0, columnspan=2,
1196		    sticky=E+W)
1197		self.scale.grid(row=3, column=0, columnspan=3, sticky=E+W)
1198		self.status.grid(row=4, column=0, columnspan=3, sticky=E+W)
1199
1200	def draw(self, file):
1201		self.master.update()
1202		ktrfile = KTRFile(file)
1203		self.display.setfile(ktrfile)
1204		self.display.drawnames(self.names)
1205		self.display.draw()
1206		self.scale.set(250000)
1207		self.display.xview_moveto(0)
1208
1209	def display_yview(self, *args):
1210		self.names.yview(*args)
1211		self.display.yview(*args)
1212
1213	def setcolor(self, tag, color):
1214		self.display.setcolor(tag, color)
1215
1216	def hide(self, tag):
1217		self.display.hide(tag)
1218
1219if (len(sys.argv) != 2):
1220	print "usage:", sys.argv[0], "<ktr file>"
1221	sys.exit(1)
1222
1223root = Tk()
1224root.title("Scheduler Graph")
1225graph = SchedGraph(root)
1226root.mainloop()
1227