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