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