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