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