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