schedgraph.py revision 187376
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 187376 2009-01-18 04:49:01Z jeff $
28
29import sys
30import re
31import random
32from Tkinter import *
33
34# To use:
35# - Install the ports/x11-toolkits/py-tkinter package; e.g.
36#	portinstall x11-toolkits/py-tkinter package
37# - Add KTR_SCHED to KTR_COMPILE and KTR_MASK in your KERNCONF; e.g.
38#	options 	KTR
39#	options 	KTR_ENTRIES=32768
40#	options 	KTR_COMPILE=(KTR_SCHED)
41#	options 	KTR_MASK=(KTR_SCHED)
42# - It is encouraged to increase KTR_ENTRIES size to gather enough
43#    information for analysis; e.g.
44#	options 	KTR_ENTRIES=262144
45#   as 32768 entries may only correspond to a second or two of profiling
46#   data depending on your workload.
47# - Rebuild kernel with proper changes to KERNCONF and boot new kernel.
48# - Run your workload to be profiled.
49# - While the workload is continuing (i.e. before it finishes), disable
50#   KTR tracing by setting 'sysctl debug.ktr.mask=0'.  This is necessary
51#   to avoid a race condition while running ktrdump, i.e. the KTR ring buffer
52#   will cycle a bit while ktrdump runs, and this confuses schedgraph because
53#   the timestamps appear to go backwards at some point.  Stopping KTR logging
54#   while the workload is still running is to avoid wasting log entries on
55#   "idle" time at the end.
56# - Dump the trace to a file: 'ktrdump -ct > ktr.out'
57# - Run the python script: 'python schedgraph.py ktr.out' optionally provide
58#   your cpu frequency in ghz: 'python schedgraph.py ktr.out 2.4'
59#
60# To do:
61# Add a per-source summary display
62# "Vertical rule" to help relate data in different rows
63# Mouse-over popup of full thread/event/row label (currently truncated)
64# More visible anchors for popup event windows
65#
66# BUGS: 1) Only 8 CPUs are supported, more CPUs require more choices of
67#          colours to represent them ;-)
68
69eventcolors = [
70	("count",	"red"),
71	("running",	"green"),
72	("idle",	"grey"),
73	("yielding",	"yellow"),
74	("swapped",	"violet"),
75	("suspended",	"purple"),
76	("iwait",	"grey"),
77	("sleep",	"blue"),
78	("blocked",	"dark red"),
79	("runq add",	"yellow"),
80	("runq rem",	"yellow"),
81	("thread exit",	"grey"),
82	("proc exit",	"grey"),
83	("callwheel idle", "grey"),
84	("callout running", "green"),
85	("lock acquire", "blue"),
86	("lock contest", "purple"),
87	("failed lock try", "red"),
88	("lock release", "grey"),
89	("tick",	"black"),
90	("prio",	"black"),
91	("lend prio",	"black"),
92	("wokeup",	"black")
93]
94
95cpucolors = [
96	("CPU 0",	"light grey"),
97	("CPU 1",	"dark grey"),
98	("CPU 2",	"light blue"),
99	("CPU 3",	"light pink"),
100	("CPU 4",	"blanched almond"),
101	("CPU 5",	"slate grey"),
102	("CPU 6",	"tan"),
103	("CPU 7",	"thistle"),
104	("CPU 8",	"white")
105]
106
107colors = [
108	"white", "thistle", "blanched almond", "tan", "chartreuse",
109	"dark red", "red", "pale violet red", "pink", "light pink",
110	"dark orange", "orange", "coral", "light coral",
111	"goldenrod", "gold", "yellow", "light yellow",
112	"dark green", "green", "light green", "light sea green",
113	"dark blue", "blue", "light blue", "steel blue", "light slate blue",
114	"dark violet", "violet", "purple", "blue violet",
115	"dark grey", "slate grey", "light grey",
116	"black",
117]
118colors.sort()
119
120ticksps = None
121status = None
122colormap = None
123ktrfile = None
124clockfreq = None
125sources = []
126lineno = -1
127
128class Colormap:
129	def __init__(self, table):
130		self.table = table
131		self.map = {}
132		for entry in table:
133			self.map[entry[0]] = entry[1]
134
135	def lookup(self, name):
136		try:
137			color = self.map[name]
138		except:
139			color = colors[random.randrange(0, len(colors))]
140			print "Picking random color", color, "for", name
141			self.map[name] = color
142			self.table.append((name, color))
143		return (color)
144
145def ticks2sec(ticks):
146	us = ticksps / 1000000
147	ticks /= us
148	if (ticks < 1000):
149		return (str(ticks) + "us")
150	ticks /= 1000
151	if (ticks < 1000):
152		return (str(ticks) + "ms")
153	ticks /= 1000
154	return (str(ticks) + "s")
155
156class Scaler(Frame):
157	def __init__(self, master, target):
158		Frame.__init__(self, master)
159		self.scale = Scale(self, command=self.scaleset,
160		    from_=1000, to_=10000000, orient=HORIZONTAL,
161		    resolution=1000)
162		self.label = Label(self, text="Ticks per pixel")
163		self.label.pack(side=LEFT)
164		self.scale.pack(fill="both", expand=1)
165		self.target = target
166		self.scale.set(target.scaleget())
167		self.initialized = 1
168
169	def scaleset(self, value):
170		self.target.scaleset(int(value))
171
172	def set(self, value):
173		self.scale.set(value)
174
175class Status(Frame):
176	def __init__(self, master):
177		Frame.__init__(self, master)
178		self.label = Label(self, bd=1, relief=SUNKEN, anchor=W)
179		self.label.pack(fill="both", expand=1)
180		self.clear()
181
182	def set(self, str):
183		self.label.config(text=str)
184
185	def clear(self):
186		self.label.config(text="")
187
188	def startup(self, str):
189		self.set(str)
190		root.update()
191
192class ColorConf(Frame):
193	def __init__(self, master, name, color):
194		Frame.__init__(self, master)
195		if (graph.getstate(name) == "hidden"):
196			enabled = 0
197		else:
198			enabled = 1
199		self.name = name
200		self.color = StringVar()
201		self.color_default = color
202		self.color_current = color
203		self.color.set(color)
204		self.enabled = IntVar()
205		self.enabled_default = enabled
206		self.enabled_current = enabled
207		self.enabled.set(enabled)
208		self.draw()
209
210	def draw(self):
211		self.label = Label(self, text=self.name, anchor=W)
212		self.sample = Canvas(self, width=24, height=24,
213		    bg='grey')
214		self.rect = self.sample.create_rectangle(0, 0, 24, 24,
215		    fill=self.color.get())
216		self.list = OptionMenu(self, self.color, command=self.setcolor,
217		    *colors)
218		self.checkbox = Checkbutton(self, text="enabled",
219		    variable=self.enabled)
220		self.label.grid(row=0, column=0, sticky=E+W)
221		self.sample.grid(row=0, column=1)
222		self.list.grid(row=0, column=2, sticky=E+W)
223		self.checkbox.grid(row=0, column=3)
224		self.columnconfigure(0, weight=1)
225		self.columnconfigure(2, minsize=150)
226
227	def setcolor(self, color):
228		self.color.set(color)
229		self.sample.itemconfigure(self.rect, fill=color)
230
231	def apply(self):
232		cchange = 0
233		echange = 0
234		if (self.color_current != self.color.get()):
235			cchange = 1
236		if (self.enabled_current != self.enabled.get()):
237			echange = 1
238		self.color_current = self.color.get()
239		self.enabled_current = self.enabled.get()
240		if (echange != 0):
241			if (self.enabled_current):
242				graph.setcolor(self.name, self.color_current)
243			else:
244				graph.hide(self.name)
245			return
246		if (cchange != 0):
247			graph.setcolor(self.name, self.color_current)
248
249	def revert(self):
250		self.setcolor(self.color_default)
251		self.enabled.set(self.enabled_default)
252
253class ColorConfigure(Toplevel):
254	def __init__(self, table, name):
255		Toplevel.__init__(self)
256		self.resizable(0, 0)
257		self.title(name)
258		self.items = LabelFrame(self, text="Item Type")
259		self.buttons = Frame(self)
260		self.drawbuttons()
261		self.items.grid(row=0, column=0, sticky=E+W)
262		self.columnconfigure(0, weight=1)
263		self.buttons.grid(row=1, column=0, sticky=E+W)
264		self.types = []
265		self.irow = 0
266		for type in table:
267			color = graph.getcolor(type[0])
268			if (color != ""):
269				self.additem(type[0], color)
270
271	def additem(self, name, color):
272		item = ColorConf(self.items, name, color)
273		self.types.append(item)
274		item.grid(row=self.irow, column=0, sticky=E+W)
275		self.irow += 1
276
277	def drawbuttons(self):
278		self.apply = Button(self.buttons, text="Apply",
279		    command=self.apress)
280		self.default = Button(self.buttons, text="Revert",
281		    command=self.rpress)
282		self.apply.grid(row=0, column=0, sticky=E+W)
283		self.default.grid(row=0, column=1, sticky=E+W)
284		self.buttons.columnconfigure(0, weight=1)
285		self.buttons.columnconfigure(1, weight=1)
286
287	def apress(self):
288		for item in self.types:
289			item.apply()
290
291	def rpress(self):
292		for item in self.types:
293			item.revert()
294
295class SourceConf(Frame):
296	def __init__(self, master, source):
297		Frame.__init__(self, master)
298		if (source.hidden == 1):
299			enabled = 0
300		else:
301			enabled = 1
302		self.source = source
303		self.name = source.name
304		self.enabled = IntVar()
305		self.enabled_default = enabled
306		self.enabled_current = enabled
307		self.enabled.set(enabled)
308		self.draw()
309
310	def draw(self):
311		self.label = Label(self, text=self.name, anchor=W)
312		self.checkbox = Checkbutton(self, text="enabled",
313		    variable=self.enabled)
314		self.label.grid(row=0, column=0, sticky=E+W)
315		self.checkbox.grid(row=0, column=1)
316		self.columnconfigure(0, weight=1)
317
318	def changed(self):
319		if (self.enabled_current != self.enabled.get()):
320			return 1
321		return 0
322
323	def apply(self):
324		self.enabled_current = self.enabled.get()
325
326	def revert(self):
327		self.enabled.set(self.enabled_default)
328
329	def check(self):
330		self.enabled.set(1)
331
332	def uncheck(self):
333		self.enabled.set(0)
334
335class SourceConfigure(Toplevel):
336	def __init__(self):
337		Toplevel.__init__(self)
338		self.resizable(0, 0)
339		self.title("Source Configuration")
340		self.items = []
341		self.iframe = Frame(self)
342		self.iframe.grid(row=0, column=0, sticky=E+W)
343		f = LabelFrame(self.iframe, bd=4, text="Sources")
344		self.items.append(f)
345		self.buttons = Frame(self)
346		self.items[0].grid(row=0, column=0, sticky=E+W)
347		self.columnconfigure(0, weight=1)
348		self.sconfig = []
349		self.irow = 0
350		self.icol = 0
351		for source in sources:
352			self.addsource(source)
353		self.drawbuttons()
354		self.buttons.grid(row=1, column=0, sticky=W)
355
356	def addsource(self, source):
357		if (self.irow > 30):
358			self.icol += 1
359			self.irow = 0
360			c = self.icol
361			f = LabelFrame(self.iframe, bd=4, text="Sources")
362			f.grid(row=0, column=c, sticky=N+E+W)
363			self.items.append(f)
364		item = SourceConf(self.items[self.icol], source)
365		self.sconfig.append(item)
366		item.grid(row=self.irow, column=0, sticky=E+W)
367		self.irow += 1
368
369	def drawbuttons(self):
370		self.apply = Button(self.buttons, text="Apply",
371		    command=self.apress)
372		self.default = Button(self.buttons, text="Revert",
373		    command=self.rpress)
374		self.checkall = Button(self.buttons, text="Check All",
375		    command=self.cpress)
376		self.uncheckall = Button(self.buttons, text="Uncheck All",
377		    command=self.upress)
378		self.checkall.grid(row=0, column=0, sticky=W)
379		self.uncheckall.grid(row=0, column=1, sticky=W)
380		self.apply.grid(row=0, column=2, sticky=W)
381		self.default.grid(row=0, column=3, sticky=W)
382		self.buttons.columnconfigure(0, weight=1)
383		self.buttons.columnconfigure(1, weight=1)
384		self.buttons.columnconfigure(2, weight=1)
385		self.buttons.columnconfigure(3, weight=1)
386
387	def apress(self):
388		disable_sources = []
389		enable_sources = []
390		for item in self.sconfig:
391			if (item.changed() == 0):
392				continue
393			if (item.enabled.get() == 1):
394				enable_sources.append(item.source)
395			else:
396				disable_sources.append(item.source)
397
398		if (len(disable_sources)):
399			graph.sourcehidelist(disable_sources)
400		if (len(enable_sources)):
401			graph.sourceshowlist(enable_sources)
402
403		for item in self.sconfig:
404			item.apply()
405
406	def rpress(self):
407		for item in self.sconfig:
408			item.revert()
409
410	def cpress(self):
411		for item in self.sconfig:
412			item.check()
413
414	def upress(self):
415		for item in self.sconfig:
416			item.uncheck()
417
418# Reverse compare of second member of the tuple
419def cmp_counts(x, y):
420	return y[1] - x[1]
421
422class SourceStats(Toplevel):
423	def __init__(self, source):
424		self.source = source
425		Toplevel.__init__(self)
426		self.resizable(0, 0)
427		self.title(source.name + " statistics")
428		self.evframe = LabelFrame(self,
429		    text="Event Frequency and Duration")
430		self.evframe.grid(row=0, column=0, sticky=E+W)
431		eventtypes={}
432		for event in self.source.events:
433			if (event.type == "pad"):
434				continue
435			duration = event.duration
436			if (eventtypes.has_key(event.name)):
437				(c, d) = eventtypes[event.name]
438				c += 1
439				d += duration
440				eventtypes[event.name] = (c, d)
441			else:
442				eventtypes[event.name] = (1, duration)
443		events = []
444		for k, v in eventtypes.iteritems():
445			(c, d) = v
446			events.append((k, c, d))
447		events.sort(cmp=cmp_counts)
448
449		ypos = 0
450		for event in events:
451			(name, c, d) = event
452			l = Label(self.evframe, text=name, bd=1,
453			    relief=SUNKEN, anchor=W, width=30)
454			m = Label(self.evframe, text=str(c), bd=1,
455			    relief=SUNKEN, anchor=W, width=10)
456			r = Label(self.evframe, text=ticks2sec(d),
457			    bd=1, relief=SUNKEN, width=10)
458			l.grid(row=ypos, column=0, sticky=E+W)
459			m.grid(row=ypos, column=1, sticky=E+W)
460			r.grid(row=ypos, column=2, sticky=E+W)
461			ypos += 1
462
463
464class SourceContext(Menu):
465	def __init__(self, event, source):
466		self.source = source
467		Menu.__init__(self, tearoff=0, takefocus=0)
468		self.add_command(label="hide", command=self.hide)
469		self.add_command(label="hide group", command=self.hidegroup)
470		self.add_command(label="stats", command=self.stats)
471		self.tk_popup(event.x_root-3, event.y_root+3)
472
473	def hide(self):
474		graph.sourcehide(self.source)
475
476	def hidegroup(self):
477		grouplist = []
478		for source in sources:
479			if (source.group == self.source.group):
480				grouplist.append(source)
481		graph.sourcehidelist(grouplist)
482
483	def show(self):
484		graph.sourceshow(self.source)
485
486	def stats(self):
487		SourceStats(self.source)
488
489class EventView(Toplevel):
490	def __init__(self, event, canvas):
491		Toplevel.__init__(self)
492		self.resizable(0, 0)
493		self.title("Event")
494		self.event = event
495		self.buttons = Frame(self)
496		self.buttons.grid(row=0, column=0, sticky=E+W)
497		self.frame = Frame(self)
498		self.frame.grid(row=1, column=0, sticky=N+S+E+W)
499		self.canvas = canvas
500		self.drawlabels()
501		self.drawbuttons()
502		event.displayref(canvas)
503		self.bind("<Destroy>", self.destroycb)
504
505	def destroycb(self, event):
506		self.unbind("<Destroy>")
507		if (self.event != None):
508			self.event.displayunref(self.canvas)
509			self.event = None
510		self.destroy()
511
512	def clearlabels(self):
513		for label in self.frame.grid_slaves():
514			label.grid_remove()
515
516	def drawlabels(self):
517		ypos = 0
518		labels = self.event.labels()
519		while (len(labels) < 7):
520			labels.append(("", ""))
521		for label in labels:
522			name, value = label
523			linked = 0
524			if (name == "linkedto"):
525				linked = 1
526			l = Label(self.frame, text=name, bd=1, width=15,
527			    relief=SUNKEN, anchor=W)
528			if (linked):
529				fgcolor = "blue"
530			else:
531				fgcolor = "black"
532			r = Label(self.frame, text=value, bd=1,
533			    relief=SUNKEN, anchor=W, fg=fgcolor)
534			l.grid(row=ypos, column=0, sticky=E+W)
535			r.grid(row=ypos, column=1, sticky=E+W)
536			if (linked):
537				r.bind("<Button-1>", self.linkpress)
538			ypos += 1
539		self.frame.columnconfigure(1, minsize=80)
540
541	def drawbuttons(self):
542		self.back = Button(self.buttons, text="<", command=self.bpress)
543		self.forw = Button(self.buttons, text=">", command=self.fpress)
544		self.new = Button(self.buttons, text="new", command=self.npress)
545		self.back.grid(row=0, column=0, sticky=E+W)
546		self.forw.grid(row=0, column=1, sticky=E+W)
547		self.new.grid(row=0, column=2, sticky=E+W)
548		self.buttons.columnconfigure(2, weight=1)
549
550	def newevent(self, event):
551		self.event.displayunref(self.canvas)
552		self.clearlabels()
553		self.event = event
554		self.event.displayref(self.canvas)
555		self.drawlabels()
556
557	def npress(self):
558		EventView(self.event, self.canvas)
559
560	def bpress(self):
561		prev = self.event.prev()
562		if (prev == None):
563			return
564		while (prev.type == "pad"):
565			prev = prev.prev()
566			if (prev == None):
567				return
568		self.newevent(prev)
569
570	def fpress(self):
571		next = self.event.next()
572		if (next == None):
573			return
574		while (next.type == "pad"):
575			next = next.next()
576			if (next == None):
577				return
578		self.newevent(next)
579
580	def linkpress(self, wevent):
581		event = self.event.getlinked()
582		if (event != None):
583			self.newevent(event)
584
585class Event:
586	def __init__(self, source, name, cpu, timestamp, attrs):
587		self.source = source
588		self.name = name
589		self.cpu = cpu
590		self.timestamp = int(timestamp)
591		self.attrs = attrs
592		self.idx = None
593		self.item = None
594		self.dispcnt = 0
595		self.duration = 0
596		self.recno = lineno
597
598	def status(self):
599		statstr = self.name + " " + self.source.name
600		statstr += " on: cpu" + str(self.cpu)
601		statstr += " at: " + str(self.timestamp)
602		statstr += " attributes: "
603		for i in range(0, len(self.attrs)):
604			attr = self.attrs[i]
605			statstr += attr[0] + ": " + str(attr[1])
606			if (i != len(self.attrs) - 1):
607				statstr += ", "
608		status.set(statstr)
609
610	def labels(self):
611		return [("Source", self.source.name),
612			("Event", self.name),
613			("CPU", self.cpu),
614			("Timestamp", self.timestamp),
615			("KTR Line ", self.recno)
616		] + self.attrs
617
618	def mouseenter(self, canvas):
619		self.displayref(canvas)
620		self.status()
621
622	def mouseexit(self, canvas):
623		self.displayunref(canvas)
624		status.clear()
625
626	def mousepress(self, canvas):
627		EventView(self, canvas)
628
629	def draw(self, canvas, xpos, ypos, item):
630		self.item = item
631		if (item != None):
632			canvas.items[item] = self
633
634	def move(self, canvas, x, y):
635		if (self.item == None):
636			return;
637		canvas.move(self.item, x, y);
638
639	def next(self):
640		return self.source.eventat(self.idx + 1)
641
642	def nexttype(self, type):
643		next = self.next()
644		while (next != None and next.type != type):
645			next = next.next()
646		return (next)
647
648	def prev(self):
649		return self.source.eventat(self.idx - 1)
650
651	def displayref(self, canvas):
652		if (self.dispcnt == 0):
653			canvas.itemconfigure(self.item, width=2)
654		self.dispcnt += 1
655
656	def displayunref(self, canvas):
657		self.dispcnt -= 1
658		if (self.dispcnt == 0):
659			canvas.itemconfigure(self.item, width=0)
660			canvas.tag_raise("point", "state")
661
662	def getlinked(self):
663		for attr in self.attrs:
664			if (attr[0] != "linkedto"):
665				continue
666			source = ktrfile.findid(attr[1])
667			return source.findevent(self.timestamp)
668		return None
669
670class PointEvent(Event):
671	type = "point"
672	def __init__(self, source, name, cpu, timestamp, attrs):
673		Event.__init__(self, source, name, cpu, timestamp, attrs)
674
675	def draw(self, canvas, xpos, ypos):
676		color = colormap.lookup(self.name)
677		l = canvas.create_oval(xpos - 6, ypos + 1, xpos + 6, ypos - 11,
678		    fill=color, width=0,
679		    tags=("all", "point", "event", self.name, self.source.tag))
680		Event.draw(self, canvas, xpos, ypos, l)
681
682		return xpos
683
684class StateEvent(Event):
685	type = "state"
686	def __init__(self, source, name, cpu, timestamp, attrs):
687		Event.__init__(self, source, name, cpu, timestamp, attrs)
688
689	def draw(self, canvas, xpos, ypos):
690		next = self.nexttype("state")
691		if (next == None):
692			return (xpos)
693		self.duration = duration = next.timestamp - self.timestamp
694		self.attrs.insert(0, ("duration", ticks2sec(duration)))
695		color = colormap.lookup(self.name)
696		if (duration < 0):
697			duration = 0
698			print "Unsynchronized timestamp"
699			print self.cpu, self.timestamp
700			print next.cpu, next.timestamp
701		delta = duration / canvas.ratio
702		l = canvas.create_rectangle(xpos, ypos,
703		    xpos + delta, ypos - 10, fill=color, width=0,
704		    tags=("all", "state", "event", self.name, self.source.tag))
705		Event.draw(self, canvas, xpos, ypos, l)
706
707		return (xpos + delta)
708
709class CountEvent(Event):
710	type = "count"
711	def __init__(self, source, count, cpu, timestamp, attrs):
712		count = int(count)
713		self.count = count
714		Event.__init__(self, source, "count", cpu, timestamp, attrs)
715
716	def draw(self, canvas, xpos, ypos):
717		next = self.nexttype("count")
718		if (next == None):
719			return (xpos)
720		color = colormap.lookup("count")
721		self.duration = duration = next.timestamp - self.timestamp
722		self.attrs.insert(0, ("count", self.count))
723		self.attrs.insert(1, ("duration", ticks2sec(duration)))
724		delta = duration / canvas.ratio
725		yhight = self.source.yscale() * self.count
726		l = canvas.create_rectangle(xpos, ypos - yhight,
727		    xpos + delta, ypos, fill=color, width=0,
728		    tags=("all", "count", "event", self.name, self.source.tag))
729		Event.draw(self, canvas, xpos, ypos, l)
730		return (xpos + delta)
731
732class PadEvent(StateEvent):
733	type = "pad"
734	def __init__(self, source, cpu, timestamp, last=0):
735		if (last):
736			cpu = source.events[len(source.events) -1].cpu
737		else:
738			cpu = source.events[0].cpu
739		StateEvent.__init__(self, source, "pad", cpu, timestamp, [])
740	def draw(self, canvas, xpos, ypos):
741		next = self.next()
742		if (next == None):
743			return (xpos)
744		duration = next.timestamp - self.timestamp
745		delta = duration / canvas.ratio
746		Event.draw(self, canvas, xpos, ypos, None)
747		return (xpos + delta)
748
749# Sort function for start y address
750def source_cmp_start(x, y):
751	return x.y - y.y
752
753class EventSource:
754	def __init__(self, group, id):
755		self.name = id
756		self.events = []
757		self.cpuitems = []
758		self.group = group
759		self.y = 0
760		self.item = None
761		self.hidden = 0
762		self.tag = group + id
763
764	def __cmp__(self, other):
765		if (other == None):
766			return -1
767		if (self.group == other.group):
768			return cmp(self.name, other.name)
769		return cmp(self.group, other.group)
770
771	# It is much faster to append items to a list then to insert them
772	# at the beginning.  As a result, we add events in reverse order
773	# and then swap the list during fixup.
774	def fixup(self):
775		self.events.reverse()
776
777	def addevent(self, event):
778		self.events.append(event)
779
780	def addlastevent(self, event):
781		self.events.insert(0, event)
782
783	def draw(self, canvas, ypos):
784		xpos = 10
785		cpux = 10
786		cpu = self.events[1].cpu
787		for i in range(0, len(self.events)):
788			self.events[i].idx = i
789		for event in self.events:
790			if (event.cpu != cpu and event.cpu != -1):
791				self.drawcpu(canvas, cpu, cpux, xpos, ypos)
792				cpux = xpos
793				cpu = event.cpu
794			xpos = event.draw(canvas, xpos, ypos)
795		self.drawcpu(canvas, cpu, cpux, xpos, ypos)
796
797	def drawname(self, canvas, ypos):
798		self.y = ypos
799		ypos = ypos - (self.ysize() / 2)
800		self.item = canvas.create_text(10, ypos, anchor="w", text=self.name)
801		return (self.item)
802
803	def drawcpu(self, canvas, cpu, fromx, tox, ypos):
804		cpu = "CPU " + str(cpu)
805		color = cpucolormap.lookup(cpu)
806		# Create the cpu background colors default to hidden
807		l = canvas.create_rectangle(fromx,
808		    ypos - self.ysize() - canvas.bdheight,
809		    tox, ypos + canvas.bdheight, fill=color, width=0,
810		    tags=("all", "cpuinfo", cpu, self.tag), state="hidden")
811		self.cpuitems.append(l)
812
813	def move(self, canvas, xpos, ypos):
814		canvas.move(self.tag, xpos, ypos)
815
816	def movename(self, canvas, xpos, ypos):
817		self.y += ypos
818		canvas.move(self.item, xpos, ypos)
819
820	def ysize(self):
821		return (10)
822
823	def eventat(self, i):
824		if (i >= len(self.events)):
825			return (None)
826		event = self.events[i]
827		return (event)
828
829	def findevent(self, timestamp):
830		for event in self.events:
831			if (event.timestamp >= timestamp and event.type != "pad"):
832				return (event)
833		return (None)
834
835class Counter(EventSource):
836	#
837	# Store a hash of counter groups that keeps the max value
838	# for a counter in this group for scaling purposes.
839	#
840	groups = {}
841	def __init__(self, group, id):
842		try:
843			Counter.cnt = Counter.groups[group]
844		except:
845			Counter.groups[group] = 0
846		EventSource.__init__(self, group, id)
847
848	def fixup(self):
849		for event in self.events:
850			if (event.type != "count"):
851				continue;
852			count = int(event.count)
853			if (count > Counter.groups[self.group]):
854				Counter.groups[self.group] = count
855		EventSource.fixup(self)
856
857	def ymax(self):
858		return (Counter.groups[self.group])
859
860	def ysize(self):
861		return (80)
862
863	def yscale(self):
864		return (self.ysize() / self.ymax())
865
866class KTRFile:
867	def __init__(self, file):
868		self.timestamp_f = None
869		self.timestamp_l = None
870		self.locks = {}
871		self.callwheels = {}
872		self.ticks = {}
873		self.load = {}
874		self.crit = {}
875		self.stathz = 0
876
877		self.parse(file)
878		self.fixup()
879		global ticksps
880		ticksps = self.ticksps()
881		timespan = self.timespan()
882		print "first tick", self.timestamp_f,
883		print "last tick", self.timestamp_l
884		print "Ticks per second", ticksps
885		print "time span", timespan, "ticks", ticks2sec(timespan)
886
887	def parse(self, file):
888		try:
889			ifp = open(file)
890		except:
891			print "Can't open", file
892			sys.exit(1)
893
894		# quoteexp matches a quoted string, no escaping
895		quoteexp = "\"([^\"]*)\""
896
897		#
898		# commaexp matches a quoted string OR the string up
899		# to the first ','
900		#
901		commaexp = "(?:" + quoteexp + "|([^,]+))"
902
903		#
904		# colonstr matches a quoted string OR the string up
905		# to the first ':'
906		#
907		colonexp = "(?:" + quoteexp + "|([^:]+))"
908
909		#
910		# Match various manditory parts of the KTR string this is
911		# fairly inflexible until you get to attributes to make
912		# parsing faster.
913		#
914		hdrexp = "\s*(\d+)\s+(\d+)\s+(\d+)\s+"
915		groupexp = "KTRGRAPH group:" + quoteexp + ", "
916		idexp = "id:" + quoteexp + ", "
917		typeexp = "([^:]+):" + commaexp + ", "
918		attribexp = "attributes: (.*)"
919
920		#
921		# Matches optional attributes in the KTR string.  This
922		# tolerates more variance as the users supply these values.
923		#
924		attrexp = colonexp + "\s*:\s*(?:" + commaexp + ", (.*)|"
925		attrexp += quoteexp +"|(.*))"
926
927		# Precompile regexp
928		ktrre = re.compile(hdrexp + groupexp + idexp + typeexp + attribexp)
929		attrre = re.compile(attrexp)
930
931		global lineno
932		lineno = 0
933		for line in ifp.readlines():
934			lineno += 1
935			if ((lineno % 2048) == 0):
936				status.startup("Parsing line " + str(lineno))
937			m = ktrre.match(line);
938			if (m == None):
939				print "Can't parse", lineno, line,
940				continue;
941			(index, cpu, timestamp, group, id, type, dat, dat1, attrstring) = m.groups();
942			if (dat == None):
943				dat = dat1
944			if (self.checkstamp(timestamp) == 0):
945				print "Bad timestamp at", lineno, ":", line,
946				continue
947			#
948			# Build the table of optional attributes
949			#
950			attrs = []
951			while (attrstring != None):
952				m = attrre.match(attrstring.strip())
953				if (m == None):
954					break;
955				#
956				# Name may or may not be quoted.
957				#
958				# For val we have four cases:
959				# 1) quotes followed by comma and more
960				#    attributes.
961				# 2) no quotes followed by comma and more
962				#    attributes.
963				# 3) no more attributes or comma with quotes.
964				# 4) no more attributes or comma without quotes.
965				#
966				(name, name1, val, val1, attrstring, end, end1) = m.groups();
967				if (name == None):
968					name = name1
969				if (end == None):
970					end = end1
971				if (val == None):
972					val = val1
973				if (val == None):
974					val = end
975				if (name == "stathz"):
976					self.setstathz(val, cpu)
977				attrs.append((name, val))
978			args = (dat, cpu, timestamp, attrs)
979			e = self.makeevent(group, id, type, args)
980			if (e == None):
981				print "Unknown type", type, lineno, line,
982
983	def makeevent(self, group, id, type, args):
984		e = None
985		source = self.makeid(group, id, type)
986		if (type == "state"):
987			e = StateEvent(source, *args)
988		elif (type == "counter"):
989			e = CountEvent(source, *args)
990		elif (type == "point"):
991			e = PointEvent(source, *args)
992		if (e != None):
993			source.addevent(e);
994		return e
995
996	def setstathz(self, val, cpu):
997		self.stathz = int(val)
998		cpu = int(cpu)
999		try:
1000			ticks = self.ticks[cpu]
1001		except:
1002			self.ticks[cpu] = 0
1003		self.ticks[cpu] += 1
1004
1005	def checkstamp(self, timestamp):
1006		timestamp = int(timestamp)
1007		if (self.timestamp_f == None):
1008			self.timestamp_f = timestamp;
1009		if (self.timestamp_l != None and timestamp > self.timestamp_l):
1010			return (0)
1011		self.timestamp_l = timestamp;
1012		return (1)
1013
1014	def makeid(self, group, id, type):
1015		for source in sources:
1016			if (source.name == id and source.group == group):
1017				return source
1018		if (type == "counter"):
1019			source = Counter(group, id)
1020		else:
1021			source = EventSource(group, id)
1022		sources.append(source)
1023		return (source)
1024
1025	def findid(self, id):
1026		for source in sources:
1027			if (source.name == id):
1028				return source
1029		return (None)
1030
1031	def timespan(self):
1032		return (self.timestamp_f - self.timestamp_l);
1033
1034	def ticksps(self):
1035		oneghz = 1000000000
1036		# Use user supplied clock first
1037		if (clockfreq != None):
1038			return int(clockfreq * oneghz)
1039
1040		# Check for a discovered clock
1041		if (self.stathz != None):
1042			return (self.timespan() / self.ticks[0]) * int(self.stathz)
1043		# Pretend we have a 1ns clock
1044		print "WARNING: No clock discovered and no frequency ",
1045		print "specified via the command line."
1046		print "Using fake 1ghz clock"
1047		return (oneghz);
1048
1049	def fixup(self):
1050		for source in sources:
1051			e = PadEvent(source, -1, self.timestamp_l)
1052			source.addevent(e)
1053			e = PadEvent(source, -1, self.timestamp_f, last=1)
1054			source.addlastevent(e)
1055			source.fixup()
1056		sources.sort()
1057
1058class SchedNames(Canvas):
1059	def __init__(self, master, display):
1060		self.display = display
1061		self.parent = master
1062		self.bdheight = master.bdheight
1063		self.items = {}
1064		self.ysize = 0
1065		self.lines = []
1066		Canvas.__init__(self, master, width=120,
1067		    height=display["height"], bg='grey',
1068		    scrollregion=(0, 0, 50, 100))
1069
1070	def moveline(self, cur_y, y):
1071		for line in self.lines:
1072			(x0, y0, x1, y1) = self.coords(line)
1073			if (cur_y != y0):
1074				continue
1075			self.move(line, 0, y)
1076			return
1077
1078	def draw(self):
1079		status.startup("Drawing names")
1080		ypos = 0
1081		self.configure(scrollregion=(0, 0,
1082		    self["width"], self.display.ysize()))
1083		for source in sources:
1084			l = self.create_line(0, ypos, self["width"], ypos,
1085			    width=1, fill="black", tags=("all","sources"))
1086			self.lines.append(l)
1087			ypos += self.bdheight
1088			ypos += source.ysize()
1089			t = source.drawname(self, ypos)
1090			self.items[t] = source
1091			ypos += self.bdheight
1092		self.ysize = ypos
1093		self.create_line(0, ypos, self["width"], ypos,
1094		    width=1, fill="black", tags=("all",))
1095		self.bind("<Button-1>", self.master.mousepress);
1096		self.bind("<Button-3>", self.master.mousepressright);
1097		self.bind("<ButtonRelease-1>", self.master.mouserelease);
1098		self.bind("<B1-Motion>", self.master.mousemotion);
1099
1100	def updatescroll(self):
1101		self.configure(scrollregion=(0, 0,
1102		    self["width"], self.display.ysize()))
1103
1104
1105class SchedDisplay(Canvas):
1106	def __init__(self, master):
1107		self.ratio = 1
1108		self.parent = master
1109		self.bdheight = master.bdheight
1110		self.items = {}
1111		self.lines = []
1112		Canvas.__init__(self, master, width=800, height=500, bg='grey',
1113		     scrollregion=(0, 0, 800, 500))
1114
1115	def prepare(self):
1116		#
1117		# Compute a ratio to ensure that the file's timespan fits into
1118		# 2^31.  Although python may handle larger values for X
1119		# values, the Tk internals do not.
1120		#
1121		self.ratio = (ktrfile.timespan() - 1) / 2**31 + 1
1122
1123	def draw(self):
1124		ypos = 0
1125		xsize = self.xsize()
1126		for source in sources:
1127			status.startup("Drawing " + source.name)
1128			l = self.create_line(0, ypos, xsize, ypos,
1129			    width=1, fill="black", tags=("all",))
1130			self.lines.append(l)
1131			ypos += self.bdheight
1132			ypos += source.ysize()
1133			source.draw(self, ypos)
1134			ypos += self.bdheight
1135			try:
1136				self.tag_raise("point", "state")
1137				self.tag_lower("cpuinfo", "all")
1138			except:
1139				pass
1140		self.create_line(0, ypos, xsize, ypos,
1141		    width=1, fill="black", tags=("all",))
1142		self.tag_bind("event", "<Enter>", self.mouseenter)
1143		self.tag_bind("event", "<Leave>", self.mouseexit)
1144		self.bind("<Button-1>", self.mousepress)
1145		self.bind("<Button-3>", self.master.mousepressright);
1146		self.bind("<Button-4>", self.wheelup)
1147		self.bind("<Button-5>", self.wheeldown)
1148		self.bind("<ButtonRelease-1>", self.master.mouserelease);
1149		self.bind("<B1-Motion>", self.master.mousemotion);
1150
1151	def moveline(self, cur_y, y):
1152		for line in self.lines:
1153			(x0, y0, x1, y1) = self.coords(line)
1154			if (cur_y != y0):
1155				continue
1156			self.move(line, 0, y)
1157			return
1158
1159	def mouseenter(self, event):
1160		item, = self.find_withtag(CURRENT)
1161		self.items[item].mouseenter(self)
1162
1163	def mouseexit(self, event):
1164		item, = self.find_withtag(CURRENT)
1165		self.items[item].mouseexit(self)
1166
1167	def mousepress(self, event):
1168		# Find out what's beneath us
1169		items = self.find_withtag(CURRENT)
1170		if (len(items) == 0):
1171			self.master.mousepress(event)
1172			return
1173		# Only grab mouse presses for things with event tags.
1174		item = items[0]
1175		tags = self.gettags(item)
1176		for tag in tags:
1177			if (tag == "event"):
1178				self.items[item].mousepress(self)
1179				return
1180		# Leave the rest to the master window
1181		self.master.mousepress(event)
1182
1183	def wheeldown(self, event):
1184		self.parent.display_yview("scroll", 1, "units")
1185
1186	def wheelup(self, event):
1187		self.parent.display_yview("scroll", -1, "units")
1188
1189	def xsize(self):
1190		return ((ktrfile.timespan() / self.ratio) + 20)
1191
1192	def ysize(self):
1193		ysize = 0
1194		for source in sources:
1195			if (source.hidden == 1):
1196				continue
1197			ysize += self.parent.sourcesize(source)
1198		return ysize
1199
1200	def scaleset(self, ratio):
1201		if (ktrfile == None):
1202			return
1203		oldratio = self.ratio
1204		xstart, xend = self.xview()
1205		midpoint = xstart + ((xend - xstart) / 2)
1206
1207		self.ratio = ratio
1208		self.updatescroll()
1209		self.scale("all", 0, 0, float(oldratio) / ratio, 1)
1210
1211		xstart, xend = self.xview()
1212		xsize = (xend - xstart) / 2
1213		self.xview_moveto(midpoint - xsize)
1214
1215	def updatescroll(self):
1216		self.configure(scrollregion=(0, 0, self.xsize(), self.ysize()))
1217
1218	def scaleget(self):
1219		return self.ratio
1220
1221	def getcolor(self, tag):
1222		return self.itemcget(tag, "fill")
1223
1224	def getstate(self, tag):
1225		return self.itemcget(tag, "state")
1226
1227	def setcolor(self, tag, color):
1228		self.itemconfigure(tag, state="normal", fill=color)
1229
1230	def hide(self, tag):
1231		self.itemconfigure(tag, state="hidden")
1232
1233class GraphMenu(Frame):
1234	def __init__(self, master):
1235		Frame.__init__(self, master, bd=2, relief=RAISED)
1236		self.conf = Menubutton(self, text="Configure")
1237		self.confmenu = Menu(self.conf, tearoff=0)
1238		self.confmenu.add_command(label="Event Colors",
1239		    command=self.econf)
1240		self.confmenu.add_command(label="CPU Colors",
1241		    command=self.cconf)
1242		self.confmenu.add_command(label="Source Configure",
1243		    command=self.sconf)
1244		self.conf["menu"] = self.confmenu
1245		self.conf.pack(side=LEFT)
1246
1247	def econf(self):
1248		ColorConfigure(eventcolors, "Event Display Configuration")
1249
1250	def cconf(self):
1251		ColorConfigure(cpucolors, "CPU Background Colors")
1252
1253	def sconf(self):
1254		SourceConfigure()
1255
1256class SchedGraph(Frame):
1257	def __init__(self, master):
1258		Frame.__init__(self, master)
1259		self.menu = None
1260		self.names = None
1261		self.display = None
1262		self.scale = None
1263		self.status = None
1264		self.bdheight = 10
1265		self.clicksource = None
1266		self.lastsource = None
1267		self.pack(expand=1, fill="both")
1268		self.buildwidgets()
1269		self.layout()
1270
1271	def buildwidgets(self):
1272		global status
1273		self.menu = GraphMenu(self)
1274		self.display = SchedDisplay(self)
1275		self.names = SchedNames(self, self.display)
1276		self.scale = Scaler(self, self.display)
1277		status = self.status = Status(self)
1278		self.scrollY = Scrollbar(self, orient="vertical",
1279		    command=self.display_yview)
1280		self.display.scrollX = Scrollbar(self, orient="horizontal",
1281		    command=self.display.xview)
1282		self.display["xscrollcommand"] = self.display.scrollX.set
1283		self.display["yscrollcommand"] = self.scrollY.set
1284		self.names["yscrollcommand"] = self.scrollY.set
1285
1286	def layout(self):
1287		self.columnconfigure(1, weight=1)
1288		self.rowconfigure(1, weight=1)
1289		self.menu.grid(row=0, column=0, columnspan=3, sticky=E+W)
1290		self.names.grid(row=1, column=0, sticky=N+S)
1291		self.display.grid(row=1, column=1, sticky=W+E+N+S)
1292		self.scrollY.grid(row=1, column=2, sticky=N+S)
1293		self.display.scrollX.grid(row=2, column=0, columnspan=2,
1294		    sticky=E+W)
1295		self.scale.grid(row=3, column=0, columnspan=3, sticky=E+W)
1296		self.status.grid(row=4, column=0, columnspan=3, sticky=E+W)
1297
1298	def draw(self):
1299		self.master.update()
1300		self.display.prepare()
1301		self.names.draw()
1302		self.display.draw()
1303		self.status.startup("")
1304		self.scale.set(250000)
1305		self.display.xview_moveto(0)
1306
1307	def mousepress(self, event):
1308		self.clicksource = self.sourceat(event.y)
1309
1310	def mousepressright(self, event):
1311		source = self.sourceat(event.y)
1312		if (source == None):
1313			return
1314		SourceContext(event, source)
1315
1316	def mouserelease(self, event):
1317		if (self.clicksource == None):
1318			return
1319		newsource = self.sourceat(event.y)
1320		if (self.clicksource != newsource):
1321			self.sourceswap(self.clicksource, newsource)
1322		self.clicksource = None
1323		self.lastsource = None
1324
1325	def mousemotion(self, event):
1326		if (self.clicksource == None):
1327			return
1328		newsource = self.sourceat(event.y)
1329		#
1330		# If we get a None source they moved off the page.
1331		# swapsource() can't handle moving multiple items so just
1332		# pretend we never clicked on anything to begin with so the
1333		# user can't mouseover a non-contiguous area.
1334		#
1335		if (newsource == None):
1336			self.clicksource = None
1337			self.lastsource = None
1338			return
1339		if (newsource == self.lastsource):
1340			return;
1341		self.lastsource = newsource
1342		if (newsource != self.clicksource):
1343			self.sourceswap(self.clicksource, newsource)
1344
1345	# These are here because this object controls layout
1346	def sourcestart(self, source):
1347		return source.y - self.bdheight - source.ysize()
1348
1349	def sourceend(self, source):
1350		return source.y + self.bdheight
1351
1352	def sourcesize(self, source):
1353		return (self.bdheight * 2) + source.ysize()
1354
1355	def sourceswap(self, source1, source2):
1356		# Sort so we always know which one is on top.
1357		if (source2.y < source1.y):
1358			swap = source1
1359			source1 = source2
1360			source2 = swap
1361		# Only swap adjacent sources
1362		if (self.sourceend(source1) != self.sourcestart(source2)):
1363			return
1364		# Compute start coordinates and target coordinates
1365		y1 = self.sourcestart(source1)
1366		y2 = self.sourcestart(source2)
1367		y1targ = y1 + self.sourcesize(source2)
1368		y2targ = y1
1369		#
1370		# If the sizes are not equal, adjust the start of the lower
1371		# source to account for the lost/gained space.
1372		#
1373		if (source1.ysize() != source2.ysize()):
1374			diff = source2.ysize() - source1.ysize()
1375			self.names.moveline(y2, diff);
1376			self.display.moveline(y2, diff)
1377		source1.move(self.display, 0, y1targ - y1)
1378		source2.move(self.display, 0, y2targ - y2)
1379		source1.movename(self.names, 0, y1targ - y1)
1380		source2.movename(self.names, 0, y2targ - y2)
1381
1382	def sourcepicky(self, source):
1383		if (source.hidden == 0):
1384			return self.sourcestart(source)
1385		# Revert to group based sort
1386		sources.sort()
1387		prev = None
1388		for s in sources:
1389			if (s == source):
1390				break
1391			if (s.hidden == 0):
1392				prev = s
1393		if (prev == None):
1394			newy = 0
1395		else:
1396			newy = self.sourcestart(prev) + self.sourcesize(prev)
1397		return newy
1398
1399	def sourceshow(self, source):
1400		if (source.hidden == 0):
1401			return;
1402		newy = self.sourcepicky(source)
1403		off = newy - self.sourcestart(source)
1404		self.sourceshiftall(newy-1, self.sourcesize(source))
1405		self.sourceshift(source, off)
1406		source.hidden = 0
1407
1408	#
1409	# Optimized source show of multiple entries that only moves each
1410	# existing entry once.  Doing sourceshow() iteratively is too
1411	# expensive due to python's canvas.move().
1412	#
1413	def sourceshowlist(self, srclist):
1414		srclist.sort(cmp=source_cmp_start)
1415		startsize = []
1416		for source in srclist:
1417			if (source.hidden == 0):
1418				srclist.remove(source)
1419			startsize.append((self.sourcepicky(source),
1420			    self.sourcesize(source)))
1421
1422		sources.sort(cmp=source_cmp_start, reverse=True)
1423		self.status.startup("Updating display...");
1424		for source in sources:
1425			if (source.hidden == 1):
1426				continue
1427			nstart = self.sourcestart(source)
1428			size = 0
1429			for hidden in startsize:
1430				(start, sz) = hidden
1431				if (start <= nstart or start+sz <= nstart):
1432					size += sz
1433			self.sourceshift(source, size)
1434		idx = 0
1435		size = 0
1436		for source in srclist:
1437			(newy, sz) = startsize[idx]
1438			off = (newy + size) - self.sourcestart(source)
1439			self.sourceshift(source, off)
1440			source.hidden = 0
1441			size += sz
1442			idx += 1
1443		self.names.updatescroll()
1444		self.display.updatescroll()
1445		self.status.set("")
1446
1447	#
1448	# Optimized source hide of multiple entries that only moves each
1449	# remaining entry once.  Doing sourcehide() iteratively is too
1450	# expensive due to python's canvas.move().
1451	#
1452	def sourcehidelist(self, srclist):
1453		srclist.sort(cmp=source_cmp_start)
1454		sources.sort(cmp=source_cmp_start)
1455		startsize = []
1456		off = len(sources) * 100
1457		self.status.startup("Updating display...");
1458		for source in srclist:
1459			if (source.hidden == 1):
1460				srclist.remove(source)
1461			#
1462			# Remember our old position so we can sort things
1463			# below us when we're done.
1464			#
1465			startsize.append((self.sourcestart(source),
1466			    self.sourcesize(source)))
1467			self.sourceshift(source, off)
1468			source.hidden = 1
1469
1470		idx = 0
1471		size = 0
1472		for hidden in startsize:
1473			(start, sz) = hidden
1474			size += sz
1475			if (idx + 1 < len(startsize)):
1476				(stop, sz) = startsize[idx+1]
1477			else:
1478				stop = self.display.ysize()
1479			idx += 1
1480			for source in sources:
1481				nstart = self.sourcestart(source)
1482				if (nstart < start or source.hidden == 1):
1483					continue
1484				if (nstart >= stop):
1485					break;
1486				self.sourceshift(source, -size)
1487		self.names.updatescroll()
1488		self.display.updatescroll()
1489		self.status.set("")
1490
1491	def sourcehide(self, source):
1492		if (source.hidden == 1):
1493			return;
1494		# Move it out of the visible area
1495		off = len(sources) * 100
1496		start = self.sourcestart(source)
1497		self.sourceshift(source, off)
1498		self.sourceshiftall(start, -self.sourcesize(source))
1499		source.hidden = 1
1500
1501	def sourceshift(self, source, off):
1502		start = self.sourcestart(source)
1503		source.move(self.display, 0, off)
1504		source.movename(self.names, 0, off)
1505		self.names.moveline(start, off);
1506		self.display.moveline(start, off)
1507		#
1508		# We update the idle tasks to shrink the dirtied area so
1509		# it does not always include the entire screen.
1510		#
1511		self.names.update_idletasks()
1512		self.display.update_idletasks()
1513
1514	def sourceshiftall(self, start, off):
1515		self.status.startup("Updating display...");
1516		for source in sources:
1517			nstart = self.sourcestart(source)
1518			if (nstart < start):
1519				continue;
1520			self.sourceshift(source, off)
1521		self.names.updatescroll()
1522		self.display.updatescroll()
1523		self.status.set("")
1524
1525	def sourceat(self, ypos):
1526		(start, end) = self.names.yview()
1527		starty = start * float(self.names.ysize)
1528		ypos += starty
1529		for source in sources:
1530			if (source.hidden == 1):
1531				continue;
1532			yend = self.sourceend(source)
1533			ystart = self.sourcestart(source)
1534			if (ypos >= ystart and ypos <= yend):
1535				return source
1536		return None
1537
1538	def display_yview(self, *args):
1539		self.names.yview(*args)
1540		self.display.yview(*args)
1541
1542	def setcolor(self, tag, color):
1543		self.display.setcolor(tag, color)
1544
1545	def hide(self, tag):
1546		self.display.hide(tag)
1547
1548	def getcolor(self, tag):
1549		return self.display.getcolor(tag)
1550
1551	def getstate(self, tag):
1552		return self.display.getstate(tag)
1553
1554if (len(sys.argv) != 2 and len(sys.argv) != 3):
1555	print "usage:", sys.argv[0], "<ktr file> [clock freq in ghz]"
1556	sys.exit(1)
1557
1558if (len(sys.argv) > 2):
1559	clockfreq = float(sys.argv[2])
1560
1561root = Tk()
1562root.title("Scheduler Graph")
1563colormap = Colormap(eventcolors)
1564cpucolormap = Colormap(cpucolors)
1565graph = SchedGraph(root)
1566ktrfile = KTRFile(sys.argv[1])
1567graph.draw()
1568root.mainloop()
1569