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