schedgraph.py revision 139319
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 139319 2004-12-26 01:18:49Z jeff $
28
29import sys
30import re
31from Tkinter import *
32
33# 1)  Add a per-thread summary display
34# 2)  Add bounding box style zoom.
35# 3)  Click to center.
36# 4)  Implement some sorting mechanism.
37
38ticksps = None
39status = None
40configtypes = []
41
42def ticks2sec(ticks):
43	ns = ticksps / 1000000000
44	ticks /= ns
45	if (ticks < 1000):
46		return (str(ticks) + "ns")
47	ticks /= 1000
48	if (ticks < 1000):
49		return (str(ticks) + "us")
50	ticks /= 1000
51	if (ticks < 1000):
52		return (str(ticks) + "ms")
53	ticks /= 1000
54	return (str(ticks) + "s")
55
56class Scaler(Frame):
57	def __init__(self, master, target):
58		Frame.__init__(self, master)
59		self.scale = Scale(self, command=self.scaleset,
60		    from_=1000, to_=1000000, orient=HORIZONTAL, resolution=1000)
61		self.label = Label(self, text="Ticks per pixel")
62		self.label.pack(side=LEFT)
63		self.scale.pack(fill="both", expand=1)
64		self.target = target
65		self.scale.set(target.scaleget())
66		self.initialized = 1
67
68	def scaleset(self, value):
69		self.target.scaleset(int(value))
70
71	def set(self, value):
72		self.scale.set(value)
73
74class Status(Frame):
75	def __init__(self, master):
76		Frame.__init__(self, master)
77		self.label = Label(self, bd=1, relief=SUNKEN, anchor=W)
78		self.label.pack(fill="both", expand=1)
79		self.clear()
80
81	def set(self, str):
82		self.label.config(text=str)
83
84	def clear(self):
85		self.label.config(text="")
86
87	def startup(self, str):
88		self.set(str)
89		root.update()
90
91class EventConf(Frame):
92	def __init__(self, master, name, color, enabled):
93		Frame.__init__(self, master)
94		self.name = name
95		self.color = StringVar()
96		self.color_default = color
97		self.color_current = color
98		self.color.set(color)
99		self.enabled = IntVar()
100		self.enabled_default = enabled
101		self.enabled_current = enabled
102		self.enabled.set(enabled)
103		self.draw()
104
105	def draw(self):
106		self.label = Label(self, text=self.name, anchor=W)
107		self.sample = Canvas(self, width=24, height=24,
108		    bg='grey')
109		self.rect = self.sample.create_rectangle(0, 0, 24, 24,
110		    fill=self.color.get())
111		self.list = OptionMenu(self, self.color,
112		    "dark red", "red", "pink",
113		    "dark orange", "orange",
114		    "yellow", "light yellow",
115		    "dark green", "green", "light green",
116		    "dark blue", "blue", "light blue",
117		    "dark violet", "violet", "purple",
118		    "dark grey", "light grey",
119		    "white", "black",
120		    command=self.setcolor)
121		self.checkbox = Checkbutton(self, text="enabled",
122		    variable=self.enabled)
123		self.label.grid(row=0, column=0, sticky=E+W)
124		self.sample.grid(row=0, column=1)
125		self.list.grid(row=0, column=2, sticky=E+W)
126		self.checkbox.grid(row=0, column=3)
127		self.columnconfigure(0, weight=1)
128		self.columnconfigure(2, minsize=110)
129
130	def setcolor(self, color):
131		self.color.set(color)
132		self.sample.itemconfigure(self.rect, fill=color)
133
134	def apply(self):
135		cchange = 0
136		echange = 0
137		if (self.color_current != self.color.get()):
138			cchange = 1
139		if (self.enabled_current != self.enabled.get()):
140			echange = 1
141		self.color_current = self.color.get()
142		self.enabled_current = self.enabled.get()
143		if (echange != 0):
144			if (self.enabled_current):
145				graph.setcolor(self.name, self.color_current)
146			else:
147				graph.hide(self.name)
148			return
149		if (cchange != 0):
150			graph.setcolor(self.name, self.color_current)
151
152	def revert(self):
153		self.setcolor(self.color_current)
154		self.enabled.set(self.enabled_current)
155
156	def default(self):
157		self.setcolor(self.color_default)
158		self.enabled.set(self.enabled_default)
159
160class EventConfigure(Toplevel):
161	def __init__(self):
162		Toplevel.__init__(self)
163		self.resizable(0, 0)
164		self.title("Event Configuration")
165		self.items = LabelFrame(self, text="Event Type")
166		self.buttons = Frame(self)
167		self.drawbuttons()
168		self.items.grid(row=0, column=0, sticky=E+W)
169		self.columnconfigure(0, weight=1)
170		self.buttons.grid(row=1, column=0, sticky=E+W)
171		self.types = []
172		self.irow = 0
173		for type in configtypes:
174			self.additem(type.name, type.color, type.enabled)
175
176	def additem(self, name, color, enabled=1):
177		item = EventConf(self.items, name, color, enabled)
178		self.types.append(item)
179		item.grid(row=self.irow, column=0, sticky=E+W)
180		self.irow += 1
181
182	def drawbuttons(self):
183		self.apply = Button(self.buttons, text="Apply",
184		    command=self.apress)
185		self.revert = Button(self.buttons, text="Revert",
186		    command=self.rpress)
187		self.default = Button(self.buttons, text="Default",
188		    command=self.dpress)
189		self.apply.grid(row=0, column=0, sticky=E+W)
190		self.revert.grid(row=0, column=1, sticky=E+W)
191		self.default.grid(row=0, column=2, sticky=E+W)
192		self.buttons.columnconfigure(0, weight=1)
193		self.buttons.columnconfigure(1, weight=1)
194		self.buttons.columnconfigure(2, weight=1)
195
196	def apress(self):
197		for item in self.types:
198			item.apply()
199
200	def rpress(self):
201		for item in self.types:
202			item.revert()
203
204	def dpress(self):
205		for item in self.types:
206			item.default()
207
208class EventView(Toplevel):
209	def __init__(self, event, canvas):
210		Toplevel.__init__(self)
211		self.resizable(0, 0)
212		self.title("Event")
213		self.event = event
214		self.frame = Frame(self)
215		self.frame.grid(row=0, column=0, sticky=N+S+E+W)
216		self.buttons = Frame(self)
217		self.buttons.grid(row=1, column=0, sticky=E+W)
218		self.canvas = canvas
219		self.drawlabels()
220		self.drawbuttons()
221		event.displayref(canvas)
222		self.bind("<Destroy>", self.destroycb)
223
224	def destroycb(self, event):
225		self.unbind("<Destroy>")
226		if (self.event != None):
227			self.event.displayunref(self.canvas)
228			self.event = None
229		self.destroy()
230
231	def clearlabels(self):
232		for label in self.frame.grid_slaves():
233			label.grid_remove()
234
235	def drawlabels(self):
236		ypos = 0
237		labels = self.event.labels()
238		while (len(labels) < 7):
239			labels.append(("", "", 0))
240		for label in labels:
241			name, value, linked = label
242			l = Label(self.frame, text=name, bd=1, width=15,
243			    relief=SUNKEN, anchor=W)
244			if (linked):
245				fgcolor = "blue"
246			else:
247				fgcolor = "black"
248			r = Label(self.frame, text=value, bd=1,
249			    relief=SUNKEN, anchor=W, fg=fgcolor)
250			l.grid(row=ypos, column=0, sticky=E+W)
251			r.grid(row=ypos, column=1, sticky=E+W)
252			if (linked):
253				r.bind("<Button-1>", self.linkpress)
254			ypos += 1
255		self.frame.columnconfigure(1, minsize=80)
256
257	def drawbuttons(self):
258		self.back = Button(self.buttons, text="<", command=self.bpress)
259		self.forw = Button(self.buttons, text=">", command=self.fpress)
260		self.new = Button(self.buttons, text="new", command=self.npress)
261		self.back.grid(row=0, column=0, sticky=E+W)
262		self.forw.grid(row=0, column=1, sticky=E+W)
263		self.new.grid(row=0, column=2, sticky=E+W)
264		self.buttons.columnconfigure(2, weight=1)
265
266	def newevent(self, event):
267		self.event.displayunref(self.canvas)
268		self.clearlabels()
269		self.event = event
270		self.event.displayref(self.canvas)
271		self.drawlabels()
272
273	def npress(self):
274		EventView(self.event, self.canvas)
275
276	def bpress(self):
277		prev = self.event.prev()
278		if (prev == None):
279			return
280		while (prev.real == 0):
281			prev = prev.prev()
282			if (prev == None):
283				return
284		self.newevent(prev)
285
286	def fpress(self):
287		next = self.event.next()
288		if (next == None):
289			return
290		while (next.real == 0):
291			next = next.next()
292			if (next == None):
293				return
294		self.newevent(next)
295
296	def linkpress(self, wevent):
297		event = self.event.getlinked()
298		if (event != None):
299			self.newevent(event)
300
301class Event:
302	name = "none"
303	color = "grey"
304	def __init__(self, source, cpu, timestamp, last=0):
305		self.source = source
306		self.cpu = cpu
307		self.timestamp = int(timestamp)
308		self.entries = []
309		self.real = 1
310		self.idx = None
311		self.state = 0
312		self.item = None
313		self.dispcnt = 0
314		self.linked = None
315		if (last):
316			source.lastevent(self)
317		else:
318			source.event(self)
319
320	def status(self):
321		statstr = self.name + " " + self.source.name
322		statstr += " on: cpu" + str(self.cpu)
323		statstr += " at: " + str(self.timestamp)
324		statstr += self.stattxt()
325		status.set(statstr)
326
327	def stattxt(self):
328		return ""
329
330	def textadd(self, tuple):
331		pass
332		self.entries.append(tuple)
333
334	def labels(self):
335		return [("Source:", self.source.name, 0),
336				("Event:", self.name, 0),
337				("CPU:", self.cpu, 0),
338				("Timestamp:", self.timestamp, 0)] + self.entries
339	def mouseenter(self, canvas, item):
340		self.displayref(canvas)
341		self.status()
342
343	def mouseexit(self, canvas, item):
344		self.displayunref(canvas)
345		status.clear()
346
347	def mousepress(self, canvas, item):
348		EventView(self, canvas)
349
350	def next(self):
351		return self.source.eventat(self.idx + 1)
352
353	def prev(self):
354		return self.source.eventat(self.idx - 1)
355
356	def displayref(self, canvas):
357		if (self.dispcnt == 0):
358			canvas.itemconfigure(self.item, width=2)
359		self.dispcnt += 1
360
361	def displayunref(self, canvas):
362		self.dispcnt -= 1
363		if (self.dispcnt == 0):
364			canvas.itemconfigure(self.item, width=0)
365			canvas.tag_raise("point", "state")
366
367	def getlinked(self):
368		return self.linked.findevent(self.timestamp)
369
370class PointEvent(Event):
371	def __init__(self, thread, cpu, timestamp, last=0):
372		Event.__init__(self, thread, cpu, timestamp, last)
373
374	def draw(self, canvas, xpos, ypos):
375		l = canvas.create_oval(xpos - 6, ypos + 1, xpos + 6, ypos - 11,
376		    fill=self.color, tags=("all", "point", "event")
377		    + (self.name,), width=0)
378		canvas.events[l] = self
379		self.item = l
380		if (self.enabled == 0):
381			canvas.itemconfigure(l, state="hidden")
382
383		return (xpos)
384
385class StateEvent(Event):
386	def __init__(self, thread, cpu, timestamp, last=0):
387		Event.__init__(self, thread, cpu, timestamp, last)
388		self.duration = 0
389		self.skipnext = 0
390		self.skipself = 0
391		self.state = 1
392
393	def draw(self, canvas, xpos, ypos):
394		next = self.nextstate()
395		if (self.skipself == 1 or next == None):
396			return (xpos)
397		if (self.skipnext):
398			skipped = next
399			next.skipself = 1
400			next.real = 0
401			next = next.nextstate()
402			if (next == None):
403				next = skipped
404		self.duration = next.timestamp - self.timestamp
405		delta = self.duration / canvas.ratio
406		l = canvas.create_rectangle(xpos, ypos,
407		    xpos + delta, ypos - 10, fill=self.color, width=0,
408		    tags=("all", "state", "event") + (self.name,))
409		canvas.events[l] = self
410		self.item = l
411		if (self.enabled == 0):
412			canvas.itemconfigure(l, state="hidden")
413
414		return (xpos + delta)
415
416	def stattxt(self):
417		return " duration: " + ticks2sec(self.duration)
418
419	def nextstate(self):
420		next = self.next()
421		while (next != None and next.state == 0):
422			next = next.next()
423		return (next)
424
425	def labels(self):
426		return [("Source:", self.source.name, 0),
427				("Event:", self.name, 0),
428				("Timestamp:", self.timestamp, 0),
429				("CPU:", self.cpu, 0),
430				("Duration:", ticks2sec(self.duration), 0)] \
431				 + self.entries
432
433class Count(Event):
434	name = "Count"
435	color = "red"
436	enabled = 1
437	def __init__(self, source, cpu, timestamp, count):
438		self.count = int(count)
439		Event.__init__(self, source, cpu, timestamp)
440		self.duration = 0
441		self.textadd(("count:", self.count, 0))
442
443	def draw(self, canvas, xpos, ypos):
444		next = self.next()
445		self.duration = next.timestamp - self.timestamp
446		delta = self.duration / canvas.ratio
447		yhight = self.source.yscale() * self.count
448		l = canvas.create_rectangle(xpos, ypos - yhight,
449		    xpos + delta, ypos, fill=self.color, width=0,
450		    tags=("all", "count", "event") + (self.name,))
451		canvas.events[l] = self
452		self.item = l
453		if (self.enabled == 0):
454			canvas.itemconfigure(l, state="hidden")
455		return (xpos + delta)
456
457	def stattxt(self):
458		return " count: " + str(self.count)
459
460configtypes.append(Count)
461
462class Running(StateEvent):
463	name = "running"
464	color = "green"
465	enabled = 1
466	def __init__(self, thread, cpu, timestamp, prio):
467		StateEvent.__init__(self, thread, cpu, timestamp)
468		self.prio = prio
469		self.textadd(("prio:", self.prio, 0))
470
471configtypes.append(Running)
472
473class Idle(StateEvent):
474	name = "idle"
475	color = "grey"
476	enabled = 0
477	def __init__(self, thread, cpu, timestamp, prio):
478		StateEvent.__init__(self, thread, cpu, timestamp)
479		self.prio = prio
480		self.textadd(("prio:", self.prio, 0))
481
482configtypes.append(Idle)
483
484class Yielding(StateEvent):
485	name = "yielding"
486	color = "yellow"
487	enabled = 1
488	def __init__(self, thread, cpu, timestamp, prio):
489		StateEvent.__init__(self, thread, cpu, timestamp)
490		self.skipnext = 1
491		self.prio = prio
492		self.textadd(("prio:", self.prio, 0))
493
494configtypes.append(Yielding)
495
496class Swapped(StateEvent):
497	name = "swapped"
498	color = "violet"
499	enabled = 1
500	def __init__(self, thread, cpu, timestamp, prio):
501		StateEvent.__init__(self, thread, cpu, timestamp)
502		self.prio = prio
503		self.textadd(("prio:", self.prio, 0))
504
505configtypes.append(Swapped)
506
507class Suspended(StateEvent):
508	name = "suspended"
509	color = "purple"
510	enabled = 1
511	def __init__(self, thread, cpu, timestamp, prio):
512		StateEvent.__init__(self, thread, cpu, timestamp)
513		self.prio = prio
514		self.textadd(("prio:", self.prio, 0))
515
516configtypes.append(Suspended)
517
518class Iwait(StateEvent):
519	name = "iwait"
520	color = "grey"
521	enabled = 0
522	def __init__(self, thread, cpu, timestamp, prio):
523		StateEvent.__init__(self, thread, cpu, timestamp)
524		self.prio = prio
525		self.textadd(("prio:", self.prio, 0))
526
527configtypes.append(Iwait)
528
529class Preempted(StateEvent):
530	name = "preempted"
531	color = "red"
532	enabled = 1
533	def __init__(self, thread, cpu, timestamp, prio, bythread):
534		StateEvent.__init__(self, thread, cpu, timestamp)
535		self.skipnext = 1
536		self.prio = prio
537		self.linked = bythread
538		self.textadd(("prio:", self.prio, 0))
539		self.textadd(("by thread:", self.linked.name, 1))
540
541configtypes.append(Preempted)
542
543class Sleep(StateEvent):
544	name = "sleep"
545	color = "blue"
546	enabled = 1
547	def __init__(self, thread, cpu, timestamp, prio, wmesg):
548		StateEvent.__init__(self, thread, cpu, timestamp)
549		self.prio = prio
550		self.wmesg = wmesg
551		self.textadd(("prio:", self.prio, 0))
552		self.textadd(("wmesg:", self.wmesg, 0))
553
554	def stattxt(self):
555		statstr = StateEvent.stattxt(self)
556		statstr += " sleeping on: " + self.wmesg
557		return (statstr)
558
559configtypes.append(Sleep)
560
561class Blocked(StateEvent):
562	name = "blocked"
563	color = "dark red"
564	enabled = 1
565	def __init__(self, thread, cpu, timestamp, prio, lock):
566		StateEvent.__init__(self, thread, cpu, timestamp)
567		self.prio = prio
568		self.lock = lock
569		self.textadd(("prio:", self.prio, 0))
570		self.textadd(("lock:", self.lock, 0))
571
572	def stattxt(self):
573		statstr = StateEvent.stattxt(self)
574		statstr += " blocked on: " + self.lock
575		return (statstr)
576
577configtypes.append(Blocked)
578
579class KsegrpRunq(StateEvent):
580	name = "KsegrpRunq"
581	color = "orange"
582	enabled = 1
583	def __init__(self, thread, cpu, timestamp, prio, bythread):
584		StateEvent.__init__(self, thread, cpu, timestamp)
585		self.prio = prio
586		self.linked = bythread
587		self.textadd(("prio:", self.prio, 0))
588		self.textadd(("by thread:", self.linked.name, 1))
589
590configtypes.append(KsegrpRunq)
591
592class Runq(StateEvent):
593	name = "Runq"
594	color = "yellow"
595	enabled = 1
596	def __init__(self, thread, cpu, timestamp, prio, bythread):
597		StateEvent.__init__(self, thread, cpu, timestamp)
598		self.prio = prio
599		self.linked = bythread
600		self.textadd(("prio:", self.prio, 0))
601		self.textadd(("by thread:", self.linked.name, 1))
602
603configtypes.append(Runq)
604
605class Sched_exit(StateEvent):
606	name = "exit"
607	color = "grey"
608	enabled = 0
609	def __init__(self, thread, cpu, timestamp, prio):
610		StateEvent.__init__(self, thread, cpu, timestamp)
611		self.name = "sched_exit"
612		self.prio = prio
613		self.textadd(("prio:", self.prio, 0))
614
615configtypes.append(Sched_exit)
616
617class Padevent(StateEvent):
618	def __init__(self, thread, cpu, timestamp, last=0):
619		StateEvent.__init__(self, thread, cpu, timestamp, last)
620		self.name = "pad"
621		self.real = 0
622
623	def draw(self, canvas, xpos, ypos):
624		next = self.next()
625		if (next == None):
626			return (xpos)
627		self.duration = next.timestamp - self.timestamp
628		delta = self.duration / canvas.ratio
629		return (xpos + delta)
630
631class Tick(PointEvent):
632	name = "tick"
633	color = "black"
634	enabled = 0
635	def __init__(self, thread, cpu, timestamp, prio, stathz):
636		PointEvent.__init__(self, thread, cpu, timestamp)
637		self.prio = prio
638		self.textadd(("prio:", self.prio, 0))
639
640configtypes.append(Tick)
641
642class Prio(PointEvent):
643	name = "prio"
644	color = "black"
645	enabled = 0
646	def __init__(self, thread, cpu, timestamp, prio, newprio, bythread):
647		PointEvent.__init__(self, thread, cpu, timestamp)
648		self.prio = prio
649		self.newprio = newprio
650		self.linked = bythread
651		self.textadd(("new prio:", self.newprio, 0))
652		self.textadd(("prio:", self.prio, 0))
653		if (self.linked != self.source):
654			self.textadd(("by thread:", self.linked.name, 1))
655		else:
656			self.textadd(("by thread:", self.linked.name, 0))
657
658configtypes.append(Prio)
659
660class Lend(PointEvent):
661	name = "lend"
662	color = "black"
663	enabled = 0
664	def __init__(self, thread, cpu, timestamp, prio, tothread):
665		PointEvent.__init__(self, thread, cpu, timestamp)
666		self.prio = prio
667		self.linked = tothread
668		self.textadd(("prio:", self.prio, 0))
669		self.textadd(("to thread:", self.linked.name, 1))
670
671configtypes.append(Lend)
672
673class Wokeup(PointEvent):
674	name = "wokeup"
675	color = "black"
676	enabled = 0
677	def __init__(self, thread, cpu, timestamp, ranthread):
678		PointEvent.__init__(self, thread, cpu, timestamp)
679		self.linked = ranthread
680		self.textadd(("ran thread:", self.linked.name, 1))
681
682configtypes.append(Wokeup)
683
684class EventSource:
685	def __init__(self, name):
686		self.name = name
687		self.events = []
688		self.cpu = 0
689		self.cpux = 0
690
691	def fixup(self):
692		pass
693
694	def event(self, event):
695		self.events.insert(0, event)
696
697	def remove(self, event):
698		self.events.remove(event)
699
700	def lastevent(self, event):
701		self.events.append(event)
702
703	def draw(self, canvas, ypos):
704		xpos = 10
705		self.cpux = 10
706		self.cpu = self.events[1].cpu
707		for i in range(0, len(self.events)):
708			self.events[i].idx = i
709		for event in self.events:
710			if (event.cpu != self.cpu and event.cpu != -1):
711				self.drawcpu(canvas, xpos, ypos)
712				self.cpux = xpos
713				self.cpu = event.cpu
714			xpos = event.draw(canvas, xpos, ypos)
715		self.drawcpu(canvas, xpos, ypos)
716
717	def drawname(self, canvas, ypos):
718		ypos = ypos - (self.ysize() / 2)
719		canvas.create_text(10, ypos, anchor="w", text=self.name)
720
721	def drawcpu(self, canvas, xpos, ypos):
722		cpu = int(self.cpu)
723		if (cpu == 0):
724			color = 'light grey'
725		elif (cpu == 1):
726			color = 'dark grey'
727		elif (cpu == 2):
728			color = 'light blue'
729		elif (cpu == 3):
730			color == 'light green'
731		else:
732			color == "white"
733		l = canvas.create_rectangle(self.cpux,
734		    ypos - self.ysize() - canvas.bdheight,
735		    xpos, ypos + canvas.bdheight, fill=color, width=0,
736		    tags=("all", "cpuinfo"))
737
738	def ysize(self):
739		return (None)
740
741	def eventat(self, i):
742		if (i >= len(self.events)):
743			return (None)
744		event = self.events[i]
745		return (event)
746
747	def findevent(self, timestamp):
748		for event in self.events:
749			if (event.timestamp >= timestamp and event.real):
750				return (event)
751		return (None)
752
753class Thread(EventSource):
754	names = {}
755	def __init__(self, td, pcomm):
756		EventSource.__init__(self, pcomm)
757		self.str = td
758		try:
759			cnt = Thread.names[pcomm]
760		except:
761			Thread.names[pcomm] = 0
762			return
763		Thread.names[pcomm] = cnt + 1
764
765	def fixup(self):
766		cnt = Thread.names[self.name]
767		if (cnt == 0):
768			return
769		cnt -= 1
770		Thread.names[self.name] = cnt
771		self.name += " td" + str(cnt)
772
773	def ysize(self):
774		return (10)
775
776class Counter(EventSource):
777	max = 0
778	def __init__(self, name):
779		EventSource.__init__(self, name)
780
781	def event(self, event):
782		EventSource.event(self, event)
783		try:
784			count = event.count
785		except:
786			return
787		count = int(count)
788		if (count > Counter.max):
789			Counter.max = count
790
791	def ysize(self):
792		return (80)
793
794	def yscale(self):
795		return (self.ysize() / Counter.max)
796
797
798class KTRFile:
799	def __init__(self, file):
800		self.timestamp_first = None
801		self.timestamp_last = None
802		self.lineno = -1
803		self.threads = []
804		self.sources = []
805		self.ticks = {}
806		self.load = {}
807
808		self.parse(file)
809		self.fixup()
810		global ticksps
811		ticksps = self.ticksps()
812
813	def parse(self, file):
814		try:
815			ifp = open(file)
816		except:
817			print "Can't open", file
818			sys.exit(1)
819
820		ktrhdr = "\s+\d+\s+(\d+)\s+(\d+)\s+"
821		tdname = "(\S+)\(([^)]*)\)"
822
823		ktrstr = "mi_switch: " + tdname
824		ktrstr += " prio (\d+) inhibit (\d+) wmesg (\S+) lock (\S+)"
825		switchout_re = re.compile(ktrhdr + ktrstr)
826
827		ktrstr = "mi_switch: " + tdname + " prio (\d+) idle"
828		idled_re = re.compile(ktrhdr + ktrstr)
829
830		ktrstr = "mi_switch: " + tdname + " prio (\d+) preempted by "
831		ktrstr += tdname
832		preempted_re = re.compile(ktrhdr + ktrstr)
833
834		ktrstr = "mi_switch: running " + tdname + " prio (\d+)"
835		switchin_re = re.compile(ktrhdr + ktrstr)
836
837		ktrstr = "sched_add: " + tdname + " prio (\d+) by " + tdname
838		sched_add_re = re.compile(ktrhdr + ktrstr)
839
840		ktrstr = "setrunqueue: " + tdname + " prio (\d+) by " + tdname
841		setrunqueue_re = re.compile(ktrhdr + ktrstr)
842
843		ktrstr = "sched_rem: " + tdname + " prio (\d+) by " + tdname
844		sched_rem_re = re.compile(ktrhdr + ktrstr)
845
846		ktrstr = "sched_exit_thread: " + tdname + " prio (\d+)"
847		sched_exit_re = re.compile(ktrhdr + ktrstr)
848
849		ktrstr = "statclock: " + tdname + " prio (\d+)"
850		ktrstr += " stathz (\d+)"
851		sched_clock_re = re.compile(ktrhdr + ktrstr)
852
853		ktrstr = "sched_prio: " + tdname + " prio (\d+)"
854		ktrstr += " newprio (\d+) by " + tdname
855		sched_prio_re = re.compile(ktrhdr + ktrstr)
856
857		cpuload_re = re.compile(ktrhdr + "load: (\d+)")
858		loadglobal_re = re.compile(ktrhdr + "global load: (\d+)")
859
860		parsers = [[cpuload_re, self.cpuload],
861			   [loadglobal_re, self.loadglobal],
862			   [switchin_re, self.switchin],
863			   [switchout_re, self.switchout],
864			   [sched_add_re, self.sched_add],
865			   [setrunqueue_re, self.sched_rem],
866			   [sched_prio_re, self.sched_prio],
867			   [preempted_re, self.preempted],
868			   [sched_rem_re, self.sched_rem],
869			   [sched_exit_re, self.sched_exit],
870			   [sched_clock_re, self.sched_clock],
871			   [idled_re, self.idled]]
872
873		for line in ifp.readlines():
874			self.lineno += 1
875			if ((self.lineno % 1024) == 0):
876				status.startup("Parsing line " +
877				    str(self.lineno))
878			for p in parsers:
879				m = p[0].match(line)
880				if (m != None):
881					p[1](*m.groups())
882					break
883			# if (m == None):
884			# 	print line,
885
886	def checkstamp(self, timestamp):
887		timestamp = int(timestamp)
888		if (self.timestamp_first == None):
889			self.timestamp_first = timestamp
890		if (timestamp > self.timestamp_first):
891			print "Bad timestamp on line ", self.lineno
892			return (0)
893		self.timestamp_last = timestamp
894		return (1)
895
896	def timespan(self):
897		return (self.timestamp_first - self.timestamp_last);
898
899	def ticksps(self):
900		return (self.timespan() / self.ticks[0]) * int(self.stathz)
901
902	def switchout(self, cpu, timestamp, td, pcomm, prio, inhibit, wmesg, lock):
903		TDI_SUSPENDED = 0x0001
904		TDI_SLEEPING = 0x0002
905		TDI_SWAPPED = 0x0004
906		TDI_LOCK = 0x0008
907		TDI_IWAIT = 0x0010
908
909		if (self.checkstamp(timestamp) == 0):
910			return
911		inhibit = int(inhibit)
912		thread = self.findtd(td, pcomm)
913		if (inhibit & TDI_SWAPPED):
914			Swapped(thread, cpu, timestamp, prio)
915		elif (inhibit & TDI_SLEEPING):
916			Sleep(thread, cpu, timestamp, prio, wmesg)
917		elif (inhibit & TDI_LOCK):
918			Blocked(thread, cpu, timestamp, prio, lock)
919		elif (inhibit & TDI_IWAIT):
920			Iwait(thread, cpu, timestamp, prio)
921		elif (inhibit & TDI_SUSPENDED):
922			Suspended(thread, cpu, timestamp, prio)
923		elif (inhibit == 0):
924			Yielding(thread, cpu, timestamp, prio)
925		else:
926			print "Unknown event", inhibit
927			sys.exit(1)
928
929	def idled(self, cpu, timestamp, td, pcomm, prio):
930		if (self.checkstamp(timestamp) == 0):
931			return
932		thread = self.findtd(td, pcomm)
933		Idle(thread, cpu, timestamp, prio)
934
935	def preempted(self, cpu, timestamp, td, pcomm, prio, bytd, bypcomm):
936		if (self.checkstamp(timestamp) == 0):
937			return
938		thread = self.findtd(td, pcomm)
939		Preempted(thread, cpu, timestamp, prio,
940		    self.findtd(bytd, bypcomm))
941
942	def switchin(self, cpu, timestamp, td, pcomm, prio):
943		if (self.checkstamp(timestamp) == 0):
944			return
945		thread = self.findtd(td, pcomm)
946		Running(thread, cpu, timestamp, prio)
947
948	def sched_add(self, cpu, timestamp, td, pcomm, prio, bytd, bypcomm):
949		if (self.checkstamp(timestamp) == 0):
950			return
951		thread = self.findtd(td, pcomm)
952		bythread = self.findtd(bytd, bypcomm)
953		Runq(thread, cpu, timestamp, prio, bythread)
954		Wokeup(bythread, cpu, timestamp, thread)
955
956	def sched_rem(self, cpu, timestamp, td, pcomm, prio, bytd, bypcomm):
957		if (self.checkstamp(timestamp) == 0):
958			return
959		thread = self.findtd(td, pcomm)
960		KsegrpRunq(thread, cpu, timestamp, prio,
961		    self.findtd(bytd, bypcomm))
962
963	def sched_exit(self, cpu, timestamp, td, pcomm, prio):
964		if (self.checkstamp(timestamp) == 0):
965			return
966		thread = self.findtd(td, pcomm)
967		Sched_exit(thread, cpu, timestamp, prio)
968
969	def sched_clock(self, cpu, timestamp, td, pcomm, prio, stathz):
970		if (self.checkstamp(timestamp) == 0):
971			return
972		self.stathz = stathz
973		cpu = int(cpu)
974		try:
975			ticks = self.ticks[cpu]
976		except:
977			self.ticks[cpu] = 0
978		self.ticks[cpu] += 1
979		thread = self.findtd(td, pcomm)
980		Tick(thread, cpu, timestamp, prio, stathz)
981
982	def sched_prio(self, cpu, timestamp, td, pcomm, prio, newprio, bytd, bypcomm):
983		if (prio == newprio):
984			return
985		if (self.checkstamp(timestamp) == 0):
986			return
987		thread = self.findtd(td, pcomm)
988		bythread = self.findtd(bytd, bypcomm)
989		Prio(thread, cpu, timestamp, prio, newprio, bythread)
990		Lend(bythread, cpu, timestamp, newprio, thread)
991
992	def cpuload(self, cpu, timestamp, count):
993		cpu = int(cpu)
994		try:
995			load = self.load[cpu]
996		except:
997			load = Counter("cpu" + str(cpu) + " load")
998			self.load[cpu] = load
999			self.sources.insert(0, load)
1000		Count(load, cpu, timestamp, count)
1001
1002	def loadglobal(self, cpu, timestamp, count):
1003		cpu = 0
1004		try:
1005			load = self.load[cpu]
1006		except:
1007			load = Counter("CPU load")
1008			self.load[cpu] = load
1009			self.sources.insert(0, load)
1010		Count(load, cpu, timestamp, count)
1011
1012	def findtd(self, td, pcomm):
1013		for thread in self.threads:
1014			if (thread.str == td and thread.name == pcomm):
1015				return thread
1016		thread = Thread(td, pcomm)
1017		self.threads.append(thread)
1018		self.sources.append(thread)
1019		return (thread)
1020
1021	def fixup(self):
1022		for source in self.sources:
1023			Padevent(source, -1, self.timestamp_last)
1024			Padevent(source, -1, self.timestamp_first, last=1)
1025			source.fixup()
1026
1027class SchedDisplay(Canvas):
1028	def __init__(self, master):
1029		self.ratio = 1
1030		self.ktrfile = None
1031		self.sources = None
1032		self.bdheight = 10
1033		self.events = {}
1034
1035		Canvas.__init__(self, master, width=800, height=500, bg='grey',
1036		     scrollregion=(0, 0, 800, 500))
1037
1038	def setfile(self, ktrfile):
1039		self.ktrfile = ktrfile
1040		self.sources = ktrfile.sources
1041
1042	def draw(self):
1043		ypos = 0
1044		xsize = self.xsize()
1045		for source in self.sources:
1046			status.startup("Drawing " + source.name)
1047			self.create_line(0, ypos, xsize, ypos,
1048			    width=1, fill="black", tags=("all",))
1049			ypos += self.bdheight
1050			ypos += source.ysize()
1051			source.draw(self, ypos)
1052			ypos += self.bdheight
1053			try:
1054				self.tag_raise("point", "state")
1055				self.tag_lower("cpuinfo", "all")
1056			except:
1057				pass
1058		self.create_line(0, ypos, xsize, ypos,
1059		    width=1, fill="black", tags=("all",))
1060		self.tag_bind("event", "<Enter>", self.mouseenter)
1061		self.tag_bind("event", "<Leave>", self.mouseexit)
1062		self.tag_bind("event", "<Button-1>", self.mousepress)
1063
1064	def mouseenter(self, event):
1065		item, = self.find_withtag(CURRENT)
1066		event = self.events[item]
1067		event.mouseenter(self, item)
1068
1069	def mouseexit(self, event):
1070		item, = self.find_withtag(CURRENT)
1071		event = self.events[item]
1072		event.mouseexit(self, item)
1073
1074	def mousepress(self, event):
1075		item, = self.find_withtag(CURRENT)
1076		event = self.events[item]
1077		event.mousepress(self, item)
1078
1079	def drawnames(self, canvas):
1080		status.startup("Drawing names")
1081		ypos = 0
1082		canvas.configure(scrollregion=(0, 0,
1083		    canvas["width"], self.ysize()))
1084		for source in self.sources:
1085			canvas.create_line(0, ypos, canvas["width"], ypos,
1086			    width=1, fill="black", tags=("all",))
1087			ypos += self.bdheight
1088			ypos += source.ysize()
1089			source.drawname(canvas, ypos)
1090			ypos += self.bdheight
1091		canvas.create_line(0, ypos, canvas["width"], ypos,
1092		    width=1, fill="black", tags=("all",))
1093
1094	def xsize(self):
1095		return ((self.ktrfile.timespan() / self.ratio) + 20)
1096
1097	def ysize(self):
1098		ysize = 0
1099		for source in self.sources:
1100			ysize += source.ysize() + (self.bdheight * 2)
1101		return (ysize)
1102
1103	def scaleset(self, ratio):
1104		if (self.ktrfile == None):
1105			return
1106		oldratio = self.ratio
1107		xstart, ystart = self.xview()
1108		length = (float(self["width"]) / self.xsize())
1109		middle = xstart + (length / 2)
1110
1111		self.ratio = ratio
1112		self.configure(scrollregion=(0, 0, self.xsize(), self.ysize()))
1113		self.scale("all", 0, 0, float(oldratio) / ratio, 1)
1114
1115		length = (float(self["width"]) / self.xsize())
1116		xstart = middle - (length / 2)
1117		self.xview_moveto(xstart)
1118
1119	def scaleget(self):
1120		return self.ratio
1121
1122	def setcolor(self, tag, color):
1123		self.itemconfigure(tag, state="normal", fill=color)
1124
1125	def hide(self, tag):
1126		self.itemconfigure(tag, state="hidden")
1127
1128class GraphMenu(Frame):
1129	def __init__(self, master):
1130		Frame.__init__(self, master, bd=2, relief=RAISED)
1131		self.view = Menubutton(self, text="Configure")
1132		self.viewmenu = Menu(self.view, tearoff=0)
1133		self.viewmenu.add_command(label="Events",
1134		    command=self.econf)
1135		self.view["menu"] = self.viewmenu
1136		self.view.pack(side=LEFT)
1137
1138	def econf(self):
1139		EventConfigure()
1140
1141
1142class SchedGraph(Frame):
1143	def __init__(self, master):
1144		Frame.__init__(self, master)
1145		self.menu = None
1146		self.names = None
1147		self.display = None
1148		self.scale = None
1149		self.status = None
1150		self.pack(expand=1, fill="both")
1151		self.buildwidgets()
1152		self.layout()
1153		self.draw(sys.argv[1])
1154
1155	def buildwidgets(self):
1156		global status
1157		self.menu = GraphMenu(self)
1158		self.display = SchedDisplay(self)
1159		self.names = Canvas(self,
1160		    width=100, height=self.display["height"],
1161		    bg='grey', scrollregion=(0, 0, 50, 100))
1162		self.scale = Scaler(self, self.display)
1163		status = self.status = Status(self)
1164		self.scrollY = Scrollbar(self, orient="vertical",
1165		    command=self.display_yview)
1166		self.display.scrollX = Scrollbar(self, orient="horizontal",
1167		    command=self.display.xview)
1168		self.display["xscrollcommand"] = self.display.scrollX.set
1169		self.display["yscrollcommand"] = self.scrollY.set
1170		self.names["yscrollcommand"] = self.scrollY.set
1171
1172	def layout(self):
1173		self.columnconfigure(1, weight=1)
1174		self.rowconfigure(1, weight=1)
1175		self.menu.grid(row=0, column=0, columnspan=3, sticky=E+W)
1176		self.names.grid(row=1, column=0, sticky=N+S)
1177		self.display.grid(row=1, column=1, sticky=W+E+N+S)
1178		self.scrollY.grid(row=1, column=2, sticky=N+S)
1179		self.display.scrollX.grid(row=2, column=0, columnspan=2,
1180		    sticky=E+W)
1181		self.scale.grid(row=3, column=0, columnspan=3, sticky=E+W)
1182		self.status.grid(row=4, column=0, columnspan=3, sticky=E+W)
1183
1184	def draw(self, file):
1185		self.master.update()
1186		ktrfile = KTRFile(file)
1187		self.display.setfile(ktrfile)
1188		self.display.drawnames(self.names)
1189		self.display.draw()
1190		self.scale.set(250000)
1191		self.display.xview_moveto(0)
1192
1193	def display_yview(self, *args):
1194		self.names.yview(*args)
1195		self.display.yview(*args)
1196
1197	def setcolor(self, tag, color):
1198		self.display.setcolor(tag, color)
1199
1200	def hide(self, tag):
1201		self.display.hide(tag)
1202
1203if (len(sys.argv) != 2):
1204	print "usage:", sys.argv[0], "<ktr file>"
1205	sys.exit(1)
1206
1207root = Tk()
1208root.title("Scheduler Graph")
1209graph = SchedGraph(root)
1210root.mainloop()
1211