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