1 #!/usr/local/bin/python
3 # Copyright (c) 2002-2003, 2009, Jeffrey Roberson <jeff@freebsd.org>
6 # Redistribution and use in source and binary forms, with or without
7 # modification, are permitted provided that the following conditions
9 # 1. Redistributions of source code must retain the above copyright
10 # notice unmodified, this list of conditions, and the following
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.
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.
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.
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'
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
66 # BUGS: 1) Only 8 CPUs are supported, more CPUs require more choices of
67 # colours to represent them ;-)
74 ("yielding", "yellow"),
75 ("swapped", "violet"),
76 ("suspended", "purple"),
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"),
90 ("lend prio", "black"),
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"),
102 ("CPU 7", "thistle"),
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",
134 def __init__(self, table):
138 self.map[entry[0]] = entry[1]
140 def lookup(self, name):
142 color = self.map[name]
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))
150 def ticks2sec(ticks):
152 ns = float(ticksps) / 1000000000
155 return ("%.2fns" % ticks)
158 return ("%.2fus" % ticks)
161 return ("%.2fms" % ticks)
163 return ("%.2fs" % ticks)
166 def __init__(self, master, target):
167 Frame.__init__(self, master)
170 self.label = Label(self, text="Ticks per pixel")
171 self.label.pack(side=LEFT)
172 self.resolution = 100
175 def scaleset(self, value):
176 self.target.scaleset(int(value))
178 def set(self, value):
179 self.scale.set(value)
181 def setmax(self, value):
183 # We can't reconfigure the to_ value so we delete the old
184 # window and make a new one when we resize.
186 if (self.scale != None):
187 self.scale.pack_forget()
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())
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)
203 self.label.config(text=str)
206 self.label.config(text="")
208 def startup(self, str):
212 class ColorConf(Frame):
213 def __init__(self, master, name, color):
214 Frame.__init__(self, master)
215 if (graph.getstate(name) == "hidden"):
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)
231 self.label = Label(self, text=self.name, anchor=W)
232 self.sample = Canvas(self, width=24, height=24,
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,
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)
247 def setcolor(self, color):
248 self.color.set(color)
249 self.sample.itemconfigure(self.rect, fill=color)
254 if (self.color_current != self.color.get()):
256 if (self.enabled_current != self.enabled.get()):
258 self.color_current = self.color.get()
259 self.enabled_current = self.enabled.get()
261 if (self.enabled_current):
262 graph.setcolor(self.name, self.color_current)
264 graph.hide(self.name)
267 graph.setcolor(self.name, self.color_current)
270 self.setcolor(self.color_default)
271 self.enabled.set(self.enabled_default)
273 class ColorConfigure(Toplevel):
274 def __init__(self, table, name):
275 Toplevel.__init__(self)
278 self.items = LabelFrame(self, text="Item Type")
279 self.buttons = Frame(self)
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)
287 color = graph.getcolor(type[0])
289 self.additem(type[0], color)
290 self.bind("<Control-w>", self.destroycb)
292 def destroycb(self, event):
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)
301 def drawbuttons(self):
302 self.apply = Button(self.buttons, text="Apply",
304 self.default = Button(self.buttons, text="Revert",
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)
312 for item in self.types:
316 for item in self.types:
319 class SourceConf(Frame):
320 def __init__(self, master, source):
321 Frame.__init__(self, master)
322 if (source.hidden == 1):
327 self.name = source.name
328 self.enabled = IntVar()
329 self.enabled_default = enabled
330 self.enabled_current = enabled
331 self.enabled.set(enabled)
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)
343 if (self.enabled_current != self.enabled.get()):
348 self.enabled_current = self.enabled.get()
351 self.enabled.set(self.enabled_default)
359 class SourceConfigure(Toplevel):
361 Toplevel.__init__(self)
363 self.title("Source Configuration")
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")
369 self.buttons = Frame(self)
370 self.items[0].grid(row=0, column=0, sticky=E+W)
371 self.columnconfigure(0, weight=1)
375 for source in sources:
376 self.addsource(source)
378 self.buttons.grid(row=1, column=0, sticky=W)
379 self.bind("<Control-w>", self.destroycb)
381 def destroycb(self, event):
384 def addsource(self, source):
389 f = LabelFrame(self.iframe, bd=4, text="Sources")
390 f.grid(row=0, column=c, sticky=N+E+W)
392 item = SourceConf(self.items[self.icol], source)
393 self.sconfig.append(item)
394 item.grid(row=self.irow, column=0, sticky=E+W)
397 def drawbuttons(self):
398 self.apply = Button(self.buttons, text="Apply",
400 self.default = Button(self.buttons, text="Revert",
402 self.checkall = Button(self.buttons, text="Check All",
404 self.uncheckall = Button(self.buttons, text="Uncheck All",
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)
418 for item in self.sconfig:
419 if (item.changed() == 0):
421 if (item.enabled.get() == 1):
422 enable_sources.append(item.source)
424 disable_sources.append(item.source)
426 if (len(disable_sources)):
427 graph.sourcehidelist(disable_sources)
428 if (len(enable_sources)):
429 graph.sourceshowlist(enable_sources)
431 for item in self.sconfig:
435 for item in self.sconfig:
439 for item in self.sconfig:
443 for item in self.sconfig:
446 # Reverse compare of second member of the tuple
447 def cmp_counts(x, y):
450 class SourceStats(Toplevel):
451 def __init__(self, source):
453 Toplevel.__init__(self)
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)
460 for event in self.source.events:
461 if (event.type == "pad"):
463 duration = event.duration
464 if (eventtypes.has_key(event.name)):
465 (c, d) = eventtypes[event.name]
468 eventtypes[event.name] = (c, d)
470 eventtypes[event.name] = (1, duration)
472 for k, v in eventtypes.iteritems():
474 events.append((k, c, d))
475 events.sort(cmp=cmp_counts)
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)
493 Label(self.evframe, text=ticks2sec(d),
494 bd=1, relief=SUNKEN, width=10).grid(
495 row=ypos, column=3, sticky=W+E)
497 self.bind("<Control-w>", self.destroycb)
499 def destroycb(self, event):
503 class SourceContext(Menu):
504 def __init__(self, event, 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)
513 graph.sourcehide(self.source)
517 for source in sources:
518 if (source.group == self.source.group):
519 grouplist.append(source)
520 graph.sourcehidelist(grouplist)
523 graph.sourceshow(self.source)
526 SourceStats(self.source)
528 class EventView(Toplevel):
529 def __init__(self, event, canvas):
530 Toplevel.__init__(self)
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)
541 event.displayref(canvas)
542 self.bind("<Destroy>", self.destroycb)
543 self.bind("<Control-w>", self.destroycb)
545 def destroycb(self, event):
546 self.unbind("<Destroy>")
547 if (self.event != None):
548 self.event.displayunref(self.canvas)
552 def clearlabels(self):
553 for label in self.frame.grid_slaves():
556 def drawlabels(self):
558 labels = self.event.labels()
559 while (len(labels) < 7):
560 labels.append(("", ""))
564 if (name == "linkedto"):
566 l = Label(self.frame, text=name, bd=1, width=15,
567 relief=SUNKEN, anchor=W)
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)
577 r.bind("<Button-1>", self.linkpress)
579 self.frame.columnconfigure(1, minsize=80)
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)
590 def newevent(self, event):
591 self.event.displayunref(self.canvas)
594 self.event.displayref(self.canvas)
598 EventView(self.event, self.canvas)
601 prev = self.event.prev()
604 while (prev.type == "pad"):
611 next = self.event.next()
614 while (next.type == "pad"):
620 def linkpress(self, wevent):
621 event = self.event.getlinked()
626 def __init__(self, source, name, cpu, timestamp, attrs):
630 self.timestamp = int(timestamp)
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)):
645 statstr += attr[0] + ": " + str(attr[1])
646 if (i != len(self.attrs) - 1):
651 return [("Source", self.source.name),
652 ("Event", self.name),
654 ("Timestamp", self.timestamp),
655 ("KTR Line ", self.recno)
658 def mouseenter(self, canvas):
659 self.displayref(canvas)
662 def mouseexit(self, canvas):
663 self.displayunref(canvas)
666 def mousepress(self, canvas):
667 EventView(self, canvas)
669 def draw(self, canvas, xpos, ypos, item):
672 canvas.items[item] = self
674 def move(self, canvas, x, y):
675 if (self.item == None):
677 canvas.move(self.item, x, y);
680 return self.source.eventat(self.idx + 1)
682 def nexttype(self, type):
684 while (next != None and next.type != type):
689 return self.source.eventat(self.idx - 1)
691 def displayref(self, canvas):
692 if (self.dispcnt == 0):
693 canvas.itemconfigure(self.item, width=2)
696 def displayunref(self, canvas):
698 if (self.dispcnt == 0):
699 canvas.itemconfigure(self.item, width=0)
700 canvas.tag_raise("point", "state")
703 for attr in self.attrs:
704 if (attr[0] != "linkedto"):
706 source = ktrfile.findid(attr[1])
707 return source.findevent(self.timestamp)
710 class PointEvent(Event):
712 def __init__(self, source, name, cpu, timestamp, attrs):
713 Event.__init__(self, source, name, cpu, timestamp, attrs)
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),
720 tags=("event", self.type, self.name, self.source.tag))
721 Event.draw(self, canvas, xpos, ypos, l)
725 class StateEvent(Event):
727 def __init__(self, source, name, cpu, timestamp, attrs):
728 Event.__init__(self, source, name, cpu, timestamp, attrs)
730 def draw(self, canvas, xpos, ypos):
731 next = self.nexttype("state")
734 self.duration = duration = next.timestamp - self.timestamp
735 self.attrs.insert(0, ("duration", ticks2sec(duration)))
736 color = colormap.lookup(self.name)
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)
748 return (xpos + delta)
750 class CountEvent(Event):
752 def __init__(self, source, count, cpu, timestamp, attrs):
755 Event.__init__(self, source, "count", cpu, timestamp, attrs)
757 def draw(self, canvas, xpos, ypos):
758 next = self.nexttype("count")
761 color = colormap.lookup("count")
762 self.duration = duration = next.timestamp - self.timestamp
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)
778 class PadEvent(StateEvent):
780 def __init__(self, source, cpu, timestamp, last=0):
782 cpu = source.events[len(source.events) -1].cpu
784 cpu = source.events[0].cpu
785 StateEvent.__init__(self, source, "pad", cpu, timestamp, [])
786 def draw(self, canvas, xpos, ypos):
790 duration = next.timestamp - self.timestamp
791 delta = duration / canvas.ratio
792 Event.draw(self, canvas, xpos, ypos, None)
793 return (xpos + delta)
795 # Sort function for start y address
796 def source_cmp_start(x, y):
800 def __init__(self, group, id):
808 self.tag = group + id
810 def __cmp__(self, other):
813 if (self.group == other.group):
814 return cmp(self.name, other.name)
815 return cmp(self.group, other.group)
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.
821 self.events.reverse()
823 def addevent(self, event):
824 self.events.append(event)
826 def addlastevent(self, event):
827 self.events.insert(0, event)
829 def draw(self, canvas, ypos):
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)
840 xpos = event.draw(canvas, xpos, ypos)
841 self.drawcpu(canvas, cpu, cpux, xpos, ypos)
843 def drawname(self, canvas, ypos):
845 ypos = ypos - (self.ysize() / 2)
846 self.item = canvas.create_text(X_BORDER, ypos, anchor="w",
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)
860 def move(self, canvas, xpos, ypos):
861 canvas.move(self.tag, xpos, ypos)
863 def movename(self, canvas, xpos, ypos):
865 canvas.move(self.item, xpos, ypos)
868 return (Y_EVENTSOURCE)
870 def eventat(self, i):
871 if (i >= len(self.events) or i < 0):
873 event = self.events[i]
876 def findevent(self, timestamp):
877 for event in self.events:
878 if (event.timestamp >= timestamp and event.type != "pad"):
882 class Counter(EventSource):
884 # Store a hash of counter groups that keeps the max value
885 # for a counter in this group for scaling purposes.
888 def __init__(self, group, id):
890 Counter.cnt = Counter.groups[group]
892 Counter.groups[group] = 0
893 EventSource.__init__(self, group, id)
896 for event in self.events:
897 if (event.type != "count"):
899 count = int(event.count)
900 if (count > Counter.groups[self.group]):
901 Counter.groups[self.group] = count
902 EventSource.fixup(self)
905 return (Counter.groups[self.group])
911 return (self.ysize() / self.ymax())
914 def __init__(self, file):
915 self.timestamp_f = None
916 self.timestamp_l = None
928 ticksps = self.ticksps()
929 span = self.timespan()
930 ghz = float(ticksps) / 1000000000.0
932 # Update the title with some stats from the file
934 titlestr = "SchedGraph: "
935 titlestr += ticks2sec(span) + " at %.3f ghz, " % ghz
936 titlestr += str(len(sources)) + " event sources, "
937 titlestr += str(self.eventcnt) + " events"
940 def parse(self, file):
944 print "Can't open", file
947 # quoteexp matches a quoted string, no escaping
948 quoteexp = "\"([^\"]*)\""
951 # commaexp matches a quoted string OR the string up
954 commaexp = "(?:" + quoteexp + "|([^,]+))"
957 # colonstr matches a quoted string OR the string up
960 colonexp = "(?:" + quoteexp + "|([^:]+))"
963 # Match various manditory parts of the KTR string this is
964 # fairly inflexible until you get to attributes to make
967 hdrexp = "\s*(\d+)\s+(\d+)\s+(\d+)\s+"
968 groupexp = "KTRGRAPH group:" + quoteexp + ", "
969 idexp = "id:" + quoteexp + ", "
970 typeexp = "([^:]+):" + commaexp + ", "
971 attribexp = "attributes: (.*)"
974 # Matches optional attributes in the KTR string. This
975 # tolerates more variance as the users supply these values.
977 attrexp = colonexp + "\s*:\s*(?:" + commaexp + ", (.*)|"
978 attrexp += quoteexp +"|(.*))"
981 ktrre = re.compile(hdrexp + groupexp + idexp + typeexp + attribexp)
982 attrre = re.compile(attrexp)
986 for line in ifp.readlines():
988 if ((lineno % 2048) == 0):
989 status.startup("Parsing line " + str(lineno))
990 m = ktrre.match(line);
992 print "Can't parse", lineno, line,
994 (index, cpu, timestamp, group, id, type, dat, dat1, attrstring) = m.groups();
997 if (self.checkstamp(timestamp) == 0):
998 print "Bad timestamp at", lineno, ":",
1002 # Build the table of optional attributes
1005 while (attrstring != None):
1006 m = attrre.match(attrstring.strip())
1010 # Name may or may not be quoted.
1012 # For val we have four cases:
1013 # 1) quotes followed by comma and more
1015 # 2) no quotes followed by comma and more
1017 # 3) no more attributes or comma with quotes.
1018 # 4) no more attributes or comma without quotes.
1020 (name, name1, val, val1, attrstring, end, end1) = m.groups();
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)
1035 print "Unknown type", type, lineno, line,
1037 def makeevent(self, group, id, type, args):
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)
1051 def setstathz(self, val, cpu):
1052 self.stathz = int(val)
1055 ticks = self.ticks[cpu]
1058 self.ticks[cpu] += 1
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):
1067 self.timestamp_l = timestamp;
1070 def makeid(self, group, id, type):
1072 if (self.taghash.has_key(tag)):
1073 return self.taghash[tag]
1074 if (type == "counter"):
1075 source = Counter(group, id)
1077 source = EventSource(group, id)
1078 sources.append(source)
1079 self.taghash[tag] = source
1082 def findid(self, id):
1083 for source in sources:
1084 if (source.name == id):
1089 return (self.timestamp_f - self.timestamp_l);
1093 # Use user supplied clock first
1094 if (clockfreq != None):
1095 return int(clockfreq * oneghz)
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"
1107 for source in sources:
1108 e = PadEvent(source, -1, self.timestamp_l)
1110 e = PadEvent(source, -1, self.timestamp_f, last=1)
1111 source.addlastevent(e)
1115 class SchedNames(Canvas):
1116 def __init__(self, master, display):
1117 self.display = display
1118 self.parent = master
1119 self.bdheight = master.bdheight
1123 Canvas.__init__(self, master, width=120,
1124 height=display["height"], bg='grey',
1125 scrollregion=(0, 0, 50, 100))
1127 def moveline(self, cur_y, y):
1128 for line in self.lines:
1129 (x0, y0, x1, y1) = self.coords(line)
1132 self.move(line, 0, y)
1136 status.startup("Drawing names")
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
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);
1157 def updatescroll(self):
1158 self.configure(scrollregion=(0, 0,
1159 self["width"], self.display.ysize()))
1162 class SchedDisplay(Canvas):
1163 def __init__(self, master):
1165 self.parent = master
1166 self.bdheight = master.bdheight
1169 Canvas.__init__(self, master, width=800, height=500, bg='grey',
1170 scrollregion=(0, 0, 800, 500))
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.
1178 self.ratio = (ktrfile.timespan() - 1) / 2**31 + 1
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);
1205 def moveline(self, cur_y, y):
1206 for line in self.lines:
1207 (x0, y0, x1, y1) = self.coords(line)
1210 self.move(line, 0, y)
1213 def mouseenter(self, event):
1214 item, = self.find_withtag(CURRENT)
1215 self.items[item].mouseenter(self)
1217 def mouseexit(self, event):
1218 item, = self.find_withtag(CURRENT)
1219 self.items[item].mouseexit(self)
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)
1227 # Only grab mouse presses for things with event tags.
1229 tags = self.gettags(item)
1231 if (tag == "event"):
1232 self.items[item].mousepress(self)
1234 # Leave the rest to the master window
1235 self.master.mousepress(event)
1237 def wheeldown(self, event):
1238 self.parent.display_yview("scroll", 1, "units")
1240 def wheelup(self, event):
1241 self.parent.display_yview("scroll", -1, "units")
1244 return ((ktrfile.timespan() / self.ratio) + (X_BORDER * 2))
1248 for source in sources:
1249 if (source.hidden == 1):
1251 ysize += self.parent.sourcesize(source)
1254 def scaleset(self, ratio):
1255 if (ktrfile == None):
1257 oldratio = self.ratio
1258 xstart, xend = self.xview()
1259 midpoint = xstart + ((xend - xstart) / 2)
1263 self.scale(ALL, 0, 0, float(oldratio) / ratio, 1)
1265 xstart, xend = self.xview()
1266 xsize = (xend - xstart) / 2
1267 self.xview_moveto(midpoint - xsize)
1269 def updatescroll(self):
1270 self.configure(scrollregion=(0, 0, self.xsize(), self.ysize()))
1275 def getcolor(self, tag):
1276 return self.itemcget(tag, "fill")
1278 def getstate(self, tag):
1279 return self.itemcget(tag, "state")
1281 def setcolor(self, tag, color):
1282 self.itemconfigure(tag, state="normal", fill=color)
1284 def hide(self, tag):
1285 self.itemconfigure(tag, state="hidden")
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",
1294 self.confmenu.add_command(label="CPU Colors",
1296 self.confmenu.add_command(label="Source Configure",
1298 self.conf["menu"] = self.confmenu
1299 self.conf.pack(side=LEFT)
1302 ColorConfigure(eventcolors, "Event Display Configuration")
1305 ColorConfigure(cpucolors, "CPU Background Colors")
1310 class SchedGraph(Frame):
1311 def __init__(self, master):
1312 Frame.__init__(self, master)
1318 self.bdheight = Y_BORDER
1319 self.clicksource = None
1320 self.lastsource = None
1321 self.pack(expand=1, fill="both")
1324 self.bind_all("<Control-q>", self.quitcb)
1326 def quitcb(self, event):
1329 def buildwidgets(self):
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
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,
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)
1357 self.master.update()
1358 self.display.prepare()
1361 self.status.startup("")
1363 # Configure scale related values
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)
1373 def mousepress(self, event):
1374 self.clicksource = self.sourceat(event.y)
1376 def mousepressright(self, event):
1377 source = self.sourceat(event.y)
1378 if (source == None):
1380 SourceContext(event, source)
1382 def mouserelease(self, event):
1383 if (self.clicksource == None):
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
1391 def mousemotion(self, event):
1392 if (self.clicksource == None):
1394 newsource = self.sourceat(event.y)
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.
1401 if (newsource == None):
1402 self.clicksource = None
1403 self.lastsource = None
1405 if (newsource == self.lastsource):
1407 self.lastsource = newsource
1408 if (newsource != self.clicksource):
1409 self.sourceswap(self.clicksource, newsource)
1411 # These are here because this object controls layout
1412 def sourcestart(self, source):
1413 return source.y - self.bdheight - source.ysize()
1415 def sourceend(self, source):
1416 return source.y + self.bdheight
1418 def sourcesize(self, source):
1419 return (self.bdheight * 2) + source.ysize()
1421 def sourceswap(self, source1, source2):
1422 # Sort so we always know which one is on top.
1423 if (source2.y < source1.y):
1427 # Only swap adjacent sources
1428 if (self.sourceend(source1) != self.sourcestart(source2)):
1430 # Compute start coordinates and target coordinates
1431 y1 = self.sourcestart(source1)
1432 y2 = self.sourcestart(source2)
1433 y1targ = y1 + self.sourcesize(source2)
1436 # If the sizes are not equal, adjust the start of the lower
1437 # source to account for the lost/gained space.
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)
1448 def sourcepicky(self, source):
1449 if (source.hidden == 0):
1450 return self.sourcestart(source)
1451 # Revert to group based sort
1462 newy = self.sourcestart(prev) + self.sourcesize(prev)
1465 def sourceshow(self, source):
1466 if (source.hidden == 0):
1468 newy = self.sourcepicky(source)
1469 off = newy - self.sourcestart(source)
1470 self.sourceshiftall(newy-1, self.sourcesize(source))
1471 self.sourceshift(source, off)
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().
1479 def sourceshowlist(self, srclist):
1480 srclist.sort(cmp=source_cmp_start)
1482 for source in srclist:
1483 if (source.hidden == 0):
1484 srclist.remove(source)
1485 startsize.append((self.sourcepicky(source),
1486 self.sourcesize(source)))
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):
1493 nstart = self.sourcestart(source)
1495 for hidden in startsize:
1496 (start, sz) = hidden
1497 if (start <= nstart or start+sz <= nstart):
1499 self.sourceshift(source, size)
1502 for source in srclist:
1503 (newy, sz) = startsize[idx]
1504 off = (newy + size) - self.sourcestart(source)
1505 self.sourceshift(source, off)
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().
1517 def sourcehidelist(self, srclist):
1518 srclist.sort(cmp=source_cmp_start)
1519 sources.sort(cmp=source_cmp_start)
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)
1527 # Remember our old position so we can sort things
1528 # below us when we're done.
1530 startsize.append((self.sourcestart(source),
1531 self.sourcesize(source)))
1532 self.sourceshift(source, off)
1537 for hidden in startsize:
1538 (start, sz) = hidden
1540 if (idx + 1 < len(startsize)):
1541 (stop, sz) = startsize[idx+1]
1543 stop = self.display.ysize()
1545 for source in sources:
1546 nstart = self.sourcestart(source)
1547 if (nstart < start or source.hidden == 1):
1549 if (nstart >= stop):
1551 self.sourceshift(source, -size)
1555 def sourcehide(self, source):
1556 if (source.hidden == 1):
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))
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)
1572 # We update the idle tasks to shrink the dirtied area so
1573 # it does not always include the entire screen.
1575 self.names.update_idletasks()
1576 self.display.update_idletasks()
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):
1584 self.sourceshift(source, off)
1588 def sourceat(self, ypos):
1589 (start, end) = self.names.yview()
1590 starty = start * float(self.names.ysize)
1592 for source in sources:
1593 if (source.hidden == 1):
1595 yend = self.sourceend(source)
1596 ystart = self.sourcestart(source)
1597 if (ypos >= ystart and ypos <= yend):
1601 def display_yview(self, *args):
1602 self.names.yview(*args)
1603 self.display.yview(*args)
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)
1610 def updatescroll(self):
1611 self.names.updatescroll()
1612 self.display.updatescroll()
1614 def setcolor(self, tag, color):
1615 self.display.setcolor(tag, color)
1617 def hide(self, tag):
1618 self.display.hide(tag)
1620 def getcolor(self, tag):
1621 return self.display.getcolor(tag)
1623 def getstate(self, tag):
1624 return self.display.getstate(tag)
1626 if (len(sys.argv) != 2 and len(sys.argv) != 3):
1627 print "usage:", sys.argv[0], "<ktr file> [clock freq in ghz]"
1630 if (len(sys.argv) > 2):
1631 clockfreq = float(sys.argv[2])
1634 root.title("SchedGraph")
1635 colormap = Colormap(eventcolors)
1636 cpucolormap = Colormap(cpucolors)
1637 graph = SchedGraph(root)
1638 ktrfile = KTRFile(sys.argv[1])