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