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