1#!/usr/local/bin/python 2 3# Copyright (c) 2002-2003, 2009, 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 31import random 32from Tkinter import * 33 34# To use: 35# - Install the ports/x11-toolkits/py-tkinter package; e.g. 36# portinstall x11-toolkits/py-tkinter package 37# - Add KTR_SCHED to KTR_COMPILE and KTR_MASK in your KERNCONF; e.g. 38# options KTR 39# options KTR_ENTRIES=32768 40# options KTR_COMPILE=(KTR_SCHED) 41# options KTR_MASK=(KTR_SCHED) 42# - It is encouraged to increase KTR_ENTRIES size to gather enough 43# information for analysis; e.g. 44# options KTR_ENTRIES=262144 45# as 32768 entries may only correspond to a second or two of profiling 46# data depending on your workload. 47# - Rebuild kernel with proper changes to KERNCONF and boot new kernel. 48# - Run your workload to be profiled. 49# - While the workload is continuing (i.e. before it finishes), disable 50# KTR tracing by setting 'sysctl debug.ktr.mask=0'. This is necessary 51# to avoid a race condition while running ktrdump, i.e. the KTR ring buffer 52# will cycle a bit while ktrdump runs, and this confuses schedgraph because 53# the timestamps appear to go backwards at some point. Stopping KTR logging 54# while the workload is still running is to avoid wasting log entries on 55# "idle" time at the end. 56# - Dump the trace to a file: 'ktrdump -ct > ktr.out' 57# - Run the python script: 'python schedgraph.py ktr.out' optionally provide 58# your cpu frequency in ghz: 'python schedgraph.py ktr.out 2.4' 59# 60# To do: 61# Add a per-source summary display 62# "Vertical rule" to help relate data in different rows 63# Mouse-over popup of full thread/event/row label (currently truncated) 64# More visible anchors for popup event windows 65# 66# BUGS: 1) Only 8 CPUs are supported, more CPUs require more choices of 67# colours to represent them ;-) 68 69eventcolors = [ 70 ("count", "red"), 71 ("running", "green"), 72 ("idle", "grey"), 73 ("yielding", "yellow"), 74 ("swapped", "violet"), 75 ("suspended", "purple"), 76 ("iwait", "grey"), 77 ("sleep", "blue"), 78 ("blocked", "dark red"), 79 ("runq add", "yellow"), 80 ("runq rem", "yellow"), 81 ("thread exit", "grey"), 82 ("proc exit", "grey"), 83 ("callwheel idle", "grey"), 84 ("callout running", "green"), 85 ("lock acquire", "blue"), 86 ("lock contest", "purple"), 87 ("failed lock try", "red"), 88 ("lock release", "grey"), 89 ("statclock", "black"), 90 ("prio", "black"), 91 ("lend prio", "black"), 92 ("wokeup", "black") 93] 94 95cpucolors = [ 96 ("CPU 0", "light grey"), 97 ("CPU 1", "dark grey"), 98 ("CPU 2", "light blue"), 99 ("CPU 3", "light pink"), 100 ("CPU 4", "blanched almond"), 101 ("CPU 5", "slate grey"), 102 ("CPU 6", "tan"), 103 ("CPU 7", "thistle"), 104 ("CPU 8", "white") 105] 106 107colors = [ 108 "white", "thistle", "blanched almond", "tan", "chartreuse", 109 "dark red", "red", "pale violet red", "pink", "light pink", 110 "dark orange", "orange", "coral", "light coral", 111 "goldenrod", "gold", "yellow", "light yellow", 112 "dark green", "green", "light green", "light sea green", 113 "dark blue", "blue", "light blue", "steel blue", "light slate blue", 114 "dark violet", "violet", "purple", "blue violet", 115 "dark grey", "slate grey", "light grey", 116 "black", 117] 118colors.sort() 119 120ticksps = None 121status = None 122colormap = None 123ktrfile = None 124clockfreq = None 125sources = [] 126lineno = -1 127 128Y_BORDER = 10 129X_BORDER = 10 130Y_COUNTER = 80 131Y_EVENTSOURCE = 10 132XY_POINT = 4 133 134class Colormap: 135 def __init__(self, table): 136 self.table = table 137 self.map = {} 138 for entry in table: 139 self.map[entry[0]] = entry[1] 140 141 def lookup(self, name): 142 try: 143 color = self.map[name] 144 except: 145 color = colors[random.randrange(0, len(colors))] 146 print "Picking random color", color, "for", name 147 self.map[name] = color 148 self.table.append((name, color)) 149 return (color) 150 151def ticks2sec(ticks): 152 ticks = float(ticks) 153 ns = float(ticksps) / 1000000000 154 ticks /= ns 155 if (ticks < 1000): 156 return ("%.2fns" % ticks) 157 ticks /= 1000 158 if (ticks < 1000): 159 return ("%.2fus" % ticks) 160 ticks /= 1000 161 if (ticks < 1000): 162 return ("%.2fms" % ticks) 163 ticks /= 1000 164 return ("%.2fs" % ticks) 165 166class Scaler(Frame): 167 def __init__(self, master, target): 168 Frame.__init__(self, master) 169 self.scale = None 170 self.target = target 171 self.label = Label(self, text="Ticks per pixel") 172 self.label.pack(side=LEFT) 173 self.resolution = 100 174 self.setmax(10000) 175 176 def scaleset(self, value): 177 self.target.scaleset(int(value)) 178 179 def set(self, value): 180 self.scale.set(value) 181 182 def setmax(self, value): 183 # 184 # We can't reconfigure the to_ value so we delete the old 185 # window and make a new one when we resize. 186 # 187 if (self.scale != None): 188 self.scale.pack_forget() 189 self.scale.destroy() 190 self.scale = Scale(self, command=self.scaleset, 191 from_=100, to_=value, orient=HORIZONTAL, 192 resolution=self.resolution) 193 self.scale.pack(fill="both", expand=1) 194 self.scale.set(self.target.scaleget()) 195 196class Status(Frame): 197 def __init__(self, master): 198 Frame.__init__(self, master) 199 self.label = Label(self, bd=1, relief=SUNKEN, anchor=W) 200 self.label.pack(fill="both", expand=1) 201 self.clear() 202 203 def set(self, str): 204 self.label.config(text=str) 205 206 def clear(self): 207 self.label.config(text="") 208 209 def startup(self, str): 210 self.set(str) 211 root.update() 212 213class ColorConf(Frame): 214 def __init__(self, master, name, color): 215 Frame.__init__(self, master) 216 if (graph.getstate(name) == "hidden"): 217 enabled = 0 218 else: 219 enabled = 1 220 self.name = name 221 self.color = StringVar() 222 self.color_default = color 223 self.color_current = color 224 self.color.set(color) 225 self.enabled = IntVar() 226 self.enabled_default = enabled 227 self.enabled_current = enabled 228 self.enabled.set(enabled) 229 self.draw() 230 231 def draw(self): 232 self.label = Label(self, text=self.name, anchor=W) 233 self.sample = Canvas(self, width=24, height=24, 234 bg='grey') 235 self.rect = self.sample.create_rectangle(0, 0, 24, 24, 236 fill=self.color.get()) 237 self.list = OptionMenu(self, self.color, command=self.setcolor, 238 *colors) 239 self.checkbox = Checkbutton(self, text="enabled", 240 variable=self.enabled) 241 self.label.grid(row=0, column=0, sticky=E+W) 242 self.sample.grid(row=0, column=1) 243 self.list.grid(row=0, column=2, sticky=E+W) 244 self.checkbox.grid(row=0, column=3) 245 self.columnconfigure(0, weight=1) 246 self.columnconfigure(2, minsize=150) 247 248 def setcolor(self, color): 249 self.color.set(color) 250 self.sample.itemconfigure(self.rect, fill=color) 251 252 def apply(self): 253 cchange = 0 254 echange = 0 255 if (self.color_current != self.color.get()): 256 cchange = 1 257 if (self.enabled_current != self.enabled.get()): 258 echange = 1 259 self.color_current = self.color.get() 260 self.enabled_current = self.enabled.get() 261 if (echange != 0): 262 if (self.enabled_current): 263 graph.setcolor(self.name, self.color_current) 264 else: 265 graph.hide(self.name) 266 return 267 if (cchange != 0): 268 graph.setcolor(self.name, self.color_current) 269 270 def revert(self): 271 self.setcolor(self.color_default) 272 self.enabled.set(self.enabled_default) 273 274class ColorConfigure(Toplevel): 275 def __init__(self, table, name): 276 Toplevel.__init__(self) 277 self.resizable(0, 0) 278 self.title(name) 279 self.items = LabelFrame(self, text="Item Type") 280 self.buttons = Frame(self) 281 self.drawbuttons() 282 self.items.grid(row=0, column=0, sticky=E+W) 283 self.columnconfigure(0, weight=1) 284 self.buttons.grid(row=1, column=0, sticky=E+W) 285 self.types = [] 286 self.irow = 0 287 for type in table: 288 color = graph.getcolor(type[0]) 289 if (color != ""): 290 self.additem(type[0], color) 291 292 def additem(self, name, color): 293 item = ColorConf(self.items, name, color) 294 self.types.append(item) 295 item.grid(row=self.irow, column=0, sticky=E+W) 296 self.irow += 1 297 298 def drawbuttons(self): 299 self.apply = Button(self.buttons, text="Apply", 300 command=self.apress) 301 self.default = Button(self.buttons, text="Revert", 302 command=self.rpress) 303 self.apply.grid(row=0, column=0, sticky=E+W) 304 self.default.grid(row=0, column=1, sticky=E+W) 305 self.buttons.columnconfigure(0, weight=1) 306 self.buttons.columnconfigure(1, weight=1) 307 308 def apress(self): 309 for item in self.types: 310 item.apply() 311 312 def rpress(self): 313 for item in self.types: 314 item.revert() 315 316class SourceConf(Frame): 317 def __init__(self, master, source): 318 Frame.__init__(self, master) 319 if (source.hidden == 1): 320 enabled = 0 321 else: 322 enabled = 1 323 self.source = source 324 self.name = source.name 325 self.enabled = IntVar() 326 self.enabled_default = enabled 327 self.enabled_current = enabled 328 self.enabled.set(enabled) 329 self.draw() 330 331 def draw(self): 332 self.label = Label(self, text=self.name, anchor=W) 333 self.checkbox = Checkbutton(self, text="enabled", 334 variable=self.enabled) 335 self.label.grid(row=0, column=0, sticky=E+W) 336 self.checkbox.grid(row=0, column=1) 337 self.columnconfigure(0, weight=1) 338 339 def changed(self): 340 if (self.enabled_current != self.enabled.get()): 341 return 1 342 return 0 343 344 def apply(self): 345 self.enabled_current = self.enabled.get() 346 347 def revert(self): 348 self.enabled.set(self.enabled_default) 349 350 def check(self): 351 self.enabled.set(1) 352 353 def uncheck(self): 354 self.enabled.set(0) 355 356class SourceConfigure(Toplevel): 357 def __init__(self): 358 Toplevel.__init__(self) 359 self.resizable(0, 0) 360 self.title("Source Configuration") 361 self.items = [] 362 self.iframe = Frame(self) 363 self.iframe.grid(row=0, column=0, sticky=E+W) 364 f = LabelFrame(self.iframe, bd=4, text="Sources") 365 self.items.append(f) 366 self.buttons = Frame(self) 367 self.items[0].grid(row=0, column=0, sticky=E+W) 368 self.columnconfigure(0, weight=1) 369 self.sconfig = [] 370 self.irow = 0 371 self.icol = 0 372 for source in sources: 373 self.addsource(source) 374 self.drawbuttons() 375 self.buttons.grid(row=1, column=0, sticky=W) 376 377 def addsource(self, source): 378 if (self.irow > 30): 379 self.icol += 1 380 self.irow = 0 381 c = self.icol 382 f = LabelFrame(self.iframe, bd=4, text="Sources") 383 f.grid(row=0, column=c, sticky=N+E+W) 384 self.items.append(f) 385 item = SourceConf(self.items[self.icol], source) 386 self.sconfig.append(item) 387 item.grid(row=self.irow, column=0, sticky=E+W) 388 self.irow += 1 389 390 def drawbuttons(self): 391 self.apply = Button(self.buttons, text="Apply", 392 command=self.apress) 393 self.default = Button(self.buttons, text="Revert", 394 command=self.rpress) 395 self.checkall = Button(self.buttons, text="Check All", 396 command=self.cpress) 397 self.uncheckall = Button(self.buttons, text="Uncheck All", 398 command=self.upress) 399 self.checkall.grid(row=0, column=0, sticky=W) 400 self.uncheckall.grid(row=0, column=1, sticky=W) 401 self.apply.grid(row=0, column=2, sticky=W) 402 self.default.grid(row=0, column=3, sticky=W) 403 self.buttons.columnconfigure(0, weight=1) 404 self.buttons.columnconfigure(1, weight=1) 405 self.buttons.columnconfigure(2, weight=1) 406 self.buttons.columnconfigure(3, weight=1) 407 408 def apress(self): 409 disable_sources = [] 410 enable_sources = [] 411 for item in self.sconfig: 412 if (item.changed() == 0): 413 continue 414 if (item.enabled.get() == 1): 415 enable_sources.append(item.source) 416 else: 417 disable_sources.append(item.source) 418 419 if (len(disable_sources)): 420 graph.sourcehidelist(disable_sources) 421 if (len(enable_sources)): 422 graph.sourceshowlist(enable_sources) 423 424 for item in self.sconfig: 425 item.apply() 426 427 def rpress(self): 428 for item in self.sconfig: 429 item.revert() 430 431 def cpress(self): 432 for item in self.sconfig: 433 item.check() 434 435 def upress(self): 436 for item in self.sconfig: 437 item.uncheck() 438 439# Reverse compare of second member of the tuple 440def cmp_counts(x, y): 441 return y[1] - x[1] 442 443class SourceStats(Toplevel): 444 def __init__(self, source): 445 self.source = source 446 Toplevel.__init__(self) 447 self.resizable(0, 0) 448 self.title(source.name + " statistics") 449 self.evframe = LabelFrame(self, 450 text="Event Count, Duration, Avg Duration") 451 self.evframe.grid(row=0, column=0, sticky=E+W) 452 eventtypes={} 453 for event in self.source.events: 454 if (event.type == "pad"): 455 continue 456 duration = event.duration 457 if (eventtypes.has_key(event.name)): 458 (c, d) = eventtypes[event.name] 459 c += 1 460 d += duration 461 eventtypes[event.name] = (c, d) 462 else: 463 eventtypes[event.name] = (1, duration) 464 events = [] 465 for k, v in eventtypes.iteritems(): 466 (c, d) = v 467 events.append((k, c, d)) 468 events.sort(cmp=cmp_counts) 469 470 ypos = 0 471 for event in events: 472 (name, c, d) = event 473 Label(self.evframe, text=name, bd=1, 474 relief=SUNKEN, anchor=W, width=30).grid( 475 row=ypos, column=0, sticky=W+E) 476 Label(self.evframe, text=str(c), bd=1, 477 relief=SUNKEN, anchor=W, width=10).grid( 478 row=ypos, column=1, sticky=W+E) 479 Label(self.evframe, text=ticks2sec(d), 480 bd=1, relief=SUNKEN, width=10).grid( 481 row=ypos, column=2, sticky=W+E) 482 if (d and c): 483 d /= c 484 else: 485 d = 0 486 Label(self.evframe, text=ticks2sec(d), 487 bd=1, relief=SUNKEN, width=10).grid( 488 row=ypos, column=3, sticky=W+E) 489 ypos += 1 490 491 492class SourceContext(Menu): 493 def __init__(self, event, source): 494 self.source = source 495 Menu.__init__(self, tearoff=0, takefocus=0) 496 self.add_command(label="hide", command=self.hide) 497 self.add_command(label="hide group", command=self.hidegroup) 498 self.add_command(label="stats", command=self.stats) 499 self.tk_popup(event.x_root-3, event.y_root+3) 500 501 def hide(self): 502 graph.sourcehide(self.source) 503 504 def hidegroup(self): 505 grouplist = [] 506 for source in sources: 507 if (source.group == self.source.group): 508 grouplist.append(source) 509 graph.sourcehidelist(grouplist) 510 511 def show(self): 512 graph.sourceshow(self.source) 513 514 def stats(self): 515 SourceStats(self.source) 516 517class EventView(Toplevel): 518 def __init__(self, event, canvas): 519 Toplevel.__init__(self) 520 self.resizable(0, 0) 521 self.title("Event") 522 self.event = event 523 self.buttons = Frame(self) 524 self.buttons.grid(row=0, column=0, sticky=E+W) 525 self.frame = Frame(self) 526 self.frame.grid(row=1, column=0, sticky=N+S+E+W) 527 self.canvas = canvas 528 self.drawlabels() 529 self.drawbuttons() 530 event.displayref(canvas) 531 self.bind("<Destroy>", self.destroycb) 532 533 def destroycb(self, event): 534 self.unbind("<Destroy>") 535 if (self.event != None): 536 self.event.displayunref(self.canvas) 537 self.event = None 538 self.destroy() 539 540 def clearlabels(self): 541 for label in self.frame.grid_slaves(): 542 label.grid_remove() 543 544 def drawlabels(self): 545 ypos = 0 546 labels = self.event.labels() 547 while (len(labels) < 7): 548 labels.append(("", "")) 549 for label in labels: 550 name, value = label 551 linked = 0 552 if (name == "linkedto"): 553 linked = 1 554 l = Label(self.frame, text=name, bd=1, width=15, 555 relief=SUNKEN, anchor=W) 556 if (linked): 557 fgcolor = "blue" 558 else: 559 fgcolor = "black" 560 r = Label(self.frame, text=value, bd=1, 561 relief=SUNKEN, anchor=W, fg=fgcolor) 562 l.grid(row=ypos, column=0, sticky=E+W) 563 r.grid(row=ypos, column=1, sticky=E+W) 564 if (linked): 565 r.bind("<Button-1>", self.linkpress) 566 ypos += 1 567 self.frame.columnconfigure(1, minsize=80) 568 569 def drawbuttons(self): 570 self.back = Button(self.buttons, text="<", command=self.bpress) 571 self.forw = Button(self.buttons, text=">", command=self.fpress) 572 self.new = Button(self.buttons, text="new", command=self.npress) 573 self.back.grid(row=0, column=0, sticky=E+W) 574 self.forw.grid(row=0, column=1, sticky=E+W) 575 self.new.grid(row=0, column=2, sticky=E+W) 576 self.buttons.columnconfigure(2, weight=1) 577 578 def newevent(self, event): 579 self.event.displayunref(self.canvas) 580 self.clearlabels() 581 self.event = event 582 self.event.displayref(self.canvas) 583 self.drawlabels() 584 585 def npress(self): 586 EventView(self.event, self.canvas) 587 588 def bpress(self): 589 prev = self.event.prev() 590 if (prev == None): 591 return 592 while (prev.type == "pad"): 593 prev = prev.prev() 594 if (prev == None): 595 return 596 self.newevent(prev) 597 598 def fpress(self): 599 next = self.event.next() 600 if (next == None): 601 return 602 while (next.type == "pad"): 603 next = next.next() 604 if (next == None): 605 return 606 self.newevent(next) 607 608 def linkpress(self, wevent): 609 event = self.event.getlinked() 610 if (event != None): 611 self.newevent(event) 612 613class Event: 614 def __init__(self, source, name, cpu, timestamp, attrs): 615 self.source = source 616 self.name = name 617 self.cpu = cpu 618 self.timestamp = int(timestamp) 619 self.attrs = attrs 620 self.idx = None 621 self.item = None 622 self.dispcnt = 0 623 self.duration = 0 624 self.recno = lineno 625 626 def status(self): 627 statstr = self.name + " " + self.source.name 628 statstr += " on: cpu" + str(self.cpu) 629 statstr += " at: " + str(self.timestamp) 630 statstr += " attributes: " 631 for i in range(0, len(self.attrs)): 632 attr = self.attrs[i] 633 statstr += attr[0] + ": " + str(attr[1]) 634 if (i != len(self.attrs) - 1): 635 statstr += ", " 636 status.set(statstr) 637 638 def labels(self): 639 return [("Source", self.source.name), 640 ("Event", self.name), 641 ("CPU", self.cpu), 642 ("Timestamp", self.timestamp), 643 ("KTR Line ", self.recno) 644 ] + self.attrs 645 646 def mouseenter(self, canvas): 647 self.displayref(canvas) 648 self.status() 649 650 def mouseexit(self, canvas): 651 self.displayunref(canvas) 652 status.clear() 653 654 def mousepress(self, canvas): 655 EventView(self, canvas) 656 657 def draw(self, canvas, xpos, ypos, item): 658 self.item = item 659 if (item != None): 660 canvas.items[item] = self 661 662 def move(self, canvas, x, y): 663 if (self.item == None): 664 return; 665 canvas.move(self.item, x, y); 666 667 def next(self): 668 return self.source.eventat(self.idx + 1) 669 670 def nexttype(self, type): 671 next = self.next() 672 while (next != None and next.type != type): 673 next = next.next() 674 return (next) 675 676 def prev(self): 677 return self.source.eventat(self.idx - 1) 678 679 def displayref(self, canvas): 680 if (self.dispcnt == 0): 681 canvas.itemconfigure(self.item, width=2) 682 self.dispcnt += 1 683 684 def displayunref(self, canvas): 685 self.dispcnt -= 1 686 if (self.dispcnt == 0): 687 canvas.itemconfigure(self.item, width=0) 688 canvas.tag_raise("point", "state") 689 690 def getlinked(self): 691 for attr in self.attrs: 692 if (attr[0] != "linkedto"): 693 continue 694 source = ktrfile.findid(attr[1]) 695 return source.findevent(self.timestamp) 696 return None 697 698class PointEvent(Event): 699 type = "point" 700 def __init__(self, source, name, cpu, timestamp, attrs): 701 Event.__init__(self, source, name, cpu, timestamp, attrs) 702 703 def draw(self, canvas, xpos, ypos): 704 color = colormap.lookup(self.name) 705 l = canvas.create_oval(xpos - XY_POINT, ypos, 706 xpos + XY_POINT, ypos - (XY_POINT * 2), 707 fill=color, width=0, 708 tags=("event", self.type, self.name, self.source.tag)) 709 Event.draw(self, canvas, xpos, ypos, l) 710 711 return xpos 712 713class StateEvent(Event): 714 type = "state" 715 def __init__(self, source, name, cpu, timestamp, attrs): 716 Event.__init__(self, source, name, cpu, timestamp, attrs) 717 718 def draw(self, canvas, xpos, ypos): 719 next = self.nexttype("state") 720 if (next == None): 721 return (xpos) 722 self.duration = duration = next.timestamp - self.timestamp 723 self.attrs.insert(0, ("duration", ticks2sec(duration))) 724 color = colormap.lookup(self.name) 725 if (duration < 0): 726 duration = 0 727 print "Unsynchronized timestamp" 728 print self.cpu, self.timestamp 729 print next.cpu, next.timestamp 730 delta = duration / canvas.ratio 731 l = canvas.create_rectangle(xpos, ypos, 732 xpos + delta, ypos - 10, fill=color, width=0, 733 tags=("event", self.type, self.name, self.source.tag)) 734 Event.draw(self, canvas, xpos, ypos, l) 735 736 return (xpos + delta) 737 738class CountEvent(Event): 739 type = "count" 740 def __init__(self, source, count, cpu, timestamp, attrs): 741 count = int(count) 742 self.count = count 743 Event.__init__(self, source, "count", cpu, timestamp, attrs) 744 745 def draw(self, canvas, xpos, ypos): 746 next = self.nexttype("count") 747 if (next == None): 748 return (xpos) 749 color = colormap.lookup("count") 750 self.duration = duration = next.timestamp - self.timestamp 751 if (duration < 0): 752 duration = 0 753 print "Unsynchronized timestamp" 754 print self.cpu, self.timestamp 755 print next.cpu, next.timestamp 756 self.attrs.insert(0, ("count", self.count)) 757 self.attrs.insert(1, ("duration", ticks2sec(duration))) 758 delta = duration / canvas.ratio 759 yhight = self.source.yscale() * self.count 760 l = canvas.create_rectangle(xpos, ypos - yhight, 761 xpos + delta, ypos, fill=color, width=0, 762 tags=("event", self.type, self.name, self.source.tag)) 763 Event.draw(self, canvas, xpos, ypos, l) 764 return (xpos + delta) 765 766class PadEvent(StateEvent): 767 type = "pad" 768 def __init__(self, source, cpu, timestamp, last=0): 769 if (last): 770 cpu = source.events[len(source.events) -1].cpu 771 else: 772 cpu = source.events[0].cpu 773 StateEvent.__init__(self, source, "pad", cpu, timestamp, []) 774 def draw(self, canvas, xpos, ypos): 775 next = self.next() 776 if (next == None): 777 return (xpos) 778 duration = next.timestamp - self.timestamp 779 delta = duration / canvas.ratio 780 Event.draw(self, canvas, xpos, ypos, None) 781 return (xpos + delta) 782 783# Sort function for start y address 784def source_cmp_start(x, y): 785 return x.y - y.y 786 787class EventSource: 788 def __init__(self, group, id): 789 self.name = id 790 self.events = [] 791 self.cpuitems = [] 792 self.group = group 793 self.y = 0 794 self.item = None 795 self.hidden = 0 796 self.tag = group + id 797 798 def __cmp__(self, other): 799 if (other == None): 800 return -1 801 if (self.group == other.group): 802 return cmp(self.name, other.name) 803 return cmp(self.group, other.group) 804 805 # It is much faster to append items to a list then to insert them 806 # at the beginning. As a result, we add events in reverse order 807 # and then swap the list during fixup. 808 def fixup(self): 809 self.events.reverse() 810 811 def addevent(self, event): 812 self.events.append(event) 813 814 def addlastevent(self, event): 815 self.events.insert(0, event) 816 817 def draw(self, canvas, ypos): 818 xpos = 10 819 cpux = 10 820 cpu = self.events[1].cpu 821 for i in range(0, len(self.events)): 822 self.events[i].idx = i 823 for event in self.events: 824 if (event.cpu != cpu and event.cpu != -1): 825 self.drawcpu(canvas, cpu, cpux, xpos, ypos) 826 cpux = xpos 827 cpu = event.cpu 828 xpos = event.draw(canvas, xpos, ypos) 829 self.drawcpu(canvas, cpu, cpux, xpos, ypos) 830 831 def drawname(self, canvas, ypos): 832 self.y = ypos 833 ypos = ypos - (self.ysize() / 2) 834 self.item = canvas.create_text(X_BORDER, ypos, anchor="w", 835 text=self.name) 836 return (self.item) 837 838 def drawcpu(self, canvas, cpu, fromx, tox, ypos): 839 cpu = "CPU " + str(cpu) 840 color = cpucolormap.lookup(cpu) 841 # Create the cpu background colors default to hidden 842 l = canvas.create_rectangle(fromx, 843 ypos - self.ysize() - canvas.bdheight, 844 tox, ypos + canvas.bdheight, fill=color, width=0, 845 tags=("cpubg", cpu, self.tag), state="hidden") 846 self.cpuitems.append(l) 847 848 def move(self, canvas, xpos, ypos): 849 canvas.move(self.tag, xpos, ypos) 850 851 def movename(self, canvas, xpos, ypos): 852 self.y += ypos 853 canvas.move(self.item, xpos, ypos) 854 855 def ysize(self): 856 return (Y_EVENTSOURCE) 857 858 def eventat(self, i): 859 if (i >= len(self.events)): 860 return (None) 861 event = self.events[i] 862 return (event) 863 864 def findevent(self, timestamp): 865 for event in self.events: 866 if (event.timestamp >= timestamp and event.type != "pad"): 867 return (event) 868 return (None) 869 870class Counter(EventSource): 871 # 872 # Store a hash of counter groups that keeps the max value 873 # for a counter in this group for scaling purposes. 874 # 875 groups = {} 876 def __init__(self, group, id): 877 try: 878 Counter.cnt = Counter.groups[group] 879 except: 880 Counter.groups[group] = 0 881 EventSource.__init__(self, group, id) 882 883 def fixup(self): 884 for event in self.events: 885 if (event.type != "count"): 886 continue; 887 count = int(event.count) 888 if (count > Counter.groups[self.group]): 889 Counter.groups[self.group] = count 890 EventSource.fixup(self) 891 892 def ymax(self): 893 return (Counter.groups[self.group]) 894 895 def ysize(self): 896 return (Y_COUNTER) 897 898 def yscale(self): 899 return (self.ysize() / self.ymax()) 900 901class KTRFile: 902 def __init__(self, file): 903 self.timestamp_f = None 904 self.timestamp_l = None 905 self.locks = {} 906 self.callwheels = {} 907 self.ticks = {} 908 self.load = {} 909 self.crit = {} 910 self.stathz = 0 911 self.eventcnt = 0 912 self.taghash = {} 913 914 self.parse(file) 915 self.fixup() 916 global ticksps 917 ticksps = self.ticksps() 918 span = self.timespan() 919 ghz = float(ticksps) / 1000000000.0 920 # 921 # Update the title with some stats from the file 922 # 923 titlestr = "SchedGraph: " 924 titlestr += ticks2sec(span) + " at %.3f ghz, " % ghz 925 titlestr += str(len(sources)) + " event sources, " 926 titlestr += str(self.eventcnt) + " events" 927 root.title(titlestr) 928 929 def parse(self, file): 930 try: 931 ifp = open(file) 932 except: 933 print "Can't open", file 934 sys.exit(1) 935 936 # quoteexp matches a quoted string, no escaping 937 quoteexp = "\"([^\"]*)\"" 938 939 # 940 # commaexp matches a quoted string OR the string up 941 # to the first ',' 942 # 943 commaexp = "(?:" + quoteexp + "|([^,]+))" 944 945 # 946 # colonstr matches a quoted string OR the string up 947 # to the first ':' 948 # 949 colonexp = "(?:" + quoteexp + "|([^:]+))" 950 951 # 952 # Match various manditory parts of the KTR string this is 953 # fairly inflexible until you get to attributes to make 954 # parsing faster. 955 # 956 hdrexp = "\s*(\d+)\s+(\d+)\s+(\d+)\s+" 957 groupexp = "KTRGRAPH group:" + quoteexp + ", " 958 idexp = "id:" + quoteexp + ", " 959 typeexp = "([^:]+):" + commaexp + ", " 960 attribexp = "attributes: (.*)" 961 962 # 963 # Matches optional attributes in the KTR string. This 964 # tolerates more variance as the users supply these values. 965 # 966 attrexp = colonexp + "\s*:\s*(?:" + commaexp + ", (.*)|" 967 attrexp += quoteexp +"|(.*))" 968 969 # Precompile regexp 970 ktrre = re.compile(hdrexp + groupexp + idexp + typeexp + attribexp) 971 attrre = re.compile(attrexp) 972 973 global lineno 974 lineno = 0 975 for line in ifp.readlines(): 976 lineno += 1 977 if ((lineno % 2048) == 0): 978 status.startup("Parsing line " + str(lineno)) 979 m = ktrre.match(line); 980 if (m == None): 981 print "Can't parse", lineno, line, 982 continue; 983 (index, cpu, timestamp, group, id, type, dat, dat1, attrstring) = m.groups(); 984 if (dat == None): 985 dat = dat1 986 if (self.checkstamp(timestamp) == 0): 987 print "Bad timestamp at", lineno, ":", 988 print cpu, timestamp 989 continue 990 # 991 # Build the table of optional attributes 992 # 993 attrs = [] 994 while (attrstring != None): 995 m = attrre.match(attrstring.strip()) 996 if (m == None): 997 break; 998 # 999 # Name may or may not be quoted. 1000 # 1001 # For val we have four cases: 1002 # 1) quotes followed by comma and more 1003 # attributes. 1004 # 2) no quotes followed by comma and more 1005 # attributes. 1006 # 3) no more attributes or comma with quotes. 1007 # 4) no more attributes or comma without quotes. 1008 # 1009 (name, name1, val, val1, attrstring, end, end1) = m.groups(); 1010 if (name == None): 1011 name = name1 1012 if (end == None): 1013 end = end1 1014 if (val == None): 1015 val = val1 1016 if (val == None): 1017 val = end 1018 if (name == "stathz"): 1019 self.setstathz(val, cpu) 1020 attrs.append((name, val)) 1021 args = (dat, cpu, timestamp, attrs) 1022 e = self.makeevent(group, id, type, args) 1023 if (e == None): 1024 print "Unknown type", type, lineno, line, 1025 1026 def makeevent(self, group, id, type, args): 1027 e = None 1028 source = self.makeid(group, id, type) 1029 if (type == "state"): 1030 e = StateEvent(source, *args) 1031 elif (type == "counter"): 1032 e = CountEvent(source, *args) 1033 elif (type == "point"): 1034 e = PointEvent(source, *args) 1035 if (e != None): 1036 self.eventcnt += 1 1037 source.addevent(e); 1038 return e 1039 1040 def setstathz(self, val, cpu): 1041 self.stathz = int(val) 1042 cpu = int(cpu) 1043 try: 1044 ticks = self.ticks[cpu] 1045 except: 1046 self.ticks[cpu] = 0 1047 self.ticks[cpu] += 1 1048 1049 def checkstamp(self, timestamp): 1050 timestamp = int(timestamp) 1051 if (self.timestamp_f == None): 1052 self.timestamp_f = timestamp; 1053 if (self.timestamp_l != None and 1054 timestamp -2048> self.timestamp_l): 1055 return (0) 1056 self.timestamp_l = timestamp; 1057 return (1) 1058 1059 def makeid(self, group, id, type): 1060 tag = group + id 1061 if (self.taghash.has_key(tag)): 1062 return self.taghash[tag] 1063 if (type == "counter"): 1064 source = Counter(group, id) 1065 else: 1066 source = EventSource(group, id) 1067 sources.append(source) 1068 self.taghash[tag] = source 1069 return (source) 1070 1071 def findid(self, id): 1072 for source in sources: 1073 if (source.name == id): 1074 return source 1075 return (None) 1076 1077 def timespan(self): 1078 return (self.timestamp_f - self.timestamp_l); 1079 1080 def ticksps(self): 1081 oneghz = 1000000000 1082 # Use user supplied clock first 1083 if (clockfreq != None): 1084 return int(clockfreq * oneghz) 1085 1086 # Check for a discovered clock 1087 if (self.stathz != 0): 1088 return (self.timespan() / self.ticks[0]) * int(self.stathz) 1089 # Pretend we have a 1ns clock 1090 print "WARNING: No clock discovered and no frequency ", 1091 print "specified via the command line." 1092 print "Using fake 1ghz clock" 1093 return (oneghz); 1094 1095 def fixup(self): 1096 for source in sources: 1097 e = PadEvent(source, -1, self.timestamp_l) 1098 source.addevent(e) 1099 e = PadEvent(source, -1, self.timestamp_f, last=1) 1100 source.addlastevent(e) 1101 source.fixup() 1102 sources.sort() 1103 1104class SchedNames(Canvas): 1105 def __init__(self, master, display): 1106 self.display = display 1107 self.parent = master 1108 self.bdheight = master.bdheight 1109 self.items = {} 1110 self.ysize = 0 1111 self.lines = [] 1112 Canvas.__init__(self, master, width=120, 1113 height=display["height"], bg='grey', 1114 scrollregion=(0, 0, 50, 100)) 1115 1116 def moveline(self, cur_y, y): 1117 for line in self.lines: 1118 (x0, y0, x1, y1) = self.coords(line) 1119 if (cur_y != y0): 1120 continue 1121 self.move(line, 0, y) 1122 return 1123 1124 def draw(self): 1125 status.startup("Drawing names") 1126 ypos = 0 1127 self.configure(scrollregion=(0, 0, 1128 self["width"], self.display.ysize())) 1129 for source in sources: 1130 l = self.create_line(0, ypos, self["width"], ypos, 1131 width=1, fill="black", tags=("all","sources")) 1132 self.lines.append(l) 1133 ypos += self.bdheight 1134 ypos += source.ysize() 1135 t = source.drawname(self, ypos) 1136 self.items[t] = source 1137 ypos += self.bdheight 1138 self.ysize = ypos 1139 self.create_line(0, ypos, self["width"], ypos, 1140 width=1, fill="black", tags=("all",)) 1141 self.bind("<Button-1>", self.master.mousepress); 1142 self.bind("<Button-3>", self.master.mousepressright); 1143 self.bind("<ButtonRelease-1>", self.master.mouserelease); 1144 self.bind("<B1-Motion>", self.master.mousemotion); 1145 1146 def updatescroll(self): 1147 self.configure(scrollregion=(0, 0, 1148 self["width"], self.display.ysize())) 1149 1150 1151class SchedDisplay(Canvas): 1152 def __init__(self, master): 1153 self.ratio = 1 1154 self.parent = master 1155 self.bdheight = master.bdheight 1156 self.items = {} 1157 self.lines = [] 1158 Canvas.__init__(self, master, width=800, height=500, bg='grey', 1159 scrollregion=(0, 0, 800, 500)) 1160 1161 def prepare(self): 1162 # 1163 # Compute a ratio to ensure that the file's timespan fits into 1164 # 2^31. Although python may handle larger values for X 1165 # values, the Tk internals do not. 1166 # 1167 self.ratio = (ktrfile.timespan() - 1) / 2**31 + 1 1168 1169 def draw(self): 1170 ypos = 0 1171 xsize = self.xsize() 1172 for source in sources: 1173 status.startup("Drawing " + source.name) 1174 l = self.create_line(0, ypos, xsize, ypos, 1175 width=1, fill="black", tags=("all",)) 1176 self.lines.append(l) 1177 ypos += self.bdheight 1178 ypos += source.ysize() 1179 source.draw(self, ypos) 1180 ypos += self.bdheight 1181 self.tag_raise("point", "state") 1182 self.tag_lower("cpubg", ALL) 1183 self.create_line(0, ypos, xsize, ypos, 1184 width=1, fill="black", tags=("lines",)) 1185 self.tag_bind("event", "<Enter>", self.mouseenter) 1186 self.tag_bind("event", "<Leave>", self.mouseexit) 1187 self.bind("<Button-1>", self.mousepress) 1188 self.bind("<Button-3>", self.master.mousepressright); 1189 self.bind("<Button-4>", self.wheelup) 1190 self.bind("<Button-5>", self.wheeldown) 1191 self.bind("<ButtonRelease-1>", self.master.mouserelease); 1192 self.bind("<B1-Motion>", self.master.mousemotion); 1193 1194 def moveline(self, cur_y, y): 1195 for line in self.lines: 1196 (x0, y0, x1, y1) = self.coords(line) 1197 if (cur_y != y0): 1198 continue 1199 self.move(line, 0, y) 1200 return 1201 1202 def mouseenter(self, event): 1203 item, = self.find_withtag(CURRENT) 1204 self.items[item].mouseenter(self) 1205 1206 def mouseexit(self, event): 1207 item, = self.find_withtag(CURRENT) 1208 self.items[item].mouseexit(self) 1209 1210 def mousepress(self, event): 1211 # Find out what's beneath us 1212 items = self.find_withtag(CURRENT) 1213 if (len(items) == 0): 1214 self.master.mousepress(event) 1215 return 1216 # Only grab mouse presses for things with event tags. 1217 item = items[0] 1218 tags = self.gettags(item) 1219 for tag in tags: 1220 if (tag == "event"): 1221 self.items[item].mousepress(self) 1222 return 1223 # Leave the rest to the master window 1224 self.master.mousepress(event) 1225 1226 def wheeldown(self, event): 1227 self.parent.display_yview("scroll", 1, "units") 1228 1229 def wheelup(self, event): 1230 self.parent.display_yview("scroll", -1, "units") 1231 1232 def xsize(self): 1233 return ((ktrfile.timespan() / self.ratio) + (X_BORDER * 2)) 1234 1235 def ysize(self): 1236 ysize = 0 1237 for source in sources: 1238 if (source.hidden == 1): 1239 continue 1240 ysize += self.parent.sourcesize(source) 1241 return ysize 1242 1243 def scaleset(self, ratio): 1244 if (ktrfile == None): 1245 return 1246 oldratio = self.ratio 1247 xstart, xend = self.xview() 1248 midpoint = xstart + ((xend - xstart) / 2) 1249 1250 self.ratio = ratio 1251 self.updatescroll() 1252 self.scale(ALL, 0, 0, float(oldratio) / ratio, 1) 1253 1254 xstart, xend = self.xview() 1255 xsize = (xend - xstart) / 2 1256 self.xview_moveto(midpoint - xsize) 1257 1258 def updatescroll(self): 1259 self.configure(scrollregion=(0, 0, self.xsize(), self.ysize())) 1260 1261 def scaleget(self): 1262 return self.ratio 1263 1264 def getcolor(self, tag): 1265 return self.itemcget(tag, "fill") 1266 1267 def getstate(self, tag): 1268 return self.itemcget(tag, "state") 1269 1270 def setcolor(self, tag, color): 1271 self.itemconfigure(tag, state="normal", fill=color) 1272 1273 def hide(self, tag): 1274 self.itemconfigure(tag, state="hidden") 1275 1276class GraphMenu(Frame): 1277 def __init__(self, master): 1278 Frame.__init__(self, master, bd=2, relief=RAISED) 1279 self.conf = Menubutton(self, text="Configure") 1280 self.confmenu = Menu(self.conf, tearoff=0) 1281 self.confmenu.add_command(label="Event Colors", 1282 command=self.econf) 1283 self.confmenu.add_command(label="CPU Colors", 1284 command=self.cconf) 1285 self.confmenu.add_command(label="Source Configure", 1286 command=self.sconf) 1287 self.conf["menu"] = self.confmenu 1288 self.conf.pack(side=LEFT) 1289 1290 def econf(self): 1291 ColorConfigure(eventcolors, "Event Display Configuration") 1292 1293 def cconf(self): 1294 ColorConfigure(cpucolors, "CPU Background Colors") 1295 1296 def sconf(self): 1297 SourceConfigure() 1298 1299class SchedGraph(Frame): 1300 def __init__(self, master): 1301 Frame.__init__(self, master) 1302 self.menu = None 1303 self.names = None 1304 self.display = None 1305 self.scale = None 1306 self.status = None 1307 self.bdheight = Y_BORDER 1308 self.clicksource = None 1309 self.lastsource = None 1310 self.pack(expand=1, fill="both") 1311 self.buildwidgets() 1312 self.layout() 1313 1314 def buildwidgets(self): 1315 global status 1316 self.menu = GraphMenu(self) 1317 self.display = SchedDisplay(self) 1318 self.names = SchedNames(self, self.display) 1319 self.scale = Scaler(self, self.display) 1320 status = self.status = Status(self) 1321 self.scrollY = Scrollbar(self, orient="vertical", 1322 command=self.display_yview) 1323 self.display.scrollX = Scrollbar(self, orient="horizontal", 1324 command=self.display.xview) 1325 self.display["xscrollcommand"] = self.display.scrollX.set 1326 self.display["yscrollcommand"] = self.scrollY.set 1327 self.names["yscrollcommand"] = self.scrollY.set 1328 1329 def layout(self): 1330 self.columnconfigure(1, weight=1) 1331 self.rowconfigure(1, weight=1) 1332 self.menu.grid(row=0, column=0, columnspan=3, sticky=E+W) 1333 self.names.grid(row=1, column=0, sticky=N+S) 1334 self.display.grid(row=1, column=1, sticky=W+E+N+S) 1335 self.scrollY.grid(row=1, column=2, sticky=N+S) 1336 self.display.scrollX.grid(row=2, column=0, columnspan=2, 1337 sticky=E+W) 1338 self.scale.grid(row=3, column=0, columnspan=3, sticky=E+W) 1339 self.status.grid(row=4, column=0, columnspan=3, sticky=E+W) 1340 1341 def draw(self): 1342 self.master.update() 1343 self.display.prepare() 1344 self.names.draw() 1345 self.display.draw() 1346 self.status.startup("") 1347 # 1348 # Configure scale related values 1349 # 1350 scalemax = ktrfile.timespan() / int(self.display["width"]) 1351 width = int(root.geometry().split('x')[0]) 1352 self.constwidth = width - int(self.display["width"]) 1353 self.scale.setmax(scalemax) 1354 self.scale.set(scalemax) 1355 self.display.xview_moveto(0) 1356 self.bind("<Configure>", self.resize) 1357 1358 def mousepress(self, event): 1359 self.clicksource = self.sourceat(event.y) 1360 1361 def mousepressright(self, event): 1362 source = self.sourceat(event.y) 1363 if (source == None): 1364 return 1365 SourceContext(event, source) 1366 1367 def mouserelease(self, event): 1368 if (self.clicksource == None): 1369 return 1370 newsource = self.sourceat(event.y) 1371 if (self.clicksource != newsource): 1372 self.sourceswap(self.clicksource, newsource) 1373 self.clicksource = None 1374 self.lastsource = None 1375 1376 def mousemotion(self, event): 1377 if (self.clicksource == None): 1378 return 1379 newsource = self.sourceat(event.y) 1380 # 1381 # If we get a None source they moved off the page. 1382 # swapsource() can't handle moving multiple items so just 1383 # pretend we never clicked on anything to begin with so the 1384 # user can't mouseover a non-contiguous area. 1385 # 1386 if (newsource == None): 1387 self.clicksource = None 1388 self.lastsource = None 1389 return 1390 if (newsource == self.lastsource): 1391 return; 1392 self.lastsource = newsource 1393 if (newsource != self.clicksource): 1394 self.sourceswap(self.clicksource, newsource) 1395 1396 # These are here because this object controls layout 1397 def sourcestart(self, source): 1398 return source.y - self.bdheight - source.ysize() 1399 1400 def sourceend(self, source): 1401 return source.y + self.bdheight 1402 1403 def sourcesize(self, source): 1404 return (self.bdheight * 2) + source.ysize() 1405 1406 def sourceswap(self, source1, source2): 1407 # Sort so we always know which one is on top. 1408 if (source2.y < source1.y): 1409 swap = source1 1410 source1 = source2 1411 source2 = swap 1412 # Only swap adjacent sources 1413 if (self.sourceend(source1) != self.sourcestart(source2)): 1414 return 1415 # Compute start coordinates and target coordinates 1416 y1 = self.sourcestart(source1) 1417 y2 = self.sourcestart(source2) 1418 y1targ = y1 + self.sourcesize(source2) 1419 y2targ = y1 1420 # 1421 # If the sizes are not equal, adjust the start of the lower 1422 # source to account for the lost/gained space. 1423 # 1424 if (source1.ysize() != source2.ysize()): 1425 diff = source2.ysize() - source1.ysize() 1426 self.names.moveline(y2, diff); 1427 self.display.moveline(y2, diff) 1428 source1.move(self.display, 0, y1targ - y1) 1429 source2.move(self.display, 0, y2targ - y2) 1430 source1.movename(self.names, 0, y1targ - y1) 1431 source2.movename(self.names, 0, y2targ - y2) 1432 1433 def sourcepicky(self, source): 1434 if (source.hidden == 0): 1435 return self.sourcestart(source) 1436 # Revert to group based sort 1437 sources.sort() 1438 prev = None 1439 for s in sources: 1440 if (s == source): 1441 break 1442 if (s.hidden == 0): 1443 prev = s 1444 if (prev == None): 1445 newy = 0 1446 else: 1447 newy = self.sourcestart(prev) + self.sourcesize(prev) 1448 return newy 1449 1450 def sourceshow(self, source): 1451 if (source.hidden == 0): 1452 return; 1453 newy = self.sourcepicky(source) 1454 off = newy - self.sourcestart(source) 1455 self.sourceshiftall(newy-1, self.sourcesize(source)) 1456 self.sourceshift(source, off) 1457 source.hidden = 0 1458 1459 # 1460 # Optimized source show of multiple entries that only moves each 1461 # existing entry once. Doing sourceshow() iteratively is too 1462 # expensive due to python's canvas.move(). 1463 # 1464 def sourceshowlist(self, srclist): 1465 srclist.sort(cmp=source_cmp_start) 1466 startsize = [] 1467 for source in srclist: 1468 if (source.hidden == 0): 1469 srclist.remove(source) 1470 startsize.append((self.sourcepicky(source), 1471 self.sourcesize(source))) 1472 1473 sources.sort(cmp=source_cmp_start, reverse=True) 1474 self.status.startup("Updating display..."); 1475 for source in sources: 1476 if (source.hidden == 1): 1477 continue 1478 nstart = self.sourcestart(source) 1479 size = 0 1480 for hidden in startsize: 1481 (start, sz) = hidden 1482 if (start <= nstart or start+sz <= nstart): 1483 size += sz 1484 self.sourceshift(source, size) 1485 idx = 0 1486 size = 0 1487 for source in srclist: 1488 (newy, sz) = startsize[idx] 1489 off = (newy + size) - self.sourcestart(source) 1490 self.sourceshift(source, off) 1491 source.hidden = 0 1492 size += sz 1493 idx += 1 1494 self.updatescroll() 1495 self.status.set("") 1496 1497 # 1498 # Optimized source hide of multiple entries that only moves each 1499 # remaining entry once. Doing sourcehide() iteratively is too 1500 # expensive due to python's canvas.move(). 1501 # 1502 def sourcehidelist(self, srclist): 1503 srclist.sort(cmp=source_cmp_start) 1504 sources.sort(cmp=source_cmp_start) 1505 startsize = [] 1506 off = len(sources) * 100 1507 self.status.startup("Updating display..."); 1508 for source in srclist: 1509 if (source.hidden == 1): 1510 srclist.remove(source) 1511 # 1512 # Remember our old position so we can sort things 1513 # below us when we're done. 1514 # 1515 startsize.append((self.sourcestart(source), 1516 self.sourcesize(source))) 1517 self.sourceshift(source, off) 1518 source.hidden = 1 1519 1520 idx = 0 1521 size = 0 1522 for hidden in startsize: 1523 (start, sz) = hidden 1524 size += sz 1525 if (idx + 1 < len(startsize)): 1526 (stop, sz) = startsize[idx+1] 1527 else: 1528 stop = self.display.ysize() 1529 idx += 1 1530 for source in sources: 1531 nstart = self.sourcestart(source) 1532 if (nstart < start or source.hidden == 1): 1533 continue 1534 if (nstart >= stop): 1535 break; 1536 self.sourceshift(source, -size) 1537 self.updatescroll() 1538 self.status.set("") 1539 1540 def sourcehide(self, source): 1541 if (source.hidden == 1): 1542 return; 1543 # Move it out of the visible area 1544 off = len(sources) * 100 1545 start = self.sourcestart(source) 1546 self.sourceshift(source, off) 1547 self.sourceshiftall(start, -self.sourcesize(source)) 1548 source.hidden = 1 1549 1550 def sourceshift(self, source, off): 1551 start = self.sourcestart(source) 1552 source.move(self.display, 0, off) 1553 source.movename(self.names, 0, off) 1554 self.names.moveline(start, off); 1555 self.display.moveline(start, off) 1556 # 1557 # We update the idle tasks to shrink the dirtied area so 1558 # it does not always include the entire screen. 1559 # 1560 self.names.update_idletasks() 1561 self.display.update_idletasks() 1562 1563 def sourceshiftall(self, start, off): 1564 self.status.startup("Updating display..."); 1565 for source in sources: 1566 nstart = self.sourcestart(source) 1567 if (nstart < start): 1568 continue; 1569 self.sourceshift(source, off) 1570 self.updatescroll() 1571 self.status.set("") 1572 1573 def sourceat(self, ypos): 1574 (start, end) = self.names.yview() 1575 starty = start * float(self.names.ysize) 1576 ypos += starty 1577 for source in sources: 1578 if (source.hidden == 1): 1579 continue; 1580 yend = self.sourceend(source) 1581 ystart = self.sourcestart(source) 1582 if (ypos >= ystart and ypos <= yend): 1583 return source 1584 return None 1585 1586 def display_yview(self, *args): 1587 self.names.yview(*args) 1588 self.display.yview(*args) 1589 1590 def resize(self, *args): 1591 width = int(root.geometry().split('x')[0]) 1592 scalemax = ktrfile.timespan() / (width - self.constwidth) 1593 self.scale.setmax(scalemax) 1594 1595 def updatescroll(self): 1596 self.names.updatescroll() 1597 self.display.updatescroll() 1598 1599 def setcolor(self, tag, color): 1600 self.display.setcolor(tag, color) 1601 1602 def hide(self, tag): 1603 self.display.hide(tag) 1604 1605 def getcolor(self, tag): 1606 return self.display.getcolor(tag) 1607 1608 def getstate(self, tag): 1609 return self.display.getstate(tag) 1610 1611if (len(sys.argv) != 2 and len(sys.argv) != 3): 1612 print "usage:", sys.argv[0], "<ktr file> [clock freq in ghz]" 1613 sys.exit(1) 1614 1615if (len(sys.argv) > 2): 1616 clockfreq = float(sys.argv[2]) 1617 1618root = Tk() 1619root.title("SchedGraph") 1620colormap = Colormap(eventcolors) 1621cpucolormap = Colormap(cpucolors) 1622graph = SchedGraph(root) 1623ktrfile = KTRFile(sys.argv[1]) 1624graph.draw() 1625root.mainloop() 1626