aboutsummaryrefslogtreecommitdiff
path: root/tools/sched
diff options
context:
space:
mode:
Diffstat (limited to 'tools/sched')
-rw-r--r--tools/sched/schedgraph.py1625
1 files changed, 1625 insertions, 0 deletions
diff --git a/tools/sched/schedgraph.py b/tools/sched/schedgraph.py
new file mode 100644
index 000000000000..4335574972e5
--- /dev/null
+++ b/tools/sched/schedgraph.py
@@ -0,0 +1,1625 @@
+#!/usr/local/bin/python
+
+# Copyright (c) 2002-2003, 2009, Jeffrey Roberson <jeff@freebsd.org>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice unmodified, this list of conditions, and the following
+# disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+# $FreeBSD$
+
+import sys
+import re
+import random
+from Tkinter import *
+
+# To use:
+# - Install the ports/x11-toolkits/py-tkinter package; e.g.
+# portinstall x11-toolkits/py-tkinter package
+# - Add KTR_SCHED to KTR_COMPILE and KTR_MASK in your KERNCONF; e.g.
+# options KTR
+# options KTR_ENTRIES=32768
+# options KTR_COMPILE=(KTR_SCHED)
+# options KTR_MASK=(KTR_SCHED)
+# - It is encouraged to increase KTR_ENTRIES size to gather enough
+# information for analysis; e.g.
+# options KTR_ENTRIES=262144
+# as 32768 entries may only correspond to a second or two of profiling
+# data depending on your workload.
+# - Rebuild kernel with proper changes to KERNCONF and boot new kernel.
+# - Run your workload to be profiled.
+# - While the workload is continuing (i.e. before it finishes), disable
+# KTR tracing by setting 'sysctl debug.ktr.mask=0'. This is necessary
+# to avoid a race condition while running ktrdump, i.e. the KTR ring buffer
+# will cycle a bit while ktrdump runs, and this confuses schedgraph because
+# the timestamps appear to go backwards at some point. Stopping KTR logging
+# while the workload is still running is to avoid wasting log entries on
+# "idle" time at the end.
+# - Dump the trace to a file: 'ktrdump -ct > ktr.out'
+# - Run the python script: 'python schedgraph.py ktr.out' optionally provide
+# your cpu frequency in ghz: 'python schedgraph.py ktr.out 2.4'
+#
+# To do:
+# Add a per-source summary display
+# "Vertical rule" to help relate data in different rows
+# Mouse-over popup of full thread/event/row label (currently truncated)
+# More visible anchors for popup event windows
+#
+# BUGS: 1) Only 8 CPUs are supported, more CPUs require more choices of
+# colours to represent them ;-)
+
+eventcolors = [
+ ("count", "red"),
+ ("running", "green"),
+ ("idle", "grey"),
+ ("yielding", "yellow"),
+ ("swapped", "violet"),
+ ("suspended", "purple"),
+ ("iwait", "grey"),
+ ("sleep", "blue"),
+ ("blocked", "dark red"),
+ ("runq add", "yellow"),
+ ("runq rem", "yellow"),
+ ("thread exit", "grey"),
+ ("proc exit", "grey"),
+ ("callwheel idle", "grey"),
+ ("callout running", "green"),
+ ("lock acquire", "blue"),
+ ("lock contest", "purple"),
+ ("failed lock try", "red"),
+ ("lock release", "grey"),
+ ("statclock", "black"),
+ ("prio", "black"),
+ ("lend prio", "black"),
+ ("wokeup", "black")
+]
+
+cpucolors = [
+ ("CPU 0", "light grey"),
+ ("CPU 1", "dark grey"),
+ ("CPU 2", "light blue"),
+ ("CPU 3", "light pink"),
+ ("CPU 4", "blanched almond"),
+ ("CPU 5", "slate grey"),
+ ("CPU 6", "tan"),
+ ("CPU 7", "thistle"),
+ ("CPU 8", "white")
+]
+
+colors = [
+ "white", "thistle", "blanched almond", "tan", "chartreuse",
+ "dark red", "red", "pale violet red", "pink", "light pink",
+ "dark orange", "orange", "coral", "light coral",
+ "goldenrod", "gold", "yellow", "light yellow",
+ "dark green", "green", "light green", "light sea green",
+ "dark blue", "blue", "light blue", "steel blue", "light slate blue",
+ "dark violet", "violet", "purple", "blue violet",
+ "dark grey", "slate grey", "light grey",
+ "black",
+]
+colors.sort()
+
+ticksps = None
+status = None
+colormap = None
+ktrfile = None
+clockfreq = None
+sources = []
+lineno = -1
+
+Y_BORDER = 10
+X_BORDER = 10
+Y_COUNTER = 80
+Y_EVENTSOURCE = 10
+XY_POINT = 4
+
+class Colormap:
+ def __init__(self, table):
+ self.table = table
+ self.map = {}
+ for entry in table:
+ self.map[entry[0]] = entry[1]
+
+ def lookup(self, name):
+ try:
+ color = self.map[name]
+ except:
+ color = colors[random.randrange(0, len(colors))]
+ print "Picking random color", color, "for", name
+ self.map[name] = color
+ self.table.append((name, color))
+ return (color)
+
+def ticks2sec(ticks):
+ ticks = float(ticks)
+ ns = float(ticksps) / 1000000000
+ ticks /= ns
+ if (ticks < 1000):
+ return ("%.2fns" % ticks)
+ ticks /= 1000
+ if (ticks < 1000):
+ return ("%.2fus" % ticks)
+ ticks /= 1000
+ if (ticks < 1000):
+ return ("%.2fms" % ticks)
+ ticks /= 1000
+ return ("%.2fs" % ticks)
+
+class Scaler(Frame):
+ def __init__(self, master, target):
+ Frame.__init__(self, master)
+ self.scale = None
+ self.target = target
+ self.label = Label(self, text="Ticks per pixel")
+ self.label.pack(side=LEFT)
+ self.resolution = 100
+ self.setmax(10000)
+
+ def scaleset(self, value):
+ self.target.scaleset(int(value))
+
+ def set(self, value):
+ self.scale.set(value)
+
+ def setmax(self, value):
+ #
+ # We can't reconfigure the to_ value so we delete the old
+ # window and make a new one when we resize.
+ #
+ if (self.scale != None):
+ self.scale.pack_forget()
+ self.scale.destroy()
+ self.scale = Scale(self, command=self.scaleset,
+ from_=100, to_=value, orient=HORIZONTAL,
+ resolution=self.resolution)
+ self.scale.pack(fill="both", expand=1)
+ self.scale.set(self.target.scaleget())
+
+class Status(Frame):
+ def __init__(self, master):
+ Frame.__init__(self, master)
+ self.label = Label(self, bd=1, relief=SUNKEN, anchor=W)
+ self.label.pack(fill="both", expand=1)
+ self.clear()
+
+ def set(self, str):
+ self.label.config(text=str)
+
+ def clear(self):
+ self.label.config(text="")
+
+ def startup(self, str):
+ self.set(str)
+ root.update()
+
+class ColorConf(Frame):
+ def __init__(self, master, name, color):
+ Frame.__init__(self, master)
+ if (graph.getstate(name) == "hidden"):
+ enabled = 0
+ else:
+ enabled = 1
+ self.name = name
+ self.color = StringVar()
+ self.color_default = color
+ self.color_current = color
+ self.color.set(color)
+ self.enabled = IntVar()
+ self.enabled_default = enabled
+ self.enabled_current = enabled
+ self.enabled.set(enabled)
+ self.draw()
+
+ def draw(self):
+ self.label = Label(self, text=self.name, anchor=W)
+ self.sample = Canvas(self, width=24, height=24,
+ bg='grey')
+ self.rect = self.sample.create_rectangle(0, 0, 24, 24,
+ fill=self.color.get())
+ self.list = OptionMenu(self, self.color, command=self.setcolor,
+ *colors)
+ self.checkbox = Checkbutton(self, text="enabled",
+ variable=self.enabled)
+ self.label.grid(row=0, column=0, sticky=E+W)
+ self.sample.grid(row=0, column=1)
+ self.list.grid(row=0, column=2, sticky=E+W)
+ self.checkbox.grid(row=0, column=3)
+ self.columnconfigure(0, weight=1)
+ self.columnconfigure(2, minsize=150)
+
+ def setcolor(self, color):
+ self.color.set(color)
+ self.sample.itemconfigure(self.rect, fill=color)
+
+ def apply(self):
+ cchange = 0
+ echange = 0
+ if (self.color_current != self.color.get()):
+ cchange = 1
+ if (self.enabled_current != self.enabled.get()):
+ echange = 1
+ self.color_current = self.color.get()
+ self.enabled_current = self.enabled.get()
+ if (echange != 0):
+ if (self.enabled_current):
+ graph.setcolor(self.name, self.color_current)
+ else:
+ graph.hide(self.name)
+ return
+ if (cchange != 0):
+ graph.setcolor(self.name, self.color_current)
+
+ def revert(self):
+ self.setcolor(self.color_default)
+ self.enabled.set(self.enabled_default)
+
+class ColorConfigure(Toplevel):
+ def __init__(self, table, name):
+ Toplevel.__init__(self)
+ self.resizable(0, 0)
+ self.title(name)
+ self.items = LabelFrame(self, text="Item Type")
+ self.buttons = Frame(self)
+ self.drawbuttons()
+ self.items.grid(row=0, column=0, sticky=E+W)
+ self.columnconfigure(0, weight=1)
+ self.buttons.grid(row=1, column=0, sticky=E+W)
+ self.types = []
+ self.irow = 0
+ for type in table:
+ color = graph.getcolor(type[0])
+ if (color != ""):
+ self.additem(type[0], color)
+
+ def additem(self, name, color):
+ item = ColorConf(self.items, name, color)
+ self.types.append(item)
+ item.grid(row=self.irow, column=0, sticky=E+W)
+ self.irow += 1
+
+ def drawbuttons(self):
+ self.apply = Button(self.buttons, text="Apply",
+ command=self.apress)
+ self.default = Button(self.buttons, text="Revert",
+ command=self.rpress)
+ self.apply.grid(row=0, column=0, sticky=E+W)
+ self.default.grid(row=0, column=1, sticky=E+W)
+ self.buttons.columnconfigure(0, weight=1)
+ self.buttons.columnconfigure(1, weight=1)
+
+ def apress(self):
+ for item in self.types:
+ item.apply()
+
+ def rpress(self):
+ for item in self.types:
+ item.revert()
+
+class SourceConf(Frame):
+ def __init__(self, master, source):
+ Frame.__init__(self, master)
+ if (source.hidden == 1):
+ enabled = 0
+ else:
+ enabled = 1
+ self.source = source
+ self.name = source.name
+ self.enabled = IntVar()
+ self.enabled_default = enabled
+ self.enabled_current = enabled
+ self.enabled.set(enabled)
+ self.draw()
+
+ def draw(self):
+ self.label = Label(self, text=self.name, anchor=W)
+ self.checkbox = Checkbutton(self, text="enabled",
+ variable=self.enabled)
+ self.label.grid(row=0, column=0, sticky=E+W)
+ self.checkbox.grid(row=0, column=1)
+ self.columnconfigure(0, weight=1)
+
+ def changed(self):
+ if (self.enabled_current != self.enabled.get()):
+ return 1
+ return 0
+
+ def apply(self):
+ self.enabled_current = self.enabled.get()
+
+ def revert(self):
+ self.enabled.set(self.enabled_default)
+
+ def check(self):
+ self.enabled.set(1)
+
+ def uncheck(self):
+ self.enabled.set(0)
+
+class SourceConfigure(Toplevel):
+ def __init__(self):
+ Toplevel.__init__(self)
+ self.resizable(0, 0)
+ self.title("Source Configuration")
+ self.items = []
+ self.iframe = Frame(self)
+ self.iframe.grid(row=0, column=0, sticky=E+W)
+ f = LabelFrame(self.iframe, bd=4, text="Sources")
+ self.items.append(f)
+ self.buttons = Frame(self)
+ self.items[0].grid(row=0, column=0, sticky=E+W)
+ self.columnconfigure(0, weight=1)
+ self.sconfig = []
+ self.irow = 0
+ self.icol = 0
+ for source in sources:
+ self.addsource(source)
+ self.drawbuttons()
+ self.buttons.grid(row=1, column=0, sticky=W)
+
+ def addsource(self, source):
+ if (self.irow > 30):
+ self.icol += 1
+ self.irow = 0
+ c = self.icol
+ f = LabelFrame(self.iframe, bd=4, text="Sources")
+ f.grid(row=0, column=c, sticky=N+E+W)
+ self.items.append(f)
+ item = SourceConf(self.items[self.icol], source)
+ self.sconfig.append(item)
+ item.grid(row=self.irow, column=0, sticky=E+W)
+ self.irow += 1
+
+ def drawbuttons(self):
+ self.apply = Button(self.buttons, text="Apply",
+ command=self.apress)
+ self.default = Button(self.buttons, text="Revert",
+ command=self.rpress)
+ self.checkall = Button(self.buttons, text="Check All",
+ command=self.cpress)
+ self.uncheckall = Button(self.buttons, text="Uncheck All",
+ command=self.upress)
+ self.checkall.grid(row=0, column=0, sticky=W)
+ self.uncheckall.grid(row=0, column=1, sticky=W)
+ self.apply.grid(row=0, column=2, sticky=W)
+ self.default.grid(row=0, column=3, sticky=W)
+ self.buttons.columnconfigure(0, weight=1)
+ self.buttons.columnconfigure(1, weight=1)
+ self.buttons.columnconfigure(2, weight=1)
+ self.buttons.columnconfigure(3, weight=1)
+
+ def apress(self):
+ disable_sources = []
+ enable_sources = []
+ for item in self.sconfig:
+ if (item.changed() == 0):
+ continue
+ if (item.enabled.get() == 1):
+ enable_sources.append(item.source)
+ else:
+ disable_sources.append(item.source)
+
+ if (len(disable_sources)):
+ graph.sourcehidelist(disable_sources)
+ if (len(enable_sources)):
+ graph.sourceshowlist(enable_sources)
+
+ for item in self.sconfig:
+ item.apply()
+
+ def rpress(self):
+ for item in self.sconfig:
+ item.revert()
+
+ def cpress(self):
+ for item in self.sconfig:
+ item.check()
+
+ def upress(self):
+ for item in self.sconfig:
+ item.uncheck()
+
+# Reverse compare of second member of the tuple
+def cmp_counts(x, y):
+ return y[1] - x[1]
+
+class SourceStats(Toplevel):
+ def __init__(self, source):
+ self.source = source
+ Toplevel.__init__(self)
+ self.resizable(0, 0)
+ self.title(source.name + " statistics")
+ self.evframe = LabelFrame(self,
+ text="Event Count, Duration, Avg Duration")
+ self.evframe.grid(row=0, column=0, sticky=E+W)
+ eventtypes={}
+ for event in self.source.events:
+ if (event.type == "pad"):
+ continue
+ duration = event.duration
+ if (eventtypes.has_key(event.name)):
+ (c, d) = eventtypes[event.name]
+ c += 1
+ d += duration
+ eventtypes[event.name] = (c, d)
+ else:
+ eventtypes[event.name] = (1, duration)
+ events = []
+ for k, v in eventtypes.iteritems():
+ (c, d) = v
+ events.append((k, c, d))
+ events.sort(cmp=cmp_counts)
+
+ ypos = 0
+ for event in events:
+ (name, c, d) = event
+ Label(self.evframe, text=name, bd=1,
+ relief=SUNKEN, anchor=W, width=30).grid(
+ row=ypos, column=0, sticky=W+E)
+ Label(self.evframe, text=str(c), bd=1,
+ relief=SUNKEN, anchor=W, width=10).grid(
+ row=ypos, column=1, sticky=W+E)
+ Label(self.evframe, text=ticks2sec(d),
+ bd=1, relief=SUNKEN, width=10).grid(
+ row=ypos, column=2, sticky=W+E)
+ if (d and c):
+ d /= c
+ else:
+ d = 0
+ Label(self.evframe, text=ticks2sec(d),
+ bd=1, relief=SUNKEN, width=10).grid(
+ row=ypos, column=3, sticky=W+E)
+ ypos += 1
+
+
+class SourceContext(Menu):
+ def __init__(self, event, source):
+ self.source = source
+ Menu.__init__(self, tearoff=0, takefocus=0)
+ self.add_command(label="hide", command=self.hide)
+ self.add_command(label="hide group", command=self.hidegroup)
+ self.add_command(label="stats", command=self.stats)
+ self.tk_popup(event.x_root-3, event.y_root+3)
+
+ def hide(self):
+ graph.sourcehide(self.source)
+
+ def hidegroup(self):
+ grouplist = []
+ for source in sources:
+ if (source.group == self.source.group):
+ grouplist.append(source)
+ graph.sourcehidelist(grouplist)
+
+ def show(self):
+ graph.sourceshow(self.source)
+
+ def stats(self):
+ SourceStats(self.source)
+
+class EventView(Toplevel):
+ def __init__(self, event, canvas):
+ Toplevel.__init__(self)
+ self.resizable(0, 0)
+ self.title("Event")
+ self.event = event
+ self.buttons = Frame(self)
+ self.buttons.grid(row=0, column=0, sticky=E+W)
+ self.frame = Frame(self)
+ self.frame.grid(row=1, column=0, sticky=N+S+E+W)
+ self.canvas = canvas
+ self.drawlabels()
+ self.drawbuttons()
+ event.displayref(canvas)
+ self.bind("<Destroy>", self.destroycb)
+
+ def destroycb(self, event):
+ self.unbind("<Destroy>")
+ if (self.event != None):
+ self.event.displayunref(self.canvas)
+ self.event = None
+ self.destroy()
+
+ def clearlabels(self):
+ for label in self.frame.grid_slaves():
+ label.grid_remove()
+
+ def drawlabels(self):
+ ypos = 0
+ labels = self.event.labels()
+ while (len(labels) < 7):
+ labels.append(("", ""))
+ for label in labels:
+ name, value = label
+ linked = 0
+ if (name == "linkedto"):
+ linked = 1
+ l = Label(self.frame, text=name, bd=1, width=15,
+ relief=SUNKEN, anchor=W)
+ if (linked):
+ fgcolor = "blue"
+ else:
+ fgcolor = "black"
+ r = Label(self.frame, text=value, bd=1,
+ relief=SUNKEN, anchor=W, fg=fgcolor)
+ l.grid(row=ypos, column=0, sticky=E+W)
+ r.grid(row=ypos, column=1, sticky=E+W)
+ if (linked):
+ r.bind("<Button-1>", self.linkpress)
+ ypos += 1
+ self.frame.columnconfigure(1, minsize=80)
+
+ def drawbuttons(self):
+ self.back = Button(self.buttons, text="<", command=self.bpress)
+ self.forw = Button(self.buttons, text=">", command=self.fpress)
+ self.new = Button(self.buttons, text="new", command=self.npress)
+ self.back.grid(row=0, column=0, sticky=E+W)
+ self.forw.grid(row=0, column=1, sticky=E+W)
+ self.new.grid(row=0, column=2, sticky=E+W)
+ self.buttons.columnconfigure(2, weight=1)
+
+ def newevent(self, event):
+ self.event.displayunref(self.canvas)
+ self.clearlabels()
+ self.event = event
+ self.event.displayref(self.canvas)
+ self.drawlabels()
+
+ def npress(self):
+ EventView(self.event, self.canvas)
+
+ def bpress(self):
+ prev = self.event.prev()
+ if (prev == None):
+ return
+ while (prev.type == "pad"):
+ prev = prev.prev()
+ if (prev == None):
+ return
+ self.newevent(prev)
+
+ def fpress(self):
+ next = self.event.next()
+ if (next == None):
+ return
+ while (next.type == "pad"):
+ next = next.next()
+ if (next == None):
+ return
+ self.newevent(next)
+
+ def linkpress(self, wevent):
+ event = self.event.getlinked()
+ if (event != None):
+ self.newevent(event)
+
+class Event:
+ def __init__(self, source, name, cpu, timestamp, attrs):
+ self.source = source
+ self.name = name
+ self.cpu = cpu
+ self.timestamp = int(timestamp)
+ self.attrs = attrs
+ self.idx = None
+ self.item = None
+ self.dispcnt = 0
+ self.duration = 0
+ self.recno = lineno
+
+ def status(self):
+ statstr = self.name + " " + self.source.name
+ statstr += " on: cpu" + str(self.cpu)
+ statstr += " at: " + str(self.timestamp)
+ statstr += " attributes: "
+ for i in range(0, len(self.attrs)):
+ attr = self.attrs[i]
+ statstr += attr[0] + ": " + str(attr[1])
+ if (i != len(self.attrs) - 1):
+ statstr += ", "
+ status.set(statstr)
+
+ def labels(self):
+ return [("Source", self.source.name),
+ ("Event", self.name),
+ ("CPU", self.cpu),
+ ("Timestamp", self.timestamp),
+ ("KTR Line ", self.recno)
+ ] + self.attrs
+
+ def mouseenter(self, canvas):
+ self.displayref(canvas)
+ self.status()
+
+ def mouseexit(self, canvas):
+ self.displayunref(canvas)
+ status.clear()
+
+ def mousepress(self, canvas):
+ EventView(self, canvas)
+
+ def draw(self, canvas, xpos, ypos, item):
+ self.item = item
+ if (item != None):
+ canvas.items[item] = self
+
+ def move(self, canvas, x, y):
+ if (self.item == None):
+ return;
+ canvas.move(self.item, x, y);
+
+ def next(self):
+ return self.source.eventat(self.idx + 1)
+
+ def nexttype(self, type):
+ next = self.next()
+ while (next != None and next.type != type):
+ next = next.next()
+ return (next)
+
+ def prev(self):
+ return self.source.eventat(self.idx - 1)
+
+ def displayref(self, canvas):
+ if (self.dispcnt == 0):
+ canvas.itemconfigure(self.item, width=2)
+ self.dispcnt += 1
+
+ def displayunref(self, canvas):
+ self.dispcnt -= 1
+ if (self.dispcnt == 0):
+ canvas.itemconfigure(self.item, width=0)
+ canvas.tag_raise("point", "state")
+
+ def getlinked(self):
+ for attr in self.attrs:
+ if (attr[0] != "linkedto"):
+ continue
+ source = ktrfile.findid(attr[1])
+ return source.findevent(self.timestamp)
+ return None
+
+class PointEvent(Event):
+ type = "point"
+ def __init__(self, source, name, cpu, timestamp, attrs):
+ Event.__init__(self, source, name, cpu, timestamp, attrs)
+
+ def draw(self, canvas, xpos, ypos):
+ color = colormap.lookup(self.name)
+ l = canvas.create_oval(xpos - XY_POINT, ypos,
+ xpos + XY_POINT, ypos - (XY_POINT * 2),
+ fill=color, width=0,
+ tags=("event", self.type, self.name, self.source.tag))
+ Event.draw(self, canvas, xpos, ypos, l)
+
+ return xpos
+
+class StateEvent(Event):
+ type = "state"
+ def __init__(self, source, name, cpu, timestamp, attrs):
+ Event.__init__(self, source, name, cpu, timestamp, attrs)
+
+ def draw(self, canvas, xpos, ypos):
+ next = self.nexttype("state")
+ if (next == None):
+ return (xpos)
+ self.duration = duration = next.timestamp - self.timestamp
+ self.attrs.insert(0, ("duration", ticks2sec(duration)))
+ color = colormap.lookup(self.name)
+ if (duration < 0):
+ duration = 0
+ print "Unsynchronized timestamp"
+ print self.cpu, self.timestamp
+ print next.cpu, next.timestamp
+ delta = duration / canvas.ratio
+ l = canvas.create_rectangle(xpos, ypos,
+ xpos + delta, ypos - 10, fill=color, width=0,
+ tags=("event", self.type, self.name, self.source.tag))
+ Event.draw(self, canvas, xpos, ypos, l)
+
+ return (xpos + delta)
+
+class CountEvent(Event):
+ type = "count"
+ def __init__(self, source, count, cpu, timestamp, attrs):
+ count = int(count)
+ self.count = count
+ Event.__init__(self, source, "count", cpu, timestamp, attrs)
+
+ def draw(self, canvas, xpos, ypos):
+ next = self.nexttype("count")
+ if (next == None):
+ return (xpos)
+ color = colormap.lookup("count")
+ self.duration = duration = next.timestamp - self.timestamp
+ if (duration < 0):
+ duration = 0
+ print "Unsynchronized timestamp"
+ print self.cpu, self.timestamp
+ print next.cpu, next.timestamp
+ self.attrs.insert(0, ("count", self.count))
+ self.attrs.insert(1, ("duration", ticks2sec(duration)))
+ delta = duration / canvas.ratio
+ yhight = self.source.yscale() * self.count
+ l = canvas.create_rectangle(xpos, ypos - yhight,
+ xpos + delta, ypos, fill=color, width=0,
+ tags=("event", self.type, self.name, self.source.tag))
+ Event.draw(self, canvas, xpos, ypos, l)
+ return (xpos + delta)
+
+class PadEvent(StateEvent):
+ type = "pad"
+ def __init__(self, source, cpu, timestamp, last=0):
+ if (last):
+ cpu = source.events[len(source.events) -1].cpu
+ else:
+ cpu = source.events[0].cpu
+ StateEvent.__init__(self, source, "pad", cpu, timestamp, [])
+ def draw(self, canvas, xpos, ypos):
+ next = self.next()
+ if (next == None):
+ return (xpos)
+ duration = next.timestamp - self.timestamp
+ delta = duration / canvas.ratio
+ Event.draw(self, canvas, xpos, ypos, None)
+ return (xpos + delta)
+
+# Sort function for start y address
+def source_cmp_start(x, y):
+ return x.y - y.y
+
+class EventSource:
+ def __init__(self, group, id):
+ self.name = id
+ self.events = []
+ self.cpuitems = []
+ self.group = group
+ self.y = 0
+ self.item = None
+ self.hidden = 0
+ self.tag = group + id
+
+ def __cmp__(self, other):
+ if (other == None):
+ return -1
+ if (self.group == other.group):
+ return cmp(self.name, other.name)
+ return cmp(self.group, other.group)
+
+ # It is much faster to append items to a list then to insert them
+ # at the beginning. As a result, we add events in reverse order
+ # and then swap the list during fixup.
+ def fixup(self):
+ self.events.reverse()
+
+ def addevent(self, event):
+ self.events.append(event)
+
+ def addlastevent(self, event):
+ self.events.insert(0, event)
+
+ def draw(self, canvas, ypos):
+ xpos = 10
+ cpux = 10
+ cpu = self.events[1].cpu
+ for i in range(0, len(self.events)):
+ self.events[i].idx = i
+ for event in self.events:
+ if (event.cpu != cpu and event.cpu != -1):
+ self.drawcpu(canvas, cpu, cpux, xpos, ypos)
+ cpux = xpos
+ cpu = event.cpu
+ xpos = event.draw(canvas, xpos, ypos)
+ self.drawcpu(canvas, cpu, cpux, xpos, ypos)
+
+ def drawname(self, canvas, ypos):
+ self.y = ypos
+ ypos = ypos - (self.ysize() / 2)
+ self.item = canvas.create_text(X_BORDER, ypos, anchor="w",
+ text=self.name)
+ return (self.item)
+
+ def drawcpu(self, canvas, cpu, fromx, tox, ypos):
+ cpu = "CPU " + str(cpu)
+ color = cpucolormap.lookup(cpu)
+ # Create the cpu background colors default to hidden
+ l = canvas.create_rectangle(fromx,
+ ypos - self.ysize() - canvas.bdheight,
+ tox, ypos + canvas.bdheight, fill=color, width=0,
+ tags=("cpubg", cpu, self.tag), state="hidden")
+ self.cpuitems.append(l)
+
+ def move(self, canvas, xpos, ypos):
+ canvas.move(self.tag, xpos, ypos)
+
+ def movename(self, canvas, xpos, ypos):
+ self.y += ypos
+ canvas.move(self.item, xpos, ypos)
+
+ def ysize(self):
+ return (Y_EVENTSOURCE)
+
+ def eventat(self, i):
+ if (i >= len(self.events)):
+ return (None)
+ event = self.events[i]
+ return (event)
+
+ def findevent(self, timestamp):
+ for event in self.events:
+ if (event.timestamp >= timestamp and event.type != "pad"):
+ return (event)
+ return (None)
+
+class Counter(EventSource):
+ #
+ # Store a hash of counter groups that keeps the max value
+ # for a counter in this group for scaling purposes.
+ #
+ groups = {}
+ def __init__(self, group, id):
+ try:
+ Counter.cnt = Counter.groups[group]
+ except:
+ Counter.groups[group] = 0
+ EventSource.__init__(self, group, id)
+
+ def fixup(self):
+ for event in self.events:
+ if (event.type != "count"):
+ continue;
+ count = int(event.count)
+ if (count > Counter.groups[self.group]):
+ Counter.groups[self.group] = count
+ EventSource.fixup(self)
+
+ def ymax(self):
+ return (Counter.groups[self.group])
+
+ def ysize(self):
+ return (Y_COUNTER)
+
+ def yscale(self):
+ return (self.ysize() / self.ymax())
+
+class KTRFile:
+ def __init__(self, file):
+ self.timestamp_f = None
+ self.timestamp_l = None
+ self.locks = {}
+ self.callwheels = {}
+ self.ticks = {}
+ self.load = {}
+ self.crit = {}
+ self.stathz = 0
+ self.eventcnt = 0
+ self.taghash = {}
+
+ self.parse(file)
+ self.fixup()
+ global ticksps
+ ticksps = self.ticksps()
+ span = self.timespan()
+ ghz = float(ticksps) / 1000000000.0
+ #
+ # Update the title with some stats from the file
+ #
+ titlestr = "SchedGraph: "
+ titlestr += ticks2sec(span) + " at %.3f ghz, " % ghz
+ titlestr += str(len(sources)) + " event sources, "
+ titlestr += str(self.eventcnt) + " events"
+ root.title(titlestr)
+
+ def parse(self, file):
+ try:
+ ifp = open(file)
+ except:
+ print "Can't open", file
+ sys.exit(1)
+
+ # quoteexp matches a quoted string, no escaping
+ quoteexp = "\"([^\"]*)\""
+
+ #
+ # commaexp matches a quoted string OR the string up
+ # to the first ','
+ #
+ commaexp = "(?:" + quoteexp + "|([^,]+))"
+
+ #
+ # colonstr matches a quoted string OR the string up
+ # to the first ':'
+ #
+ colonexp = "(?:" + quoteexp + "|([^:]+))"
+
+ #
+ # Match various manditory parts of the KTR string this is
+ # fairly inflexible until you get to attributes to make
+ # parsing faster.
+ #
+ hdrexp = "\s*(\d+)\s+(\d+)\s+(\d+)\s+"
+ groupexp = "KTRGRAPH group:" + quoteexp + ", "
+ idexp = "id:" + quoteexp + ", "
+ typeexp = "([^:]+):" + commaexp + ", "
+ attribexp = "attributes: (.*)"
+
+ #
+ # Matches optional attributes in the KTR string. This
+ # tolerates more variance as the users supply these values.
+ #
+ attrexp = colonexp + "\s*:\s*(?:" + commaexp + ", (.*)|"
+ attrexp += quoteexp +"|(.*))"
+
+ # Precompile regexp
+ ktrre = re.compile(hdrexp + groupexp + idexp + typeexp + attribexp)
+ attrre = re.compile(attrexp)
+
+ global lineno
+ lineno = 0
+ for line in ifp.readlines():
+ lineno += 1
+ if ((lineno % 2048) == 0):
+ status.startup("Parsing line " + str(lineno))
+ m = ktrre.match(line);
+ if (m == None):
+ print "Can't parse", lineno, line,
+ continue;
+ (index, cpu, timestamp, group, id, type, dat, dat1, attrstring) = m.groups();
+ if (dat == None):
+ dat = dat1
+ if (self.checkstamp(timestamp) == 0):
+ print "Bad timestamp at", lineno, ":",
+ print cpu, timestamp
+ continue
+ #
+ # Build the table of optional attributes
+ #
+ attrs = []
+ while (attrstring != None):
+ m = attrre.match(attrstring.strip())
+ if (m == None):
+ break;
+ #
+ # Name may or may not be quoted.
+ #
+ # For val we have four cases:
+ # 1) quotes followed by comma and more
+ # attributes.
+ # 2) no quotes followed by comma and more
+ # attributes.
+ # 3) no more attributes or comma with quotes.
+ # 4) no more attributes or comma without quotes.
+ #
+ (name, name1, val, val1, attrstring, end, end1) = m.groups();
+ if (name == None):
+ name = name1
+ if (end == None):
+ end = end1
+ if (val == None):
+ val = val1
+ if (val == None):
+ val = end
+ if (name == "stathz"):
+ self.setstathz(val, cpu)
+ attrs.append((name, val))
+ args = (dat, cpu, timestamp, attrs)
+ e = self.makeevent(group, id, type, args)
+ if (e == None):
+ print "Unknown type", type, lineno, line,
+
+ def makeevent(self, group, id, type, args):
+ e = None
+ source = self.makeid(group, id, type)
+ if (type == "state"):
+ e = StateEvent(source, *args)
+ elif (type == "counter"):
+ e = CountEvent(source, *args)
+ elif (type == "point"):
+ e = PointEvent(source, *args)
+ if (e != None):
+ self.eventcnt += 1
+ source.addevent(e);
+ return e
+
+ def setstathz(self, val, cpu):
+ self.stathz = int(val)
+ cpu = int(cpu)
+ try:
+ ticks = self.ticks[cpu]
+ except:
+ self.ticks[cpu] = 0
+ self.ticks[cpu] += 1
+
+ def checkstamp(self, timestamp):
+ timestamp = int(timestamp)
+ if (self.timestamp_f == None):
+ self.timestamp_f = timestamp;
+ if (self.timestamp_l != None and
+ timestamp -2048> self.timestamp_l):
+ return (0)
+ self.timestamp_l = timestamp;
+ return (1)
+
+ def makeid(self, group, id, type):
+ tag = group + id
+ if (self.taghash.has_key(tag)):
+ return self.taghash[tag]
+ if (type == "counter"):
+ source = Counter(group, id)
+ else:
+ source = EventSource(group, id)
+ sources.append(source)
+ self.taghash[tag] = source
+ return (source)
+
+ def findid(self, id):
+ for source in sources:
+ if (source.name == id):
+ return source
+ return (None)
+
+ def timespan(self):
+ return (self.timestamp_f - self.timestamp_l);
+
+ def ticksps(self):
+ oneghz = 1000000000
+ # Use user supplied clock first
+ if (clockfreq != None):
+ return int(clockfreq * oneghz)
+
+ # Check for a discovered clock
+ if (self.stathz != 0):
+ return (self.timespan() / self.ticks[0]) * int(self.stathz)
+ # Pretend we have a 1ns clock
+ print "WARNING: No clock discovered and no frequency ",
+ print "specified via the command line."
+ print "Using fake 1ghz clock"
+ return (oneghz);
+
+ def fixup(self):
+ for source in sources:
+ e = PadEvent(source, -1, self.timestamp_l)
+ source.addevent(e)
+ e = PadEvent(source, -1, self.timestamp_f, last=1)
+ source.addlastevent(e)
+ source.fixup()
+ sources.sort()
+
+class SchedNames(Canvas):
+ def __init__(self, master, display):
+ self.display = display
+ self.parent = master
+ self.bdheight = master.bdheight
+ self.items = {}
+ self.ysize = 0
+ self.lines = []
+ Canvas.__init__(self, master, width=120,
+ height=display["height"], bg='grey',
+ scrollregion=(0, 0, 50, 100))
+
+ def moveline(self, cur_y, y):
+ for line in self.lines:
+ (x0, y0, x1, y1) = self.coords(line)
+ if (cur_y != y0):
+ continue
+ self.move(line, 0, y)
+ return
+
+ def draw(self):
+ status.startup("Drawing names")
+ ypos = 0
+ self.configure(scrollregion=(0, 0,
+ self["width"], self.display.ysize()))
+ for source in sources:
+ l = self.create_line(0, ypos, self["width"], ypos,
+ width=1, fill="black", tags=("all","sources"))
+ self.lines.append(l)
+ ypos += self.bdheight
+ ypos += source.ysize()
+ t = source.drawname(self, ypos)
+ self.items[t] = source
+ ypos += self.bdheight
+ self.ysize = ypos
+ self.create_line(0, ypos, self["width"], ypos,
+ width=1, fill="black", tags=("all",))
+ self.bind("<Button-1>", self.master.mousepress);
+ self.bind("<Button-3>", self.master.mousepressright);
+ self.bind("<ButtonRelease-1>", self.master.mouserelease);
+ self.bind("<B1-Motion>", self.master.mousemotion);
+
+ def updatescroll(self):
+ self.configure(scrollregion=(0, 0,
+ self["width"], self.display.ysize()))
+
+
+class SchedDisplay(Canvas):
+ def __init__(self, master):
+ self.ratio = 1
+ self.parent = master
+ self.bdheight = master.bdheight
+ self.items = {}
+ self.lines = []
+ Canvas.__init__(self, master, width=800, height=500, bg='grey',
+ scrollregion=(0, 0, 800, 500))
+
+ def prepare(self):
+ #
+ # Compute a ratio to ensure that the file's timespan fits into
+ # 2^31. Although python may handle larger values for X
+ # values, the Tk internals do not.
+ #
+ self.ratio = (ktrfile.timespan() - 1) / 2**31 + 1
+
+ def draw(self):
+ ypos = 0
+ xsize = self.xsize()
+ for source in sources:
+ status.startup("Drawing " + source.name)
+ l = self.create_line(0, ypos, xsize, ypos,
+ width=1, fill="black", tags=("all",))
+ self.lines.append(l)
+ ypos += self.bdheight
+ ypos += source.ysize()
+ source.draw(self, ypos)
+ ypos += self.bdheight
+ self.tag_raise("point", "state")
+ self.tag_lower("cpubg", ALL)
+ self.create_line(0, ypos, xsize, ypos,
+ width=1, fill="black", tags=("lines",))
+ self.tag_bind("event", "<Enter>", self.mouseenter)
+ self.tag_bind("event", "<Leave>", self.mouseexit)
+ self.bind("<Button-1>", self.mousepress)
+ self.bind("<Button-3>", self.master.mousepressright);
+ self.bind("<Button-4>", self.wheelup)
+ self.bind("<Button-5>", self.wheeldown)
+ self.bind("<ButtonRelease-1>", self.master.mouserelease);
+ self.bind("<B1-Motion>", self.master.mousemotion);
+
+ def moveline(self, cur_y, y):
+ for line in self.lines:
+ (x0, y0, x1, y1) = self.coords(line)
+ if (cur_y != y0):
+ continue
+ self.move(line, 0, y)
+ return
+
+ def mouseenter(self, event):
+ item, = self.find_withtag(CURRENT)
+ self.items[item].mouseenter(self)
+
+ def mouseexit(self, event):
+ item, = self.find_withtag(CURRENT)
+ self.items[item].mouseexit(self)
+
+ def mousepress(self, event):
+ # Find out what's beneath us
+ items = self.find_withtag(CURRENT)
+ if (len(items) == 0):
+ self.master.mousepress(event)
+ return
+ # Only grab mouse presses for things with event tags.
+ item = items[0]
+ tags = self.gettags(item)
+ for tag in tags:
+ if (tag == "event"):
+ self.items[item].mousepress(self)
+ return
+ # Leave the rest to the master window
+ self.master.mousepress(event)
+
+ def wheeldown(self, event):
+ self.parent.display_yview("scroll", 1, "units")
+
+ def wheelup(self, event):
+ self.parent.display_yview("scroll", -1, "units")
+
+ def xsize(self):
+ return ((ktrfile.timespan() / self.ratio) + (X_BORDER * 2))
+
+ def ysize(self):
+ ysize = 0
+ for source in sources:
+ if (source.hidden == 1):
+ continue
+ ysize += self.parent.sourcesize(source)
+ return ysize
+
+ def scaleset(self, ratio):
+ if (ktrfile == None):
+ return
+ oldratio = self.ratio
+ xstart, xend = self.xview()
+ midpoint = xstart + ((xend - xstart) / 2)
+
+ self.ratio = ratio
+ self.updatescroll()
+ self.scale(ALL, 0, 0, float(oldratio) / ratio, 1)
+
+ xstart, xend = self.xview()
+ xsize = (xend - xstart) / 2
+ self.xview_moveto(midpoint - xsize)
+
+ def updatescroll(self):
+ self.configure(scrollregion=(0, 0, self.xsize(), self.ysize()))
+
+ def scaleget(self):
+ return self.ratio
+
+ def getcolor(self, tag):
+ return self.itemcget(tag, "fill")
+
+ def getstate(self, tag):
+ return self.itemcget(tag, "state")
+
+ def setcolor(self, tag, color):
+ self.itemconfigure(tag, state="normal", fill=color)
+
+ def hide(self, tag):
+ self.itemconfigure(tag, state="hidden")
+
+class GraphMenu(Frame):
+ def __init__(self, master):
+ Frame.__init__(self, master, bd=2, relief=RAISED)
+ self.conf = Menubutton(self, text="Configure")
+ self.confmenu = Menu(self.conf, tearoff=0)
+ self.confmenu.add_command(label="Event Colors",
+ command=self.econf)
+ self.confmenu.add_command(label="CPU Colors",
+ command=self.cconf)
+ self.confmenu.add_command(label="Source Configure",
+ command=self.sconf)
+ self.conf["menu"] = self.confmenu
+ self.conf.pack(side=LEFT)
+
+ def econf(self):
+ ColorConfigure(eventcolors, "Event Display Configuration")
+
+ def cconf(self):
+ ColorConfigure(cpucolors, "CPU Background Colors")
+
+ def sconf(self):
+ SourceConfigure()
+
+class SchedGraph(Frame):
+ def __init__(self, master):
+ Frame.__init__(self, master)
+ self.menu = None
+ self.names = None
+ self.display = None
+ self.scale = None
+ self.status = None
+ self.bdheight = Y_BORDER
+ self.clicksource = None
+ self.lastsource = None
+ self.pack(expand=1, fill="both")
+ self.buildwidgets()
+ self.layout()
+
+ def buildwidgets(self):
+ global status
+ self.menu = GraphMenu(self)
+ self.display = SchedDisplay(self)
+ self.names = SchedNames(self, self.display)
+ self.scale = Scaler(self, self.display)
+ status = self.status = Status(self)
+ self.scrollY = Scrollbar(self, orient="vertical",
+ command=self.display_yview)
+ self.display.scrollX = Scrollbar(self, orient="horizontal",
+ command=self.display.xview)
+ self.display["xscrollcommand"] = self.display.scrollX.set
+ self.display["yscrollcommand"] = self.scrollY.set
+ self.names["yscrollcommand"] = self.scrollY.set
+
+ def layout(self):
+ self.columnconfigure(1, weight=1)
+ self.rowconfigure(1, weight=1)
+ self.menu.grid(row=0, column=0, columnspan=3, sticky=E+W)
+ self.names.grid(row=1, column=0, sticky=N+S)
+ self.display.grid(row=1, column=1, sticky=W+E+N+S)
+ self.scrollY.grid(row=1, column=2, sticky=N+S)
+ self.display.scrollX.grid(row=2, column=0, columnspan=2,
+ sticky=E+W)
+ self.scale.grid(row=3, column=0, columnspan=3, sticky=E+W)
+ self.status.grid(row=4, column=0, columnspan=3, sticky=E+W)
+
+ def draw(self):
+ self.master.update()
+ self.display.prepare()
+ self.names.draw()
+ self.display.draw()
+ self.status.startup("")
+ #
+ # Configure scale related values
+ #
+ scalemax = ktrfile.timespan() / int(self.display["width"])
+ width = int(root.geometry().split('x')[0])
+ self.constwidth = width - int(self.display["width"])
+ self.scale.setmax(scalemax)
+ self.scale.set(scalemax)
+ self.display.xview_moveto(0)
+ self.bind("<Configure>", self.resize)
+
+ def mousepress(self, event):
+ self.clicksource = self.sourceat(event.y)
+
+ def mousepressright(self, event):
+ source = self.sourceat(event.y)
+ if (source == None):
+ return
+ SourceContext(event, source)
+
+ def mouserelease(self, event):
+ if (self.clicksource == None):
+ return
+ newsource = self.sourceat(event.y)
+ if (self.clicksource != newsource):
+ self.sourceswap(self.clicksource, newsource)
+ self.clicksource = None
+ self.lastsource = None
+
+ def mousemotion(self, event):
+ if (self.clicksource == None):
+ return
+ newsource = self.sourceat(event.y)
+ #
+ # If we get a None source they moved off the page.
+ # swapsource() can't handle moving multiple items so just
+ # pretend we never clicked on anything to begin with so the
+ # user can't mouseover a non-contiguous area.
+ #
+ if (newsource == None):
+ self.clicksource = None
+ self.lastsource = None
+ return
+ if (newsource == self.lastsource):
+ return;
+ self.lastsource = newsource
+ if (newsource != self.clicksource):
+ self.sourceswap(self.clicksource, newsource)
+
+ # These are here because this object controls layout
+ def sourcestart(self, source):
+ return source.y - self.bdheight - source.ysize()
+
+ def sourceend(self, source):
+ return source.y + self.bdheight
+
+ def sourcesize(self, source):
+ return (self.bdheight * 2) + source.ysize()
+
+ def sourceswap(self, source1, source2):
+ # Sort so we always know which one is on top.
+ if (source2.y < source1.y):
+ swap = source1
+ source1 = source2
+ source2 = swap
+ # Only swap adjacent sources
+ if (self.sourceend(source1) != self.sourcestart(source2)):
+ return
+ # Compute start coordinates and target coordinates
+ y1 = self.sourcestart(source1)
+ y2 = self.sourcestart(source2)
+ y1targ = y1 + self.sourcesize(source2)
+ y2targ = y1
+ #
+ # If the sizes are not equal, adjust the start of the lower
+ # source to account for the lost/gained space.
+ #
+ if (source1.ysize() != source2.ysize()):
+ diff = source2.ysize() - source1.ysize()
+ self.names.moveline(y2, diff);
+ self.display.moveline(y2, diff)
+ source1.move(self.display, 0, y1targ - y1)
+ source2.move(self.display, 0, y2targ - y2)
+ source1.movename(self.names, 0, y1targ - y1)
+ source2.movename(self.names, 0, y2targ - y2)
+
+ def sourcepicky(self, source):
+ if (source.hidden == 0):
+ return self.sourcestart(source)
+ # Revert to group based sort
+ sources.sort()
+ prev = None
+ for s in sources:
+ if (s == source):
+ break
+ if (s.hidden == 0):
+ prev = s
+ if (prev == None):
+ newy = 0
+ else:
+ newy = self.sourcestart(prev) + self.sourcesize(prev)
+ return newy
+
+ def sourceshow(self, source):
+ if (source.hidden == 0):
+ return;
+ newy = self.sourcepicky(source)
+ off = newy - self.sourcestart(source)
+ self.sourceshiftall(newy-1, self.sourcesize(source))
+ self.sourceshift(source, off)
+ source.hidden = 0
+
+ #
+ # Optimized source show of multiple entries that only moves each
+ # existing entry once. Doing sourceshow() iteratively is too
+ # expensive due to python's canvas.move().
+ #
+ def sourceshowlist(self, srclist):
+ srclist.sort(cmp=source_cmp_start)
+ startsize = []
+ for source in srclist:
+ if (source.hidden == 0):
+ srclist.remove(source)
+ startsize.append((self.sourcepicky(source),
+ self.sourcesize(source)))
+
+ sources.sort(cmp=source_cmp_start, reverse=True)
+ self.status.startup("Updating display...");
+ for source in sources:
+ if (source.hidden == 1):
+ continue
+ nstart = self.sourcestart(source)
+ size = 0
+ for hidden in startsize:
+ (start, sz) = hidden
+ if (start <= nstart or start+sz <= nstart):
+ size += sz
+ self.sourceshift(source, size)
+ idx = 0
+ size = 0
+ for source in srclist:
+ (newy, sz) = startsize[idx]
+ off = (newy + size) - self.sourcestart(source)
+ self.sourceshift(source, off)
+ source.hidden = 0
+ size += sz
+ idx += 1
+ self.updatescroll()
+ self.status.set("")
+
+ #
+ # Optimized source hide of multiple entries that only moves each
+ # remaining entry once. Doing sourcehide() iteratively is too
+ # expensive due to python's canvas.move().
+ #
+ def sourcehidelist(self, srclist):
+ srclist.sort(cmp=source_cmp_start)
+ sources.sort(cmp=source_cmp_start)
+ startsize = []
+ off = len(sources) * 100
+ self.status.startup("Updating display...");
+ for source in srclist:
+ if (source.hidden == 1):
+ srclist.remove(source)
+ #
+ # Remember our old position so we can sort things
+ # below us when we're done.
+ #
+ startsize.append((self.sourcestart(source),
+ self.sourcesize(source)))
+ self.sourceshift(source, off)
+ source.hidden = 1
+
+ idx = 0
+ size = 0
+ for hidden in startsize:
+ (start, sz) = hidden
+ size += sz
+ if (idx + 1 < len(startsize)):
+ (stop, sz) = startsize[idx+1]
+ else:
+ stop = self.display.ysize()
+ idx += 1
+ for source in sources:
+ nstart = self.sourcestart(source)
+ if (nstart < start or source.hidden == 1):
+ continue
+ if (nstart >= stop):
+ break;
+ self.sourceshift(source, -size)
+ self.updatescroll()
+ self.status.set("")
+
+ def sourcehide(self, source):
+ if (source.hidden == 1):
+ return;
+ # Move it out of the visible area
+ off = len(sources) * 100
+ start = self.sourcestart(source)
+ self.sourceshift(source, off)
+ self.sourceshiftall(start, -self.sourcesize(source))
+ source.hidden = 1
+
+ def sourceshift(self, source, off):
+ start = self.sourcestart(source)
+ source.move(self.display, 0, off)
+ source.movename(self.names, 0, off)
+ self.names.moveline(start, off);
+ self.display.moveline(start, off)
+ #
+ # We update the idle tasks to shrink the dirtied area so
+ # it does not always include the entire screen.
+ #
+ self.names.update_idletasks()
+ self.display.update_idletasks()
+
+ def sourceshiftall(self, start, off):
+ self.status.startup("Updating display...");
+ for source in sources:
+ nstart = self.sourcestart(source)
+ if (nstart < start):
+ continue;
+ self.sourceshift(source, off)
+ self.updatescroll()
+ self.status.set("")
+
+ def sourceat(self, ypos):
+ (start, end) = self.names.yview()
+ starty = start * float(self.names.ysize)
+ ypos += starty
+ for source in sources:
+ if (source.hidden == 1):
+ continue;
+ yend = self.sourceend(source)
+ ystart = self.sourcestart(source)
+ if (ypos >= ystart and ypos <= yend):
+ return source
+ return None
+
+ def display_yview(self, *args):
+ self.names.yview(*args)
+ self.display.yview(*args)
+
+ def resize(self, *args):
+ width = int(root.geometry().split('x')[0])
+ scalemax = ktrfile.timespan() / (width - self.constwidth)
+ self.scale.setmax(scalemax)
+
+ def updatescroll(self):
+ self.names.updatescroll()
+ self.display.updatescroll()
+
+ def setcolor(self, tag, color):
+ self.display.setcolor(tag, color)
+
+ def hide(self, tag):
+ self.display.hide(tag)
+
+ def getcolor(self, tag):
+ return self.display.getcolor(tag)
+
+ def getstate(self, tag):
+ return self.display.getstate(tag)
+
+if (len(sys.argv) != 2 and len(sys.argv) != 3):
+ print "usage:", sys.argv[0], "<ktr file> [clock freq in ghz]"
+ sys.exit(1)
+
+if (len(sys.argv) > 2):
+ clockfreq = float(sys.argv[2])
+
+root = Tk()
+root.title("SchedGraph")
+colormap = Colormap(eventcolors)
+cpucolormap = Colormap(cpucolors)
+graph = SchedGraph(root)
+ktrfile = KTRFile(sys.argv[1])
+graph.draw()
+root.mainloop()