schedgraph.py revision 139321
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 139321 2004-12-26 03:25:08Z 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		while (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.skipnext -= 1
405		self.duration = next.timestamp - self.timestamp
406		delta = self.duration / canvas.ratio
407		l = canvas.create_rectangle(xpos, ypos,
408		    xpos + delta, ypos - 10, fill=self.color, width=0,
409		    tags=("all", "state", "event") + (self.name,))
410		canvas.events[l] = self
411		self.item = l
412		if (self.enabled == 0):
413			canvas.itemconfigure(l, state="hidden")
414
415		return (xpos + delta)
416
417	def stattxt(self):
418		return " duration: " + ticks2sec(self.duration)
419
420	def nextstate(self):
421		next = self.next()
422		while (next != None and next.state == 0):
423			next = next.next()
424		return (next)
425
426	def labels(self):
427		return [("Source:", self.source.name, 0),
428				("Event:", self.name, 0),
429				("Timestamp:", self.timestamp, 0),
430				("CPU:", self.cpu, 0),
431				("Duration:", ticks2sec(self.duration), 0)] \
432				 + self.entries
433
434class Count(Event):
435	name = "Count"
436	color = "red"
437	enabled = 1
438	def __init__(self, source, cpu, timestamp, count):
439		self.count = int(count)
440		Event.__init__(self, source, cpu, timestamp)
441		self.duration = 0
442		self.textadd(("count:", self.count, 0))
443
444	def draw(self, canvas, xpos, ypos):
445		next = self.next()
446		self.duration = next.timestamp - self.timestamp
447		delta = self.duration / canvas.ratio
448		yhight = self.source.yscale() * self.count
449		l = canvas.create_rectangle(xpos, ypos - yhight,
450		    xpos + delta, ypos, fill=self.color, width=0,
451		    tags=("all", "count", "event") + (self.name,))
452		canvas.events[l] = self
453		self.item = l
454		if (self.enabled == 0):
455			canvas.itemconfigure(l, state="hidden")
456		return (xpos + delta)
457
458	def stattxt(self):
459		return " count: " + str(self.count)
460
461configtypes.append(Count)
462
463class Running(StateEvent):
464	name = "running"
465	color = "green"
466	enabled = 1
467	def __init__(self, thread, cpu, timestamp, prio):
468		StateEvent.__init__(self, thread, cpu, timestamp)
469		self.prio = prio
470		self.textadd(("prio:", self.prio, 0))
471
472configtypes.append(Running)
473
474class Idle(StateEvent):
475	name = "idle"
476	color = "grey"
477	enabled = 0
478	def __init__(self, thread, cpu, timestamp, prio):
479		StateEvent.__init__(self, thread, cpu, timestamp)
480		self.prio = prio
481		self.textadd(("prio:", self.prio, 0))
482
483configtypes.append(Idle)
484
485class Yielding(StateEvent):
486	name = "yielding"
487	color = "yellow"
488	enabled = 1
489	def __init__(self, thread, cpu, timestamp, prio):
490		StateEvent.__init__(self, thread, cpu, timestamp)
491		self.skipnext = 2
492		self.prio = prio
493		self.textadd(("prio:", self.prio, 0))
494
495configtypes.append(Yielding)
496
497class Swapped(StateEvent):
498	name = "swapped"
499	color = "violet"
500	enabled = 1
501	def __init__(self, thread, cpu, timestamp, prio):
502		StateEvent.__init__(self, thread, cpu, timestamp)
503		self.prio = prio
504		self.textadd(("prio:", self.prio, 0))
505
506configtypes.append(Swapped)
507
508class Suspended(StateEvent):
509	name = "suspended"
510	color = "purple"
511	enabled = 1
512	def __init__(self, thread, cpu, timestamp, prio):
513		StateEvent.__init__(self, thread, cpu, timestamp)
514		self.prio = prio
515		self.textadd(("prio:", self.prio, 0))
516
517configtypes.append(Suspended)
518
519class Iwait(StateEvent):
520	name = "iwait"
521	color = "grey"
522	enabled = 0
523	def __init__(self, thread, cpu, timestamp, prio):
524		StateEvent.__init__(self, thread, cpu, timestamp)
525		self.prio = prio
526		self.textadd(("prio:", self.prio, 0))
527
528configtypes.append(Iwait)
529
530class Preempted(StateEvent):
531	name = "preempted"
532	color = "red"
533	enabled = 1
534	def __init__(self, thread, cpu, timestamp, prio, bythread):
535		StateEvent.__init__(self, thread, cpu, timestamp)
536		self.skipnext = 2
537		self.prio = prio
538		self.linked = bythread
539		self.textadd(("prio:", self.prio, 0))
540		self.textadd(("by thread:", self.linked.name, 1))
541
542configtypes.append(Preempted)
543
544class Sleep(StateEvent):
545	name = "sleep"
546	color = "blue"
547	enabled = 1
548	def __init__(self, thread, cpu, timestamp, prio, wmesg):
549		StateEvent.__init__(self, thread, cpu, timestamp)
550		self.prio = prio
551		self.wmesg = wmesg
552		self.textadd(("prio:", self.prio, 0))
553		self.textadd(("wmesg:", self.wmesg, 0))
554
555	def stattxt(self):
556		statstr = StateEvent.stattxt(self)
557		statstr += " sleeping on: " + self.wmesg
558		return (statstr)
559
560configtypes.append(Sleep)
561
562class Blocked(StateEvent):
563	name = "blocked"
564	color = "dark red"
565	enabled = 1
566	def __init__(self, thread, cpu, timestamp, prio, lock):
567		StateEvent.__init__(self, thread, cpu, timestamp)
568		self.prio = prio
569		self.lock = lock
570		self.textadd(("prio:", self.prio, 0))
571		self.textadd(("lock:", self.lock, 0))
572
573	def stattxt(self):
574		statstr = StateEvent.stattxt(self)
575		statstr += " blocked on: " + self.lock
576		return (statstr)
577
578configtypes.append(Blocked)
579
580class KsegrpRunq(StateEvent):
581	name = "KsegrpRunq"
582	color = "orange"
583	enabled = 1
584	def __init__(self, thread, cpu, timestamp, prio, bythread):
585		StateEvent.__init__(self, thread, cpu, timestamp)
586		self.prio = prio
587		self.linked = bythread
588		self.textadd(("prio:", self.prio, 0))
589		self.textadd(("by thread:", self.linked.name, 1))
590
591configtypes.append(KsegrpRunq)
592
593class Runq(StateEvent):
594	name = "Runq"
595	color = "yellow"
596	enabled = 1
597	def __init__(self, thread, cpu, timestamp, prio, bythread):
598		StateEvent.__init__(self, thread, cpu, timestamp)
599		self.prio = prio
600		self.linked = bythread
601		self.textadd(("prio:", self.prio, 0))
602		self.textadd(("by thread:", self.linked.name, 1))
603
604configtypes.append(Runq)
605
606class Sched_exit(StateEvent):
607	name = "exit"
608	color = "grey"
609	enabled = 0
610	def __init__(self, thread, cpu, timestamp, prio):
611		StateEvent.__init__(self, thread, cpu, timestamp)
612		self.name = "sched_exit"
613		self.prio = prio
614		self.textadd(("prio:", self.prio, 0))
615
616configtypes.append(Sched_exit)
617
618class Padevent(StateEvent):
619	def __init__(self, thread, cpu, timestamp, last=0):
620		StateEvent.__init__(self, thread, cpu, timestamp, last)
621		self.name = "pad"
622		self.real = 0
623
624	def draw(self, canvas, xpos, ypos):
625		next = self.next()
626		if (next == None):
627			return (xpos)
628		self.duration = next.timestamp - self.timestamp
629		delta = self.duration / canvas.ratio
630		return (xpos + delta)
631
632class Tick(PointEvent):
633	name = "tick"
634	color = "black"
635	enabled = 0
636	def __init__(self, thread, cpu, timestamp, prio, stathz):
637		PointEvent.__init__(self, thread, cpu, timestamp)
638		self.prio = prio
639		self.textadd(("prio:", self.prio, 0))
640
641configtypes.append(Tick)
642
643class Prio(PointEvent):
644	name = "prio"
645	color = "black"
646	enabled = 0
647	def __init__(self, thread, cpu, timestamp, prio, newprio, bythread):
648		PointEvent.__init__(self, thread, cpu, timestamp)
649		self.prio = prio
650		self.newprio = newprio
651		self.linked = bythread
652		self.textadd(("new prio:", self.newprio, 0))
653		self.textadd(("prio:", self.prio, 0))
654		if (self.linked != self.source):
655			self.textadd(("by thread:", self.linked.name, 1))
656		else:
657			self.textadd(("by thread:", self.linked.name, 0))
658
659configtypes.append(Prio)
660
661class Lend(PointEvent):
662	name = "lend"
663	color = "black"
664	enabled = 0
665	def __init__(self, thread, cpu, timestamp, prio, tothread):
666		PointEvent.__init__(self, thread, cpu, timestamp)
667		self.prio = prio
668		self.linked = tothread
669		self.textadd(("prio:", self.prio, 0))
670		self.textadd(("to thread:", self.linked.name, 1))
671
672configtypes.append(Lend)
673
674class Wokeup(PointEvent):
675	name = "wokeup"
676	color = "black"
677	enabled = 0
678	def __init__(self, thread, cpu, timestamp, ranthread):
679		PointEvent.__init__(self, thread, cpu, timestamp)
680		self.linked = ranthread
681		self.textadd(("ran thread:", self.linked.name, 1))
682
683configtypes.append(Wokeup)
684
685class EventSource:
686	def __init__(self, name):
687		self.name = name
688		self.events = []
689		self.cpu = 0
690		self.cpux = 0
691
692	def fixup(self):
693		pass
694
695	def event(self, event):
696		self.events.insert(0, event)
697
698	def remove(self, event):
699		self.events.remove(event)
700
701	def lastevent(self, event):
702		self.events.append(event)
703
704	def draw(self, canvas, ypos):
705		xpos = 10
706		self.cpux = 10
707		self.cpu = self.events[1].cpu
708		for i in range(0, len(self.events)):
709			self.events[i].idx = i
710		for event in self.events:
711			if (event.cpu != self.cpu and event.cpu != -1):
712				self.drawcpu(canvas, xpos, ypos)
713				self.cpux = xpos
714				self.cpu = event.cpu
715			xpos = event.draw(canvas, xpos, ypos)
716		self.drawcpu(canvas, xpos, ypos)
717
718	def drawname(self, canvas, ypos):
719		ypos = ypos - (self.ysize() / 2)
720		canvas.create_text(10, ypos, anchor="w", text=self.name)
721
722	def drawcpu(self, canvas, xpos, ypos):
723		cpu = int(self.cpu)
724		if (cpu == 0):
725			color = 'light grey'
726		elif (cpu == 1):
727			color = 'dark grey'
728		elif (cpu == 2):
729			color = 'light blue'
730		elif (cpu == 3):
731			color == 'light green'
732		else:
733			color == "white"
734		l = canvas.create_rectangle(self.cpux,
735		    ypos - self.ysize() - canvas.bdheight,
736		    xpos, ypos + canvas.bdheight, fill=color, width=0,
737		    tags=("all", "cpuinfo"))
738
739	def ysize(self):
740		return (None)
741
742	def eventat(self, i):
743		if (i >= len(self.events)):
744			return (None)
745		event = self.events[i]
746		return (event)
747
748	def findevent(self, timestamp):
749		for event in self.events:
750			if (event.timestamp >= timestamp and event.real):
751				return (event)
752		return (None)
753
754class Thread(EventSource):
755	names = {}
756	def __init__(self, td, pcomm):
757		EventSource.__init__(self, pcomm)
758		self.str = td
759		try:
760			cnt = Thread.names[pcomm]
761		except:
762			Thread.names[pcomm] = 0
763			return
764		Thread.names[pcomm] = cnt + 1
765
766	def fixup(self):
767		cnt = Thread.names[self.name]
768		if (cnt == 0):
769			return
770		cnt -= 1
771		Thread.names[self.name] = cnt
772		self.name += " td" + str(cnt)
773
774	def ysize(self):
775		return (10)
776
777class Counter(EventSource):
778	max = 0
779	def __init__(self, name):
780		EventSource.__init__(self, name)
781
782	def event(self, event):
783		EventSource.event(self, event)
784		try:
785			count = event.count
786		except:
787			return
788		count = int(count)
789		if (count > Counter.max):
790			Counter.max = count
791
792	def ysize(self):
793		return (80)
794
795	def yscale(self):
796		return (self.ysize() / Counter.max)
797
798
799class KTRFile:
800	def __init__(self, file):
801		self.timestamp_first = None
802		self.timestamp_last = None
803		self.lineno = -1
804		self.threads = []
805		self.sources = []
806		self.ticks = {}
807		self.load = {}
808
809		self.parse(file)
810		self.fixup()
811		global ticksps
812		ticksps = self.ticksps()
813
814	def parse(self, file):
815		try:
816			ifp = open(file)
817		except:
818			print "Can't open", file
819			sys.exit(1)
820
821		ktrhdr = "\s+\d+\s+(\d+)\s+(\d+)\s+"
822		tdname = "(\S+)\(([^)]*)\)"
823
824		ktrstr = "mi_switch: " + tdname
825		ktrstr += " prio (\d+) inhibit (\d+) wmesg (\S+) lock (\S+)"
826		switchout_re = re.compile(ktrhdr + ktrstr)
827
828		ktrstr = "mi_switch: " + tdname + " prio (\d+) idle"
829		idled_re = re.compile(ktrhdr + ktrstr)
830
831		ktrstr = "mi_switch: " + tdname + " prio (\d+) preempted by "
832		ktrstr += tdname
833		preempted_re = re.compile(ktrhdr + ktrstr)
834
835		ktrstr = "mi_switch: running " + tdname + " prio (\d+)"
836		switchin_re = re.compile(ktrhdr + ktrstr)
837
838		ktrstr = "sched_add: " + tdname + " prio (\d+) by " + tdname
839		sched_add_re = re.compile(ktrhdr + ktrstr)
840
841		ktrstr = "setrunqueue: " + tdname + " prio (\d+) by " + tdname
842		setrunqueue_re = re.compile(ktrhdr + ktrstr)
843
844		ktrstr = "sched_rem: " + tdname + " prio (\d+) by " + tdname
845		sched_rem_re = re.compile(ktrhdr + ktrstr)
846
847		ktrstr = "sched_exit_thread: " + tdname + " prio (\d+)"
848		sched_exit_re = re.compile(ktrhdr + ktrstr)
849
850		ktrstr = "statclock: " + tdname + " prio (\d+)"
851		ktrstr += " stathz (\d+)"
852		sched_clock_re = re.compile(ktrhdr + ktrstr)
853
854		ktrstr = "sched_prio: " + tdname + " prio (\d+)"
855		ktrstr += " newprio (\d+) by " + tdname
856		sched_prio_re = re.compile(ktrhdr + ktrstr)
857
858		cpuload_re = re.compile(ktrhdr + "load: (\d+)")
859		loadglobal_re = re.compile(ktrhdr + "global load: (\d+)")
860
861		parsers = [[cpuload_re, self.cpuload],
862			   [loadglobal_re, self.loadglobal],
863			   [switchin_re, self.switchin],
864			   [switchout_re, self.switchout],
865			   [sched_add_re, self.sched_add],
866			   [setrunqueue_re, self.sched_rem],
867			   [sched_prio_re, self.sched_prio],
868			   [preempted_re, self.preempted],
869			   [sched_rem_re, self.sched_rem],
870			   [sched_exit_re, self.sched_exit],
871			   [sched_clock_re, self.sched_clock],
872			   [idled_re, self.idled]]
873
874		for line in ifp.readlines():
875			self.lineno += 1
876			if ((self.lineno % 1024) == 0):
877				status.startup("Parsing line " +
878				    str(self.lineno))
879			for p in parsers:
880				m = p[0].match(line)
881				if (m != None):
882					p[1](*m.groups())
883					break
884			# if (m == None):
885			# 	print line,
886
887	def checkstamp(self, timestamp):
888		timestamp = int(timestamp)
889		if (self.timestamp_first == None):
890			self.timestamp_first = timestamp
891		if (timestamp > self.timestamp_first):
892			print "Bad timestamp on line ", self.lineno
893			return (0)
894		self.timestamp_last = timestamp
895		return (1)
896
897	def timespan(self):
898		return (self.timestamp_first - self.timestamp_last);
899
900	def ticksps(self):
901		return (self.timespan() / self.ticks[0]) * int(self.stathz)
902
903	def switchout(self, cpu, timestamp, td, pcomm, prio, inhibit, wmesg, lock):
904		TDI_SUSPENDED = 0x0001
905		TDI_SLEEPING = 0x0002
906		TDI_SWAPPED = 0x0004
907		TDI_LOCK = 0x0008
908		TDI_IWAIT = 0x0010
909
910		if (self.checkstamp(timestamp) == 0):
911			return
912		inhibit = int(inhibit)
913		thread = self.findtd(td, pcomm)
914		if (inhibit & TDI_SWAPPED):
915			Swapped(thread, cpu, timestamp, prio)
916		elif (inhibit & TDI_SLEEPING):
917			Sleep(thread, cpu, timestamp, prio, wmesg)
918		elif (inhibit & TDI_LOCK):
919			Blocked(thread, cpu, timestamp, prio, lock)
920		elif (inhibit & TDI_IWAIT):
921			Iwait(thread, cpu, timestamp, prio)
922		elif (inhibit & TDI_SUSPENDED):
923			Suspended(thread, cpu, timestamp, prio)
924		elif (inhibit == 0):
925			Yielding(thread, cpu, timestamp, prio)
926		else:
927			print "Unknown event", inhibit
928			sys.exit(1)
929
930	def idled(self, cpu, timestamp, td, pcomm, prio):
931		if (self.checkstamp(timestamp) == 0):
932			return
933		thread = self.findtd(td, pcomm)
934		Idle(thread, cpu, timestamp, prio)
935
936	def preempted(self, cpu, timestamp, td, pcomm, prio, bytd, bypcomm):
937		if (self.checkstamp(timestamp) == 0):
938			return
939		thread = self.findtd(td, pcomm)
940		Preempted(thread, cpu, timestamp, prio,
941		    self.findtd(bytd, bypcomm))
942
943	def switchin(self, cpu, timestamp, td, pcomm, prio):
944		if (self.checkstamp(timestamp) == 0):
945			return
946		thread = self.findtd(td, pcomm)
947		Running(thread, cpu, timestamp, prio)
948
949	def sched_add(self, cpu, timestamp, td, pcomm, prio, bytd, bypcomm):
950		if (self.checkstamp(timestamp) == 0):
951			return
952		thread = self.findtd(td, pcomm)
953		bythread = self.findtd(bytd, bypcomm)
954		Runq(thread, cpu, timestamp, prio, bythread)
955		Wokeup(bythread, cpu, timestamp, thread)
956
957	def sched_rem(self, cpu, timestamp, td, pcomm, prio, bytd, bypcomm):
958		if (self.checkstamp(timestamp) == 0):
959			return
960		thread = self.findtd(td, pcomm)
961		KsegrpRunq(thread, cpu, timestamp, prio,
962		    self.findtd(bytd, bypcomm))
963
964	def sched_exit(self, cpu, timestamp, td, pcomm, prio):
965		if (self.checkstamp(timestamp) == 0):
966			return
967		thread = self.findtd(td, pcomm)
968		Sched_exit(thread, cpu, timestamp, prio)
969
970	def sched_clock(self, cpu, timestamp, td, pcomm, prio, stathz):
971		if (self.checkstamp(timestamp) == 0):
972			return
973		self.stathz = stathz
974		cpu = int(cpu)
975		try:
976			ticks = self.ticks[cpu]
977		except:
978			self.ticks[cpu] = 0
979		self.ticks[cpu] += 1
980		thread = self.findtd(td, pcomm)
981		Tick(thread, cpu, timestamp, prio, stathz)
982
983	def sched_prio(self, cpu, timestamp, td, pcomm, prio, newprio, bytd, bypcomm):
984		if (prio == newprio):
985			return
986		if (self.checkstamp(timestamp) == 0):
987			return
988		thread = self.findtd(td, pcomm)
989		bythread = self.findtd(bytd, bypcomm)
990		Prio(thread, cpu, timestamp, prio, newprio, bythread)
991		Lend(bythread, cpu, timestamp, newprio, thread)
992
993	def cpuload(self, cpu, timestamp, count):
994		if (self.checkstamp(timestamp) == 0):
995			return
996		cpu = int(cpu)
997		try:
998			load = self.load[cpu]
999		except:
1000			load = Counter("cpu" + str(cpu) + " load")
1001			self.load[cpu] = load
1002			self.sources.insert(0, load)
1003		Count(load, cpu, timestamp, count)
1004
1005	def loadglobal(self, cpu, timestamp, count):
1006		if (self.checkstamp(timestamp) == 0):
1007			return
1008		cpu = 0
1009		try:
1010			load = self.load[cpu]
1011		except:
1012			load = Counter("CPU load")
1013			self.load[cpu] = load
1014			self.sources.insert(0, load)
1015		Count(load, cpu, timestamp, count)
1016
1017	def findtd(self, td, pcomm):
1018		for thread in self.threads:
1019			if (thread.str == td and thread.name == pcomm):
1020				return thread
1021		thread = Thread(td, pcomm)
1022		self.threads.append(thread)
1023		self.sources.append(thread)
1024		return (thread)
1025
1026	def fixup(self):
1027		for source in self.sources:
1028			Padevent(source, -1, self.timestamp_last)
1029			Padevent(source, -1, self.timestamp_first, last=1)
1030			source.fixup()
1031
1032class SchedDisplay(Canvas):
1033	def __init__(self, master):
1034		self.ratio = 1
1035		self.ktrfile = None
1036		self.sources = None
1037		self.bdheight = 10
1038		self.events = {}
1039
1040		Canvas.__init__(self, master, width=800, height=500, bg='grey',
1041		     scrollregion=(0, 0, 800, 500))
1042
1043	def setfile(self, ktrfile):
1044		self.ktrfile = ktrfile
1045		self.sources = ktrfile.sources
1046
1047	def draw(self):
1048		ypos = 0
1049		xsize = self.xsize()
1050		for source in self.sources:
1051			status.startup("Drawing " + source.name)
1052			self.create_line(0, ypos, xsize, ypos,
1053			    width=1, fill="black", tags=("all",))
1054			ypos += self.bdheight
1055			ypos += source.ysize()
1056			source.draw(self, ypos)
1057			ypos += self.bdheight
1058			try:
1059				self.tag_raise("point", "state")
1060				self.tag_lower("cpuinfo", "all")
1061			except:
1062				pass
1063		self.create_line(0, ypos, xsize, ypos,
1064		    width=1, fill="black", tags=("all",))
1065		self.tag_bind("event", "<Enter>", self.mouseenter)
1066		self.tag_bind("event", "<Leave>", self.mouseexit)
1067		self.tag_bind("event", "<Button-1>", self.mousepress)
1068
1069	def mouseenter(self, event):
1070		item, = self.find_withtag(CURRENT)
1071		event = self.events[item]
1072		event.mouseenter(self, item)
1073
1074	def mouseexit(self, event):
1075		item, = self.find_withtag(CURRENT)
1076		event = self.events[item]
1077		event.mouseexit(self, item)
1078
1079	def mousepress(self, event):
1080		item, = self.find_withtag(CURRENT)
1081		event = self.events[item]
1082		event.mousepress(self, item)
1083
1084	def drawnames(self, canvas):
1085		status.startup("Drawing names")
1086		ypos = 0
1087		canvas.configure(scrollregion=(0, 0,
1088		    canvas["width"], self.ysize()))
1089		for source in self.sources:
1090			canvas.create_line(0, ypos, canvas["width"], ypos,
1091			    width=1, fill="black", tags=("all",))
1092			ypos += self.bdheight
1093			ypos += source.ysize()
1094			source.drawname(canvas, ypos)
1095			ypos += self.bdheight
1096		canvas.create_line(0, ypos, canvas["width"], ypos,
1097		    width=1, fill="black", tags=("all",))
1098
1099	def xsize(self):
1100		return ((self.ktrfile.timespan() / self.ratio) + 20)
1101
1102	def ysize(self):
1103		ysize = 0
1104		for source in self.sources:
1105			ysize += source.ysize() + (self.bdheight * 2)
1106		return (ysize)
1107
1108	def scaleset(self, ratio):
1109		if (self.ktrfile == None):
1110			return
1111		oldratio = self.ratio
1112		xstart, ystart = self.xview()
1113		length = (float(self["width"]) / self.xsize())
1114		middle = xstart + (length / 2)
1115
1116		self.ratio = ratio
1117		self.configure(scrollregion=(0, 0, self.xsize(), self.ysize()))
1118		self.scale("all", 0, 0, float(oldratio) / ratio, 1)
1119
1120		length = (float(self["width"]) / self.xsize())
1121		xstart = middle - (length / 2)
1122		self.xview_moveto(xstart)
1123
1124	def scaleget(self):
1125		return self.ratio
1126
1127	def setcolor(self, tag, color):
1128		self.itemconfigure(tag, state="normal", fill=color)
1129
1130	def hide(self, tag):
1131		self.itemconfigure(tag, state="hidden")
1132
1133class GraphMenu(Frame):
1134	def __init__(self, master):
1135		Frame.__init__(self, master, bd=2, relief=RAISED)
1136		self.view = Menubutton(self, text="Configure")
1137		self.viewmenu = Menu(self.view, tearoff=0)
1138		self.viewmenu.add_command(label="Events",
1139		    command=self.econf)
1140		self.view["menu"] = self.viewmenu
1141		self.view.pack(side=LEFT)
1142
1143	def econf(self):
1144		EventConfigure()
1145
1146
1147class SchedGraph(Frame):
1148	def __init__(self, master):
1149		Frame.__init__(self, master)
1150		self.menu = None
1151		self.names = None
1152		self.display = None
1153		self.scale = None
1154		self.status = None
1155		self.pack(expand=1, fill="both")
1156		self.buildwidgets()
1157		self.layout()
1158		self.draw(sys.argv[1])
1159
1160	def buildwidgets(self):
1161		global status
1162		self.menu = GraphMenu(self)
1163		self.display = SchedDisplay(self)
1164		self.names = Canvas(self,
1165		    width=100, height=self.display["height"],
1166		    bg='grey', scrollregion=(0, 0, 50, 100))
1167		self.scale = Scaler(self, self.display)
1168		status = self.status = Status(self)
1169		self.scrollY = Scrollbar(self, orient="vertical",
1170		    command=self.display_yview)
1171		self.display.scrollX = Scrollbar(self, orient="horizontal",
1172		    command=self.display.xview)
1173		self.display["xscrollcommand"] = self.display.scrollX.set
1174		self.display["yscrollcommand"] = self.scrollY.set
1175		self.names["yscrollcommand"] = self.scrollY.set
1176
1177	def layout(self):
1178		self.columnconfigure(1, weight=1)
1179		self.rowconfigure(1, weight=1)
1180		self.menu.grid(row=0, column=0, columnspan=3, sticky=E+W)
1181		self.names.grid(row=1, column=0, sticky=N+S)
1182		self.display.grid(row=1, column=1, sticky=W+E+N+S)
1183		self.scrollY.grid(row=1, column=2, sticky=N+S)
1184		self.display.scrollX.grid(row=2, column=0, columnspan=2,
1185		    sticky=E+W)
1186		self.scale.grid(row=3, column=0, columnspan=3, sticky=E+W)
1187		self.status.grid(row=4, column=0, columnspan=3, sticky=E+W)
1188
1189	def draw(self, file):
1190		self.master.update()
1191		ktrfile = KTRFile(file)
1192		self.display.setfile(ktrfile)
1193		self.display.drawnames(self.names)
1194		self.display.draw()
1195		self.scale.set(250000)
1196		self.display.xview_moveto(0)
1197
1198	def display_yview(self, *args):
1199		self.names.yview(*args)
1200		self.display.yview(*args)
1201
1202	def setcolor(self, tag, color):
1203		self.display.setcolor(tag, color)
1204
1205	def hide(self, tag):
1206		self.display.hide(tag)
1207
1208if (len(sys.argv) != 2):
1209	print "usage:", sys.argv[0], "<ktr file>"
1210	sys.exit(1)
1211
1212root = Tk()
1213root.title("Scheduler Graph")
1214graph = SchedGraph(root)
1215root.mainloop()
1216