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