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_f = None 870 self.timestamp_l = None 871 self.threads = [] 872 self.sources = [] 873 self.ticks = {} 874 self.load = {} 875 self.crit = {} 876 self.stathz = 0 877 878 self.parse(file) 879 self.fixup() 880 global ticksps 881 print "first", self.timestamp_f, "last", self.timestamp_l 882 print "time span", self.timespan() 883 print "stathz", self.stathz 884 ticksps = self.ticksps() 885 print "Ticks per second", ticksps 886 887 def parse(self, file): 888 try: 889 ifp = open(file) 890 except: 891 print "Can't open", file 892 sys.exit(1) 893 894 ktrhdr = "\s*\d+\s+(\d+)\s+(\d+)\s+" 895 tdname = "(\S+)\(([^)]*)\)" 896 crittdname = "(\S+)\s+\(\d+,\s+([^)]*)\)" 897 898# XXX doesn't handle: 899# 371 0 61628682318 mi_switch: 0xc075c070(swapper) prio 180 inhibit 2 wmesg ATA request done lock (null) 900 ktrstr = "mi_switch: " + tdname 901 ktrstr += " prio (\d+) inhibit (\d+) wmesg (\S+) lock (\S+)" 902 switchout_re = re.compile(ktrhdr + ktrstr) 903 904 ktrstr = "mi_switch: " + tdname + " prio (\d+) idle" 905 idled_re = re.compile(ktrhdr + ktrstr) 906 907 ktrstr = "mi_switch: " + tdname + " prio (\d+) preempted by " 908 ktrstr += tdname 909 preempted_re = re.compile(ktrhdr + ktrstr) 910 911 ktrstr = "mi_switch: running " + tdname + " prio (\d+)" 912 switchin_re = re.compile(ktrhdr + ktrstr) 913 914 ktrstr = "sched_add: " + tdname + " prio (\d+) by " + tdname 915 sched_add_re = re.compile(ktrhdr + ktrstr) 916 917 ktrstr = "setrunqueue: " + tdname + " prio (\d+) by " + tdname 918 setrunqueue_re = re.compile(ktrhdr + ktrstr) 919 920 ktrstr = "sched_rem: " + tdname + " prio (\d+) by " + tdname 921 sched_rem_re = re.compile(ktrhdr + ktrstr) 922 923 ktrstr = "sched_exit_thread: " + tdname + " prio (\d+)" 924 sched_exit_thread_re = re.compile(ktrhdr + ktrstr) 925 926 ktrstr = "sched_exit: " + tdname + " prio (\d+)" 927 sched_exit_re = re.compile(ktrhdr + ktrstr) 928 929 ktrstr = "statclock: " + tdname + " prio (\d+)" 930 ktrstr += " stathz (\d+)" 931 sched_clock_re = re.compile(ktrhdr + ktrstr) 932 933 ktrstr = "sched_prio: " + tdname + " prio (\d+)" 934 ktrstr += " newprio (\d+) by " + tdname 935 sched_prio_re = re.compile(ktrhdr + ktrstr) 936 937 cpuload_re = re.compile(ktrhdr + "load: (\d+)") 938 cpuload2_re = re.compile(ktrhdr + "cpu (\d+) load: (\d+)") 939 loadglobal_re = re.compile(ktrhdr + "global load: (\d+)") 940 941 ktrstr = "critical_\S+ by thread " + crittdname + " to (\d+)" 942 critsec_re = re.compile(ktrhdr + ktrstr) 943 944 parsers = [[cpuload_re, self.cpuload], 945 [cpuload2_re, self.cpuload2], 946 [loadglobal_re, self.loadglobal], 947 [switchin_re, self.switchin], 948 [switchout_re, self.switchout], 949 [sched_add_re, self.sched_add], 950 [setrunqueue_re, self.sched_rem], 951 [sched_prio_re, self.sched_prio], 952 [preempted_re, self.preempted], 953 [sched_rem_re, self.sched_rem], 954 [sched_exit_thread_re, self.sched_exit_thread], 955 [sched_exit_re, self.sched_exit], 956 [sched_clock_re, self.sched_clock], 957 [critsec_re, self.critsec], 958 [idled_re, self.idled]] 959 960 global lineno 961 lineno = 0 962 for line in ifp.readlines(): 963 lineno += 1 964 if ((lineno % 1024) == 0): 965 status.startup("Parsing line " + str(lineno)) 966 for p in parsers: 967 m = p[0].match(line) 968 if (m != None): 969 p[1](*m.groups()) 970 break 971 if (m == None): 972 print line, 973 974 def checkstamp(self, cpu, timestamp): 975 timestamp = int(timestamp) 976 if (self.timestamp_f == None): 977 self.timestamp_f = timestamp; 978 if (self.timestamp_l != None and timestamp > self.timestamp_l): 979 return (0) 980 self.timestamp_l = timestamp; 981 return (timestamp) 982 983 def timespan(self): 984 return (self.timestamp_f - self.timestamp_l); 985 986 def ticksps(self): 987 return (self.timespan() / self.ticks[0]) * int(self.stathz) 988 989 def switchout(self, cpu, timestamp, td, pcomm, prio, inhibit, wmesg, lock): 990 TDI_SUSPENDED = 0x0001 991 TDI_SLEEPING = 0x0002 992 TDI_SWAPPED = 0x0004 993 TDI_LOCK = 0x0008 994 TDI_IWAIT = 0x0010 995 996 timestamp = self.checkstamp(cpu, timestamp) 997 if (timestamp == 0): 998 return 999 inhibit = int(inhibit) 1000 thread = self.findtd(td, pcomm) 1001 if (inhibit & TDI_SWAPPED): 1002 Swapped(thread, cpu, timestamp, prio) 1003 elif (inhibit & TDI_SLEEPING): 1004 Sleep(thread, cpu, timestamp, prio, wmesg) 1005 elif (inhibit & TDI_LOCK): 1006 Blocked(thread, cpu, timestamp, prio, lock) 1007 elif (inhibit & TDI_IWAIT): 1008 Iwait(thread, cpu, timestamp, prio) 1009 elif (inhibit & TDI_SUSPENDED): 1010 Suspended(thread, cpu, timestamp, prio) 1011 elif (inhibit == 0): 1012 Yielding(thread, cpu, timestamp, prio) 1013 else: 1014 print "Unknown event", inhibit 1015 sys.exit(1) 1016 1017 def idled(self, cpu, timestamp, td, pcomm, prio): 1018 timestamp = self.checkstamp(cpu, timestamp) 1019 if (timestamp == 0): 1020 return 1021 thread = self.findtd(td, pcomm) 1022 Idle(thread, cpu, timestamp, prio) 1023 1024 def preempted(self, cpu, timestamp, td, pcomm, prio, bytd, bypcomm): 1025 timestamp = self.checkstamp(cpu, timestamp) 1026 if (timestamp == 0): 1027 return 1028 thread = self.findtd(td, pcomm) 1029 Preempted(thread, cpu, timestamp, prio, 1030 self.findtd(bytd, bypcomm)) 1031 1032 def switchin(self, cpu, timestamp, td, pcomm, prio): 1033 timestamp = self.checkstamp(cpu, timestamp) 1034 if (timestamp == 0): 1035 return 1036 thread = self.findtd(td, pcomm) 1037 Running(thread, cpu, timestamp, prio) 1038 1039 def sched_add(self, cpu, timestamp, td, pcomm, prio, bytd, bypcomm): 1040 timestamp = self.checkstamp(cpu, timestamp) 1041 if (timestamp == 0): 1042 return 1043 thread = self.findtd(td, pcomm) 1044 bythread = self.findtd(bytd, bypcomm) 1045 Runq(thread, cpu, timestamp, prio, bythread) 1046 Wokeup(bythread, cpu, timestamp, thread) 1047 1048 def sched_rem(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 KsegrpRunq(thread, cpu, timestamp, prio, 1054 self.findtd(bytd, bypcomm)) 1055 1056 def sched_exit_thread(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 Sched_exit_thread(thread, cpu, timestamp, prio) 1062 1063 def sched_exit(self, cpu, timestamp, td, pcomm, prio): 1064 timestamp = self.checkstamp(cpu, timestamp) 1065 if (timestamp == 0): 1066 return 1067 thread = self.findtd(td, pcomm) 1068 Sched_exit(thread, cpu, timestamp, prio) 1069 1070 def sched_clock(self, cpu, timestamp, td, pcomm, prio, stathz): 1071 timestamp = self.checkstamp(cpu, timestamp) 1072 if (timestamp == 0): 1073 return 1074 self.stathz = stathz 1075 cpu = int(cpu) 1076 try: 1077 ticks = self.ticks[cpu] 1078 except: 1079 self.ticks[cpu] = 0 1080 self.ticks[cpu] += 1 1081 thread = self.findtd(td, pcomm) 1082 Tick(thread, cpu, timestamp, prio, stathz) 1083 1084 def sched_prio(self, cpu, timestamp, td, pcomm, prio, newprio, bytd, bypcomm): 1085 if (prio == newprio): 1086 return 1087 timestamp = self.checkstamp(cpu, timestamp) 1088 if (timestamp == 0): 1089 return 1090 thread = self.findtd(td, pcomm) 1091 bythread = self.findtd(bytd, bypcomm) 1092 Prio(thread, cpu, timestamp, prio, newprio, bythread) 1093 Lend(bythread, cpu, timestamp, newprio, thread) 1094 1095 def cpuload(self, cpu, timestamp, count): 1096 timestamp = self.checkstamp(cpu, timestamp) 1097 if (timestamp == 0): 1098 return 1099 cpu = int(cpu) 1100 try: 1101 load = self.load[cpu] 1102 except: 1103 load = Counter("cpu" + str(cpu) + " load") 1104 self.load[cpu] = load 1105 self.sources.insert(0, load) 1106 Count(load, cpu, timestamp, count) 1107 1108 def cpuload2(self, cpu, timestamp, ncpu, count): 1109 timestamp = self.checkstamp(cpu, timestamp) 1110 if (timestamp == 0): 1111 return 1112 cpu = int(ncpu) 1113 try: 1114 load = self.load[cpu] 1115 except: 1116 load = Counter("cpu" + str(cpu) + " load") 1117 self.load[cpu] = load 1118 self.sources.insert(0, load) 1119 Count(load, cpu, timestamp, count) 1120 1121 def loadglobal(self, cpu, timestamp, count): 1122 timestamp = self.checkstamp(cpu, timestamp) 1123 if (timestamp == 0): 1124 return 1125 cpu = 0 1126 try: 1127 load = self.load[cpu] 1128 except: 1129 load = Counter("CPU load") 1130 self.load[cpu] = load 1131 self.sources.insert(0, load) 1132 Count(load, cpu, timestamp, count) 1133 1134 def critsec(self, cpu, timestamp, td, pcomm, to): 1135 timestamp = self.checkstamp(cpu, timestamp) 1136 if (timestamp == 0): 1137 return 1138 cpu = int(cpu) 1139 try: 1140 crit = self.crit[cpu] 1141 except: 1142 crit = Counter("Critical Section") 1143 self.crit[cpu] = crit 1144 self.sources.insert(0, crit) 1145 Count(crit, cpu, timestamp, to) 1146 1147 def findtd(self, td, pcomm): 1148 for thread in self.threads: 1149 if (thread.str == td and thread.name == pcomm): 1150 return thread 1151 thread = Thread(td, pcomm) 1152 self.threads.append(thread) 1153 self.sources.append(thread) 1154 return (thread) 1155 1156 def fixup(self): 1157 for source in self.sources: 1158 Padevent(source, -1, self.timestamp_l) 1159 Padevent(source, -1, self.timestamp_f, last=1) 1160 source.fixup() 1161 1162class SchedDisplay(Canvas): 1163 def __init__(self, master): 1164 self.ratio = 10 1165 self.ktrfile = None 1166 self.sources = None 1167 self.bdheight = 10 1168 self.events = {} 1169 1170 Canvas.__init__(self, master, width=800, height=500, bg='grey', 1171 scrollregion=(0, 0, 800, 500)) 1172 1173 def setfile(self, ktrfile): 1174 self.ktrfile = ktrfile 1175 self.sources = ktrfile.sources 1176 1177 def draw(self): 1178 ypos = 0 1179 xsize = self.xsize() 1180 for source in self.sources: 1181 status.startup("Drawing " + source.name) 1182 self.create_line(0, ypos, xsize, ypos, 1183 width=1, fill="black", tags=("all",)) 1184 ypos += self.bdheight 1185 ypos += source.ysize() 1186 source.draw(self, ypos) 1187 ypos += self.bdheight 1188 try: 1189 self.tag_raise("point", "state") 1190 self.tag_lower("cpuinfo", "all") 1191 except: 1192 pass 1193 self.create_line(0, ypos, xsize, ypos, 1194 width=1, fill="black", tags=("all",)) 1195 self.tag_bind("event", "<Enter>", self.mouseenter) 1196 self.tag_bind("event", "<Leave>", self.mouseexit) 1197 self.tag_bind("event", "<Button-1>", self.mousepress) 1198 1199 def mouseenter(self, event): 1200 item, = self.find_withtag(CURRENT) 1201 event = self.events[item] 1202 event.mouseenter(self, item) 1203 1204 def mouseexit(self, event): 1205 item, = self.find_withtag(CURRENT) 1206 event = self.events[item] 1207 event.mouseexit(self, item) 1208 1209 def mousepress(self, event): 1210 item, = self.find_withtag(CURRENT) 1211 event = self.events[item] 1212 event.mousepress(self, item) 1213 1214 def drawnames(self, canvas): 1215 status.startup("Drawing names") 1216 ypos = 0 1217 canvas.configure(scrollregion=(0, 0, 1218 canvas["width"], self.ysize())) 1219 for source in self.sources: 1220 canvas.create_line(0, ypos, canvas["width"], ypos, 1221 width=1, fill="black", tags=("all",)) 1222 ypos += self.bdheight 1223 ypos += source.ysize() 1224 source.drawname(canvas, ypos) 1225 ypos += self.bdheight 1226 canvas.create_line(0, ypos, canvas["width"], ypos, 1227 width=1, fill="black", tags=("all",)) 1228 1229 def xsize(self): 1230 return ((self.ktrfile.timespan() / self.ratio) + 20) 1231 1232 def ysize(self): 1233 ysize = 0 1234 for source in self.sources: 1235 ysize += source.ysize() + (self.bdheight * 2) 1236 return (ysize) 1237 1238 def scaleset(self, ratio): 1239 if (self.ktrfile == None): 1240 return 1241 oldratio = self.ratio 1242 xstart, ystart = self.xview() 1243 length = (float(self["width"]) / self.xsize()) 1244 middle = xstart + (length / 2) 1245 1246 self.ratio = ratio 1247 self.configure(scrollregion=(0, 0, self.xsize(), self.ysize())) 1248 self.scale("all", 0, 0, float(oldratio) / ratio, 1) 1249 1250 length = (float(self["width"]) / self.xsize()) 1251 xstart = middle - (length / 2) 1252 self.xview_moveto(xstart) 1253 1254 def scaleget(self): 1255 return self.ratio 1256 1257 def setcolor(self, tag, color): 1258 self.itemconfigure(tag, state="normal", fill=color) 1259 1260 def hide(self, tag): 1261 self.itemconfigure(tag, state="hidden") 1262 1263class GraphMenu(Frame): 1264 def __init__(self, master): 1265 Frame.__init__(self, master, bd=2, relief=RAISED) 1266 self.view = Menubutton(self, text="Configure") 1267 self.viewmenu = Menu(self.view, tearoff=0) 1268 self.viewmenu.add_command(label="Events", 1269 command=self.econf) 1270 self.view["menu"] = self.viewmenu 1271 self.view.pack(side=LEFT) 1272 1273 def econf(self): 1274 EventConfigure() 1275 1276 1277class SchedGraph(Frame): 1278 def __init__(self, master): 1279 Frame.__init__(self, master) 1280 self.menu = None 1281 self.names = None 1282 self.display = None 1283 self.scale = None 1284 self.status = None 1285 self.pack(expand=1, fill="both") 1286 self.buildwidgets() 1287 self.layout() 1288 self.draw(sys.argv[1]) 1289 1290 def buildwidgets(self): 1291 global status 1292 self.menu = GraphMenu(self) 1293 self.display = SchedDisplay(self) 1294 self.names = Canvas(self, 1295 width=100, height=self.display["height"], 1296 bg='grey', scrollregion=(0, 0, 50, 100)) 1297 self.scale = Scaler(self, self.display) 1298 status = self.status = Status(self) 1299 self.scrollY = Scrollbar(self, orient="vertical", 1300 command=self.display_yview) 1301 self.display.scrollX = Scrollbar(self, orient="horizontal", 1302 command=self.display.xview) 1303 self.display["xscrollcommand"] = self.display.scrollX.set 1304 self.display["yscrollcommand"] = self.scrollY.set 1305 self.names["yscrollcommand"] = self.scrollY.set 1306 1307 def layout(self): 1308 self.columnconfigure(1, weight=1) 1309 self.rowconfigure(1, weight=1) 1310 self.menu.grid(row=0, column=0, columnspan=3, sticky=E+W) 1311 self.names.grid(row=1, column=0, sticky=N+S) 1312 self.display.grid(row=1, column=1, sticky=W+E+N+S) 1313 self.scrollY.grid(row=1, column=2, sticky=N+S) 1314 self.display.scrollX.grid(row=2, column=0, columnspan=2, 1315 sticky=E+W) 1316 self.scale.grid(row=3, column=0, columnspan=3, sticky=E+W) 1317 self.status.grid(row=4, column=0, columnspan=3, sticky=E+W) 1318 1319 def draw(self, file): 1320 self.master.update() 1321 ktrfile = KTRFile(file) 1322 self.display.setfile(ktrfile) 1323 self.display.drawnames(self.names) 1324 self.display.draw() 1325 self.scale.set(250000) 1326 self.display.xview_moveto(0) 1327 1328 def display_yview(self, *args): 1329 self.names.yview(*args) 1330 self.display.yview(*args) 1331 1332 def setcolor(self, tag, color): 1333 self.display.setcolor(tag, color) 1334 1335 def hide(self, tag): 1336 self.display.hide(tag) 1337 1338if (len(sys.argv) != 2): 1339 print "usage:", sys.argv[0], "<ktr file>" 1340 sys.exit(1) 1341 1342root = Tk() 1343root.title("Scheduler Graph") 1344graph = SchedGraph(root) 1345root.mainloop() 1346