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