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