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