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