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