svn commit: r187896 - stable/7/tools/sched
John Baldwin
jhb at FreeBSD.org
Thu Jan 29 10:33:47 PST 2009
Author: jhb
Date: Thu Jan 29 18:33:46 2009
New Revision: 187896
URL: http://svn.freebsd.org/changeset/base/187896
Log:
Merge all the changes in HEAD prior to the generic tracing changes.
Modified:
stable/7/tools/sched/ (props changed)
stable/7/tools/sched/schedgraph.py
Modified: stable/7/tools/sched/schedgraph.py
==============================================================================
--- stable/7/tools/sched/schedgraph.py Thu Jan 29 16:51:09 2009 (r187895)
+++ stable/7/tools/sched/schedgraph.py Thu Jan 29 18:33:46 2009 (r187896)
@@ -31,10 +31,18 @@ import re
from Tkinter import *
# To use:
-# - Install the ports/x11-toolkits/py-tkinter package.
-# - Add KTR_SCHED to KTR_COMPILE and KTR_MASK in your KERNCONF
-# - It is encouraged to increase KTR_ENTRIES size to 32768 to gather
-# enough information for analysis.
+# - 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
@@ -52,6 +60,11 @@ from Tkinter import *
# 2) Add bounding box style zoom.
# 3) Click to center.
# 4) Implement some sorting mechanism.
+# 5) Widget to display variable-range data (e.g. q length)
+# 6) Reorder rows, hide rows, etc.
+# 7) "Vertical rule" to help relate data in different rows
+# 8) Mouse-over popup of full thread/event/row lable (currently truncated)
+# 9) More visible anchors for popup event windows
#
# BUGS: 1) Only 8 CPUs are supported, more CPUs require more choices of
# colours to represent them ;-)
@@ -62,6 +75,7 @@ from Tkinter import *
ticksps = None
status = None
configtypes = []
+lineno = -1
def ticks2sec(ticks):
us = ticksps / 1000000
@@ -334,6 +348,7 @@ class Event:
self.item = None
self.dispcnt = 0
self.linked = None
+ self.recno = lineno
if (last):
source.lastevent(self)
else:
@@ -355,9 +370,11 @@ class Event:
def labels(self):
return [("Source:", self.source.name, 0),
- ("Event:", self.name, 0),
- ("CPU:", self.cpu, 0),
- ("Timestamp:", self.timestamp, 0)] + self.entries
+ ("Event:", self.name, 0),
+ ("CPU:", self.cpu, 0),
+ ("Timestamp:", self.timestamp, 0),
+ ("Record: ", self.recno, 0)
+ ] + self.entries
def mouseenter(self, canvas, item):
self.displayref(canvas)
self.status()
@@ -452,11 +469,12 @@ class StateEvent(Event):
def labels(self):
return [("Source:", self.source.name, 0),
- ("Event:", self.name, 0),
- ("Timestamp:", self.timestamp, 0),
- ("CPU:", self.cpu, 0),
- ("Duration:", ticks2sec(self.duration), 0)] \
- + self.entries
+ ("Event:", self.name, 0),
+ ("Timestamp:", self.timestamp, 0),
+ ("CPU:", self.cpu, 0),
+ ("Record:", self.recno, 0),
+ ("Duration:", ticks2sec(self.duration), 0)
+ ] + self.entries
class Count(Event):
name = "Count"
@@ -515,7 +533,7 @@ class Yielding(StateEvent):
enabled = 1
def __init__(self, thread, cpu, timestamp, prio):
StateEvent.__init__(self, thread, cpu, timestamp)
- self.skipnext = 1
+ self.skipnext = 0
self.prio = prio
self.textadd(("prio:", self.prio, 0))
@@ -630,6 +648,18 @@ class Runq(StateEvent):
configtypes.append(Runq)
+class Sched_exit_thread(StateEvent):
+ name = "exit_thread"
+ color = "grey"
+ enabled = 0
+ def __init__(self, thread, cpu, timestamp, prio):
+ StateEvent.__init__(self, thread, cpu, timestamp)
+ self.name = "sched_exit_thread"
+ self.prio = prio
+ self.textadd(("prio:", self.prio, 0))
+
+configtypes.append(Sched_exit_thread)
+
class Sched_exit(StateEvent):
name = "exit"
color = "grey"
@@ -642,6 +672,86 @@ class Sched_exit(StateEvent):
configtypes.append(Sched_exit)
+# Events for running callout routines
+
+class CalloutIdle(StateEvent):
+ name = "callwheel idle"
+ color = "grey"
+ enabled = 0
+ def __init__(self, wheel, cpu, timestamp):
+ StateEvent.__init__(self, wheel, cpu, timestamp)
+
+configtypes.append(CalloutIdle)
+
+class CalloutRunning(StateEvent):
+ name = "callout running"
+ color = "green"
+ enabled = 1
+ def __init__(self, wheel, cpu, timestamp, func, arg):
+ StateEvent.__init__(self, wheel, cpu, timestamp)
+ self.textadd(("function:", func, 0))
+ self.textadd(("argument:", arg, 0))
+ self.arg = arg
+ self.func = func
+
+ def stattxt(self):
+ statstr = StateEvent.stattxt(self)
+ statstr += " executing %s(%s)" % (self.func, self.arg)
+ return (statstr)
+
+configtypes.append(CalloutRunning)
+
+# Events on locks
+#
+# XXX: No support for upgrade/downgrade currently or differentiating
+# between read/write in general.
+#
+# XXX: Point events for recursion perhaps?
+
+class LockAcquire(StateEvent):
+ name = "lock acquire"
+ color = "blue"
+ enabled = 1
+ def __init__(self, lock, cpu, timestamp, file, line):
+ StateEvent.__init__(self, lock, cpu, timestamp)
+ self.textadd(("file:", file, 0))
+ self.textadd(("line:", line, 0))
+
+configtypes.append(LockAcquire)
+
+class LockContest(StateEvent):
+ name = "lock contest"
+ color = "purple"
+ enabled = 1
+ def __init__(self, lock, cpu, timestamp, file, line):
+ StateEvent.__init__(self, lock, cpu, timestamp)
+ self.textadd(("file:", file, 0))
+ self.textadd(("line:", line, 0))
+
+configtypes.append(LockContest)
+
+class LockFailedTry(PointEvent):
+ name = "failed lock try"
+ color = "red"
+ enabled = 1
+ def __init__(self, lock, cpu, timestamp, file, line):
+ PointEvent.__init__(self, lock, cpu, timestamp)
+ self.textadd(("file:", file, 0))
+ self.textadd(("line:", line, 0))
+
+configtypes.append(LockFailedTry)
+
+class LockRelease(StateEvent):
+ name = "lock release"
+ color = "grey"
+ enabled = 0
+ def __init__(self, lock, cpu, timestamp, file, line):
+ StateEvent.__init__(self, lock, cpu, timestamp)
+ self.textadd(("file:", file, 0))
+ self.textadd(("line:", line, 0))
+
+configtypes.append(LockRelease)
+
class Padevent(StateEvent):
def __init__(self, thread, cpu, timestamp, last=0):
StateEvent.__init__(self, thread, cpu, timestamp, last)
@@ -709,24 +819,36 @@ class Wokeup(PointEvent):
configtypes.append(Wokeup)
+(DEFAULT, LOAD, COUNT, CALLWHEEL, LOCK, THREAD) = range(6)
+
class EventSource:
- def __init__(self, name):
+ def __init__(self, name, group=DEFAULT, order=0):
self.name = name
self.events = []
self.cpu = 0
self.cpux = 0
+ self.group = group
+ self.order = order
+ def __cmp__(self, other):
+ if (self.group == other.group):
+ return cmp(self.order, other.order)
+ 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):
- pass
+ self.events.reverse()
def event(self, event):
- self.events.insert(0, event)
+ self.events.append(event)
def remove(self, event):
self.events.remove(event)
def lastevent(self, event):
- self.events.append(event)
+ self.events.insert(0, event)
def draw(self, canvas, ypos):
xpos = 10
@@ -789,7 +911,7 @@ class EventSource:
class Thread(EventSource):
names = {}
def __init__(self, td, pcomm):
- EventSource.__init__(self, pcomm)
+ EventSource.__init__(self, pcomm, THREAD)
self.str = td
try:
cnt = Thread.names[pcomm]
@@ -799,6 +921,7 @@ class Thread(EventSource):
Thread.names[pcomm] = cnt + 1
def fixup(self):
+ EventSource.fixup(self)
cnt = Thread.names[self.name]
if (cnt == 0):
return
@@ -809,10 +932,33 @@ class Thread(EventSource):
def ysize(self):
return (10)
+class Callwheel(EventSource):
+ count = 0
+ def __init__(self, cpu):
+ EventSource.__init__(self, "Callwheel", CALLWHEEL, cpu)
+ self.wheel = cpu
+ Callwheel.count += 1
+
+ def fixup(self):
+ EventSource.fixup(self)
+ if (Callwheel.count == 1):
+ return
+ self.name += " (CPU %d)" % (self.wheel)
+
+ def ysize(self):
+ return (10)
+
+class Lock(EventSource):
+ def __init__(self, lock):
+ EventSource.__init__(self, lock, LOCK)
+
+ def ysize(self):
+ return (10)
+
class Counter(EventSource):
max = 0
def __init__(self, name):
- EventSource.__init__(self, name)
+ EventSource.__init__(self, name, COUNT)
def event(self, event):
EventSource.event(self, event)
@@ -824,23 +970,29 @@ class Counter(EventSource):
if (count > Counter.max):
Counter.max = count
+ def ymax(self):
+ return (Counter.max)
+
def ysize(self):
return (80)
def yscale(self):
return (self.ysize() / Counter.max)
+class CPULoad(Counter):
+ def __init__(self, cpu):
+ Counter.__init__(self, "cpu" + str(cpu) + " load")
+ self.group = LOAD
+ self.order = cpu
class KTRFile:
def __init__(self, file):
- self.timestamp_first = {}
- self.timestamp_last = {}
- self.timestamp_adjust = {}
self.timestamp_f = None
self.timestamp_l = None
- self.lineno = -1
self.threads = []
self.sources = []
+ self.locks = {}
+ self.callwheels = {}
self.ticks = {}
self.load = {}
self.crit = {}
@@ -862,10 +1014,12 @@ class KTRFile:
print "Can't open", file
sys.exit(1)
- ktrhdr = "\s+\d+\s+(\d+)\s+(\d+)\s+"
+ ktrhdr = "\s*\d+\s+(\d+)\s+(\d+)\s+"
tdname = "(\S+)\(([^)]*)\)"
crittdname = "(\S+)\s+\(\d+,\s+([^)]*)\)"
+# XXX doesn't handle:
+# 371 0 61628682318 mi_switch: 0xc075c070(swapper) prio 180 inhibit 2 wmesg ATA request done lock (null)
ktrstr = "mi_switch: " + tdname
ktrstr += " prio (\d+) inhibit (\d+) wmesg (\S+) lock (\S+)"
switchout_re = re.compile(ktrhdr + ktrstr)
@@ -890,6 +1044,9 @@ class KTRFile:
sched_rem_re = re.compile(ktrhdr + ktrstr)
ktrstr = "sched_exit_thread: " + tdname + " prio (\d+)"
+ sched_exit_thread_re = re.compile(ktrhdr + ktrstr)
+
+ ktrstr = "sched_exit: " + tdname + " prio (\d+)"
sched_exit_re = re.compile(ktrhdr + ktrstr)
ktrstr = "statclock: " + tdname + " prio (\d+)"
@@ -901,12 +1058,54 @@ class KTRFile:
sched_prio_re = re.compile(ktrhdr + ktrstr)
cpuload_re = re.compile(ktrhdr + "load: (\d+)")
+ cpuload2_re = re.compile(ktrhdr + "cpu (\d+) load: (\d+)")
loadglobal_re = re.compile(ktrhdr + "global load: (\d+)")
ktrstr = "critical_\S+ by thread " + crittdname + " to (\d+)"
critsec_re = re.compile(ktrhdr + ktrstr)
+ ktrstr = "callout 0x[a-f\d]+ "
+ ktrstr += "func (0x[a-f\d]+) arg (0x[a-f\d]+)"
+ callout_start_re = re.compile(ktrhdr + ktrstr)
+
+ ktrstr = "callout mpsafe 0x[a-f\d]+ "
+ ktrstr += "func (0x[a-f\d]+) arg (0x[a-f\d]+)"
+ callout_mpsafe_re = re.compile(ktrhdr + ktrstr)
+
+ ktrstr = "callout mtx 0x[a-f\d]+ "
+ ktrstr += "func (0x[a-f\d]+) arg (0x[a-f\d]+)"
+ callout_mtx_re = re.compile(ktrhdr + ktrstr)
+
+ ktrstr = "callout 0x[a-f\d]+ finished"
+ callout_stop_re = re.compile(ktrhdr + ktrstr)
+
+ ktrstr = "TRY_([RSWX]?LOCK) \(.*\) (.*) r = ([0-9]+)"
+ ktrstr += " at (?:\.\./)*(.*):([0-9]+)"
+ lock_try_re = re.compile(ktrhdr + ktrstr)
+
+ ktrstr = "([RSWX]?UNLOCK) \(.*\) (.*) r = ([0-9]+)"
+ ktrstr += " at (?:\.\./)*(.*):([0-9]+)"
+ lock_release_re = re.compile(ktrhdr + ktrstr)
+
+ ktrstr = "([RSWX]?LOCK) \(.*\) (.*) r = ([0-9]+)"
+ ktrstr += " at (?:\.\./)*(.*):([0-9]+)"
+ lock_acquire_re = re.compile(ktrhdr + ktrstr)
+
+ ktrstr = "_mtx_lock_sleep: (.*) contested \(lock=0x?[0-9a-f]*\)"
+ ktrstr += " at (?:\.\./)*(.*):([0-9]+)"
+ mtx_contested_re = re.compile(ktrhdr + ktrstr)
+
+ # XXX: Spin lock traces don't have lock name or file/line
+
+ ktrstr = "_rw_wlock_hard: (.*) contested \(lock=0x?[0-9a-f]*\)"
+ ktrstr += " at (?:\.\./)*(.*):([0-9]+)"
+ rw_contested_re = re.compile(ktrhdr + ktrstr)
+
+ # XXX: Read lock traces for rwlocks contesting don't have
+ # lock name or file/line
+
parsers = [[cpuload_re, self.cpuload],
+ [cpuload2_re, self.cpuload2],
[loadglobal_re, self.loadglobal],
[switchin_re, self.switchin],
[switchout_re, self.switchout],
@@ -915,93 +1114,42 @@ class KTRFile:
[sched_prio_re, self.sched_prio],
[preempted_re, self.preempted],
[sched_rem_re, self.sched_rem],
+ [sched_exit_thread_re, self.sched_exit_thread],
[sched_exit_re, self.sched_exit],
[sched_clock_re, self.sched_clock],
[critsec_re, self.critsec],
+ [callout_start_re, self.callout_start],
+ [callout_mpsafe_re, self.callout_start],
+ [callout_mtx_re, self.callout_start],
+ [callout_stop_re, self.callout_stop],
+ [lock_try_re, self.lock_try],
+ [lock_release_re, self.lock_release],
+ [lock_acquire_re, self.lock_acquire],
+ [mtx_contested_re, self.lock_contest],
+ [rw_contested_re, self.lock_contest],
[idled_re, self.idled]]
- lines = ifp.readlines()
- self.synchstamp(lines)
- for line in lines:
- self.lineno += 1
- if ((self.lineno % 1024) == 0):
- status.startup("Parsing line " +
- str(self.lineno))
+ global lineno
+ lineno = 0
+ for line in ifp.readlines():
+ lineno += 1
+ if ((lineno % 1024) == 0):
+ status.startup("Parsing line " + str(lineno))
for p in parsers:
m = p[0].match(line)
if (m != None):
p[1](*m.groups())
break
- # if (m == None):
- # print line,
-
- def synchstamp(self, lines):
- status.startup("Rationalizing Timestamps")
- tstamp_re = re.compile("\s+\d+\s+(\d+)\s+(\d+)\s+.*")
- for line in lines:
- m = tstamp_re.match(line)
- if (m != None):
- self.addstamp(*m.groups())
- self.pickstamp()
- self.monostamp(lines)
-
-
- def monostamp(self, lines):
- laststamp = None
- tstamp_re = re.compile("\s+\d+\s+(\d+)\s+(\d+)\s+.*")
- for line in lines:
- m = tstamp_re.match(line)
if (m == None):
- continue
- (cpu, timestamp) = m.groups()
- timestamp = int(timestamp)
- cpu = int(cpu)
- timestamp -= self.timestamp_adjust[cpu]
- if (laststamp != None and timestamp > laststamp):
- self.timestamp_adjust[cpu] += timestamp - laststamp
- laststamp = timestamp
-
- def addstamp(self, cpu, timestamp):
- timestamp = int(timestamp)
- cpu = int(cpu)
- try:
- if (timestamp > self.timestamp_first[cpu]):
- return
- except:
- self.timestamp_first[cpu] = timestamp
- self.timestamp_last[cpu] = timestamp
-
- def pickstamp(self):
- base = self.timestamp_last[0]
- for i in range(0, len(self.timestamp_last)):
- if (self.timestamp_last[i] < base):
- base = self.timestamp_last[i]
-
- print "Adjusting to base stamp", base
- for i in range(0, len(self.timestamp_last)):
- self.timestamp_adjust[i] = self.timestamp_last[i] - base;
- print "CPU ", i, "adjust by ", self.timestamp_adjust[i]
-
- self.timestamp_f = 0
- for i in range(0, len(self.timestamp_first)):
- first = self.timestamp_first[i] - self.timestamp_adjust[i]
- if (first > self.timestamp_f):
- self.timestamp_f = first
-
- self.timestamp_l = 0
- for i in range(0, len(self.timestamp_last)):
- last = self.timestamp_last[i] - self.timestamp_adjust[i]
- if (last > self.timestamp_l):
- self.timestamp_l = last
-
+ print line,
def checkstamp(self, cpu, timestamp):
- cpu = int(cpu)
timestamp = int(timestamp)
- if (timestamp > self.timestamp_first[cpu]):
- print "Bad timestamp on line ", self.lineno
+ if (self.timestamp_f == None):
+ self.timestamp_f = timestamp;
+ if (self.timestamp_l != None and timestamp > self.timestamp_l):
return (0)
- timestamp -= self.timestamp_adjust[cpu]
+ self.timestamp_l = timestamp;
return (timestamp)
def timespan(self):
@@ -1077,6 +1225,13 @@ class KTRFile:
KsegrpRunq(thread, cpu, timestamp, prio,
self.findtd(bytd, bypcomm))
+ def sched_exit_thread(self, cpu, timestamp, td, pcomm, prio):
+ timestamp = self.checkstamp(cpu, timestamp)
+ if (timestamp == 0):
+ return
+ thread = self.findtd(td, pcomm)
+ Sched_exit_thread(thread, cpu, timestamp, prio)
+
def sched_exit(self, cpu, timestamp, td, pcomm, prio):
timestamp = self.checkstamp(cpu, timestamp)
if (timestamp == 0):
@@ -1117,9 +1272,22 @@ class KTRFile:
try:
load = self.load[cpu]
except:
- load = Counter("cpu" + str(cpu) + " load")
+ load = CPULoad(cpu)
+ self.load[cpu] = load
+ self.sources.append(load)
+ Count(load, cpu, timestamp, count)
+
+ def cpuload2(self, cpu, timestamp, ncpu, count):
+ timestamp = self.checkstamp(cpu, timestamp)
+ if (timestamp == 0):
+ return
+ cpu = int(ncpu)
+ try:
+ load = self.load[cpu]
+ except:
+ load = CPULoad(cpu)
self.load[cpu] = load
- self.sources.insert(0, load)
+ self.sources.append(load)
Count(load, cpu, timestamp, count)
def loadglobal(self, cpu, timestamp, count):
@@ -1132,7 +1300,7 @@ class KTRFile:
except:
load = Counter("CPU load")
self.load[cpu] = load
- self.sources.insert(0, load)
+ self.sources.append(load)
Count(load, cpu, timestamp, count)
def critsec(self, cpu, timestamp, td, pcomm, to):
@@ -1145,9 +1313,77 @@ class KTRFile:
except:
crit = Counter("Critical Section")
self.crit[cpu] = crit
- self.sources.insert(0, crit)
+ self.sources.append(crit)
Count(crit, cpu, timestamp, to)
+ def callout_start(self, cpu, timestamp, func, arg):
+ timestamp = self.checkstamp(cpu, timestamp)
+ if (timestamp == 0):
+ return
+ wheel = self.findwheel(cpu)
+ CalloutRunning(wheel, cpu, timestamp, func, arg)
+
+ def callout_stop(self, cpu, timestamp):
+ timestamp = self.checkstamp(cpu, timestamp)
+ if (timestamp == 0):
+ return
+ wheel = self.findwheel(cpu)
+ CalloutIdle(wheel, cpu, timestamp)
+
+ def lock_try(self, cpu, timestamp, op, name, result, file, line):
+ timestamp = self.checkstamp(cpu, timestamp)
+ if (timestamp == 0):
+ return
+ lock = self.findlock(name)
+ if (int(result) == 0):
+ LockFailedTry(lock, cpu, timestamp, file, line)
+ else:
+ LockAcquire(lock, cpu, timestamp, file, line)
+
+ def lock_acquire(self, cpu, timestamp, op, name, recurse, file, line):
+ if (int(recurse) != 0):
+ return
+ timestamp = self.checkstamp(cpu, timestamp)
+ if (timestamp == 0):
+ return
+ lock = self.findlock(name)
+ LockAcquire(lock, cpu, timestamp, file, line)
+
+ def lock_release(self, cpu, timestamp, op, name, recurse, file, line):
+ if (int(recurse) != 0):
+ return
+ timestamp = self.checkstamp(cpu, timestamp)
+ if (timestamp == 0):
+ return
+ lock = self.findlock(name)
+ LockRelease(lock, cpu, timestamp, file, line)
+
+ def lock_contest(self, cpu, timestamp, name, file, line):
+ timestamp = self.checkstamp(cpu, timestamp)
+ if (timestamp == 0):
+ return
+ lock = self.findlock(name)
+ LockContest(lock, cpu, timestamp, file, line)
+
+ def findlock(self, name):
+ try:
+ lock = self.locks[name]
+ except:
+ lock = Lock(name)
+ self.locks[name] = lock
+ self.sources.append(lock)
+ return (lock)
+
+ def findwheel(self, cpu):
+ cpu = int(cpu)
+ try:
+ wheel = self.callwheels[cpu]
+ except:
+ wheel = Callwheel(cpu)
+ self.callwheels[cpu] = wheel
+ self.sources.append(wheel)
+ return (wheel)
+
def findtd(self, td, pcomm):
for thread in self.threads:
if (thread.str == td and thread.name == pcomm):
@@ -1162,12 +1398,14 @@ class KTRFile:
Padevent(source, -1, self.timestamp_l)
Padevent(source, -1, self.timestamp_f, last=1)
source.fixup()
+ self.sources.sort()
class SchedDisplay(Canvas):
def __init__(self, master):
self.ratio = 1
self.ktrfile = None
self.sources = None
+ self.parent = master
self.bdheight = 10
self.events = {}
@@ -1178,6 +1416,11 @@ class SchedDisplay(Canvas):
self.ktrfile = ktrfile
self.sources = ktrfile.sources
+ # 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()
@@ -1199,6 +1442,8 @@ class SchedDisplay(Canvas):
self.tag_bind("event", "<Enter>", self.mouseenter)
self.tag_bind("event", "<Leave>", self.mouseexit)
self.tag_bind("event", "<Button-1>", self.mousepress)
+ self.bind("<Button-4>", self.wheelup)
+ self.bind("<Button-5>", self.wheeldown)
def mouseenter(self, event):
item, = self.find_withtag(CURRENT)
@@ -1215,6 +1460,12 @@ class SchedDisplay(Canvas):
event = self.events[item]
event.mousepress(self, item)
+ def wheeldown(self, event):
+ self.parent.display_yview("scroll", 1, "units")
+
+ def wheelup(self, event):
+ self.parent.display_yview("scroll", -1, "units")
+
def drawnames(self, canvas):
status.startup("Drawing names")
ypos = 0
@@ -1296,7 +1547,7 @@ class SchedGraph(Frame):
self.menu = GraphMenu(self)
self.display = SchedDisplay(self)
self.names = Canvas(self,
- width=100, height=self.display["height"],
+ width=120, height=self.display["height"],
bg='grey', scrollregion=(0, 0, 50, 100))
self.scale = Scaler(self, self.display)
status = self.status = Status(self)
More information about the svn-src-stable-7
mailing list