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