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