schedgraph.py revision 165824
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 165824 2007-01-06 04:40:44Z 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	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		elif (cpu == 4):
743			color = 'blanched almond'
744		elif (cpu == 5):
745			color = 'slate grey'
746		elif (cpu == 6):
747			color = 'light slate blue'
748		elif (cpu == 7):
749			color = 'thistle'
750		else:
751			color = "white"
752		l = canvas.create_rectangle(self.cpux,
753		    ypos - self.ysize() - canvas.bdheight,
754		    xpos, ypos + canvas.bdheight, fill=color, width=0,
755		    tags=("all", "cpuinfo"))
756
757	def ysize(self):
758		return (None)
759
760	def eventat(self, i):
761		if (i >= len(self.events)):
762			return (None)
763		event = self.events[i]
764		return (event)
765
766	def findevent(self, timestamp):
767		for event in self.events:
768			if (event.timestamp >= timestamp and event.real):
769				return (event)
770		return (None)
771
772class Thread(EventSource):
773	names = {}
774	def __init__(self, td, pcomm):
775		EventSource.__init__(self, pcomm)
776		self.str = td
777		try:
778			cnt = Thread.names[pcomm]
779		except:
780			Thread.names[pcomm] = 0
781			return
782		Thread.names[pcomm] = cnt + 1
783
784	def fixup(self):
785		cnt = Thread.names[self.name]
786		if (cnt == 0):
787			return
788		cnt -= 1
789		Thread.names[self.name] = cnt
790		self.name += " td" + str(cnt)
791
792	def ysize(self):
793		return (10)
794
795class Counter(EventSource):
796	max = 0
797	def __init__(self, name):
798		EventSource.__init__(self, name)
799
800	def event(self, event):
801		EventSource.event(self, event)
802		try:
803			count = event.count
804		except:
805			return
806		count = int(count)
807		if (count > Counter.max):
808			Counter.max = count
809
810	def ysize(self):
811		return (80)
812
813	def yscale(self):
814		return (self.ysize() / Counter.max)
815
816
817class KTRFile:
818	def __init__(self, file):
819		self.timestamp_first = None
820		self.timestamp_last = None
821		self.lineno = -1
822		self.threads = []
823		self.sources = []
824		self.ticks = {}
825		self.load = {}
826		self.crit = {}
827
828		self.parse(file)
829		self.fixup()
830		global ticksps
831		ticksps = self.ticksps()
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