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