1#!/usr/local/bin/python 2 3# Copyright (c) 2002-2003, Jeffrey Roberson <jeff@freebsd.org> 4# All rights reserved. 5# 6# Redistribution and use in source and binary forms, with or without 7# modification, are permitted provided that the following conditions 8# are met: 9# 1. Redistributions of source code must retain the above copyright 10# notice unmodified, this list of conditions, and the following 11# disclaimer. 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. 15# 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. 26# 27# $FreeBSD$ 28 29import sys 30import re 31from Tkinter import * 32 33# To use: 34# - Install the ports/x11-toolkits/py-tkinter package; e.g. 35# portinstall x11-toolkits/py-tkinter package 36# - Add KTR_SCHED to KTR_COMPILE and KTR_MASK in your KERNCONF; e.g. 37# options KTR 38# options KTR_ENTRIES=32768 39# options KTR_COMPILE=(KTR_SCHED) 40# options KTR_MASK=(KTR_SCHED) 41# - It is encouraged to increase KTR_ENTRIES size to gather enough 42# information for analysis; e.g. 43# options KTR_ENTRIES=262144 44# as 32768 entries may only correspond to a second or two of profiling 45# data depending on your workload. 46# - Rebuild kernel with proper changes to KERNCONF and boot new kernel. 47# - Run your workload to be profiled. 48# - While the workload is continuing (i.e. before it finishes), disable 49# KTR tracing by setting 'sysctl debug.ktr.mask=0'. This is necessary 50# to avoid a race condition while running ktrdump, i.e. the KTR ring buffer 51# will cycle a bit while ktrdump runs, and this confuses schedgraph because 52# the timestamps appear to go backwards at some point. Stopping KTR logging 53# while the workload is still running is to avoid wasting log entries on 54# "idle" time at the end. 55# - Dump the trace to a file: 'ktrdump -ct > ktr.out' 56# - Run the python script: 'python schedgraph.py ktr.out' 57# 58# To do: 59# 1) Add a per-thread summary display 60# 2) Add bounding box style zoom. 61# 3) Click to center. 62# 4) Implement some sorting mechanism. 63# 5) Widget to display variable-range data (e.g. q length) 64# 6) Reorder rows, hide rows, etc. 65# 7) "Vertical rule" to help relate data in different rows 66# 8) Mouse-over popup of full thread/event/row lable (currently truncated) 67# 9) More visible anchors for popup event windows 68# 69# BUGS: 1) Only 8 CPUs are supported, more CPUs require more choices of 70# colours to represent them ;-) 71# 2) Extremely short traces may cause a crash because the code 72# assumes there is always at least one stathz entry logged, and 73# the number of such events is used as a denominator 74 75ticksps = None 76status = None 77configtypes = [] 78lineno = -1 79 80def ticks2sec(ticks): 81 us = ticksps / 1000000 82 ticks /= us 83 if (ticks < 1000): 84 return (str(ticks) + "us") 85 ticks /= 1000 86 if (ticks < 1000): 87 return (str(ticks) + "ms") 88 ticks /= 1000 89 return (str(ticks) + "s") 90 91class Scaler(Frame): 92 def __init__(self, master, target): 93 Frame.__init__(self, master) 94 self.scale = Scale(self, command=self.scaleset, 95 from_=1000, to_=10000000, orient=HORIZONTAL, 96 resolution=1000) 97 self.label = Label(self, text="Ticks per pixel") 98 self.label.pack(side=LEFT) 99 self.scale.pack(fill="both", expand=1) 100 self.target = target 101 self.scale.set(target.scaleget()) 102 self.initialized = 1 103 104 def scaleset(self, value): 105 self.target.scaleset(int(value)) 106 107 def set(self, value): 108 self.scale.set(value) 109 110class Status(Frame): 111 def __init__(self, master): 112 Frame.__init__(self, master) 113 self.label = Label(self, bd=1, relief=SUNKEN, anchor=W) 114 self.label.pack(fill="both", expand=1) 115 self.clear() 116 117 def set(self, str): 118 self.label.config(text=str) 119 120 def clear(self): 121 self.label.config(text="") 122 123 def startup(self, str): 124 self.set(str) 125 root.update() 126 127class EventConf(Frame): 128 def __init__(self, master, name, color, enabled): 129 Frame.__init__(self, master) 130 self.name = name 131 self.color = StringVar() 132 self.color_default = color 133 self.color_current = color 134 self.color.set(color) 135 self.enabled = IntVar() 136 self.enabled_default = enabled 137 self.enabled_current = enabled 138 self.enabled.set(enabled) 139 self.draw() 140 141 def draw(self): 142 self.label = Label(self, text=self.name, anchor=W) 143 self.sample = Canvas(self, width=24, height=24, 144 bg='grey') 145 self.rect = self.sample.create_rectangle(0, 0, 24, 24, 146 fill=self.color.get()) 147 self.list = OptionMenu(self, self.color, 148 "dark red", "red", "pink", 149 "dark orange", "orange", 150 "yellow", "light yellow", 151 "dark green", "green", "light green", 152 "dark blue", "blue", "light blue", 153 "dark violet", "violet", "purple", 154 "dark grey", "light grey", 155 "white", "black", 156 command=self.setcolor) 157 self.checkbox = Checkbutton(self, text="enabled", 158 variable=self.enabled) 159 self.label.grid(row=0, column=0, sticky=E+W) 160 self.sample.grid(row=0, column=1) 161 self.list.grid(row=0, column=2, sticky=E+W) 162 self.checkbox.grid(row=0, column=3) 163 self.columnconfigure(0, weight=1) 164 self.columnconfigure(2, minsize=110) 165 166 def setcolor(self, color): 167 self.color.set(color) 168 self.sample.itemconfigure(self.rect, fill=color) 169 170 def apply(self): 171 cchange = 0 172 echange = 0 173 if (self.color_current != self.color.get()): 174 cchange = 1 175 if (self.enabled_current != self.enabled.get()): 176 echange = 1 177 self.color_current = self.color.get() 178 self.enabled_current = self.enabled.get() 179 if (echange != 0): 180 if (self.enabled_current): 181 graph.setcolor(self.name, self.color_current) 182 else: 183 graph.hide(self.name) 184 return 185 if (cchange != 0): 186 graph.setcolor(self.name, self.color_current) 187 188 def revert(self): 189 self.setcolor(self.color_current) 190 self.enabled.set(self.enabled_current) 191 192 def default(self): 193 self.setcolor(self.color_default) 194 self.enabled.set(self.enabled_default) 195 196class EventConfigure(Toplevel): 197 def __init__(self): 198 Toplevel.__init__(self) 199 self.resizable(0, 0) 200 self.title("Event Configuration") 201 self.items = LabelFrame(self, text="Event Type") 202 self.buttons = Frame(self) 203 self.drawbuttons() 204 self.items.grid(row=0, column=0, sticky=E+W) 205 self.columnconfigure(0, weight=1) 206 self.buttons.grid(row=1, column=0, sticky=E+W) 207 self.types = [] 208 self.irow = 0 209 for type in configtypes: 210 self.additem(type.name, type.color, type.enabled) 211 212 def additem(self, name, color, enabled=1): 213 item = EventConf(self.items, name, color, enabled) 214 self.types.append(item) 215 item.grid(row=self.irow, column=0, sticky=E+W) 216 self.irow += 1 217 218 def drawbuttons(self): 219 self.apply = Button(self.buttons, text="Apply", 220 command=self.apress) 221 self.revert = Button(self.buttons, text="Revert", 222 command=self.rpress) 223 self.default = Button(self.buttons, text="Default", 224 command=self.dpress) 225 self.apply.grid(row=0, column=0, sticky=E+W) 226 self.revert.grid(row=0, column=1, sticky=E+W) 227 self.default.grid(row=0, column=2, sticky=E+W) 228 self.buttons.columnconfigure(0, weight=1) 229 self.buttons.columnconfigure(1, weight=1) 230 self.buttons.columnconfigure(2, weight=1) 231 232 def apress(self): 233 for item in self.types: 234 item.apply() 235 236 def rpress(self): 237 for item in self.types: 238 item.revert() 239 240 def dpress(self): 241 for item in self.types: 242 item.default() 243 244class EventView(Toplevel): 245 def __init__(self, event, canvas): 246 Toplevel.__init__(self) 247 self.resizable(0, 0) 248 self.title("Event") 249 self.event = event 250 self.frame = Frame(self) 251 self.frame.grid(row=0, column=0, sticky=N+S+E+W) 252 self.buttons = Frame(self) 253 self.buttons.grid(row=1, column=0, sticky=E+W) 254 self.canvas = canvas 255 self.drawlabels() 256 self.drawbuttons() 257 event.displayref(canvas) 258 self.bind("<Destroy>", self.destroycb) 259 260 def destroycb(self, event): 261 self.unbind("<Destroy>") 262 if (self.event != None): 263 self.event.displayunref(self.canvas) 264 self.event = None 265 self.destroy() 266 267 def clearlabels(self): 268 for label in self.frame.grid_slaves(): 269 label.grid_remove() 270 271 def drawlabels(self): 272 ypos = 0 273 labels = self.event.labels() 274 while (len(labels) < 7): 275 labels.append(("", "", 0)) 276 for label in labels: 277 name, value, linked = label 278 l = Label(self.frame, text=name, bd=1, width=15, 279 relief=SUNKEN, anchor=W) 280 if (linked): 281 fgcolor = "blue" 282 else: 283 fgcolor = "black" 284 r = Label(self.frame, text=value, bd=1, 285 relief=SUNKEN, anchor=W, fg=fgcolor) 286 l.grid(row=ypos, column=0, sticky=E+W) 287 r.grid(row=ypos, column=1, sticky=E+W) 288 if (linked): 289 r.bind("<Button-1>", self.linkpress) 290 ypos += 1 291 self.frame.columnconfigure(1, minsize=80) 292 293 def drawbuttons(self): 294 self.back = Button(self.buttons, text="<", command=self.bpress) 295 self.forw = Button(self.buttons, text=">", command=self.fpress) 296 self.new = Button(self.buttons, text="new", command=self.npress) 297 self.back.grid(row=0, column=0, sticky=E+W) 298 self.forw.grid(row=0, column=1, sticky=E+W) 299 self.new.grid(row=0, column=2, sticky=E+W) 300 self.buttons.columnconfigure(2, weight=1) 301 302 def newevent(self, event): 303 self.event.displayunref(self.canvas) 304 self.clearlabels() 305 self.event = event 306 self.event.displayref(self.canvas) 307 self.drawlabels() 308 309 def npress(self): 310 EventView(self.event, self.canvas) 311 312 def bpress(self): 313 prev = self.event.prev() 314 if (prev == None): 315 return 316 while (prev.real == 0): 317 prev = prev.prev() 318 if (prev == None): 319 return 320 self.newevent(prev) 321 322 def fpress(self): 323 next = self.event.next() 324 if (next == None): 325 return 326 while (next.real == 0): 327 next = next.next() 328 if (next == None): 329 return 330 self.newevent(next) 331 332 def linkpress(self, wevent): 333 event = self.event.getlinked() 334 if (event != None): 335 self.newevent(event) 336 337class Event: 338 name = "none" 339 color = "grey" 340 def __init__(self, source, cpu, timestamp, last=0): 341 self.source = source 342 self.cpu = cpu 343 self.timestamp = int(timestamp) 344 self.entries = [] 345 self.real = 1 346 self.idx = None 347 self.state = 0 348 self.item = None 349 self.dispcnt = 0 350 self.linked = None 351 self.recno = lineno 352 if (last): 353 source.lastevent(self) 354 else: 355 source.event(self) 356 357 def status(self): 358 statstr = self.name + " " + self.source.name 359 statstr += " on: cpu" + str(self.cpu) 360 statstr += " at: " + str(self.timestamp) 361 statstr += self.stattxt() 362 status.set(statstr) 363 364 def stattxt(self): 365 return "" 366 367 def textadd(self, tuple): 368 pass 369 self.entries.append(tuple) 370 371 def labels(self): 372 return [("Source:", self.source.name, 0), 373 ("Event:", self.name, 0), 374 ("CPU:", self.cpu, 0), 375 ("Timestamp:", self.timestamp, 0), 376 ("Record: ", self.recno, 0) 377 ] + self.entries 378 def mouseenter(self, canvas, item): 379 self.displayref(canvas) 380 self.status() 381 382 def mouseexit(self, canvas, item): 383 self.displayunref(canvas) 384 status.clear() 385 386 def mousepress(self, canvas, item): 387 EventView(self, canvas) 388 389 def next(self): 390 return self.source.eventat(self.idx + 1) 391 392 def prev(self): 393 return self.source.eventat(self.idx - 1) 394 395 def displayref(self, canvas): 396 if (self.dispcnt == 0): 397 canvas.itemconfigure(self.item, width=2) 398 self.dispcnt += 1 399 400 def displayunref(self, canvas): 401 self.dispcnt -= 1 402 if (self.dispcnt == 0): 403 canvas.itemconfigure(self.item, width=0) 404 canvas.tag_raise("point", "state") 405 406 def getlinked(self): 407 return self.linked.findevent(self.timestamp) 408 409class PointEvent(Event): 410 def __init__(self, thread, cpu, timestamp, last=0): 411 Event.__init__(self, thread, cpu, timestamp, last) 412 413 def draw(self, canvas, xpos, ypos): 414 l = canvas.create_oval(xpos - 6, ypos + 1, xpos + 6, ypos - 11, 415 fill=self.color, tags=("all", "point", "event") 416 + (self.name,), width=0) 417 canvas.events[l] = self 418 self.item = l 419 if (self.enabled == 0): 420 canvas.itemconfigure(l, state="hidden") 421 422 return (xpos) 423 424class StateEvent(Event): 425 def __init__(self, thread, cpu, timestamp, last=0): 426 Event.__init__(self, thread, cpu, timestamp, last) 427 self.duration = 0 428 self.skipnext = 0 429 self.skipself = 0 430 self.state = 1 431 432 def draw(self, canvas, xpos, ypos): 433 next = self.nextstate() 434 if (self.skipself == 1 or next == None): 435 return (xpos) 436 while (self.skipnext): 437 skipped = next 438 next.skipself = 1 439 next.real = 0 440 next = next.nextstate() 441 if (next == None): 442 next = skipped 443 self.skipnext -= 1 444 self.duration = next.timestamp - self.timestamp 445 if (self.duration < 0): 446 self.duration = 0 447 print "Unsynchronized timestamp" 448 print self.cpu, self.timestamp 449 print next.cpu, next.timestamp 450 delta = self.duration / canvas.ratio 451 l = canvas.create_rectangle(xpos, ypos, 452 xpos + delta, ypos - 10, fill=self.color, width=0, 453 tags=("all", "state", "event") + (self.name,)) 454 canvas.events[l] = self 455 self.item = l 456 if (self.enabled == 0): 457 canvas.itemconfigure(l, state="hidden") 458 459 return (xpos + delta) 460 461 def stattxt(self): 462 return " duration: " + ticks2sec(self.duration) 463 464 def nextstate(self): 465 next = self.next() 466 while (next != None and next.state == 0): 467 next = next.next() 468 return (next) 469 470 def labels(self): 471 return [("Source:", self.source.name, 0), 472 ("Event:", self.name, 0), 473 ("Timestamp:", self.timestamp, 0), 474 ("CPU:", self.cpu, 0), 475 ("Record:", self.recno, 0), 476 ("Duration:", ticks2sec(self.duration), 0) 477 ] + self.entries 478 479class Count(Event): 480 name = "Count" 481 color = "red" 482 enabled = 1 483 def __init__(self, source, cpu, timestamp, count): 484 self.count = int(count) 485 Event.__init__(self, source, cpu, timestamp) 486 self.duration = 0 487 self.textadd(("count:", self.count, 0)) 488 489 def draw(self, canvas, xpos, ypos): 490 next = self.next() 491 self.duration = next.timestamp - self.timestamp 492 delta = self.duration / canvas.ratio 493 yhight = self.source.yscale() * self.count 494 l = canvas.create_rectangle(xpos, ypos - yhight, 495 xpos + delta, ypos, fill=self.color, width=0, 496 tags=("all", "count", "event") + (self.name,)) 497 canvas.events[l] = self 498 self.item = l 499 if (self.enabled == 0): 500 canvas.itemconfigure(l, state="hidden") 501 return (xpos + delta) 502 503 def stattxt(self): 504 return " count: " + str(self.count) 505 506configtypes.append(Count) 507 508class Running(StateEvent): 509 name = "running" 510 color = "green" 511 enabled = 1 512 def __init__(self, thread, cpu, timestamp, prio): 513 StateEvent.__init__(self, thread, cpu, timestamp) 514 self.prio = prio 515 self.textadd(("prio:", self.prio, 0)) 516 517configtypes.append(Running) 518 519class Idle(StateEvent): 520 name = "idle" 521 color = "grey" 522 enabled = 0 523 def __init__(self, thread, cpu, timestamp, prio): 524 StateEvent.__init__(self, thread, cpu, timestamp) 525 self.prio = prio 526 self.textadd(("prio:", self.prio, 0)) 527 528configtypes.append(Idle) 529 530class Yielding(StateEvent): 531 name = "yielding" 532 color = "yellow" 533 enabled = 1 534 def __init__(self, thread, cpu, timestamp, prio): 535 StateEvent.__init__(self, thread, cpu, timestamp) 536 self.skipnext = 0 537 self.prio = prio 538 self.textadd(("prio:", self.prio, 0)) 539 540configtypes.append(Yielding) 541 542class Swapped(StateEvent): 543 name = "swapped" 544 color = "violet" 545 enabled = 1 546 def __init__(self, thread, cpu, timestamp, prio): 547 StateEvent.__init__(self, thread, cpu, timestamp) 548 self.prio = prio 549 self.textadd(("prio:", self.prio, 0)) 550 551configtypes.append(Swapped) 552 553class Suspended(StateEvent): 554 name = "suspended" 555 color = "purple" 556 enabled = 1 557 def __init__(self, thread, cpu, timestamp, prio): 558 StateEvent.__init__(self, thread, cpu, timestamp) 559 self.prio = prio 560 self.textadd(("prio:", self.prio, 0)) 561 562configtypes.append(Suspended) 563 564class Iwait(StateEvent): 565 name = "iwait" 566 color = "grey" 567 enabled = 0 568 def __init__(self, thread, cpu, timestamp, prio): 569 StateEvent.__init__(self, thread, cpu, timestamp) 570 self.prio = prio 571 self.textadd(("prio:", self.prio, 0)) 572 573configtypes.append(Iwait) 574 575class Preempted(StateEvent): 576 name = "preempted" 577 color = "red" 578 enabled = 1 579 def __init__(self, thread, cpu, timestamp, prio, bythread): 580 StateEvent.__init__(self, thread, cpu, timestamp) 581 self.skipnext = 1 582 self.prio = prio 583 self.linked = bythread 584 self.textadd(("prio:", self.prio, 0)) 585 self.textadd(("by thread:", self.linked.name, 1)) 586 587configtypes.append(Preempted) 588 589class Sleep(StateEvent): 590 name = "sleep" 591 color = "blue" 592 enabled = 1 593 def __init__(self, thread, cpu, timestamp, prio, wmesg): 594 StateEvent.__init__(self, thread, cpu, timestamp) 595 self.prio = prio 596 self.wmesg = wmesg 597 self.textadd(("prio:", self.prio, 0)) 598 self.textadd(("wmesg:", self.wmesg, 0)) 599 600 def stattxt(self): 601 statstr = StateEvent.stattxt(self) 602 statstr += " sleeping on: " + self.wmesg 603 return (statstr) 604 605configtypes.append(Sleep) 606 607class Blocked(StateEvent): 608 name = "blocked" 609 color = "dark red" 610 enabled = 1 611 def __init__(self, thread, cpu, timestamp, prio, lock): 612 StateEvent.__init__(self, thread, cpu, timestamp) 613 self.prio = prio 614 self.lock = lock 615 self.textadd(("prio:", self.prio, 0)) 616 self.textadd(("lock:", self.lock, 0)) 617 618 def stattxt(self): 619 statstr = StateEvent.stattxt(self) 620 statstr += " blocked on: " + self.lock 621 return (statstr) 622 623configtypes.append(Blocked) 624 625class KsegrpRunq(StateEvent): 626 name = "KsegrpRunq" 627 color = "orange" 628 enabled = 1 629 def __init__(self, thread, cpu, timestamp, prio, bythread): 630 StateEvent.__init__(self, thread, cpu, timestamp) 631 self.prio = prio 632 self.linked = bythread 633 self.textadd(("prio:", self.prio, 0)) 634 self.textadd(("by thread:", self.linked.name, 1)) 635 636configtypes.append(KsegrpRunq) 637 638class Runq(StateEvent): 639 name = "Runq" 640 color = "yellow" 641 enabled = 1 642 def __init__(self, thread, cpu, timestamp, prio, bythread): 643 StateEvent.__init__(self, thread, cpu, timestamp) 644 self.prio = prio 645 self.linked = bythread 646 self.textadd(("prio:", self.prio, 0)) 647 self.textadd(("by thread:", self.linked.name, 1)) 648 649configtypes.append(Runq) 650 651class Sched_exit_thread(StateEvent): 652 name = "exit_thread" 653 color = "grey" 654 enabled = 0 655 def __init__(self, thread, cpu, timestamp, prio): 656 StateEvent.__init__(self, thread, cpu, timestamp) 657 self.name = "sched_exit_thread" 658 self.prio = prio 659 self.textadd(("prio:", self.prio, 0)) 660 661configtypes.append(Sched_exit_thread) 662 663class Sched_exit(StateEvent): 664 name = "exit" 665 color = "grey" 666 enabled = 0 667 def __init__(self, thread, cpu, timestamp, prio): 668 StateEvent.__init__(self, thread, cpu, timestamp) 669 self.name = "sched_exit" 670 self.prio = prio 671 self.textadd(("prio:", self.prio, 0)) 672 673configtypes.append(Sched_exit) 674 675class Padevent(StateEvent): 676 def __init__(self, thread, cpu, timestamp, last=0): 677 StateEvent.__init__(self, thread, cpu, timestamp, last) 678 self.name = "pad" 679 self.real = 0 680 681 def draw(self, canvas, xpos, ypos): 682 next = self.next() 683 if (next == None): 684 return (xpos) 685 self.duration = next.timestamp - self.timestamp 686 delta = self.duration / canvas.ratio 687 return (xpos + delta) 688 689class Tick(PointEvent): 690 name = "tick" 691 color = "black" 692 enabled = 0 693 def __init__(self, thread, cpu, timestamp, prio, stathz): 694 PointEvent.__init__(self, thread, cpu, timestamp) 695 self.prio = prio 696 self.textadd(("prio:", self.prio, 0)) 697 698configtypes.append(Tick) 699 700class Prio(PointEvent): 701 name = "prio" 702 color = "black" 703 enabled = 0 704 def __init__(self, thread, cpu, timestamp, prio, newprio, bythread): 705 PointEvent.__init__(self, thread, cpu, timestamp) 706 self.prio = prio 707 self.newprio = newprio 708 self.linked = bythread 709 self.textadd(("new prio:", self.newprio, 0)) 710 self.textadd(("prio:", self.prio, 0)) 711 if (self.linked != self.source): 712 self.textadd(("by thread:", self.linked.name, 1)) 713 else: 714 self.textadd(("by thread:", self.linked.name, 0)) 715 716configtypes.append(Prio) 717 718class Lend(PointEvent): 719 name = "lend" 720 color = "black" 721 enabled = 0 722 def __init__(self, thread, cpu, timestamp, prio, tothread): 723 PointEvent.__init__(self, thread, cpu, timestamp) 724 self.prio = prio 725 self.linked = tothread 726 self.textadd(("prio:", self.prio, 0)) 727 self.textadd(("to thread:", self.linked.name, 1)) 728 729configtypes.append(Lend) 730 731class Wokeup(PointEvent): 732 name = "wokeup" 733 color = "black" 734 enabled = 0 735 def __init__(self, thread, cpu, timestamp, ranthread): 736 PointEvent.__init__(self, thread, cpu, timestamp) 737 self.linked = ranthread 738 self.textadd(("ran thread:", self.linked.name, 1)) 739 740configtypes.append(Wokeup) 741 742class EventSource: 743 def __init__(self, name): 744 self.name = name 745 self.events = [] 746 self.cpu = 0 747 self.cpux = 0 748 749 def fixup(self): 750 pass 751 752 def event(self, event): 753 self.events.insert(0, event) 754 755 def remove(self, event): 756 self.events.remove(event) 757 758 def lastevent(self, event): 759 self.events.append(event) 760 761 def draw(self, canvas, ypos): 762 xpos = 10 763 self.cpux = 10 764 self.cpu = self.events[1].cpu 765 for i in range(0, len(self.events)): 766 self.events[i].idx = i 767 for event in self.events: 768 if (event.cpu != self.cpu and event.cpu != -1): 769 self.drawcpu(canvas, xpos, ypos) 770 self.cpux = xpos 771 self.cpu = event.cpu 772 xpos = event.draw(canvas, xpos, ypos) 773 self.drawcpu(canvas, xpos, ypos) 774 775 def drawname(self, canvas, ypos): 776 ypos = ypos - (self.ysize() / 2) 777 canvas.create_text(10, ypos, anchor="w", text=self.name) 778 779 def drawcpu(self, canvas, xpos, ypos): 780 cpu = int(self.cpu) 781 if (cpu == 0): 782 color = 'light grey' 783 elif (cpu == 1): 784 color = 'dark grey' 785 elif (cpu == 2): 786 color = 'light blue' 787 elif (cpu == 3): 788 color = 'light green' 789 elif (cpu == 4): 790 color = 'blanched almond' 791 elif (cpu == 5): 792 color = 'slate grey' 793 elif (cpu == 6): 794 color = 'light slate blue' 795 elif (cpu == 7): 796 color = 'thistle' 797 else: 798 color = "white" 799 l = canvas.create_rectangle(self.cpux, 800 ypos - self.ysize() - canvas.bdheight, 801 xpos, ypos + canvas.bdheight, fill=color, width=0, 802 tags=("all", "cpuinfo")) 803 804 def ysize(self): 805 return (None) 806 807 def eventat(self, i): 808 if (i >= len(self.events)): 809 return (None) 810 event = self.events[i] 811 return (event) 812 813 def findevent(self, timestamp): 814 for event in self.events: 815 if (event.timestamp >= timestamp and event.real): 816 return (event) 817 return (None) 818 819class Thread(EventSource): 820 names = {} 821 def __init__(self, td, pcomm): 822 EventSource.__init__(self, pcomm) 823 self.str = td 824 try: 825 cnt = Thread.names[pcomm] 826 except: 827 Thread.names[pcomm] = 0 828 return 829 Thread.names[pcomm] = cnt + 1 830 831 def fixup(self): 832 cnt = Thread.names[self.name] 833 if (cnt == 0): 834 return 835 cnt -= 1 836 Thread.names[self.name] = cnt 837 self.name += " td" + str(cnt) 838 839 def ysize(self): 840 return (10) 841 842class Counter(EventSource): 843 max = 0 844 def __init__(self, name): 845 EventSource.__init__(self, name) 846 847 def event(self, event): 848 EventSource.event(self, event) 849 try: 850 count = event.count 851 except: 852 return 853 count = int(count) 854 if (count > Counter.max): 855 Counter.max = count 856 857 def ymax(self): 858 return (Counter.max) 859 860 def ysize(self): 861 return (80) 862 863 def yscale(self): 864 return (self.ysize() / Counter.max) 865 866 867class KTRFile: 868 def __init__(self, file): 869 self.timestamp_first = {} 870 self.timestamp_last = {} 871 self.timestamp_adjust = {} 872 self.timestamp_f = None 873 self.timestamp_l = None 874 self.threads = [] 875 self.sources = [] 876 self.ticks = {} 877 self.load = {} 878 self.crit = {} 879 self.stathz = 0 880 881 self.parse(file) 882 self.fixup() 883 global ticksps 884 print "first", self.timestamp_f, "last", self.timestamp_l 885 print "time span", self.timespan() 886 print "stathz", self.stathz 887 ticksps = self.ticksps() 888 print "Ticks per second", ticksps 889 890 def parse(self, file): 891 try: 892 ifp = open(file) 893 except: 894 print "Can't open", file 895 sys.exit(1) 896 897 ktrhdr = "\s*\d+\s+(\d+)\s+(\d+)\s+" 898 tdname = "(\S+)\(([^)]*)\)" 899 crittdname = "(\S+)\s+\(\d+,\s+([^)]*)\)" 900 901# XXX doesn't handle: 902# 371 0 61628682318 mi_switch: 0xc075c070(swapper) prio 180 inhibit 2 wmesg ATA request done lock (null) 903 ktrstr = "mi_switch: " + tdname 904 ktrstr += " prio (\d+) inhibit (\d+) wmesg (\S+) lock (\S+)" 905 switchout_re = re.compile(ktrhdr + ktrstr) 906 907 ktrstr = "mi_switch: " + tdname + " prio (\d+) idle" 908 idled_re = re.compile(ktrhdr + ktrstr) 909 910 ktrstr = "mi_switch: " + tdname + " prio (\d+) preempted by " 911 ktrstr += tdname 912 preempted_re = re.compile(ktrhdr + ktrstr) 913 914 ktrstr = "mi_switch: running " + tdname + " prio (\d+)" 915 switchin_re = re.compile(ktrhdr + ktrstr) 916 917 ktrstr = "sched_add: " + tdname + " prio (\d+) by " + tdname 918 sched_add_re = re.compile(ktrhdr + ktrstr) 919 920 ktrstr = "setrunqueue: " + tdname + " prio (\d+) by " + tdname 921 setrunqueue_re = re.compile(ktrhdr + ktrstr) 922 923 ktrstr = "sched_rem: " + tdname + " prio (\d+) by " + tdname 924 sched_rem_re = re.compile(ktrhdr + ktrstr) 925 926 ktrstr = "sched_exit_thread: " + tdname + " prio (\d+)" 927 sched_exit_thread_re = re.compile(ktrhdr + ktrstr) 928 929 ktrstr = "sched_exit: " + tdname + " prio (\d+)" 930 sched_exit_re = re.compile(ktrhdr + ktrstr) 931 932 ktrstr = "statclock: " + tdname + " prio (\d+)" 933 ktrstr += " stathz (\d+)" 934 sched_clock_re = re.compile(ktrhdr + ktrstr) 935 936 ktrstr = "sched_prio: " + tdname + " prio (\d+)" 937 ktrstr += " newprio (\d+) by " + tdname 938 sched_prio_re = re.compile(ktrhdr + ktrstr) 939 940 cpuload_re = re.compile(ktrhdr + "load: (\d+)") 941 cpuload2_re = re.compile(ktrhdr + "cpu (\d+) load: (\d+)") 942 loadglobal_re = re.compile(ktrhdr + "global load: (\d+)") 943 944 ktrstr = "critical_\S+ by thread " + crittdname + " to (\d+)" 945 critsec_re = re.compile(ktrhdr + ktrstr) 946 947 parsers = [[cpuload_re, self.cpuload], 948 [cpuload2_re, self.cpuload2], 949 [loadglobal_re, self.loadglobal], 950 [switchin_re, self.switchin], 951 [switchout_re, self.switchout], 952 [sched_add_re, self.sched_add], 953 [setrunqueue_re, self.sched_rem], 954 [sched_prio_re, self.sched_prio], 955 [preempted_re, self.preempted], 956 [sched_rem_re, self.sched_rem], 957 [sched_exit_thread_re, self.sched_exit_thread], 958 [sched_exit_re, self.sched_exit], 959 [sched_clock_re, self.sched_clock], 960 [critsec_re, self.critsec], 961 [idled_re, self.idled]] 962 963 global lineno 964 lineno = 0 965 lines = ifp.readlines() 966 self.synchstamp(lines) 967 for line in lines: 968 lineno += 1 969 if ((lineno % 1024) == 0): 970 status.startup("Parsing line " + str(lineno)) 971 for p in parsers: 972 m = p[0].match(line) 973 if (m != None): 974 p[1](*m.groups()) 975 break 976 if (m == None): 977 print line, 978 979 def synchstamp(self, lines): 980 status.startup("Rationalizing Timestamps") 981 tstamp_re = re.compile("\s*\d+\s+(\d+)\s+(\d+)\s+.*") 982 for line in lines: 983 m = tstamp_re.match(line) 984 if (m != None): 985 self.addstamp(*m.groups()) 986 self.pickstamp() 987 self.monostamp(lines) 988 989 990 def monostamp(self, lines): 991 laststamp = None 992 tstamp_re = re.compile("\s*\d+\s+(\d+)\s+(\d+)\s+.*") 993 for line in lines: 994 m = tstamp_re.match(line) 995 if (m == None): 996 continue 997 (cpu, timestamp) = m.groups() 998 timestamp = int(timestamp) 999 cpu = int(cpu) 1000 timestamp -= self.timestamp_adjust[cpu] 1001 if (laststamp != None and timestamp > laststamp): 1002 self.timestamp_adjust[cpu] += timestamp - laststamp 1003 laststamp = timestamp 1004 1005 def addstamp(self, cpu, timestamp): 1006 timestamp = int(timestamp) 1007 cpu = int(cpu) 1008 try: 1009 if (timestamp > self.timestamp_first[cpu]): 1010 return 1011 except: 1012 self.timestamp_first[cpu] = timestamp 1013 self.timestamp_last[cpu] = timestamp 1014 1015 def pickstamp(self): 1016 base = self.timestamp_last[0] 1017 for i in range(0, len(self.timestamp_last)): 1018 if (self.timestamp_last[i] < base): 1019 base = self.timestamp_last[i] 1020 1021 print "Adjusting to base stamp", base 1022 for i in range(0, len(self.timestamp_last)): 1023 self.timestamp_adjust[i] = self.timestamp_last[i] - base; 1024 print "CPU ", i, "adjust by ", self.timestamp_adjust[i] 1025 1026 self.timestamp_f = 0 1027 for i in range(0, len(self.timestamp_first)): 1028 first = self.timestamp_first[i] - self.timestamp_adjust[i] 1029 if (first > self.timestamp_f): 1030 self.timestamp_f = first 1031 1032 self.timestamp_l = 0 1033 for i in range(0, len(self.timestamp_last)): 1034 last = self.timestamp_last[i] - self.timestamp_adjust[i] 1035 if (last > self.timestamp_l): 1036 self.timestamp_l = last 1037 1038 1039 def checkstamp(self, cpu, timestamp): 1040 cpu = int(cpu) 1041 timestamp = int(timestamp) 1042 if (timestamp > self.timestamp_first[cpu]): 1043 print "Bad timestamp on line ", lineno, " (", timestamp, " > ", self.timestamp_first[cpu], ")" 1044 return (0) 1045 timestamp -= self.timestamp_adjust[cpu] 1046 return (timestamp) 1047 1048 def timespan(self): 1049 return (self.timestamp_f - self.timestamp_l); 1050 1051 def ticksps(self): 1052 return (self.timespan() / self.ticks[0]) * int(self.stathz) 1053 1054 def switchout(self, cpu, timestamp, td, pcomm, prio, inhibit, wmesg, lock): 1055 TDI_SUSPENDED = 0x0001 1056 TDI_SLEEPING = 0x0002 1057 TDI_SWAPPED = 0x0004 1058 TDI_LOCK = 0x0008 1059 TDI_IWAIT = 0x0010 1060 1061 timestamp = self.checkstamp(cpu, timestamp) 1062 if (timestamp == 0): 1063 return 1064 inhibit = int(inhibit) 1065 thread = self.findtd(td, pcomm) 1066 if (inhibit & TDI_SWAPPED): 1067 Swapped(thread, cpu, timestamp, prio) 1068 elif (inhibit & TDI_SLEEPING): 1069 Sleep(thread, cpu, timestamp, prio, wmesg) 1070 elif (inhibit & TDI_LOCK): 1071 Blocked(thread, cpu, timestamp, prio, lock) 1072 elif (inhibit & TDI_IWAIT): 1073 Iwait(thread, cpu, timestamp, prio) 1074 elif (inhibit & TDI_SUSPENDED): 1075 Suspended(thread, cpu, timestamp, prio) 1076 elif (inhibit == 0): 1077 Yielding(thread, cpu, timestamp, prio) 1078 else: 1079 print "Unknown event", inhibit 1080 sys.exit(1) 1081 1082 def idled(self, cpu, timestamp, td, pcomm, prio): 1083 timestamp = self.checkstamp(cpu, timestamp) 1084 if (timestamp == 0): 1085 return 1086 thread = self.findtd(td, pcomm) 1087 Idle(thread, cpu, timestamp, prio) 1088 1089 def preempted(self, cpu, timestamp, td, pcomm, prio, bytd, bypcomm): 1090 timestamp = self.checkstamp(cpu, timestamp) 1091 if (timestamp == 0): 1092 return 1093 thread = self.findtd(td, pcomm) 1094 Preempted(thread, cpu, timestamp, prio, 1095 self.findtd(bytd, bypcomm)) 1096 1097 def switchin(self, cpu, timestamp, td, pcomm, prio): 1098 timestamp = self.checkstamp(cpu, timestamp) 1099 if (timestamp == 0): 1100 return 1101 thread = self.findtd(td, pcomm) 1102 Running(thread, cpu, timestamp, prio) 1103 1104 def sched_add(self, cpu, timestamp, td, pcomm, prio, bytd, bypcomm): 1105 timestamp = self.checkstamp(cpu, timestamp) 1106 if (timestamp == 0): 1107 return 1108 thread = self.findtd(td, pcomm) 1109 bythread = self.findtd(bytd, bypcomm) 1110 Runq(thread, cpu, timestamp, prio, bythread) 1111 Wokeup(bythread, cpu, timestamp, thread) 1112 1113 def sched_rem(self, cpu, timestamp, td, pcomm, prio, bytd, bypcomm): 1114 timestamp = self.checkstamp(cpu, timestamp) 1115 if (timestamp == 0): 1116 return 1117 thread = self.findtd(td, pcomm) 1118 KsegrpRunq(thread, cpu, timestamp, prio, 1119 self.findtd(bytd, bypcomm)) 1120 1121 def sched_exit_thread(self, cpu, timestamp, td, pcomm, prio): 1122 timestamp = self.checkstamp(cpu, timestamp) 1123 if (timestamp == 0): 1124 return 1125 thread = self.findtd(td, pcomm) 1126 Sched_exit_thread(thread, cpu, timestamp, prio) 1127 1128 def sched_exit(self, cpu, timestamp, td, pcomm, prio): 1129 timestamp = self.checkstamp(cpu, timestamp) 1130 if (timestamp == 0): 1131 return 1132 thread = self.findtd(td, pcomm) 1133 Sched_exit(thread, cpu, timestamp, prio) 1134 1135 def sched_clock(self, cpu, timestamp, td, pcomm, prio, stathz): 1136 timestamp = self.checkstamp(cpu, timestamp) 1137 if (timestamp == 0): 1138 return 1139 self.stathz = stathz 1140 cpu = int(cpu) 1141 try: 1142 ticks = self.ticks[cpu] 1143 except: 1144 self.ticks[cpu] = 0 1145 self.ticks[cpu] += 1 1146 thread = self.findtd(td, pcomm) 1147 Tick(thread, cpu, timestamp, prio, stathz) 1148 1149 def sched_prio(self, cpu, timestamp, td, pcomm, prio, newprio, bytd, bypcomm): 1150 if (prio == newprio): 1151 return 1152 timestamp = self.checkstamp(cpu, timestamp) 1153 if (timestamp == 0): 1154 return 1155 thread = self.findtd(td, pcomm) 1156 bythread = self.findtd(bytd, bypcomm) 1157 Prio(thread, cpu, timestamp, prio, newprio, bythread) 1158 Lend(bythread, cpu, timestamp, newprio, thread) 1159 1160 def cpuload(self, cpu, timestamp, count): 1161 timestamp = self.checkstamp(cpu, timestamp) 1162 if (timestamp == 0): 1163 return 1164 cpu = int(cpu) 1165 try: 1166 load = self.load[cpu] 1167 except: 1168 load = Counter("cpu" + str(cpu) + " load") 1169 self.load[cpu] = load 1170 self.sources.insert(0, load) 1171 Count(load, cpu, timestamp, count) 1172 1173 def cpuload2(self, cpu, timestamp, ncpu, count): 1174 timestamp = self.checkstamp(cpu, timestamp) 1175 if (timestamp == 0): 1176 return 1177 cpu = int(ncpu) 1178 try: 1179 load = self.load[cpu] 1180 except: 1181 load = Counter("cpu" + str(cpu) + " load") 1182 self.load[cpu] = load 1183 self.sources.insert(0, load) 1184 Count(load, cpu, timestamp, count) 1185 1186 def loadglobal(self, cpu, timestamp, count): 1187 timestamp = self.checkstamp(cpu, timestamp) 1188 if (timestamp == 0): 1189 return 1190 cpu = 0 1191 try: 1192 load = self.load[cpu] 1193 except: 1194 load = Counter("CPU load") 1195 self.load[cpu] = load 1196 self.sources.insert(0, load) 1197 Count(load, cpu, timestamp, count) 1198 1199 def critsec(self, cpu, timestamp, td, pcomm, to): 1200 timestamp = self.checkstamp(cpu, timestamp) 1201 if (timestamp == 0): 1202 return 1203 cpu = int(cpu) 1204 try: 1205 crit = self.crit[cpu] 1206 except: 1207 crit = Counter("Critical Section") 1208 self.crit[cpu] = crit 1209 self.sources.insert(0, crit) 1210 Count(crit, cpu, timestamp, to) 1211 1212 def findtd(self, td, pcomm): 1213 for thread in self.threads: 1214 if (thread.str == td and thread.name == pcomm): 1215 return thread 1216 thread = Thread(td, pcomm) 1217 self.threads.append(thread) 1218 self.sources.append(thread) 1219 return (thread) 1220 1221 def fixup(self): 1222 for source in self.sources: 1223 Padevent(source, -1, self.timestamp_l) 1224 Padevent(source, -1, self.timestamp_f, last=1) 1225 source.fixup() 1226 1227class SchedDisplay(Canvas): 1228 def __init__(self, master): 1229 self.ratio = 10 1230 self.ktrfile = None 1231 self.sources = None 1232 self.bdheight = 10 1233 self.events = {} 1234 1235 Canvas.__init__(self, master, width=800, height=500, bg='grey', 1236 scrollregion=(0, 0, 800, 500)) 1237 1238 def setfile(self, ktrfile): 1239 self.ktrfile = ktrfile 1240 self.sources = ktrfile.sources 1241 1242 def draw(self): 1243 ypos = 0 1244 xsize = self.xsize() 1245 for source in self.sources: 1246 status.startup("Drawing " + source.name) 1247 self.create_line(0, ypos, xsize, ypos, 1248 width=1, fill="black", tags=("all",)) 1249 ypos += self.bdheight 1250 ypos += source.ysize() 1251 source.draw(self, ypos) 1252 ypos += self.bdheight 1253 try: 1254 self.tag_raise("point", "state") 1255 self.tag_lower("cpuinfo", "all") 1256 except: 1257 pass 1258 self.create_line(0, ypos, xsize, ypos, 1259 width=1, fill="black", tags=("all",)) 1260 self.tag_bind("event", "<Enter>", self.mouseenter) 1261 self.tag_bind("event", "<Leave>", self.mouseexit) 1262 self.tag_bind("event", "<Button-1>", self.mousepress) 1263 1264 def mouseenter(self, event): 1265 item, = self.find_withtag(CURRENT) 1266 event = self.events[item] 1267 event.mouseenter(self, item) 1268 1269 def mouseexit(self, event): 1270 item, = self.find_withtag(CURRENT) 1271 event = self.events[item] 1272 event.mouseexit(self, item) 1273 1274 def mousepress(self, event): 1275 item, = self.find_withtag(CURRENT) 1276 event = self.events[item] 1277 event.mousepress(self, item) 1278 1279 def drawnames(self, canvas): 1280 status.startup("Drawing names") 1281 ypos = 0 1282 canvas.configure(scrollregion=(0, 0, 1283 canvas["width"], self.ysize())) 1284 for source in self.sources: 1285 canvas.create_line(0, ypos, canvas["width"], ypos, 1286 width=1, fill="black", tags=("all",)) 1287 ypos += self.bdheight 1288 ypos += source.ysize() 1289 source.drawname(canvas, ypos) 1290 ypos += self.bdheight 1291 canvas.create_line(0, ypos, canvas["width"], ypos, 1292 width=1, fill="black", tags=("all",)) 1293 1294 def xsize(self): 1295 return ((self.ktrfile.timespan() / self.ratio) + 20) 1296 1297 def ysize(self): 1298 ysize = 0 1299 for source in self.sources: 1300 ysize += source.ysize() + (self.bdheight * 2) 1301 return (ysize) 1302 1303 def scaleset(self, ratio): 1304 if (self.ktrfile == None): 1305 return 1306 oldratio = self.ratio 1307 xstart, ystart = self.xview() 1308 length = (float(self["width"]) / self.xsize()) 1309 middle = xstart + (length / 2) 1310 1311 self.ratio = ratio 1312 self.configure(scrollregion=(0, 0, self.xsize(), self.ysize())) 1313 self.scale("all", 0, 0, float(oldratio) / ratio, 1) 1314 1315 length = (float(self["width"]) / self.xsize()) 1316 xstart = middle - (length / 2) 1317 self.xview_moveto(xstart) 1318 1319 def scaleget(self): 1320 return self.ratio 1321 1322 def setcolor(self, tag, color): 1323 self.itemconfigure(tag, state="normal", fill=color) 1324 1325 def hide(self, tag): 1326 self.itemconfigure(tag, state="hidden") 1327 1328class GraphMenu(Frame): 1329 def __init__(self, master): 1330 Frame.__init__(self, master, bd=2, relief=RAISED) 1331 self.view = Menubutton(self, text="Configure") 1332 self.viewmenu = Menu(self.view, tearoff=0) 1333 self.viewmenu.add_command(label="Events", 1334 command=self.econf) 1335 self.view["menu"] = self.viewmenu 1336 self.view.pack(side=LEFT) 1337 1338 def econf(self): 1339 EventConfigure() 1340 1341 1342class SchedGraph(Frame): 1343 def __init__(self, master): 1344 Frame.__init__(self, master) 1345 self.menu = None 1346 self.names = None 1347 self.display = None 1348 self.scale = None 1349 self.status = None 1350 self.pack(expand=1, fill="both") 1351 self.buildwidgets() 1352 self.layout() 1353 self.draw(sys.argv[1]) 1354 1355 def buildwidgets(self): 1356 global status 1357 self.menu = GraphMenu(self) 1358 self.display = SchedDisplay(self) 1359 self.names = Canvas(self, 1360 width=100, height=self.display["height"], 1361 bg='grey', scrollregion=(0, 0, 50, 100)) 1362 self.scale = Scaler(self, self.display) 1363 status = self.status = Status(self) 1364 self.scrollY = Scrollbar(self, orient="vertical", 1365 command=self.display_yview) 1366 self.display.scrollX = Scrollbar(self, orient="horizontal", 1367 command=self.display.xview) 1368 self.display["xscrollcommand"] = self.display.scrollX.set 1369 self.display["yscrollcommand"] = self.scrollY.set 1370 self.names["yscrollcommand"] = self.scrollY.set 1371 1372 def layout(self): 1373 self.columnconfigure(1, weight=1) 1374 self.rowconfigure(1, weight=1) 1375 self.menu.grid(row=0, column=0, columnspan=3, sticky=E+W) 1376 self.names.grid(row=1, column=0, sticky=N+S) 1377 self.display.grid(row=1, column=1, sticky=W+E+N+S) 1378 self.scrollY.grid(row=1, column=2, sticky=N+S) 1379 self.display.scrollX.grid(row=2, column=0, columnspan=2, 1380 sticky=E+W) 1381 self.scale.grid(row=3, column=0, columnspan=3, sticky=E+W) 1382 self.status.grid(row=4, column=0, columnspan=3, sticky=E+W) 1383 1384 def draw(self, file): 1385 self.master.update() 1386 ktrfile = KTRFile(file) 1387 self.display.setfile(ktrfile) 1388 self.display.drawnames(self.names) 1389 self.display.draw() 1390 self.scale.set(250000) 1391 self.display.xview_moveto(0) 1392 1393 def display_yview(self, *args): 1394 self.names.yview(*args) 1395 self.display.yview(*args) 1396 1397 def setcolor(self, tag, color): 1398 self.display.setcolor(tag, color) 1399 1400 def hide(self, tag): 1401 self.display.hide(tag) 1402 1403if (len(sys.argv) != 2): 1404 print "usage:", sys.argv[0], "<ktr file>" 1405 sys.exit(1) 1406 1407root = Tk() 1408root.title("Scheduler Graph") 1409graph = SchedGraph(root) 1410root.mainloop() 1411