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