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
33 from operator import attrgetter, itemgetter
34 from functools import total_ordering
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.
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'
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
71 # BUGS: 1) Only 8 CPUs are supported, more CPUs require more choices of
72 # colours to represent them ;-)
79 ("yielding", "yellow"),
80 ("swapped", "violet"),
81 ("suspended", "purple"),
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"),
95 ("lend prio", "black"),
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"),
107 ("CPU 7", "thistle"),
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",
139 def __init__(self, table):
143 self.map[entry[0]] = entry[1]
145 def lookup(self, name):
147 color = self.map[name]
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))
155 def ticks2sec(ticks):
157 ns = float(ticksps) / 1000000000
160 return ("%.2fns" % ticks)
163 return ("%.2fus" % ticks)
166 return ("%.2fms" % ticks)
168 return ("%.2fs" % ticks)
171 def __init__(self, master, target):
172 Frame.__init__(self, master)
175 self.label = Label(self, text="Ticks per pixel")
176 self.label.pack(side=LEFT)
177 self.resolution = 100
180 def scaleset(self, value):
181 self.target.scaleset(int(value))
183 def set(self, value):
184 self.scale.set(value)
186 def setmax(self, value):
188 # We can't reconfigure the to_ value so we delete the old
189 # window and make a new one when we resize.
191 if (self.scale != None):
192 self.scale.pack_forget()
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())
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)
208 self.label.config(text=str)
211 self.label.config(text="")
213 def startup(self, str):
217 class ColorConf(Frame):
218 def __init__(self, master, name, color):
219 Frame.__init__(self, master)
220 if (graph.getstate(name) == "hidden"):
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)
236 self.label = Label(self, text=self.name, anchor=W)
237 self.sample = Canvas(self, width=24, height=24,
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,
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)
252 def setcolor(self, color):
253 self.color.set(color)
254 self.sample.itemconfigure(self.rect, fill=color)
259 if (self.color_current != self.color.get()):
261 if (self.enabled_current != self.enabled.get()):
263 self.color_current = self.color.get()
264 self.enabled_current = self.enabled.get()
266 if (self.enabled_current):
267 graph.setcolor(self.name, self.color_current)
269 graph.hide(self.name)
272 graph.setcolor(self.name, self.color_current)
275 self.setcolor(self.color_default)
276 self.enabled.set(self.enabled_default)
278 class ColorConfigure(Toplevel):
279 def __init__(self, table, name):
280 Toplevel.__init__(self)
283 self.items = LabelFrame(self, text="Item Type")
284 self.buttons = Frame(self)
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)
292 color = graph.getcolor(type[0])
294 self.additem(type[0], color)
295 self.bind("<Control-w>", self.destroycb)
297 def destroycb(self, event):
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)
306 def drawbuttons(self):
307 self.apply = Button(self.buttons, text="Apply",
309 self.default = Button(self.buttons, text="Revert",
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)
317 for item in self.types:
321 for item in self.types:
324 class SourceConf(Frame):
325 def __init__(self, master, source):
326 Frame.__init__(self, master)
327 if (source.hidden == 1):
332 self.name = source.name
333 self.enabled = IntVar()
334 self.enabled_default = enabled
335 self.enabled_current = enabled
336 self.enabled.set(enabled)
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)
348 if (self.enabled_current != self.enabled.get()):
353 self.enabled_current = self.enabled.get()
356 self.enabled.set(self.enabled_default)
364 class SourceConfigure(Toplevel):
366 Toplevel.__init__(self)
368 self.title("Source Configuration")
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")
374 self.buttons = Frame(self)
375 self.items[0].grid(row=0, column=0, sticky=E+W)
376 self.columnconfigure(0, weight=1)
380 for source in sources:
381 self.addsource(source)
383 self.buttons.grid(row=1, column=0, sticky=W)
384 self.bind("<Control-w>", self.destroycb)
386 def destroycb(self, event):
389 def addsource(self, source):
394 f = LabelFrame(self.iframe, bd=4, text="Sources")
395 f.grid(row=0, column=c, sticky=N+E+W)
397 item = SourceConf(self.items[self.icol], source)
398 self.sconfig.append(item)
399 item.grid(row=self.irow, column=0, sticky=E+W)
402 def drawbuttons(self):
403 self.apply = Button(self.buttons, text="Apply",
405 self.default = Button(self.buttons, text="Revert",
407 self.checkall = Button(self.buttons, text="Check All",
409 self.uncheckall = Button(self.buttons, text="Uncheck All",
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)
423 for item in self.sconfig:
424 if (item.changed() == 0):
426 if (item.enabled.get() == 1):
427 enable_sources.append(item.source)
429 disable_sources.append(item.source)
431 if (len(disable_sources)):
432 graph.sourcehidelist(disable_sources)
433 if (len(enable_sources)):
434 graph.sourceshowlist(enable_sources)
436 for item in self.sconfig:
440 for item in self.sconfig:
444 for item in self.sconfig:
448 for item in self.sconfig:
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(key=itemgetter(1), reverse=True)
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)
799 def __init__(self, group, id):
807 self.tag = group + id
809 def __lt__(self, other):
812 return (self.group < other.group or
813 self.group == other.group and self.name < other.name)
815 def __eq__(self, other):
818 return self.group == other.group and self.name == other.name
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.
824 self.events.reverse()
826 def addevent(self, event):
827 self.events.append(event)
829 def addlastevent(self, event):
830 self.events.insert(0, event)
832 def draw(self, canvas, ypos):
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)
843 xpos = event.draw(canvas, xpos, ypos)
844 self.drawcpu(canvas, cpu, cpux, xpos, ypos)
846 def drawname(self, canvas, ypos):
848 ypos = ypos - (self.ysize() / 2)
849 self.item = canvas.create_text(X_BORDER, ypos, anchor="w",
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)
863 def move(self, canvas, xpos, ypos):
864 canvas.move(self.tag, xpos, ypos)
866 def movename(self, canvas, xpos, ypos):
868 canvas.move(self.item, xpos, ypos)
871 return (Y_EVENTSOURCE)
873 def eventat(self, i):
874 if (i >= len(self.events) or i < 0):
876 event = self.events[i]
879 def findevent(self, timestamp):
880 for event in self.events:
881 if (event.timestamp >= timestamp and event.type != "pad"):
885 class Counter(EventSource):
887 # Store a hash of counter groups that keeps the max value
888 # for a counter in this group for scaling purposes.
891 def __init__(self, group, id):
893 Counter.cnt = Counter.groups[group]
895 Counter.groups[group] = 0
896 EventSource.__init__(self, group, id)
899 for event in self.events:
900 if (event.type != "count"):
902 count = int(event.count)
903 if (count > Counter.groups[self.group]):
904 Counter.groups[self.group] = count
905 EventSource.fixup(self)
908 return (Counter.groups[self.group])
914 return (self.ysize() / self.ymax())
917 def __init__(self, file):
918 self.timestamp_f = None
919 self.timestamp_l = None
931 ticksps = self.ticksps()
932 span = self.timespan()
933 ghz = float(ticksps) / 1000000000.0
935 # Update the title with some stats from the file
937 titlestr = "SchedGraph: "
938 titlestr += ticks2sec(span) + " at %.3f ghz, " % ghz
939 titlestr += str(len(sources)) + " event sources, "
940 titlestr += str(self.eventcnt) + " events"
943 def parse(self, file):
947 print("Can't open", file)
950 # quoteexp matches a quoted string, no escaping
951 quoteexp = "\"([^\"]*)\""
954 # commaexp matches a quoted string OR the string up
957 commaexp = "(?:" + quoteexp + "|([^,]+))"
960 # colonstr matches a quoted string OR the string up
963 colonexp = "(?:" + quoteexp + "|([^:]+))"
966 # Match various manditory parts of the KTR string this is
967 # fairly inflexible until you get to attributes to make
970 hdrexp = "\s*(\d+)\s+(\d+)\s+(\d+)\s+"
971 groupexp = "KTRGRAPH group:" + quoteexp + ", "
972 idexp = "id:" + quoteexp + ", "
973 typeexp = "([^:]+):" + commaexp + ", "
974 attribexp = "attributes: (.*)"
977 # Matches optional attributes in the KTR string. This
978 # tolerates more variance as the users supply these values.
980 attrexp = colonexp + "\s*:\s*(?:" + commaexp + ", (.*)|"
981 attrexp += quoteexp +"|(.*))"
984 ktrre = re.compile(hdrexp + groupexp + idexp + typeexp + attribexp)
985 attrre = re.compile(attrexp)
989 for line in ifp.readlines():
991 if ((lineno % 2048) == 0):
992 status.startup("Parsing line " + str(lineno))
993 m = ktrre.match(line);
995 print("Can't parse", lineno, line, end=' ')
997 (index, cpu, timestamp, group, id, type, dat, dat1, attrstring) = m.groups();
1000 if (self.checkstamp(timestamp) == 0):
1001 print("Bad timestamp at", lineno, ":", end=' ')
1002 print(cpu, timestamp)
1005 # Build the table of optional attributes
1008 while (attrstring != None):
1009 m = attrre.match(attrstring.strip())
1013 # Name may or may not be quoted.
1015 # For val we have four cases:
1016 # 1) quotes followed by comma and more
1018 # 2) no quotes followed by comma and more
1020 # 3) no more attributes or comma with quotes.
1021 # 4) no more attributes or comma without quotes.
1023 (name, name1, val, val1, attrstring, end, end1) = m.groups();
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)
1038 print("Unknown type", type, lineno, line, end=' ')
1040 def makeevent(self, group, id, type, args):
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)
1054 def setstathz(self, val, cpu):
1055 self.stathz = int(val)
1058 ticks = self.ticks[cpu]
1061 self.ticks[cpu] += 1
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):
1070 self.timestamp_l = timestamp;
1073 def makeid(self, group, id, type):
1075 if (tag in self.taghash):
1076 return self.taghash[tag]
1077 if (type == "counter"):
1078 source = Counter(group, id)
1080 source = EventSource(group, id)
1081 sources.append(source)
1082 self.taghash[tag] = source
1085 def findid(self, id):
1086 for source in sources:
1087 if (source.name == id):
1092 return (self.timestamp_f - self.timestamp_l);
1096 # Use user supplied clock first
1097 if (clockfreq != None):
1098 return int(clockfreq * oneghz)
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")
1110 for source in sources:
1111 e = PadEvent(source, -1, self.timestamp_l)
1113 e = PadEvent(source, -1, self.timestamp_f, last=1)
1114 source.addlastevent(e)
1118 class SchedNames(Canvas):
1119 def __init__(self, master, display):
1120 self.display = display
1121 self.parent = master
1122 self.bdheight = master.bdheight
1126 Canvas.__init__(self, master, width=120,
1127 height=display["height"], bg='grey',
1128 scrollregion=(0, 0, 50, 100))
1130 def moveline(self, cur_y, y):
1131 for line in self.lines:
1132 (x0, y0, x1, y1) = self.coords(line)
1135 self.move(line, 0, y)
1139 status.startup("Drawing names")
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
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);
1160 def updatescroll(self):
1161 self.configure(scrollregion=(0, 0,
1162 self["width"], self.display.ysize()))
1165 class SchedDisplay(Canvas):
1166 def __init__(self, master):
1168 self.parent = master
1169 self.bdheight = master.bdheight
1172 Canvas.__init__(self, master, width=800, height=500, bg='grey',
1173 scrollregion=(0, 0, 800, 500))
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.
1181 self.ratio = (ktrfile.timespan() - 1) / 2**31 + 1
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);
1208 def moveline(self, cur_y, y):
1209 for line in self.lines:
1210 (x0, y0, x1, y1) = self.coords(line)
1213 self.move(line, 0, y)
1216 def mouseenter(self, event):
1217 item, = self.find_withtag(CURRENT)
1218 self.items[item].mouseenter(self)
1220 def mouseexit(self, event):
1221 item, = self.find_withtag(CURRENT)
1222 self.items[item].mouseexit(self)
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)
1230 # Only grab mouse presses for things with event tags.
1232 tags = self.gettags(item)
1234 if (tag == "event"):
1235 self.items[item].mousepress(self)
1237 # Leave the rest to the master window
1238 self.master.mousepress(event)
1240 def wheeldown(self, event):
1241 self.parent.display_yview("scroll", 1, "units")
1243 def wheelup(self, event):
1244 self.parent.display_yview("scroll", -1, "units")
1247 return ((ktrfile.timespan() / self.ratio) + (X_BORDER * 2))
1251 for source in sources:
1252 if (source.hidden == 1):
1254 ysize += self.parent.sourcesize(source)
1257 def scaleset(self, ratio):
1258 if (ktrfile == None):
1260 oldratio = self.ratio
1261 xstart, xend = self.xview()
1262 midpoint = xstart + ((xend - xstart) / 2)
1266 self.scale(ALL, 0, 0, float(oldratio) / ratio, 1)
1268 xstart, xend = self.xview()
1269 xsize = (xend - xstart) / 2
1270 self.xview_moveto(midpoint - xsize)
1272 def updatescroll(self):
1273 self.configure(scrollregion=(0, 0, self.xsize(), self.ysize()))
1278 def getcolor(self, tag):
1279 return self.itemcget(tag, "fill")
1281 def getstate(self, tag):
1282 return self.itemcget(tag, "state")
1284 def setcolor(self, tag, color):
1285 self.itemconfigure(tag, state="normal", fill=color)
1287 def hide(self, tag):
1288 self.itemconfigure(tag, state="hidden")
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",
1297 self.confmenu.add_command(label="CPU Colors",
1299 self.confmenu.add_command(label="Source Configure",
1301 self.conf["menu"] = self.confmenu
1302 self.conf.pack(side=LEFT)
1305 ColorConfigure(eventcolors, "Event Display Configuration")
1308 ColorConfigure(cpucolors, "CPU Background Colors")
1313 class SchedGraph(Frame):
1314 def __init__(self, master):
1315 Frame.__init__(self, master)
1321 self.bdheight = Y_BORDER
1322 self.clicksource = None
1323 self.lastsource = None
1324 self.pack(expand=1, fill="both")
1327 self.bind_all("<Control-q>", self.quitcb)
1329 def quitcb(self, event):
1332 def buildwidgets(self):
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
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,
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)
1360 self.master.update()
1361 self.display.prepare()
1364 self.status.startup("")
1366 # Configure scale related values
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)
1376 def mousepress(self, event):
1377 self.clicksource = self.sourceat(event.y)
1379 def mousepressright(self, event):
1380 source = self.sourceat(event.y)
1381 if (source == None):
1383 SourceContext(event, source)
1385 def mouserelease(self, event):
1386 if (self.clicksource == None):
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
1394 def mousemotion(self, event):
1395 if (self.clicksource == None):
1397 newsource = self.sourceat(event.y)
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.
1404 if (newsource == None):
1405 self.clicksource = None
1406 self.lastsource = None
1408 if (newsource == self.lastsource):
1410 self.lastsource = newsource
1411 if (newsource != self.clicksource):
1412 self.sourceswap(self.clicksource, newsource)
1414 # These are here because this object controls layout
1415 def sourcestart(self, source):
1416 return source.y - self.bdheight - source.ysize()
1418 def sourceend(self, source):
1419 return source.y + self.bdheight
1421 def sourcesize(self, source):
1422 return (self.bdheight * 2) + source.ysize()
1424 def sourceswap(self, source1, source2):
1425 # Sort so we always know which one is on top.
1426 if (source2.y < source1.y):
1430 # Only swap adjacent sources
1431 if (self.sourceend(source1) != self.sourcestart(source2)):
1433 # Compute start coordinates and target coordinates
1434 y1 = self.sourcestart(source1)
1435 y2 = self.sourcestart(source2)
1436 y1targ = y1 + self.sourcesize(source2)
1439 # If the sizes are not equal, adjust the start of the lower
1440 # source to account for the lost/gained space.
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)
1451 def sourcepicky(self, source):
1452 if (source.hidden == 0):
1453 return self.sourcestart(source)
1454 # Revert to group based sort
1465 newy = self.sourcestart(prev) + self.sourcesize(prev)
1468 def sourceshow(self, source):
1469 if (source.hidden == 0):
1471 newy = self.sourcepicky(source)
1472 off = newy - self.sourcestart(source)
1473 self.sourceshiftall(newy-1, self.sourcesize(source))
1474 self.sourceshift(source, off)
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().
1482 def sourceshowlist(self, srclist):
1483 srclist.sort(key=attrgetter('y'))
1485 for source in srclist:
1486 if (source.hidden == 0):
1487 srclist.remove(source)
1488 startsize.append((self.sourcepicky(source),
1489 self.sourcesize(source)))
1491 sources.sort(key=attrgetter('y'), reverse=True)
1492 self.status.startup("Updating display...");
1493 for source in sources:
1494 if (source.hidden == 1):
1496 nstart = self.sourcestart(source)
1498 for hidden in startsize:
1499 (start, sz) = hidden
1500 if (start <= nstart or start+sz <= nstart):
1502 self.sourceshift(source, size)
1505 for source in srclist:
1506 (newy, sz) = startsize[idx]
1507 off = (newy + size) - self.sourcestart(source)
1508 self.sourceshift(source, off)
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().
1520 def sourcehidelist(self, srclist):
1521 srclist.sort(key=attrgetter('y'))
1522 sources.sort(key=attrgetter('y'))
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)
1530 # Remember our old position so we can sort things
1531 # below us when we're done.
1533 startsize.append((self.sourcestart(source),
1534 self.sourcesize(source)))
1535 self.sourceshift(source, off)
1540 for hidden in startsize:
1541 (start, sz) = hidden
1543 if (idx + 1 < len(startsize)):
1544 (stop, sz) = startsize[idx+1]
1546 stop = self.display.ysize()
1548 for source in sources:
1549 nstart = self.sourcestart(source)
1550 if (nstart < start or source.hidden == 1):
1552 if (nstart >= stop):
1554 self.sourceshift(source, -size)
1558 def sourcehide(self, source):
1559 if (source.hidden == 1):
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))
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)
1575 # We update the idle tasks to shrink the dirtied area so
1576 # it does not always include the entire screen.
1578 self.names.update_idletasks()
1579 self.display.update_idletasks()
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):
1587 self.sourceshift(source, off)
1591 def sourceat(self, ypos):
1592 (start, end) = self.names.yview()
1593 starty = start * float(self.names.ysize)
1595 for source in sources:
1596 if (source.hidden == 1):
1598 yend = self.sourceend(source)
1599 ystart = self.sourcestart(source)
1600 if (ypos >= ystart and ypos <= yend):
1604 def display_yview(self, *args):
1605 self.names.yview(*args)
1606 self.display.yview(*args)
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)
1613 def updatescroll(self):
1614 self.names.updatescroll()
1615 self.display.updatescroll()
1617 def setcolor(self, tag, color):
1618 self.display.setcolor(tag, color)
1620 def hide(self, tag):
1621 self.display.hide(tag)
1623 def getcolor(self, tag):
1624 return self.display.getcolor(tag)
1626 def getstate(self, tag):
1627 return self.display.getstate(tag)
1629 if (len(sys.argv) != 2 and len(sys.argv) != 3):
1630 print("usage:", sys.argv[0], "<ktr file> [clock freq in ghz]")
1633 if (len(sys.argv) > 2):
1634 clockfreq = float(sys.argv[2])
1637 root.title("SchedGraph")
1638 colormap = Colormap(eventcolors)
1639 cpucolormap = Colormap(cpucolors)
1640 graph = SchedGraph(root)
1641 ktrfile = KTRFile(sys.argv[1])