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