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