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