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