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