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