]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tools/sched/schedgraph.py
contrib/bc: merge from vendor release 6.2.2
[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 operator import attrgetter, itemgetter
34 from functools import total_ordering
35 from tkinter import *
36
37 # To use:
38 # - Install the ports/x11-toolkits/py-tkinter package; e.g.
39 #       pkg install x11-toolkits/py-tkinter
40 # - Add KTR_SCHED to KTR_COMPILE and KTR_MASK in your KERNCONF; e.g.
41 #       options         KTR
42 #       options         KTR_ENTRIES=32768
43 #       options         KTR_COMPILE=(KTR_SCHED)
44 #       options         KTR_MASK=(KTR_SCHED)
45 # - It is encouraged to increase KTR_ENTRIES size to gather enough
46 #    information for analysis; e.g.
47 #       options         KTR_ENTRIES=262144
48 #   as 32768 entries may only correspond to a second or two of profiling
49 #   data depending on your workload.
50 # - Rebuild kernel with proper changes to KERNCONF and boot new kernel.
51 # - Run your workload to be profiled.
52 # - While the workload is continuing (i.e. before it finishes), disable
53 #   KTR tracing by setting 'sysctl debug.ktr.mask=0'.  This is necessary
54 #   to avoid a race condition while running ktrdump, i.e. the KTR ring buffer
55 #   will cycle a bit while ktrdump runs, and this confuses schedgraph because
56 #   the timestamps appear to go backwards at some point.  Stopping KTR logging
57 #   while the workload is still running is to avoid wasting log entries on
58 #   "idle" time at the end.
59 # - Dump the trace to a file: 'ktrdump -ct > ktr.out'
60 # - Alternatively, use schedgraph.d script in this directory for getting
61 #   the trace data by means of DTrace.  See the script for details.
62 # - Run the python script: 'python schedgraph.py ktr.out' optionally provide
63 #   your cpu frequency in ghz: 'python schedgraph.py ktr.out 2.4'
64 #
65 # To do:
66 # Add a per-source summary display
67 # "Vertical rule" to help relate data in different rows
68 # Mouse-over popup of full thread/event/row label (currently truncated)
69 # More visible anchors for popup event windows
70 #
71 # BUGS: 1) Only 8 CPUs are supported, more CPUs require more choices of
72 #          colours to represent them ;-)
73
74 eventcolors = [
75         ("count",       "red"),
76         ("running",     "green"),
77         ("idle",        "grey"),
78         ("spinning",    "red"),
79         ("yielding",    "yellow"),
80         ("swapped",     "violet"),
81         ("suspended",   "purple"),
82         ("iwait",       "grey"),
83         ("sleep",       "blue"),
84         ("blocked",     "dark red"),
85         ("runq add",    "yellow"),
86         ("runq rem",    "yellow"),
87         ("thread exit", "grey"),
88         ("proc exit",   "grey"),
89         ("lock acquire", "blue"),
90         ("lock contest", "purple"),
91         ("failed lock try", "red"),
92         ("lock release", "grey"),
93         ("statclock",   "black"),
94         ("prio",        "black"),
95         ("lend prio",   "black"),
96         ("wokeup",      "black")
97 ]
98
99 cpucolors = [
100         ("CPU 0",       "light grey"),
101         ("CPU 1",       "dark grey"),
102         ("CPU 2",       "light blue"),
103         ("CPU 3",       "light pink"),
104         ("CPU 4",       "blanched almond"),
105         ("CPU 5",       "slate grey"),
106         ("CPU 6",       "tan"),
107         ("CPU 7",       "thistle"),
108         ("CPU 8",       "white")
109 ]
110
111 colors = [
112         "white", "thistle", "blanched almond", "tan", "chartreuse",
113         "dark red", "red", "pale violet red", "pink", "light pink",
114         "dark orange", "orange", "coral", "light coral",
115         "goldenrod", "gold", "yellow", "light yellow",
116         "dark green", "green", "light green", "light sea green",
117         "dark blue", "blue", "light blue", "steel blue", "light slate blue",
118         "dark violet", "violet", "purple", "blue violet",
119         "dark grey", "slate grey", "light grey",
120         "black",
121 ]
122 colors.sort()
123
124 ticksps = None
125 status = None
126 colormap = None
127 ktrfile = None
128 clockfreq = None
129 sources = []
130 lineno = -1
131
132 Y_BORDER = 10
133 X_BORDER = 10
134 Y_COUNTER = 80
135 Y_EVENTSOURCE = 10
136 XY_POINT = 4
137
138 class Colormap:
139         def __init__(self, table):
140                 self.table = table
141                 self.map = {}
142                 for entry in table:
143                         self.map[entry[0]] = entry[1]
144
145         def lookup(self, name):
146                 try:
147                         color = self.map[name]
148                 except:
149                         color = colors[random.randrange(0, len(colors))]
150                         print("Picking random color", color, "for", name)
151                         self.map[name] = color
152                         self.table.append((name, color))
153                 return (color)
154
155 def ticks2sec(ticks):
156         ticks = float(ticks)
157         ns = float(ticksps) / 1000000000
158         ticks /= ns
159         if (ticks < 1000):
160                 return ("%.2fns" % ticks)
161         ticks /= 1000
162         if (ticks < 1000):
163                 return ("%.2fus" % ticks)
164         ticks /= 1000
165         if (ticks < 1000):
166                 return ("%.2fms" % ticks)
167         ticks /= 1000
168         return ("%.2fs" % ticks)
169
170 class Scaler(Frame):
171         def __init__(self, master, target):
172                 Frame.__init__(self, master)
173                 self.scale = None
174                 self.target = target
175                 self.label = Label(self, text="Ticks per pixel")
176                 self.label.pack(side=LEFT)
177                 self.resolution = 100
178                 self.setmax(10000)
179
180         def scaleset(self, value):
181                 self.target.scaleset(int(value))
182
183         def set(self, value):
184                 self.scale.set(value)
185
186         def setmax(self, value):
187                 #
188                 # We can't reconfigure the to_ value so we delete the old
189                 # window and make a new one when we resize.
190                 #
191                 if (self.scale != None):
192                         self.scale.pack_forget()
193                         self.scale.destroy()
194                 self.scale = Scale(self, command=self.scaleset,
195                     from_=100, to_=value, orient=HORIZONTAL,
196                     resolution=self.resolution)
197                 self.scale.pack(fill="both", expand=1)
198                 self.scale.set(self.target.scaleget())
199
200 class Status(Frame):
201         def __init__(self, master):
202                 Frame.__init__(self, master)
203                 self.label = Label(self, bd=1, relief=SUNKEN, anchor=W)
204                 self.label.pack(fill="both", expand=1)
205                 self.clear()
206
207         def set(self, str):
208                 self.label.config(text=str)
209
210         def clear(self):
211                 self.label.config(text="")
212
213         def startup(self, str):
214                 self.set(str)
215                 root.update()
216
217 class ColorConf(Frame):
218         def __init__(self, master, name, color):
219                 Frame.__init__(self, master)
220                 if (graph.getstate(name) == "hidden"):
221                         enabled = 0
222                 else:
223                         enabled = 1
224                 self.name = name
225                 self.color = StringVar()
226                 self.color_default = color
227                 self.color_current = color
228                 self.color.set(color)
229                 self.enabled = IntVar()
230                 self.enabled_default = enabled
231                 self.enabled_current = enabled
232                 self.enabled.set(enabled)
233                 self.draw()
234
235         def draw(self):
236                 self.label = Label(self, text=self.name, anchor=W)
237                 self.sample = Canvas(self, width=24, height=24,
238                     bg='grey')
239                 self.rect = self.sample.create_rectangle(0, 0, 24, 24,
240                     fill=self.color.get())
241                 self.list = OptionMenu(self, self.color, command=self.setcolor,
242                     *colors)
243                 self.checkbox = Checkbutton(self, text="enabled",
244                     variable=self.enabled)
245                 self.label.grid(row=0, column=0, sticky=E+W)
246                 self.sample.grid(row=0, column=1)
247                 self.list.grid(row=0, column=2, sticky=E+W)
248                 self.checkbox.grid(row=0, column=3)
249                 self.columnconfigure(0, weight=1)
250                 self.columnconfigure(2, minsize=150)
251
252         def setcolor(self, color):
253                 self.color.set(color)
254                 self.sample.itemconfigure(self.rect, fill=color)
255
256         def apply(self):
257                 cchange = 0
258                 echange = 0
259                 if (self.color_current != self.color.get()):
260                         cchange = 1
261                 if (self.enabled_current != self.enabled.get()):
262                         echange = 1
263                 self.color_current = self.color.get()
264                 self.enabled_current = self.enabled.get()
265                 if (echange != 0):
266                         if (self.enabled_current):
267                                 graph.setcolor(self.name, self.color_current)
268                         else:
269                                 graph.hide(self.name)
270                         return
271                 if (cchange != 0):
272                         graph.setcolor(self.name, self.color_current)
273
274         def revert(self):
275                 self.setcolor(self.color_default)
276                 self.enabled.set(self.enabled_default)
277
278 class ColorConfigure(Toplevel):
279         def __init__(self, table, name):
280                 Toplevel.__init__(self)
281                 self.resizable(0, 0)
282                 self.title(name)
283                 self.items = LabelFrame(self, text="Item Type")
284                 self.buttons = Frame(self)
285                 self.drawbuttons()
286                 self.items.grid(row=0, column=0, sticky=E+W)
287                 self.columnconfigure(0, weight=1)
288                 self.buttons.grid(row=1, column=0, sticky=E+W)
289                 self.types = []
290                 self.irow = 0
291                 for type in table:
292                         color = graph.getcolor(type[0])
293                         if (color != ""):
294                                 self.additem(type[0], color)
295                 self.bind("<Control-w>", self.destroycb)
296
297         def destroycb(self, event):
298                 self.destroy()
299
300         def additem(self, name, color):
301                 item = ColorConf(self.items, name, color)
302                 self.types.append(item)
303                 item.grid(row=self.irow, column=0, sticky=E+W)
304                 self.irow += 1
305
306         def drawbuttons(self):
307                 self.apply = Button(self.buttons, text="Apply",
308                     command=self.apress)
309                 self.default = Button(self.buttons, text="Revert",
310                     command=self.rpress)
311                 self.apply.grid(row=0, column=0, sticky=E+W)
312                 self.default.grid(row=0, column=1, sticky=E+W)
313                 self.buttons.columnconfigure(0, weight=1)
314                 self.buttons.columnconfigure(1, weight=1)
315
316         def apress(self):
317                 for item in self.types:
318                         item.apply()
319
320         def rpress(self):
321                 for item in self.types:
322                         item.revert()
323
324 class SourceConf(Frame):
325         def __init__(self, master, source):
326                 Frame.__init__(self, master)
327                 if (source.hidden == 1):
328                         enabled = 0
329                 else:
330                         enabled = 1
331                 self.source = source
332                 self.name = source.name
333                 self.enabled = IntVar()
334                 self.enabled_default = enabled
335                 self.enabled_current = enabled
336                 self.enabled.set(enabled)
337                 self.draw()
338
339         def draw(self):
340                 self.label = Label(self, text=self.name, anchor=W)
341                 self.checkbox = Checkbutton(self, text="enabled",
342                     variable=self.enabled)
343                 self.label.grid(row=0, column=0, sticky=E+W)
344                 self.checkbox.grid(row=0, column=1)
345                 self.columnconfigure(0, weight=1)
346
347         def changed(self):
348                 if (self.enabled_current != self.enabled.get()):
349                         return 1
350                 return 0
351
352         def apply(self):
353                 self.enabled_current = self.enabled.get()
354
355         def revert(self):
356                 self.enabled.set(self.enabled_default)
357
358         def check(self):
359                 self.enabled.set(1)
360
361         def uncheck(self):
362                 self.enabled.set(0)
363
364 class SourceConfigure(Toplevel):
365         def __init__(self):
366                 Toplevel.__init__(self)
367                 self.resizable(0, 0)
368                 self.title("Source Configuration")
369                 self.items = []
370                 self.iframe = Frame(self)
371                 self.iframe.grid(row=0, column=0, sticky=E+W)
372                 f = LabelFrame(self.iframe, bd=4, text="Sources")
373                 self.items.append(f)
374                 self.buttons = Frame(self)
375                 self.items[0].grid(row=0, column=0, sticky=E+W)
376                 self.columnconfigure(0, weight=1)
377                 self.sconfig = []
378                 self.irow = 0
379                 self.icol = 0
380                 for source in sources:
381                         self.addsource(source)
382                 self.drawbuttons()
383                 self.buttons.grid(row=1, column=0, sticky=W)
384                 self.bind("<Control-w>", self.destroycb)
385
386         def destroycb(self, event):
387                 self.destroy()
388
389         def addsource(self, source):
390                 if (self.irow > 30):
391                         self.icol += 1
392                         self.irow = 0
393                         c = self.icol
394                         f = LabelFrame(self.iframe, bd=4, text="Sources")
395                         f.grid(row=0, column=c, sticky=N+E+W)
396                         self.items.append(f)
397                 item = SourceConf(self.items[self.icol], source)
398                 self.sconfig.append(item)
399                 item.grid(row=self.irow, column=0, sticky=E+W)
400                 self.irow += 1
401
402         def drawbuttons(self):
403                 self.apply = Button(self.buttons, text="Apply",
404                     command=self.apress)
405                 self.default = Button(self.buttons, text="Revert",
406                     command=self.rpress)
407                 self.checkall = Button(self.buttons, text="Check All",
408                     command=self.cpress)
409                 self.uncheckall = Button(self.buttons, text="Uncheck All",
410                     command=self.upress)
411                 self.checkall.grid(row=0, column=0, sticky=W)
412                 self.uncheckall.grid(row=0, column=1, sticky=W)
413                 self.apply.grid(row=0, column=2, sticky=W)
414                 self.default.grid(row=0, column=3, sticky=W)
415                 self.buttons.columnconfigure(0, weight=1)
416                 self.buttons.columnconfigure(1, weight=1)
417                 self.buttons.columnconfigure(2, weight=1)
418                 self.buttons.columnconfigure(3, weight=1)
419
420         def apress(self):
421                 disable_sources = []
422                 enable_sources = []
423                 for item in self.sconfig:
424                         if (item.changed() == 0):
425                                 continue
426                         if (item.enabled.get() == 1):
427                                 enable_sources.append(item.source)
428                         else:
429                                 disable_sources.append(item.source)
430
431                 if (len(disable_sources)):
432                         graph.sourcehidelist(disable_sources)
433                 if (len(enable_sources)):
434                         graph.sourceshowlist(enable_sources)
435
436                 for item in self.sconfig:
437                         item.apply()
438
439         def rpress(self):
440                 for item in self.sconfig:
441                         item.revert()
442
443         def cpress(self):
444                 for item in self.sconfig:
445                         item.check()
446
447         def upress(self):
448                 for item in self.sconfig:
449                         item.uncheck()
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(key=itemgetter(1), reverse=True)
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
797 @total_ordering
798 class EventSource:
799         def __init__(self, group, id):
800                 self.name = id
801                 self.events = []
802                 self.cpuitems = []
803                 self.group = group
804                 self.y = 0
805                 self.item = None
806                 self.hidden = 0
807                 self.tag = group + id
808
809         def __lt__(self, other):
810                 if other is None:
811                         return False
812                 return (self.group < other.group or
813                                 self.group == other.group and self.name < other.name)
814
815         def __eq__(self, other):
816                 if other is None:
817                         return False
818                 return self.group == other.group and self.name == other.name
819
820         # It is much faster to append items to a list then to insert them
821         # at the beginning.  As a result, we add events in reverse order
822         # and then swap the list during fixup.
823         def fixup(self):
824                 self.events.reverse()
825
826         def addevent(self, event):
827                 self.events.append(event)
828
829         def addlastevent(self, event):
830                 self.events.insert(0, event)
831
832         def draw(self, canvas, ypos):
833                 xpos = 10
834                 cpux = 10
835                 cpu = self.events[1].cpu
836                 for i in range(0, len(self.events)):
837                         self.events[i].idx = i
838                 for event in self.events:
839                         if (event.cpu != cpu and event.cpu != -1):
840                                 self.drawcpu(canvas, cpu, cpux, xpos, ypos)
841                                 cpux = xpos
842                                 cpu = event.cpu
843                         xpos = event.draw(canvas, xpos, ypos)
844                 self.drawcpu(canvas, cpu, cpux, xpos, ypos)
845
846         def drawname(self, canvas, ypos):
847                 self.y = ypos
848                 ypos = ypos - (self.ysize() / 2)
849                 self.item = canvas.create_text(X_BORDER, ypos, anchor="w",
850                     text=self.name)
851                 return (self.item)
852
853         def drawcpu(self, canvas, cpu, fromx, tox, ypos):
854                 cpu = "CPU " + str(cpu)
855                 color = cpucolormap.lookup(cpu)
856                 # Create the cpu background colors default to hidden
857                 l = canvas.create_rectangle(fromx,
858                     ypos - self.ysize() - canvas.bdheight,
859                     tox, ypos + canvas.bdheight, fill=color, width=0,
860                     tags=("cpubg", cpu, self.tag), state="hidden")
861                 self.cpuitems.append(l)
862
863         def move(self, canvas, xpos, ypos):
864                 canvas.move(self.tag, xpos, ypos)
865
866         def movename(self, canvas, xpos, ypos):
867                 self.y += ypos
868                 canvas.move(self.item, xpos, ypos)
869
870         def ysize(self):
871                 return (Y_EVENTSOURCE)
872
873         def eventat(self, i):
874                 if (i >= len(self.events) or i < 0):
875                         return (None)
876                 event = self.events[i]
877                 return (event)
878
879         def findevent(self, timestamp):
880                 for event in self.events:
881                         if (event.timestamp >= timestamp and event.type != "pad"):
882                                 return (event)
883                 return (None)
884
885 class Counter(EventSource):
886         #
887         # Store a hash of counter groups that keeps the max value
888         # for a counter in this group for scaling purposes.
889         #
890         groups = {}
891         def __init__(self, group, id):
892                 try:
893                         Counter.cnt = Counter.groups[group]
894                 except:
895                         Counter.groups[group] = 0
896                 EventSource.__init__(self, group, id)
897
898         def fixup(self):
899                 for event in self.events:
900                         if (event.type != "count"):
901                                 continue;
902                         count = int(event.count)
903                         if (count > Counter.groups[self.group]):
904                                 Counter.groups[self.group] = count
905                 EventSource.fixup(self)
906
907         def ymax(self):
908                 return (Counter.groups[self.group])
909
910         def ysize(self):
911                 return (Y_COUNTER)
912
913         def yscale(self):
914                 return (self.ysize() / self.ymax())
915
916 class KTRFile:
917         def __init__(self, file):
918                 self.timestamp_f = None
919                 self.timestamp_l = None
920                 self.locks = {}
921                 self.ticks = {}
922                 self.load = {}
923                 self.crit = {}
924                 self.stathz = 0
925                 self.eventcnt = 0
926                 self.taghash = {}
927
928                 self.parse(file)
929                 self.fixup()
930                 global ticksps
931                 ticksps = self.ticksps()
932                 span = self.timespan()
933                 ghz = float(ticksps) / 1000000000.0
934                 #
935                 # Update the title with some stats from the file
936                 #
937                 titlestr = "SchedGraph: "
938                 titlestr += ticks2sec(span) + " at %.3f ghz, " % ghz
939                 titlestr += str(len(sources)) + " event sources, "
940                 titlestr += str(self.eventcnt) + " events"
941                 root.title(titlestr)
942
943         def parse(self, file):
944                 try:
945                         ifp = open(file)
946                 except:
947                         print("Can't open", file)
948                         sys.exit(1)
949
950                 # quoteexp matches a quoted string, no escaping
951                 quoteexp = "\"([^\"]*)\""
952
953                 #
954                 # commaexp matches a quoted string OR the string up
955                 # to the first ','
956                 #
957                 commaexp = "(?:" + quoteexp + "|([^,]+))"
958
959                 #
960                 # colonstr matches a quoted string OR the string up
961                 # to the first ':'
962                 #
963                 colonexp = "(?:" + quoteexp + "|([^:]+))"
964
965                 #
966                 # Match various manditory parts of the KTR string this is
967                 # fairly inflexible until you get to attributes to make
968                 # parsing faster.
969                 #
970                 hdrexp = "\s*(\d+)\s+(\d+)\s+(\d+)\s+"
971                 groupexp = "KTRGRAPH group:" + quoteexp + ", "
972                 idexp = "id:" + quoteexp + ", "
973                 typeexp = "([^:]+):" + commaexp + ", "
974                 attribexp = "attributes: (.*)"
975
976                 #
977                 # Matches optional attributes in the KTR string.  This
978                 # tolerates more variance as the users supply these values.
979                 #
980                 attrexp = colonexp + "\s*:\s*(?:" + commaexp + ", (.*)|"
981                 attrexp += quoteexp +"|(.*))"
982
983                 # Precompile regexp
984                 ktrre = re.compile(hdrexp + groupexp + idexp + typeexp + attribexp)
985                 attrre = re.compile(attrexp)
986
987                 global lineno
988                 lineno = 0
989                 for line in ifp.readlines():
990                         lineno += 1
991                         if ((lineno % 2048) == 0):
992                                 status.startup("Parsing line " + str(lineno))
993                         m = ktrre.match(line);
994                         if (m == None):
995                                 print("Can't parse", lineno, line, end=' ')
996                                 continue;
997                         (index, cpu, timestamp, group, id, type, dat, dat1, attrstring) = m.groups();
998                         if (dat == None):
999                                 dat = dat1
1000                         if (self.checkstamp(timestamp) == 0):
1001                                 print("Bad timestamp at", lineno, ":", end=' ')
1002                                 print(cpu, timestamp) 
1003                                 continue
1004                         #
1005                         # Build the table of optional attributes
1006                         #
1007                         attrs = []
1008                         while (attrstring != None):
1009                                 m = attrre.match(attrstring.strip())
1010                                 if (m == None):
1011                                         break;
1012                                 #
1013                                 # Name may or may not be quoted.
1014                                 #
1015                                 # For val we have four cases:
1016                                 # 1) quotes followed by comma and more
1017                                 #    attributes.
1018                                 # 2) no quotes followed by comma and more
1019                                 #    attributes.
1020                                 # 3) no more attributes or comma with quotes.
1021                                 # 4) no more attributes or comma without quotes.
1022                                 #
1023                                 (name, name1, val, val1, attrstring, end, end1) = m.groups();
1024                                 if (name == None):
1025                                         name = name1
1026                                 if (end == None):
1027                                         end = end1
1028                                 if (val == None):
1029                                         val = val1
1030                                 if (val == None):
1031                                         val = end
1032                                 if (name == "stathz"):
1033                                         self.setstathz(val, cpu)
1034                                 attrs.append((name, val))
1035                         args = (dat, cpu, timestamp, attrs)
1036                         e = self.makeevent(group, id, type, args)
1037                         if (e == None):
1038                                 print("Unknown type", type, lineno, line, end=' ')
1039
1040         def makeevent(self, group, id, type, args):
1041                 e = None
1042                 source = self.makeid(group, id, type)
1043                 if (type == "state"):
1044                         e = StateEvent(source, *args)
1045                 elif (type == "counter"):
1046                         e = CountEvent(source, *args)
1047                 elif (type == "point"):
1048                         e = PointEvent(source, *args)
1049                 if (e != None):
1050                         self.eventcnt += 1
1051                         source.addevent(e);
1052                 return e
1053
1054         def setstathz(self, val, cpu):
1055                 self.stathz = int(val)
1056                 cpu = int(cpu)
1057                 try:
1058                         ticks = self.ticks[cpu]
1059                 except:
1060                         self.ticks[cpu] = 0
1061                 self.ticks[cpu] += 1
1062
1063         def checkstamp(self, timestamp):
1064                 timestamp = int(timestamp)
1065                 if (self.timestamp_f == None):
1066                         self.timestamp_f = timestamp;
1067                 if (self.timestamp_l != None and
1068                     timestamp -2048> self.timestamp_l):
1069                         return (0)
1070                 self.timestamp_l = timestamp;
1071                 return (1)
1072
1073         def makeid(self, group, id, type):
1074                 tag = group + id
1075                 if (tag in self.taghash):
1076                         return self.taghash[tag]
1077                 if (type == "counter"):
1078                         source = Counter(group, id)
1079                 else:
1080                         source = EventSource(group, id)
1081                 sources.append(source)
1082                 self.taghash[tag] = source
1083                 return (source)
1084
1085         def findid(self, id):
1086                 for source in sources:
1087                         if (source.name == id):
1088                                 return source
1089                 return (None)
1090
1091         def timespan(self):
1092                 return (self.timestamp_f - self.timestamp_l);
1093
1094         def ticksps(self):
1095                 oneghz = 1000000000
1096                 # Use user supplied clock first
1097                 if (clockfreq != None):
1098                         return int(clockfreq * oneghz)
1099
1100                 # Check for a discovered clock
1101                 if (self.stathz != 0):
1102                         return (self.timespan() / self.ticks[0]) * int(self.stathz)
1103                 # Pretend we have a 1ns clock
1104                 print("WARNING: No clock discovered and no frequency ", end=' ')
1105                 print("specified via the command line.")
1106                 print("Using fake 1ghz clock")
1107                 return (oneghz);
1108
1109         def fixup(self):
1110                 for source in sources:
1111                         e = PadEvent(source, -1, self.timestamp_l)
1112                         source.addevent(e)
1113                         e = PadEvent(source, -1, self.timestamp_f, last=1)
1114                         source.addlastevent(e)
1115                         source.fixup()
1116                 sources.sort()
1117
1118 class SchedNames(Canvas):
1119         def __init__(self, master, display):
1120                 self.display = display
1121                 self.parent = master
1122                 self.bdheight = master.bdheight
1123                 self.items = {}
1124                 self.ysize = 0
1125                 self.lines = []
1126                 Canvas.__init__(self, master, width=120,
1127                     height=display["height"], bg='grey',
1128                     scrollregion=(0, 0, 50, 100))
1129
1130         def moveline(self, cur_y, y):
1131                 for line in self.lines:
1132                         (x0, y0, x1, y1) = self.coords(line)
1133                         if (cur_y != y0):
1134                                 continue
1135                         self.move(line, 0, y)
1136                         return
1137
1138         def draw(self):
1139                 status.startup("Drawing names")
1140                 ypos = 0
1141                 self.configure(scrollregion=(0, 0,
1142                     self["width"], self.display.ysize()))
1143                 for source in sources:
1144                         l = self.create_line(0, ypos, self["width"], ypos,
1145                             width=1, fill="black", tags=("all","sources"))
1146                         self.lines.append(l)
1147                         ypos += self.bdheight
1148                         ypos += source.ysize()
1149                         t = source.drawname(self, ypos)
1150                         self.items[t] = source
1151                         ypos += self.bdheight
1152                 self.ysize = ypos
1153                 self.create_line(0, ypos, self["width"], ypos,
1154                     width=1, fill="black", tags=("all",))
1155                 self.bind("<Button-1>", self.master.mousepress);
1156                 self.bind("<Button-3>", self.master.mousepressright);
1157                 self.bind("<ButtonRelease-1>", self.master.mouserelease);
1158                 self.bind("<B1-Motion>", self.master.mousemotion);
1159
1160         def updatescroll(self):
1161                 self.configure(scrollregion=(0, 0,
1162                     self["width"], self.display.ysize()))
1163
1164
1165 class SchedDisplay(Canvas):
1166         def __init__(self, master):
1167                 self.ratio = 1
1168                 self.parent = master
1169                 self.bdheight = master.bdheight
1170                 self.items = {}
1171                 self.lines = []
1172                 Canvas.__init__(self, master, width=800, height=500, bg='grey',
1173                      scrollregion=(0, 0, 800, 500))
1174
1175         def prepare(self):
1176                 #
1177                 # Compute a ratio to ensure that the file's timespan fits into
1178                 # 2^31.  Although python may handle larger values for X
1179                 # values, the Tk internals do not.
1180                 #
1181                 self.ratio = (ktrfile.timespan() - 1) / 2**31 + 1
1182
1183         def draw(self):
1184                 ypos = 0
1185                 xsize = self.xsize()
1186                 for source in sources:
1187                         status.startup("Drawing " + source.name)
1188                         l = self.create_line(0, ypos, xsize, ypos,
1189                             width=1, fill="black", tags=("all",))
1190                         self.lines.append(l)
1191                         ypos += self.bdheight
1192                         ypos += source.ysize()
1193                         source.draw(self, ypos)
1194                         ypos += self.bdheight
1195                 self.tag_raise("point", "state")
1196                 self.tag_lower("cpubg", ALL)
1197                 self.create_line(0, ypos, xsize, ypos,
1198                     width=1, fill="black", tags=("lines",))
1199                 self.tag_bind("event", "<Enter>", self.mouseenter)
1200                 self.tag_bind("event", "<Leave>", self.mouseexit)
1201                 self.bind("<Button-1>", self.mousepress)
1202                 self.bind("<Button-3>", self.master.mousepressright);
1203                 self.bind("<Button-4>", self.wheelup)
1204                 self.bind("<Button-5>", self.wheeldown)
1205                 self.bind("<ButtonRelease-1>", self.master.mouserelease);
1206                 self.bind("<B1-Motion>", self.master.mousemotion);
1207
1208         def moveline(self, cur_y, y):
1209                 for line in self.lines:
1210                         (x0, y0, x1, y1) = self.coords(line)
1211                         if (cur_y != y0):
1212                                 continue
1213                         self.move(line, 0, y)
1214                         return
1215
1216         def mouseenter(self, event):
1217                 item, = self.find_withtag(CURRENT)
1218                 self.items[item].mouseenter(self)
1219
1220         def mouseexit(self, event):
1221                 item, = self.find_withtag(CURRENT)
1222                 self.items[item].mouseexit(self)
1223
1224         def mousepress(self, event):
1225                 # Find out what's beneath us
1226                 items = self.find_withtag(CURRENT)
1227                 if (len(items) == 0):
1228                         self.master.mousepress(event)
1229                         return
1230                 # Only grab mouse presses for things with event tags.
1231                 item = items[0]
1232                 tags = self.gettags(item)
1233                 for tag in tags:
1234                         if (tag == "event"):
1235                                 self.items[item].mousepress(self)
1236                                 return
1237                 # Leave the rest to the master window
1238                 self.master.mousepress(event)
1239
1240         def wheeldown(self, event):
1241                 self.parent.display_yview("scroll", 1, "units")
1242
1243         def wheelup(self, event):
1244                 self.parent.display_yview("scroll", -1, "units")
1245
1246         def xsize(self):
1247                 return ((ktrfile.timespan() / self.ratio) + (X_BORDER * 2))
1248
1249         def ysize(self):
1250                 ysize = 0
1251                 for source in sources:
1252                         if (source.hidden == 1):
1253                                 continue
1254                         ysize += self.parent.sourcesize(source)
1255                 return ysize
1256
1257         def scaleset(self, ratio):
1258                 if (ktrfile == None):
1259                         return
1260                 oldratio = self.ratio
1261                 xstart, xend = self.xview()
1262                 midpoint = xstart + ((xend - xstart) / 2)
1263
1264                 self.ratio = ratio
1265                 self.updatescroll()
1266                 self.scale(ALL, 0, 0, float(oldratio) / ratio, 1)
1267
1268                 xstart, xend = self.xview()
1269                 xsize = (xend - xstart) / 2
1270                 self.xview_moveto(midpoint - xsize)
1271
1272         def updatescroll(self):
1273                 self.configure(scrollregion=(0, 0, self.xsize(), self.ysize()))
1274
1275         def scaleget(self):
1276                 return self.ratio
1277
1278         def getcolor(self, tag):
1279                 return self.itemcget(tag, "fill")
1280
1281         def getstate(self, tag):
1282                 return self.itemcget(tag, "state")
1283
1284         def setcolor(self, tag, color):
1285                 self.itemconfigure(tag, state="normal", fill=color)
1286
1287         def hide(self, tag):
1288                 self.itemconfigure(tag, state="hidden")
1289
1290 class GraphMenu(Frame):
1291         def __init__(self, master):
1292                 Frame.__init__(self, master, bd=2, relief=RAISED)
1293                 self.conf = Menubutton(self, text="Configure")
1294                 self.confmenu = Menu(self.conf, tearoff=0)
1295                 self.confmenu.add_command(label="Event Colors",
1296                     command=self.econf)
1297                 self.confmenu.add_command(label="CPU Colors",
1298                     command=self.cconf)
1299                 self.confmenu.add_command(label="Source Configure",
1300                     command=self.sconf)
1301                 self.conf["menu"] = self.confmenu
1302                 self.conf.pack(side=LEFT)
1303
1304         def econf(self):
1305                 ColorConfigure(eventcolors, "Event Display Configuration")
1306
1307         def cconf(self):
1308                 ColorConfigure(cpucolors, "CPU Background Colors")
1309
1310         def sconf(self):
1311                 SourceConfigure()
1312
1313 class SchedGraph(Frame):
1314         def __init__(self, master):
1315                 Frame.__init__(self, master)
1316                 self.menu = None
1317                 self.names = None
1318                 self.display = None
1319                 self.scale = None
1320                 self.status = None
1321                 self.bdheight = Y_BORDER
1322                 self.clicksource = None
1323                 self.lastsource = None
1324                 self.pack(expand=1, fill="both")
1325                 self.buildwidgets()
1326                 self.layout()
1327                 self.bind_all("<Control-q>", self.quitcb)
1328
1329         def quitcb(self, event):
1330                 self.quit()
1331
1332         def buildwidgets(self):
1333                 global status
1334                 self.menu = GraphMenu(self)
1335                 self.display = SchedDisplay(self)
1336                 self.names = SchedNames(self, self.display)
1337                 self.scale = Scaler(self, self.display)
1338                 status = self.status = Status(self)
1339                 self.scrollY = Scrollbar(self, orient="vertical",
1340                     command=self.display_yview)
1341                 self.display.scrollX = Scrollbar(self, orient="horizontal",
1342                     command=self.display.xview)
1343                 self.display["xscrollcommand"] = self.display.scrollX.set
1344                 self.display["yscrollcommand"] = self.scrollY.set
1345                 self.names["yscrollcommand"] = self.scrollY.set
1346
1347         def layout(self):
1348                 self.columnconfigure(1, weight=1)
1349                 self.rowconfigure(1, weight=1)
1350                 self.menu.grid(row=0, column=0, columnspan=3, sticky=E+W)
1351                 self.names.grid(row=1, column=0, sticky=N+S)
1352                 self.display.grid(row=1, column=1, sticky=W+E+N+S)
1353                 self.scrollY.grid(row=1, column=2, sticky=N+S)
1354                 self.display.scrollX.grid(row=2, column=0, columnspan=2,
1355                     sticky=E+W)
1356                 self.scale.grid(row=3, column=0, columnspan=3, sticky=E+W)
1357                 self.status.grid(row=4, column=0, columnspan=3, sticky=E+W)
1358
1359         def draw(self):
1360                 self.master.update()
1361                 self.display.prepare()
1362                 self.names.draw()
1363                 self.display.draw()
1364                 self.status.startup("")
1365                 #
1366                 # Configure scale related values
1367                 #
1368                 scalemax = ktrfile.timespan() / int(self.display["width"])
1369                 width = int(root.geometry().split('x')[0])
1370                 self.constwidth = width - int(self.display["width"])
1371                 self.scale.setmax(scalemax)
1372                 self.scale.set(scalemax)
1373                 self.display.xview_moveto(0)
1374                 self.bind("<Configure>", self.resize)
1375
1376         def mousepress(self, event):
1377                 self.clicksource = self.sourceat(event.y)
1378
1379         def mousepressright(self, event):
1380                 source = self.sourceat(event.y)
1381                 if (source == None):
1382                         return
1383                 SourceContext(event, source)
1384
1385         def mouserelease(self, event):
1386                 if (self.clicksource == None):
1387                         return
1388                 newsource = self.sourceat(event.y)
1389                 if (self.clicksource != newsource):
1390                         self.sourceswap(self.clicksource, newsource)
1391                 self.clicksource = None
1392                 self.lastsource = None
1393
1394         def mousemotion(self, event):
1395                 if (self.clicksource == None):
1396                         return
1397                 newsource = self.sourceat(event.y)
1398                 #
1399                 # If we get a None source they moved off the page.
1400                 # swapsource() can't handle moving multiple items so just
1401                 # pretend we never clicked on anything to begin with so the
1402                 # user can't mouseover a non-contiguous area.
1403                 #
1404                 if (newsource == None):
1405                         self.clicksource = None
1406                         self.lastsource = None
1407                         return
1408                 if (newsource == self.lastsource):
1409                         return;
1410                 self.lastsource = newsource
1411                 if (newsource != self.clicksource):
1412                         self.sourceswap(self.clicksource, newsource)
1413
1414         # These are here because this object controls layout
1415         def sourcestart(self, source):
1416                 return source.y - self.bdheight - source.ysize()
1417
1418         def sourceend(self, source):
1419                 return source.y + self.bdheight
1420
1421         def sourcesize(self, source):
1422                 return (self.bdheight * 2) + source.ysize()
1423
1424         def sourceswap(self, source1, source2):
1425                 # Sort so we always know which one is on top.
1426                 if (source2.y < source1.y):
1427                         swap = source1
1428                         source1 = source2
1429                         source2 = swap
1430                 # Only swap adjacent sources
1431                 if (self.sourceend(source1) != self.sourcestart(source2)):
1432                         return
1433                 # Compute start coordinates and target coordinates
1434                 y1 = self.sourcestart(source1)
1435                 y2 = self.sourcestart(source2)
1436                 y1targ = y1 + self.sourcesize(source2)
1437                 y2targ = y1
1438                 #
1439                 # If the sizes are not equal, adjust the start of the lower
1440                 # source to account for the lost/gained space.
1441                 #
1442                 if (source1.ysize() != source2.ysize()):
1443                         diff = source2.ysize() - source1.ysize()
1444                         self.names.moveline(y2, diff);
1445                         self.display.moveline(y2, diff)
1446                 source1.move(self.display, 0, y1targ - y1)
1447                 source2.move(self.display, 0, y2targ - y2)
1448                 source1.movename(self.names, 0, y1targ - y1)
1449                 source2.movename(self.names, 0, y2targ - y2)
1450
1451         def sourcepicky(self, source):
1452                 if (source.hidden == 0):
1453                         return self.sourcestart(source)
1454                 # Revert to group based sort
1455                 sources.sort()
1456                 prev = None
1457                 for s in sources:
1458                         if (s == source):
1459                                 break
1460                         if (s.hidden == 0):
1461                                 prev = s
1462                 if (prev == None):
1463                         newy = 0
1464                 else:
1465                         newy = self.sourcestart(prev) + self.sourcesize(prev)
1466                 return newy
1467
1468         def sourceshow(self, source):
1469                 if (source.hidden == 0):
1470                         return;
1471                 newy = self.sourcepicky(source)
1472                 off = newy - self.sourcestart(source)
1473                 self.sourceshiftall(newy-1, self.sourcesize(source))
1474                 self.sourceshift(source, off)
1475                 source.hidden = 0
1476
1477         #
1478         # Optimized source show of multiple entries that only moves each
1479         # existing entry once.  Doing sourceshow() iteratively is too
1480         # expensive due to python's canvas.move().
1481         #
1482         def sourceshowlist(self, srclist):
1483                 srclist.sort(key=attrgetter('y'))
1484                 startsize = []
1485                 for source in srclist:
1486                         if (source.hidden == 0):
1487                                 srclist.remove(source)
1488                         startsize.append((self.sourcepicky(source),
1489                             self.sourcesize(source)))
1490
1491                 sources.sort(key=attrgetter('y'), reverse=True)
1492                 self.status.startup("Updating display...");
1493                 for source in sources:
1494                         if (source.hidden == 1):
1495                                 continue
1496                         nstart = self.sourcestart(source)
1497                         size = 0
1498                         for hidden in startsize:
1499                                 (start, sz) = hidden
1500                                 if (start <= nstart or start+sz <= nstart):
1501                                         size += sz
1502                         self.sourceshift(source, size)
1503                 idx = 0
1504                 size = 0
1505                 for source in srclist:
1506                         (newy, sz) = startsize[idx]
1507                         off = (newy + size) - self.sourcestart(source)
1508                         self.sourceshift(source, off)
1509                         source.hidden = 0
1510                         size += sz
1511                         idx += 1
1512                 self.updatescroll()
1513                 self.status.set("")
1514
1515         #
1516         # Optimized source hide of multiple entries that only moves each
1517         # remaining entry once.  Doing sourcehide() iteratively is too
1518         # expensive due to python's canvas.move().
1519         #
1520         def sourcehidelist(self, srclist):
1521                 srclist.sort(key=attrgetter('y'))
1522                 sources.sort(key=attrgetter('y'))
1523                 startsize = []
1524                 off = len(sources) * 100
1525                 self.status.startup("Updating display...");
1526                 for source in srclist:
1527                         if (source.hidden == 1):
1528                                 srclist.remove(source)
1529                         #
1530                         # Remember our old position so we can sort things
1531                         # below us when we're done.
1532                         #
1533                         startsize.append((self.sourcestart(source),
1534                             self.sourcesize(source)))
1535                         self.sourceshift(source, off)
1536                         source.hidden = 1
1537
1538                 idx = 0
1539                 size = 0
1540                 for hidden in startsize:
1541                         (start, sz) = hidden
1542                         size += sz
1543                         if (idx + 1 < len(startsize)):
1544                                 (stop, sz) = startsize[idx+1]
1545                         else:
1546                                 stop = self.display.ysize()
1547                         idx += 1
1548                         for source in sources:
1549                                 nstart = self.sourcestart(source)
1550                                 if (nstart < start or source.hidden == 1):
1551                                         continue
1552                                 if (nstart >= stop):
1553                                         break;
1554                                 self.sourceshift(source, -size)
1555                 self.updatescroll()
1556                 self.status.set("")
1557
1558         def sourcehide(self, source):
1559                 if (source.hidden == 1):
1560                         return;
1561                 # Move it out of the visible area
1562                 off = len(sources) * 100
1563                 start = self.sourcestart(source)
1564                 self.sourceshift(source, off)
1565                 self.sourceshiftall(start, -self.sourcesize(source))
1566                 source.hidden = 1
1567
1568         def sourceshift(self, source, off):
1569                 start = self.sourcestart(source)
1570                 source.move(self.display, 0, off)
1571                 source.movename(self.names, 0, off)
1572                 self.names.moveline(start, off);
1573                 self.display.moveline(start, off)
1574                 #
1575                 # We update the idle tasks to shrink the dirtied area so
1576                 # it does not always include the entire screen.
1577                 #
1578                 self.names.update_idletasks()
1579                 self.display.update_idletasks()
1580
1581         def sourceshiftall(self, start, off):
1582                 self.status.startup("Updating display...");
1583                 for source in sources:
1584                         nstart = self.sourcestart(source)
1585                         if (nstart < start):
1586                                 continue;
1587                         self.sourceshift(source, off)
1588                 self.updatescroll()
1589                 self.status.set("")
1590
1591         def sourceat(self, ypos):
1592                 (start, end) = self.names.yview()
1593                 starty = start * float(self.names.ysize)
1594                 ypos += starty
1595                 for source in sources:
1596                         if (source.hidden == 1):
1597                                 continue;
1598                         yend = self.sourceend(source)
1599                         ystart = self.sourcestart(source)
1600                         if (ypos >= ystart and ypos <= yend):
1601                                 return source
1602                 return None
1603
1604         def display_yview(self, *args):
1605                 self.names.yview(*args)
1606                 self.display.yview(*args)
1607
1608         def resize(self, *args):
1609                 width = int(root.geometry().split('x')[0])
1610                 scalemax = ktrfile.timespan() / (width - self.constwidth)
1611                 self.scale.setmax(scalemax)
1612
1613         def updatescroll(self):
1614                 self.names.updatescroll()
1615                 self.display.updatescroll()
1616
1617         def setcolor(self, tag, color):
1618                 self.display.setcolor(tag, color)
1619
1620         def hide(self, tag):
1621                 self.display.hide(tag)
1622
1623         def getcolor(self, tag):
1624                 return self.display.getcolor(tag)
1625
1626         def getstate(self, tag):
1627                 return self.display.getstate(tag)
1628
1629 if (len(sys.argv) != 2 and len(sys.argv) != 3):
1630         print("usage:", sys.argv[0], "<ktr file> [clock freq in ghz]")
1631         sys.exit(1)
1632
1633 if (len(sys.argv) > 2):
1634         clockfreq = float(sys.argv[2])
1635
1636 root = Tk()
1637 root.title("SchedGraph")
1638 colormap = Colormap(eventcolors)
1639 cpucolormap = Colormap(cpucolors)
1640 graph = SchedGraph(root)
1641 ktrfile = KTRFile(sys.argv[1])
1642 graph.draw()
1643 root.mainloop()