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.
29 from __future__ import print_function
36 # - Install the ports/x11-toolkits/py-tkinter package; e.g.
37 # portinstall x11-toolkits/py-tkinter package
38 # - Add KTR_SCHED to KTR_COMPILE and KTR_MASK in your KERNCONF; e.g.
40 # options KTR_ENTRIES=32768
41 # options KTR_COMPILE=(KTR_SCHED)
42 # options KTR_MASK=(KTR_SCHED)
43 # - It is encouraged to increase KTR_ENTRIES size to gather enough
44 # information for analysis; e.g.
45 # options KTR_ENTRIES=262144
46 # as 32768 entries may only correspond to a second or two of profiling
47 # data depending on your workload.
48 # - Rebuild kernel with proper changes to KERNCONF and boot new kernel.
49 # - Run your workload to be profiled.
50 # - While the workload is continuing (i.e. before it finishes), disable
51 # KTR tracing by setting 'sysctl debug.ktr.mask=0'. This is necessary
52 # to avoid a race condition while running ktrdump, i.e. the KTR ring buffer
53 # will cycle a bit while ktrdump runs, and this confuses schedgraph because
54 # the timestamps appear to go backwards at some point. Stopping KTR logging
55 # while the workload is still running is to avoid wasting log entries on
56 # "idle" time at the end.
57 # - Dump the trace to a file: 'ktrdump -ct > ktr.out'
58 # - Run the python script: 'python schedgraph.py ktr.out' optionally provide
59 # your cpu frequency in ghz: 'python schedgraph.py ktr.out 2.4'
62 # Add a per-source summary display
63 # "Vertical rule" to help relate data in different rows
64 # Mouse-over popup of full thread/event/row label (currently truncated)
65 # More visible anchors for popup event windows
67 # BUGS: 1) Only 8 CPUs are supported, more CPUs require more choices of
68 # colours to represent them ;-)
75 ("yielding", "yellow"),
76 ("swapped", "violet"),
77 ("suspended", "purple"),
80 ("blocked", "dark red"),
81 ("runq add", "yellow"),
82 ("runq rem", "yellow"),
83 ("thread exit", "grey"),
84 ("proc exit", "grey"),
85 ("lock acquire", "blue"),
86 ("lock contest", "purple"),
87 ("failed lock try", "red"),
88 ("lock release", "grey"),
89 ("statclock", "black"),
91 ("lend prio", "black"),
96 ("CPU 0", "light grey"),
97 ("CPU 1", "dark grey"),
98 ("CPU 2", "light blue"),
99 ("CPU 3", "light pink"),
100 ("CPU 4", "blanched almond"),
101 ("CPU 5", "slate grey"),
103 ("CPU 7", "thistle"),
108 "white", "thistle", "blanched almond", "tan", "chartreuse",
109 "dark red", "red", "pale violet red", "pink", "light pink",
110 "dark orange", "orange", "coral", "light coral",
111 "goldenrod", "gold", "yellow", "light yellow",
112 "dark green", "green", "light green", "light sea green",
113 "dark blue", "blue", "light blue", "steel blue", "light slate blue",
114 "dark violet", "violet", "purple", "blue violet",
115 "dark grey", "slate grey", "light grey",
135 def __init__(self, table):
139 self.map[entry[0]] = entry[1]
141 def lookup(self, name):
143 color = self.map[name]
145 color = colors[random.randrange(0, len(colors))]
146 print("Picking random color", color, "for", name)
147 self.map[name] = color
148 self.table.append((name, color))
151 def ticks2sec(ticks):
153 ns = float(ticksps) / 1000000000
156 return ("%.2fns" % ticks)
159 return ("%.2fus" % ticks)
162 return ("%.2fms" % ticks)
164 return ("%.2fs" % ticks)
167 def __init__(self, master, target):
168 Frame.__init__(self, master)
171 self.label = Label(self, text="Ticks per pixel")
172 self.label.pack(side=LEFT)
173 self.resolution = 100
176 def scaleset(self, value):
177 self.target.scaleset(int(value))
179 def set(self, value):
180 self.scale.set(value)
182 def setmax(self, value):
184 # We can't reconfigure the to_ value so we delete the old
185 # window and make a new one when we resize.
187 if (self.scale != None):
188 self.scale.pack_forget()
190 self.scale = Scale(self, command=self.scaleset,
191 from_=100, to_=value, orient=HORIZONTAL,
192 resolution=self.resolution)
193 self.scale.pack(fill="both", expand=1)
194 self.scale.set(self.target.scaleget())
197 def __init__(self, master):
198 Frame.__init__(self, master)
199 self.label = Label(self, bd=1, relief=SUNKEN, anchor=W)
200 self.label.pack(fill="both", expand=1)
204 self.label.config(text=str)
207 self.label.config(text="")
209 def startup(self, str):
213 class ColorConf(Frame):
214 def __init__(self, master, name, color):
215 Frame.__init__(self, master)
216 if (graph.getstate(name) == "hidden"):
221 self.color = StringVar()
222 self.color_default = color
223 self.color_current = color
224 self.color.set(color)
225 self.enabled = IntVar()
226 self.enabled_default = enabled
227 self.enabled_current = enabled
228 self.enabled.set(enabled)
232 self.label = Label(self, text=self.name, anchor=W)
233 self.sample = Canvas(self, width=24, height=24,
235 self.rect = self.sample.create_rectangle(0, 0, 24, 24,
236 fill=self.color.get())
237 self.list = OptionMenu(self, self.color, command=self.setcolor,
239 self.checkbox = Checkbutton(self, text="enabled",
240 variable=self.enabled)
241 self.label.grid(row=0, column=0, sticky=E+W)
242 self.sample.grid(row=0, column=1)
243 self.list.grid(row=0, column=2, sticky=E+W)
244 self.checkbox.grid(row=0, column=3)
245 self.columnconfigure(0, weight=1)
246 self.columnconfigure(2, minsize=150)
248 def setcolor(self, color):
249 self.color.set(color)
250 self.sample.itemconfigure(self.rect, fill=color)
255 if (self.color_current != self.color.get()):
257 if (self.enabled_current != self.enabled.get()):
259 self.color_current = self.color.get()
260 self.enabled_current = self.enabled.get()
262 if (self.enabled_current):
263 graph.setcolor(self.name, self.color_current)
265 graph.hide(self.name)
268 graph.setcolor(self.name, self.color_current)
271 self.setcolor(self.color_default)
272 self.enabled.set(self.enabled_default)
274 class ColorConfigure(Toplevel):
275 def __init__(self, table, name):
276 Toplevel.__init__(self)
279 self.items = LabelFrame(self, text="Item Type")
280 self.buttons = Frame(self)
282 self.items.grid(row=0, column=0, sticky=E+W)
283 self.columnconfigure(0, weight=1)
284 self.buttons.grid(row=1, column=0, sticky=E+W)
288 color = graph.getcolor(type[0])
290 self.additem(type[0], color)
291 self.bind("<Control-w>", self.destroycb)
293 def destroycb(self, event):
296 def additem(self, name, color):
297 item = ColorConf(self.items, name, color)
298 self.types.append(item)
299 item.grid(row=self.irow, column=0, sticky=E+W)
302 def drawbuttons(self):
303 self.apply = Button(self.buttons, text="Apply",
305 self.default = Button(self.buttons, text="Revert",
307 self.apply.grid(row=0, column=0, sticky=E+W)
308 self.default.grid(row=0, column=1, sticky=E+W)
309 self.buttons.columnconfigure(0, weight=1)
310 self.buttons.columnconfigure(1, weight=1)
313 for item in self.types:
317 for item in self.types:
320 class SourceConf(Frame):
321 def __init__(self, master, source):
322 Frame.__init__(self, master)
323 if (source.hidden == 1):
328 self.name = source.name
329 self.enabled = IntVar()
330 self.enabled_default = enabled
331 self.enabled_current = enabled
332 self.enabled.set(enabled)
336 self.label = Label(self, text=self.name, anchor=W)
337 self.checkbox = Checkbutton(self, text="enabled",
338 variable=self.enabled)
339 self.label.grid(row=0, column=0, sticky=E+W)
340 self.checkbox.grid(row=0, column=1)
341 self.columnconfigure(0, weight=1)
344 if (self.enabled_current != self.enabled.get()):
349 self.enabled_current = self.enabled.get()
352 self.enabled.set(self.enabled_default)
360 class SourceConfigure(Toplevel):
362 Toplevel.__init__(self)
364 self.title("Source Configuration")
366 self.iframe = Frame(self)
367 self.iframe.grid(row=0, column=0, sticky=E+W)
368 f = LabelFrame(self.iframe, bd=4, text="Sources")
370 self.buttons = Frame(self)
371 self.items[0].grid(row=0, column=0, sticky=E+W)
372 self.columnconfigure(0, weight=1)
376 for source in sources:
377 self.addsource(source)
379 self.buttons.grid(row=1, column=0, sticky=W)
380 self.bind("<Control-w>", self.destroycb)
382 def destroycb(self, event):
385 def addsource(self, source):
390 f = LabelFrame(self.iframe, bd=4, text="Sources")
391 f.grid(row=0, column=c, sticky=N+E+W)
393 item = SourceConf(self.items[self.icol], source)
394 self.sconfig.append(item)
395 item.grid(row=self.irow, column=0, sticky=E+W)
398 def drawbuttons(self):
399 self.apply = Button(self.buttons, text="Apply",
401 self.default = Button(self.buttons, text="Revert",
403 self.checkall = Button(self.buttons, text="Check All",
405 self.uncheckall = Button(self.buttons, text="Uncheck All",
407 self.checkall.grid(row=0, column=0, sticky=W)
408 self.uncheckall.grid(row=0, column=1, sticky=W)
409 self.apply.grid(row=0, column=2, sticky=W)
410 self.default.grid(row=0, column=3, sticky=W)
411 self.buttons.columnconfigure(0, weight=1)
412 self.buttons.columnconfigure(1, weight=1)
413 self.buttons.columnconfigure(2, weight=1)
414 self.buttons.columnconfigure(3, weight=1)
419 for item in self.sconfig:
420 if (item.changed() == 0):
422 if (item.enabled.get() == 1):
423 enable_sources.append(item.source)
425 disable_sources.append(item.source)
427 if (len(disable_sources)):
428 graph.sourcehidelist(disable_sources)
429 if (len(enable_sources)):
430 graph.sourceshowlist(enable_sources)
432 for item in self.sconfig:
436 for item in self.sconfig:
440 for item in self.sconfig:
444 for item in self.sconfig:
447 # Reverse compare of second member of the tuple
448 def cmp_counts(x, y):
451 class SourceStats(Toplevel):
452 def __init__(self, source):
454 Toplevel.__init__(self)
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)
461 for event in self.source.events:
462 if (event.type == "pad"):
464 duration = event.duration
465 if (event.name in eventtypes):
466 (c, d) = eventtypes[event.name]
469 eventtypes[event.name] = (c, d)
471 eventtypes[event.name] = (1, duration)
473 for k, v in eventtypes.iteritems():
475 events.append((k, c, d))
476 events.sort(cmp=cmp_counts)
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)
494 Label(self.evframe, text=ticks2sec(d),
495 bd=1, relief=SUNKEN, width=10).grid(
496 row=ypos, column=3, sticky=W+E)
498 self.bind("<Control-w>", self.destroycb)
500 def destroycb(self, event):
504 class SourceContext(Menu):
505 def __init__(self, event, 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)
514 graph.sourcehide(self.source)
518 for source in sources:
519 if (source.group == self.source.group):
520 grouplist.append(source)
521 graph.sourcehidelist(grouplist)
524 graph.sourceshow(self.source)
527 SourceStats(self.source)
529 class EventView(Toplevel):
530 def __init__(self, event, canvas):
531 Toplevel.__init__(self)
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)
542 event.displayref(canvas)
543 self.bind("<Destroy>", self.destroycb)
544 self.bind("<Control-w>", self.destroycb)
546 def destroycb(self, event):
547 self.unbind("<Destroy>")
548 if (self.event != None):
549 self.event.displayunref(self.canvas)
553 def clearlabels(self):
554 for label in self.frame.grid_slaves():
557 def drawlabels(self):
559 labels = self.event.labels()
560 while (len(labels) < 7):
561 labels.append(("", ""))
565 if (name == "linkedto"):
567 l = Label(self.frame, text=name, bd=1, width=15,
568 relief=SUNKEN, anchor=W)
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)
578 r.bind("<Button-1>", self.linkpress)
580 self.frame.columnconfigure(1, minsize=80)
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)
591 def newevent(self, event):
592 self.event.displayunref(self.canvas)
595 self.event.displayref(self.canvas)
599 EventView(self.event, self.canvas)
602 prev = self.event.prev()
605 while (prev.type == "pad"):
612 next = self.event.next()
615 while (next.type == "pad"):
621 def linkpress(self, wevent):
622 event = self.event.getlinked()
627 def __init__(self, source, name, cpu, timestamp, attrs):
631 self.timestamp = int(timestamp)
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)):
646 statstr += attr[0] + ": " + str(attr[1])
647 if (i != len(self.attrs) - 1):
652 return [("Source", self.source.name),
653 ("Event", self.name),
655 ("Timestamp", self.timestamp),
656 ("KTR Line ", self.recno)
659 def mouseenter(self, canvas):
660 self.displayref(canvas)
663 def mouseexit(self, canvas):
664 self.displayunref(canvas)
667 def mousepress(self, canvas):
668 EventView(self, canvas)
670 def draw(self, canvas, xpos, ypos, item):
673 canvas.items[item] = self
675 def move(self, canvas, x, y):
676 if (self.item == None):
678 canvas.move(self.item, x, y);
681 return self.source.eventat(self.idx + 1)
683 def nexttype(self, type):
685 while (next != None and next.type != type):
690 return self.source.eventat(self.idx - 1)
692 def displayref(self, canvas):
693 if (self.dispcnt == 0):
694 canvas.itemconfigure(self.item, width=2)
697 def displayunref(self, canvas):
699 if (self.dispcnt == 0):
700 canvas.itemconfigure(self.item, width=0)
701 canvas.tag_raise("point", "state")
704 for attr in self.attrs:
705 if (attr[0] != "linkedto"):
707 source = ktrfile.findid(attr[1])
708 return source.findevent(self.timestamp)
711 class PointEvent(Event):
713 def __init__(self, source, name, cpu, timestamp, attrs):
714 Event.__init__(self, source, name, cpu, timestamp, attrs)
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),
721 tags=("event", self.type, self.name, self.source.tag))
722 Event.draw(self, canvas, xpos, ypos, l)
726 class StateEvent(Event):
728 def __init__(self, source, name, cpu, timestamp, attrs):
729 Event.__init__(self, source, name, cpu, timestamp, attrs)
731 def draw(self, canvas, xpos, ypos):
732 next = self.nexttype("state")
735 self.duration = duration = next.timestamp - self.timestamp
736 self.attrs.insert(0, ("duration", ticks2sec(duration)))
737 color = colormap.lookup(self.name)
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)
749 return (xpos + delta)
751 class CountEvent(Event):
753 def __init__(self, source, count, cpu, timestamp, attrs):
756 Event.__init__(self, source, "count", cpu, timestamp, attrs)
758 def draw(self, canvas, xpos, ypos):
759 next = self.nexttype("count")
762 color = colormap.lookup("count")
763 self.duration = duration = next.timestamp - self.timestamp
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)
779 class PadEvent(StateEvent):
781 def __init__(self, source, cpu, timestamp, last=0):
783 cpu = source.events[len(source.events) -1].cpu
785 cpu = source.events[0].cpu
786 StateEvent.__init__(self, source, "pad", cpu, timestamp, [])
787 def draw(self, canvas, xpos, ypos):
791 duration = next.timestamp - self.timestamp
792 delta = duration / canvas.ratio
793 Event.draw(self, canvas, xpos, ypos, None)
794 return (xpos + delta)
796 # Sort function for start y address
797 def source_cmp_start(x, y):
801 def __init__(self, group, id):
809 self.tag = group + id
811 def __cmp__(self, other):
814 if (self.group == other.group):
815 return cmp(self.name, other.name)
816 return cmp(self.group, other.group)
818 # It is much faster to append items to a list then to insert them
819 # at the beginning. As a result, we add events in reverse order
820 # and then swap the list during fixup.
822 self.events.reverse()
824 def addevent(self, event):
825 self.events.append(event)
827 def addlastevent(self, event):
828 self.events.insert(0, event)
830 def draw(self, canvas, ypos):
833 cpu = self.events[1].cpu
834 for i in range(0, len(self.events)):
835 self.events[i].idx = i
836 for event in self.events:
837 if (event.cpu != cpu and event.cpu != -1):
838 self.drawcpu(canvas, cpu, cpux, xpos, ypos)
841 xpos = event.draw(canvas, xpos, ypos)
842 self.drawcpu(canvas, cpu, cpux, xpos, ypos)
844 def drawname(self, canvas, ypos):
846 ypos = ypos - (self.ysize() / 2)
847 self.item = canvas.create_text(X_BORDER, ypos, anchor="w",
851 def drawcpu(self, canvas, cpu, fromx, tox, ypos):
852 cpu = "CPU " + str(cpu)
853 color = cpucolormap.lookup(cpu)
854 # Create the cpu background colors default to hidden
855 l = canvas.create_rectangle(fromx,
856 ypos - self.ysize() - canvas.bdheight,
857 tox, ypos + canvas.bdheight, fill=color, width=0,
858 tags=("cpubg", cpu, self.tag), state="hidden")
859 self.cpuitems.append(l)
861 def move(self, canvas, xpos, ypos):
862 canvas.move(self.tag, xpos, ypos)
864 def movename(self, canvas, xpos, ypos):
866 canvas.move(self.item, xpos, ypos)
869 return (Y_EVENTSOURCE)
871 def eventat(self, i):
872 if (i >= len(self.events) or i < 0):
874 event = self.events[i]
877 def findevent(self, timestamp):
878 for event in self.events:
879 if (event.timestamp >= timestamp and event.type != "pad"):
883 class Counter(EventSource):
885 # Store a hash of counter groups that keeps the max value
886 # for a counter in this group for scaling purposes.
889 def __init__(self, group, id):
891 Counter.cnt = Counter.groups[group]
893 Counter.groups[group] = 0
894 EventSource.__init__(self, group, id)
897 for event in self.events:
898 if (event.type != "count"):
900 count = int(event.count)
901 if (count > Counter.groups[self.group]):
902 Counter.groups[self.group] = count
903 EventSource.fixup(self)
906 return (Counter.groups[self.group])
912 return (self.ysize() / self.ymax())
915 def __init__(self, file):
916 self.timestamp_f = None
917 self.timestamp_l = None
929 ticksps = self.ticksps()
930 span = self.timespan()
931 ghz = float(ticksps) / 1000000000.0
933 # Update the title with some stats from the file
935 titlestr = "SchedGraph: "
936 titlestr += ticks2sec(span) + " at %.3f ghz, " % ghz
937 titlestr += str(len(sources)) + " event sources, "
938 titlestr += str(self.eventcnt) + " events"
941 def parse(self, file):
945 print("Can't open", file)
948 # quoteexp matches a quoted string, no escaping
949 quoteexp = "\"([^\"]*)\""
952 # commaexp matches a quoted string OR the string up
955 commaexp = "(?:" + quoteexp + "|([^,]+))"
958 # colonstr matches a quoted string OR the string up
961 colonexp = "(?:" + quoteexp + "|([^:]+))"
964 # Match various manditory parts of the KTR string this is
965 # fairly inflexible until you get to attributes to make
968 hdrexp = "\s*(\d+)\s+(\d+)\s+(\d+)\s+"
969 groupexp = "KTRGRAPH group:" + quoteexp + ", "
970 idexp = "id:" + quoteexp + ", "
971 typeexp = "([^:]+):" + commaexp + ", "
972 attribexp = "attributes: (.*)"
975 # Matches optional attributes in the KTR string. This
976 # tolerates more variance as the users supply these values.
978 attrexp = colonexp + "\s*:\s*(?:" + commaexp + ", (.*)|"
979 attrexp += quoteexp +"|(.*))"
982 ktrre = re.compile(hdrexp + groupexp + idexp + typeexp + attribexp)
983 attrre = re.compile(attrexp)
987 for line in ifp.readlines():
989 if ((lineno % 2048) == 0):
990 status.startup("Parsing line " + str(lineno))
991 m = ktrre.match(line);
993 print("Can't parse", lineno, line, end=' ')
995 (index, cpu, timestamp, group, id, type, dat, dat1, attrstring) = m.groups();
998 if (self.checkstamp(timestamp) == 0):
999 print("Bad timestamp at", lineno, ":", end=' ')
1000 print(cpu, timestamp)
1003 # Build the table of optional attributes
1006 while (attrstring != None):
1007 m = attrre.match(attrstring.strip())
1011 # Name may or may not be quoted.
1013 # For val we have four cases:
1014 # 1) quotes followed by comma and more
1016 # 2) no quotes followed by comma and more
1018 # 3) no more attributes or comma with quotes.
1019 # 4) no more attributes or comma without quotes.
1021 (name, name1, val, val1, attrstring, end, end1) = m.groups();
1030 if (name == "stathz"):
1031 self.setstathz(val, cpu)
1032 attrs.append((name, val))
1033 args = (dat, cpu, timestamp, attrs)
1034 e = self.makeevent(group, id, type, args)
1036 print("Unknown type", type, lineno, line, end=' ')
1038 def makeevent(self, group, id, type, args):
1040 source = self.makeid(group, id, type)
1041 if (type == "state"):
1042 e = StateEvent(source, *args)
1043 elif (type == "counter"):
1044 e = CountEvent(source, *args)
1045 elif (type == "point"):
1046 e = PointEvent(source, *args)
1052 def setstathz(self, val, cpu):
1053 self.stathz = int(val)
1056 ticks = self.ticks[cpu]
1059 self.ticks[cpu] += 1
1061 def checkstamp(self, timestamp):
1062 timestamp = int(timestamp)
1063 if (self.timestamp_f == None):
1064 self.timestamp_f = timestamp;
1065 if (self.timestamp_l != None and
1066 timestamp -2048> self.timestamp_l):
1068 self.timestamp_l = timestamp;
1071 def makeid(self, group, id, type):
1073 if (tag in self.taghash):
1074 return self.taghash[tag]
1075 if (type == "counter"):
1076 source = Counter(group, id)
1078 source = EventSource(group, id)
1079 sources.append(source)
1080 self.taghash[tag] = source
1083 def findid(self, id):
1084 for source in sources:
1085 if (source.name == id):
1090 return (self.timestamp_f - self.timestamp_l);
1094 # Use user supplied clock first
1095 if (clockfreq != None):
1096 return int(clockfreq * oneghz)
1098 # Check for a discovered clock
1099 if (self.stathz != 0):
1100 return (self.timespan() / self.ticks[0]) * int(self.stathz)
1101 # Pretend we have a 1ns clock
1102 print("WARNING: No clock discovered and no frequency ", end=' ')
1103 print("specified via the command line.")
1104 print("Using fake 1ghz clock")
1108 for source in sources:
1109 e = PadEvent(source, -1, self.timestamp_l)
1111 e = PadEvent(source, -1, self.timestamp_f, last=1)
1112 source.addlastevent(e)
1116 class SchedNames(Canvas):
1117 def __init__(self, master, display):
1118 self.display = display
1119 self.parent = master
1120 self.bdheight = master.bdheight
1124 Canvas.__init__(self, master, width=120,
1125 height=display["height"], bg='grey',
1126 scrollregion=(0, 0, 50, 100))
1128 def moveline(self, cur_y, y):
1129 for line in self.lines:
1130 (x0, y0, x1, y1) = self.coords(line)
1133 self.move(line, 0, y)
1137 status.startup("Drawing names")
1139 self.configure(scrollregion=(0, 0,
1140 self["width"], self.display.ysize()))
1141 for source in sources:
1142 l = self.create_line(0, ypos, self["width"], ypos,
1143 width=1, fill="black", tags=("all","sources"))
1144 self.lines.append(l)
1145 ypos += self.bdheight
1146 ypos += source.ysize()
1147 t = source.drawname(self, ypos)
1148 self.items[t] = source
1149 ypos += self.bdheight
1151 self.create_line(0, ypos, self["width"], ypos,
1152 width=1, fill="black", tags=("all",))
1153 self.bind("<Button-1>", self.master.mousepress);
1154 self.bind("<Button-3>", self.master.mousepressright);
1155 self.bind("<ButtonRelease-1>", self.master.mouserelease);
1156 self.bind("<B1-Motion>", self.master.mousemotion);
1158 def updatescroll(self):
1159 self.configure(scrollregion=(0, 0,
1160 self["width"], self.display.ysize()))
1163 class SchedDisplay(Canvas):
1164 def __init__(self, master):
1166 self.parent = master
1167 self.bdheight = master.bdheight
1170 Canvas.__init__(self, master, width=800, height=500, bg='grey',
1171 scrollregion=(0, 0, 800, 500))
1175 # Compute a ratio to ensure that the file's timespan fits into
1176 # 2^31. Although python may handle larger values for X
1177 # values, the Tk internals do not.
1179 self.ratio = (ktrfile.timespan() - 1) / 2**31 + 1
1183 xsize = self.xsize()
1184 for source in sources:
1185 status.startup("Drawing " + source.name)
1186 l = self.create_line(0, ypos, xsize, ypos,
1187 width=1, fill="black", tags=("all",))
1188 self.lines.append(l)
1189 ypos += self.bdheight
1190 ypos += source.ysize()
1191 source.draw(self, ypos)
1192 ypos += self.bdheight
1193 self.tag_raise("point", "state")
1194 self.tag_lower("cpubg", ALL)
1195 self.create_line(0, ypos, xsize, ypos,
1196 width=1, fill="black", tags=("lines",))
1197 self.tag_bind("event", "<Enter>", self.mouseenter)
1198 self.tag_bind("event", "<Leave>", self.mouseexit)
1199 self.bind("<Button-1>", self.mousepress)
1200 self.bind("<Button-3>", self.master.mousepressright);
1201 self.bind("<Button-4>", self.wheelup)
1202 self.bind("<Button-5>", self.wheeldown)
1203 self.bind("<ButtonRelease-1>", self.master.mouserelease);
1204 self.bind("<B1-Motion>", self.master.mousemotion);
1206 def moveline(self, cur_y, y):
1207 for line in self.lines:
1208 (x0, y0, x1, y1) = self.coords(line)
1211 self.move(line, 0, y)
1214 def mouseenter(self, event):
1215 item, = self.find_withtag(CURRENT)
1216 self.items[item].mouseenter(self)
1218 def mouseexit(self, event):
1219 item, = self.find_withtag(CURRENT)
1220 self.items[item].mouseexit(self)
1222 def mousepress(self, event):
1223 # Find out what's beneath us
1224 items = self.find_withtag(CURRENT)
1225 if (len(items) == 0):
1226 self.master.mousepress(event)
1228 # Only grab mouse presses for things with event tags.
1230 tags = self.gettags(item)
1232 if (tag == "event"):
1233 self.items[item].mousepress(self)
1235 # Leave the rest to the master window
1236 self.master.mousepress(event)
1238 def wheeldown(self, event):
1239 self.parent.display_yview("scroll", 1, "units")
1241 def wheelup(self, event):
1242 self.parent.display_yview("scroll", -1, "units")
1245 return ((ktrfile.timespan() / self.ratio) + (X_BORDER * 2))
1249 for source in sources:
1250 if (source.hidden == 1):
1252 ysize += self.parent.sourcesize(source)
1255 def scaleset(self, ratio):
1256 if (ktrfile == None):
1258 oldratio = self.ratio
1259 xstart, xend = self.xview()
1260 midpoint = xstart + ((xend - xstart) / 2)
1264 self.scale(ALL, 0, 0, float(oldratio) / ratio, 1)
1266 xstart, xend = self.xview()
1267 xsize = (xend - xstart) / 2
1268 self.xview_moveto(midpoint - xsize)
1270 def updatescroll(self):
1271 self.configure(scrollregion=(0, 0, self.xsize(), self.ysize()))
1276 def getcolor(self, tag):
1277 return self.itemcget(tag, "fill")
1279 def getstate(self, tag):
1280 return self.itemcget(tag, "state")
1282 def setcolor(self, tag, color):
1283 self.itemconfigure(tag, state="normal", fill=color)
1285 def hide(self, tag):
1286 self.itemconfigure(tag, state="hidden")
1288 class GraphMenu(Frame):
1289 def __init__(self, master):
1290 Frame.__init__(self, master, bd=2, relief=RAISED)
1291 self.conf = Menubutton(self, text="Configure")
1292 self.confmenu = Menu(self.conf, tearoff=0)
1293 self.confmenu.add_command(label="Event Colors",
1295 self.confmenu.add_command(label="CPU Colors",
1297 self.confmenu.add_command(label="Source Configure",
1299 self.conf["menu"] = self.confmenu
1300 self.conf.pack(side=LEFT)
1303 ColorConfigure(eventcolors, "Event Display Configuration")
1306 ColorConfigure(cpucolors, "CPU Background Colors")
1311 class SchedGraph(Frame):
1312 def __init__(self, master):
1313 Frame.__init__(self, master)
1319 self.bdheight = Y_BORDER
1320 self.clicksource = None
1321 self.lastsource = None
1322 self.pack(expand=1, fill="both")
1325 self.bind_all("<Control-q>", self.quitcb)
1327 def quitcb(self, event):
1330 def buildwidgets(self):
1332 self.menu = GraphMenu(self)
1333 self.display = SchedDisplay(self)
1334 self.names = SchedNames(self, self.display)
1335 self.scale = Scaler(self, self.display)
1336 status = self.status = Status(self)
1337 self.scrollY = Scrollbar(self, orient="vertical",
1338 command=self.display_yview)
1339 self.display.scrollX = Scrollbar(self, orient="horizontal",
1340 command=self.display.xview)
1341 self.display["xscrollcommand"] = self.display.scrollX.set
1342 self.display["yscrollcommand"] = self.scrollY.set
1343 self.names["yscrollcommand"] = self.scrollY.set
1346 self.columnconfigure(1, weight=1)
1347 self.rowconfigure(1, weight=1)
1348 self.menu.grid(row=0, column=0, columnspan=3, sticky=E+W)
1349 self.names.grid(row=1, column=0, sticky=N+S)
1350 self.display.grid(row=1, column=1, sticky=W+E+N+S)
1351 self.scrollY.grid(row=1, column=2, sticky=N+S)
1352 self.display.scrollX.grid(row=2, column=0, columnspan=2,
1354 self.scale.grid(row=3, column=0, columnspan=3, sticky=E+W)
1355 self.status.grid(row=4, column=0, columnspan=3, sticky=E+W)
1358 self.master.update()
1359 self.display.prepare()
1362 self.status.startup("")
1364 # Configure scale related values
1366 scalemax = ktrfile.timespan() / int(self.display["width"])
1367 width = int(root.geometry().split('x')[0])
1368 self.constwidth = width - int(self.display["width"])
1369 self.scale.setmax(scalemax)
1370 self.scale.set(scalemax)
1371 self.display.xview_moveto(0)
1372 self.bind("<Configure>", self.resize)
1374 def mousepress(self, event):
1375 self.clicksource = self.sourceat(event.y)
1377 def mousepressright(self, event):
1378 source = self.sourceat(event.y)
1379 if (source == None):
1381 SourceContext(event, source)
1383 def mouserelease(self, event):
1384 if (self.clicksource == None):
1386 newsource = self.sourceat(event.y)
1387 if (self.clicksource != newsource):
1388 self.sourceswap(self.clicksource, newsource)
1389 self.clicksource = None
1390 self.lastsource = None
1392 def mousemotion(self, event):
1393 if (self.clicksource == None):
1395 newsource = self.sourceat(event.y)
1397 # If we get a None source they moved off the page.
1398 # swapsource() can't handle moving multiple items so just
1399 # pretend we never clicked on anything to begin with so the
1400 # user can't mouseover a non-contiguous area.
1402 if (newsource == None):
1403 self.clicksource = None
1404 self.lastsource = None
1406 if (newsource == self.lastsource):
1408 self.lastsource = newsource
1409 if (newsource != self.clicksource):
1410 self.sourceswap(self.clicksource, newsource)
1412 # These are here because this object controls layout
1413 def sourcestart(self, source):
1414 return source.y - self.bdheight - source.ysize()
1416 def sourceend(self, source):
1417 return source.y + self.bdheight
1419 def sourcesize(self, source):
1420 return (self.bdheight * 2) + source.ysize()
1422 def sourceswap(self, source1, source2):
1423 # Sort so we always know which one is on top.
1424 if (source2.y < source1.y):
1428 # Only swap adjacent sources
1429 if (self.sourceend(source1) != self.sourcestart(source2)):
1431 # Compute start coordinates and target coordinates
1432 y1 = self.sourcestart(source1)
1433 y2 = self.sourcestart(source2)
1434 y1targ = y1 + self.sourcesize(source2)
1437 # If the sizes are not equal, adjust the start of the lower
1438 # source to account for the lost/gained space.
1440 if (source1.ysize() != source2.ysize()):
1441 diff = source2.ysize() - source1.ysize()
1442 self.names.moveline(y2, diff);
1443 self.display.moveline(y2, diff)
1444 source1.move(self.display, 0, y1targ - y1)
1445 source2.move(self.display, 0, y2targ - y2)
1446 source1.movename(self.names, 0, y1targ - y1)
1447 source2.movename(self.names, 0, y2targ - y2)
1449 def sourcepicky(self, source):
1450 if (source.hidden == 0):
1451 return self.sourcestart(source)
1452 # Revert to group based sort
1463 newy = self.sourcestart(prev) + self.sourcesize(prev)
1466 def sourceshow(self, source):
1467 if (source.hidden == 0):
1469 newy = self.sourcepicky(source)
1470 off = newy - self.sourcestart(source)
1471 self.sourceshiftall(newy-1, self.sourcesize(source))
1472 self.sourceshift(source, off)
1476 # Optimized source show of multiple entries that only moves each
1477 # existing entry once. Doing sourceshow() iteratively is too
1478 # expensive due to python's canvas.move().
1480 def sourceshowlist(self, srclist):
1481 srclist.sort(cmp=source_cmp_start)
1483 for source in srclist:
1484 if (source.hidden == 0):
1485 srclist.remove(source)
1486 startsize.append((self.sourcepicky(source),
1487 self.sourcesize(source)))
1489 sources.sort(cmp=source_cmp_start, reverse=True)
1490 self.status.startup("Updating display...");
1491 for source in sources:
1492 if (source.hidden == 1):
1494 nstart = self.sourcestart(source)
1496 for hidden in startsize:
1497 (start, sz) = hidden
1498 if (start <= nstart or start+sz <= nstart):
1500 self.sourceshift(source, size)
1503 for source in srclist:
1504 (newy, sz) = startsize[idx]
1505 off = (newy + size) - self.sourcestart(source)
1506 self.sourceshift(source, off)
1514 # Optimized source hide of multiple entries that only moves each
1515 # remaining entry once. Doing sourcehide() iteratively is too
1516 # expensive due to python's canvas.move().
1518 def sourcehidelist(self, srclist):
1519 srclist.sort(cmp=source_cmp_start)
1520 sources.sort(cmp=source_cmp_start)
1522 off = len(sources) * 100
1523 self.status.startup("Updating display...");
1524 for source in srclist:
1525 if (source.hidden == 1):
1526 srclist.remove(source)
1528 # Remember our old position so we can sort things
1529 # below us when we're done.
1531 startsize.append((self.sourcestart(source),
1532 self.sourcesize(source)))
1533 self.sourceshift(source, off)
1538 for hidden in startsize:
1539 (start, sz) = hidden
1541 if (idx + 1 < len(startsize)):
1542 (stop, sz) = startsize[idx+1]
1544 stop = self.display.ysize()
1546 for source in sources:
1547 nstart = self.sourcestart(source)
1548 if (nstart < start or source.hidden == 1):
1550 if (nstart >= stop):
1552 self.sourceshift(source, -size)
1556 def sourcehide(self, source):
1557 if (source.hidden == 1):
1559 # Move it out of the visible area
1560 off = len(sources) * 100
1561 start = self.sourcestart(source)
1562 self.sourceshift(source, off)
1563 self.sourceshiftall(start, -self.sourcesize(source))
1566 def sourceshift(self, source, off):
1567 start = self.sourcestart(source)
1568 source.move(self.display, 0, off)
1569 source.movename(self.names, 0, off)
1570 self.names.moveline(start, off);
1571 self.display.moveline(start, off)
1573 # We update the idle tasks to shrink the dirtied area so
1574 # it does not always include the entire screen.
1576 self.names.update_idletasks()
1577 self.display.update_idletasks()
1579 def sourceshiftall(self, start, off):
1580 self.status.startup("Updating display...");
1581 for source in sources:
1582 nstart = self.sourcestart(source)
1583 if (nstart < start):
1585 self.sourceshift(source, off)
1589 def sourceat(self, ypos):
1590 (start, end) = self.names.yview()
1591 starty = start * float(self.names.ysize)
1593 for source in sources:
1594 if (source.hidden == 1):
1596 yend = self.sourceend(source)
1597 ystart = self.sourcestart(source)
1598 if (ypos >= ystart and ypos <= yend):
1602 def display_yview(self, *args):
1603 self.names.yview(*args)
1604 self.display.yview(*args)
1606 def resize(self, *args):
1607 width = int(root.geometry().split('x')[0])
1608 scalemax = ktrfile.timespan() / (width - self.constwidth)
1609 self.scale.setmax(scalemax)
1611 def updatescroll(self):
1612 self.names.updatescroll()
1613 self.display.updatescroll()
1615 def setcolor(self, tag, color):
1616 self.display.setcolor(tag, color)
1618 def hide(self, tag):
1619 self.display.hide(tag)
1621 def getcolor(self, tag):
1622 return self.display.getcolor(tag)
1624 def getstate(self, tag):
1625 return self.display.getstate(tag)
1627 if (len(sys.argv) != 2 and len(sys.argv) != 3):
1628 print("usage:", sys.argv[0], "<ktr file> [clock freq in ghz]")
1631 if (len(sys.argv) > 2):
1632 clockfreq = float(sys.argv[2])
1635 root.title("SchedGraph")
1636 colormap = Colormap(eventcolors)
1637 cpucolormap = Colormap(cpucolors)
1638 graph = SchedGraph(root)
1639 ktrfile = KTRFile(sys.argv[1])