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