1#!/usr/local/bin/python 2 3# Copyright (c) 2002-2003, Jeffrey Roberson <jeff@freebsd.org> 4# All rights reserved. 5# 6# Redistribution and use in source and binary forms, with or without 7# modification, are permitted provided that the following conditions 8# are met: --- 10 unchanged lines hidden (view full) --- 19# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 20# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 21# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 23# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 25# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26# |
27# $FreeBSD: head/tools/sched/schedgraph.py 187358 2009-01-17 07:24:25Z jeff $ |
28 29import sys 30import re |
31import random |
32from Tkinter import * 33 34# To use: 35# - Install the ports/x11-toolkits/py-tkinter package; e.g. 36# portinstall x11-toolkits/py-tkinter package 37# - Add KTR_SCHED to KTR_COMPILE and KTR_MASK in your KERNCONF; e.g. 38# options KTR 39# options KTR_ENTRIES=32768 --- 9 unchanged lines hidden (view full) --- 49# - While the workload is continuing (i.e. before it finishes), disable 50# KTR tracing by setting 'sysctl debug.ktr.mask=0'. This is necessary 51# to avoid a race condition while running ktrdump, i.e. the KTR ring buffer 52# will cycle a bit while ktrdump runs, and this confuses schedgraph because 53# the timestamps appear to go backwards at some point. Stopping KTR logging 54# while the workload is still running is to avoid wasting log entries on 55# "idle" time at the end. 56# - Dump the trace to a file: 'ktrdump -ct > ktr.out' |
57# - Run the python script: 'python schedgraph.py ktr.out' optionally provide 58# your cpu frequency in ghz: 'python schedgraph.py ktr.out 2.4' |
59# 60# To do: |
61# Add a per-source summary display 62# Click to move. 63# Hide rows 64# "Vertical rule" to help relate data in different rows 65# Mouse-over popup of full thread/event/row label (currently truncated) 66# More visible anchors for popup event windows |
67# 68# BUGS: 1) Only 8 CPUs are supported, more CPUs require more choices of 69# colours to represent them ;-) |
70 |
71eventcolors = [ 72 ("count", "red"), 73 ("running", "green"), 74 ("idle", "grey"), 75 ("yielding", "yellow"), 76 ("swapped", "violet"), 77 ("suspended", "purple"), 78 ("iwait", "grey"), 79 ("sleep", "blue"), 80 ("blocked", "dark red"), 81 ("runq add", "yellow"), 82 ("runq rem", "yellow"), 83 ("thread exit", "grey"), 84 ("proc exit", "grey"), 85 ("callwheel idle", "grey"), 86 ("callout running", "green"), 87 ("lock acquire", "blue"), 88 ("lock contest", "purple"), 89 ("failed lock try", "red"), 90 ("lock release", "grey"), 91 ("tick", "black"), 92 ("prio", "black"), 93 ("lend prio", "black"), 94 ("wokeup", "black") 95] 96 97cpucolors = [ 98 ("CPU 0", "light grey"), 99 ("CPU 1", "dark grey"), 100 ("CPU 2", "light blue"), 101 ("CPU 3", "light pink"), 102 ("CPU 4", "blanched almond"), 103 ("CPU 5", "slate grey"), 104 ("CPU 6", "tan"), 105 ("CPU 7", "thistle"), 106 ("CPU 8", "white") 107] 108 109colors = [ 110 "white", "thistle", "blanched almond", "tan", "chartreuse", 111 "dark red", "red", "pale violet red", "pink", "light pink", 112 "dark orange", "orange", "coral", "light coral", 113 "goldenrod", "gold", "yellow", "light yellow", 114 "dark green", "green", "light green", "light sea green", 115 "dark blue", "blue", "light blue", "steel blue", "light slate blue", 116 "dark violet", "violet", "purple", "blue violet", 117 "dark grey", "slate grey", "light grey", 118 "black", 119] 120colors.sort() 121 |
122ticksps = None 123status = None |
124colormap = None 125ktrfile = None 126clockfreq = None 127sources = [] |
128lineno = -1 129 |
130class Colormap: 131 def __init__(self, table): 132 self.table = table 133 self.map = {} 134 for entry in table: 135 self.map[entry[0]] = entry[1] 136 137 def lookup(self, name): 138 try: 139 color = self.map[name] 140 except: 141 color = colors[random.randrange(0, len(colors))] 142 print "Picking random color", color, "for", name 143 self.map[name] = color 144 self.table.append((name, color)) 145 return (color) 146 |
147def ticks2sec(ticks): 148 us = ticksps / 1000000 149 ticks /= us 150 if (ticks < 1000): 151 return (str(ticks) + "us") 152 ticks /= 1000 153 if (ticks < 1000): 154 return (str(ticks) + "ms") --- 31 unchanged lines hidden (view full) --- 186 187 def clear(self): 188 self.label.config(text="") 189 190 def startup(self, str): 191 self.set(str) 192 root.update() 193 |
194class ColorConf(Frame): 195 def __init__(self, master, name, color): |
196 Frame.__init__(self, master) |
197 if (graph.getstate(name) == "hidden"): 198 enabled = 0 199 else: 200 enabled = 1 |
201 self.name = name 202 self.color = StringVar() 203 self.color_default = color 204 self.color_current = color 205 self.color.set(color) 206 self.enabled = IntVar() 207 self.enabled_default = enabled 208 self.enabled_current = enabled 209 self.enabled.set(enabled) 210 self.draw() 211 212 def draw(self): 213 self.label = Label(self, text=self.name, anchor=W) 214 self.sample = Canvas(self, width=24, height=24, 215 bg='grey') 216 self.rect = self.sample.create_rectangle(0, 0, 24, 24, 217 fill=self.color.get()) |
218 self.list = OptionMenu(self, self.color, command=self.setcolor, 219 *colors) |
220 self.checkbox = Checkbutton(self, text="enabled", 221 variable=self.enabled) 222 self.label.grid(row=0, column=0, sticky=E+W) 223 self.sample.grid(row=0, column=1) 224 self.list.grid(row=0, column=2, sticky=E+W) 225 self.checkbox.grid(row=0, column=3) 226 self.columnconfigure(0, weight=1) |
227 self.columnconfigure(2, minsize=150) |
228 229 def setcolor(self, color): 230 self.color.set(color) 231 self.sample.itemconfigure(self.rect, fill=color) 232 233 def apply(self): 234 cchange = 0 235 echange = 0 --- 8 unchanged lines hidden (view full) --- 244 graph.setcolor(self.name, self.color_current) 245 else: 246 graph.hide(self.name) 247 return 248 if (cchange != 0): 249 graph.setcolor(self.name, self.color_current) 250 251 def revert(self): |
252 self.setcolor(self.color_default) 253 self.enabled.set(self.enabled_default) 254 |
255class ColorConfigure(Toplevel): 256 def __init__(self, table, name): |
257 Toplevel.__init__(self) 258 self.resizable(0, 0) |
259 self.title(name) 260 self.items = LabelFrame(self, text="Item Type") |
261 self.buttons = Frame(self) 262 self.drawbuttons() 263 self.items.grid(row=0, column=0, sticky=E+W) 264 self.columnconfigure(0, weight=1) 265 self.buttons.grid(row=1, column=0, sticky=E+W) 266 self.types = [] 267 self.irow = 0 |
268 for type in table: 269 color = graph.getcolor(type[0]) 270 if (color != ""): 271 self.additem(type[0], color) |
272 |
273 def additem(self, name, color): 274 item = ColorConf(self.items, name, color) |
275 self.types.append(item) 276 item.grid(row=self.irow, column=0, sticky=E+W) 277 self.irow += 1 278 279 def drawbuttons(self): 280 self.apply = Button(self.buttons, text="Apply", 281 command=self.apress) |
282 self.default = Button(self.buttons, text="Revert", |
283 command=self.rpress) |
284 self.apply.grid(row=0, column=0, sticky=E+W) |
285 self.default.grid(row=0, column=1, sticky=E+W) |
286 self.buttons.columnconfigure(0, weight=1) 287 self.buttons.columnconfigure(1, weight=1) |
288 289 def apress(self): 290 for item in self.types: 291 item.apply() 292 293 def rpress(self): 294 for item in self.types: 295 item.revert() 296 |
297class EventView(Toplevel): 298 def __init__(self, event, canvas): 299 Toplevel.__init__(self) 300 self.resizable(0, 0) 301 self.title("Event") 302 self.event = event |
303 self.buttons = Frame(self) |
304 self.buttons.grid(row=0, column=0, sticky=E+W) 305 self.frame = Frame(self) 306 self.frame.grid(row=1, column=0, sticky=N+S+E+W) |
307 self.canvas = canvas 308 self.drawlabels() 309 self.drawbuttons() 310 event.displayref(canvas) 311 self.bind("<Destroy>", self.destroycb) 312 313 def destroycb(self, event): 314 self.unbind("<Destroy>") --- 5 unchanged lines hidden (view full) --- 320 def clearlabels(self): 321 for label in self.frame.grid_slaves(): 322 label.grid_remove() 323 324 def drawlabels(self): 325 ypos = 0 326 labels = self.event.labels() 327 while (len(labels) < 7): |
328 labels.append(("", "")) |
329 for label in labels: |
330 name, value = label 331 linked = 0 332 if (name == "linkedto"): 333 linked = 1 |
334 l = Label(self.frame, text=name, bd=1, width=15, 335 relief=SUNKEN, anchor=W) 336 if (linked): 337 fgcolor = "blue" 338 else: 339 fgcolor = "black" 340 r = Label(self.frame, text=value, bd=1, 341 relief=SUNKEN, anchor=W, fg=fgcolor) --- 22 unchanged lines hidden (view full) --- 364 365 def npress(self): 366 EventView(self.event, self.canvas) 367 368 def bpress(self): 369 prev = self.event.prev() 370 if (prev == None): 371 return |
372 while (prev.type == "pad"): |
373 prev = prev.prev() 374 if (prev == None): 375 return 376 self.newevent(prev) 377 378 def fpress(self): 379 next = self.event.next() 380 if (next == None): 381 return |
382 while (next.type == "pad"): |
383 next = next.next() 384 if (next == None): 385 return 386 self.newevent(next) 387 388 def linkpress(self, wevent): 389 event = self.event.getlinked() 390 if (event != None): 391 self.newevent(event) 392 393class Event: |
394 def __init__(self, source, name, cpu, timestamp, attrs): |
395 self.source = source |
396 self.name = name |
397 self.cpu = cpu 398 self.timestamp = int(timestamp) |
399 self.attrs = attrs |
400 self.idx = None |
401 self.item = None 402 self.dispcnt = 0 |
403 self.recno = lineno |
404 405 def status(self): 406 statstr = self.name + " " + self.source.name 407 statstr += " on: cpu" + str(self.cpu) 408 statstr += " at: " + str(self.timestamp) |
409 statstr += " attributes: " 410 for i in range(0, len(self.attrs)): 411 attr = self.attrs[i] 412 statstr += attr[0] + ": " + str(attr[1]) 413 if (i != len(self.attrs) - 1): 414 statstr += ", " |
415 status.set(statstr) 416 |
417 def labels(self): |
418 return [("Source", self.source.name), 419 ("Event", self.name), 420 ("CPU", self.cpu), 421 ("Timestamp", self.timestamp), 422 ("KTR Line ", self.recno) 423 ] + self.attrs 424 425 def mouseenter(self, canvas): |
426 self.displayref(canvas) 427 self.status() 428 |
429 def mouseexit(self, canvas): |
430 self.displayunref(canvas) 431 status.clear() 432 |
433 def mousepress(self, canvas): |
434 EventView(self, canvas) 435 |
436 def draw(self, canvas, xpos, ypos, item): 437 self.item = item 438 if (item != None): 439 canvas.items[item] = self 440 441 def move(self, canvas, x, y): 442 if (self.item == None): 443 return; 444 canvas.move(self.item, x, y); 445 |
446 def next(self): 447 return self.source.eventat(self.idx + 1) 448 |
449 def nexttype(self, type): 450 next = self.next() 451 while (next != None and next.type != type): 452 next = next.next() 453 return (next) 454 |
455 def prev(self): 456 return self.source.eventat(self.idx - 1) 457 458 def displayref(self, canvas): 459 if (self.dispcnt == 0): 460 canvas.itemconfigure(self.item, width=2) 461 self.dispcnt += 1 462 463 def displayunref(self, canvas): 464 self.dispcnt -= 1 465 if (self.dispcnt == 0): 466 canvas.itemconfigure(self.item, width=0) 467 canvas.tag_raise("point", "state") 468 469 def getlinked(self): |
470 for attr in self.attrs: 471 if (attr[0] != "linkedto"): 472 continue 473 source = ktrfile.findid(attr[1]) 474 return source.findevent(self.timestamp) 475 return None |
476 477class PointEvent(Event): |
478 type = "point" 479 def __init__(self, source, name, cpu, timestamp, attrs): 480 Event.__init__(self, source, name, cpu, timestamp, attrs) |
481 482 def draw(self, canvas, xpos, ypos): |
483 color = colormap.lookup(self.name) |
484 l = canvas.create_oval(xpos - 6, ypos + 1, xpos + 6, ypos - 11, |
485 fill=color, tags=("all", "point", "event", self.name), 486 width=0) 487 Event.draw(self, canvas, xpos, ypos, l) |
488 |
489 return xpos |
490 491class StateEvent(Event): |
492 type = "state" 493 def __init__(self, source, name, cpu, timestamp, attrs): 494 Event.__init__(self, source, name, cpu, timestamp, attrs) |
495 496 def draw(self, canvas, xpos, ypos): |
497 next = self.nexttype("state") 498 if (next == None): |
499 return (xpos) |
500 duration = next.timestamp - self.timestamp 501 self.attrs.insert(0, ("duration", ticks2sec(duration))) 502 color = colormap.lookup(self.name) 503 if (duration < 0): 504 duration = 0 |
505 print "Unsynchronized timestamp" 506 print self.cpu, self.timestamp 507 print next.cpu, next.timestamp |
508 delta = duration / canvas.ratio |
509 l = canvas.create_rectangle(xpos, ypos, |
510 xpos + delta, ypos - 10, fill=color, width=0, 511 tags=("all", "state", "event", self.name)) 512 Event.draw(self, canvas, xpos, ypos, l) |
513 514 return (xpos + delta) 515 |
516class CountEvent(Event): 517 type = "count" 518 def __init__(self, source, count, cpu, timestamp, attrs): 519 count = int(count) 520 self.count = count 521 Event.__init__(self, source, "count", cpu, timestamp, attrs) |
522 |
523 def draw(self, canvas, xpos, ypos): |
524 next = self.nexttype("count") 525 if (next == None): 526 return (xpos) 527 color = colormap.lookup("count") 528 duration = next.timestamp - self.timestamp 529 self.attrs.insert(0, ("count", self.count)) 530 self.attrs.insert(1, ("duration", ticks2sec(duration))) 531 delta = duration / canvas.ratio |
532 yhight = self.source.yscale() * self.count 533 l = canvas.create_rectangle(xpos, ypos - yhight, |
534 xpos + delta, ypos, fill=color, width=0, 535 tags=("all", "count", "event", self.name)) 536 Event.draw(self, canvas, xpos, ypos, l) |
537 return (xpos + delta) 538 |
539class PadEvent(StateEvent): 540 type = "pad" 541 def __init__(self, source, cpu, timestamp, last=0): 542 if (last): 543 cpu = source.events[len(source.events) -1].cpu 544 else: 545 cpu = source.events[0].cpu 546 StateEvent.__init__(self, source, "pad", cpu, timestamp, []) |
547 def draw(self, canvas, xpos, ypos): 548 next = self.next() 549 if (next == None): 550 return (xpos) |
551 duration = next.timestamp - self.timestamp 552 delta = duration / canvas.ratio 553 Event.draw(self, canvas, xpos, ypos, None) |
554 return (xpos + delta) 555 |
556class EventSource: |
557 def __init__(self, group, id): 558 self.name = id |
559 self.events = [] |
560 self.cpuitems = [] |
561 self.group = group |
562 self.y = 0 563 self.item = None |
564 565 def __cmp__(self, other): |
566 if (other == None): 567 return -1 |
568 if (self.group == other.group): |
569 return cmp(self.name, other.name) |
570 return cmp(self.group, other.group) 571 572 # It is much faster to append items to a list then to insert them 573 # at the beginning. As a result, we add events in reverse order 574 # and then swap the list during fixup. 575 def fixup(self): 576 self.events.reverse() 577 |
578 def addevent(self, event): |
579 self.events.append(event) 580 |
581 def addlastevent(self, event): |
582 self.events.insert(0, event) 583 584 def draw(self, canvas, ypos): 585 xpos = 10 |
586 cpux = 10 587 cpu = self.events[1].cpu |
588 for i in range(0, len(self.events)): 589 self.events[i].idx = i 590 for event in self.events: |
591 if (event.cpu != cpu and event.cpu != -1): 592 self.drawcpu(canvas, cpu, cpux, xpos, ypos) 593 cpux = xpos 594 cpu = event.cpu |
595 xpos = event.draw(canvas, xpos, ypos) |
596 self.drawcpu(canvas, cpu, cpux, xpos, ypos) |
597 598 def drawname(self, canvas, ypos): |
599 self.y = ypos |
600 ypos = ypos - (self.ysize() / 2) |
601 self.item = canvas.create_text(10, ypos, anchor="w", text=self.name) 602 return (self.item) |
603 |
604 def drawcpu(self, canvas, cpu, fromx, tox, ypos): 605 cpu = "CPU " + str(cpu) 606 color = cpucolormap.lookup(cpu) 607 # Create the cpu background colors default to hidden 608 l = canvas.create_rectangle(fromx, |
609 ypos - self.ysize() - canvas.bdheight, |
610 tox, ypos + canvas.bdheight, fill=color, width=0, 611 tags=("all", "cpuinfo", cpu), state="hidden") 612 self.cpuitems.append(l) |
613 |
614 def move(self, canvas, xpos, ypos): 615 for event in self.events: 616 event.move(canvas, xpos, ypos) 617 for item in self.cpuitems: 618 canvas.move(item, xpos, ypos) 619 620 def movename(self, canvas, xpos, ypos): 621 self.y += ypos 622 canvas.move(self.item, xpos, ypos) 623 |
624 def ysize(self): |
625 return (10) |
626 627 def eventat(self, i): 628 if (i >= len(self.events)): 629 return (None) 630 event = self.events[i] 631 return (event) 632 633 def findevent(self, timestamp): 634 for event in self.events: |
635 if (event.timestamp >= timestamp and event.type != "pad"): |
636 return (event) 637 return (None) 638 |
639class Counter(EventSource): 640 # 641 # Store a hash of counter groups that keeps the max value 642 # for a counter in this group for scaling purposes. 643 # 644 groups = {} 645 def __init__(self, group, id): |
646 try: |
647 Counter.cnt = Counter.groups[group] |
648 except: |
649 Counter.groups[group] = 0 650 EventSource.__init__(self, group, id) |
651 652 def fixup(self): |
653 for event in self.events: 654 if (event.type != "count"): 655 continue; 656 count = int(event.count) 657 if (count > Counter.groups[self.group]): 658 Counter.groups[self.group] = count |
659 EventSource.fixup(self) |
660 |
661 def ymax(self): |
662 return (Counter.groups[self.group]) |
663 664 def ysize(self): 665 return (80) 666 667 def yscale(self): |
668 return (self.ysize() / self.ymax()) |
669 |
670class KTRFile: 671 def __init__(self, file): 672 self.timestamp_f = None 673 self.timestamp_l = None |
674 self.locks = {} 675 self.callwheels = {} 676 self.ticks = {} 677 self.load = {} 678 self.crit = {} 679 self.stathz = 0 680 681 self.parse(file) 682 self.fixup() 683 global ticksps |
684 ticksps = self.ticksps() |
685 timespan = self.timespan() 686 print "first tick", self.timestamp_f, 687 print "last tick", self.timestamp_l |
688 print "Ticks per second", ticksps |
689 print "time span", timespan, "ticks", ticks2sec(timespan) |
690 691 def parse(self, file): 692 try: 693 ifp = open(file) 694 except: 695 print "Can't open", file 696 sys.exit(1) 697 |
698 # quoteexp matches a quoted string, no escaping 699 quoteexp = "\"([^\"]*)\"" |
700 |
701 # 702 # commaexp matches a quoted string OR the string up 703 # to the first ',' 704 # 705 commaexp = "(?:" + quoteexp + "|([^,]+))" |
706 |
707 # 708 # colonstr matches a quoted string OR the string up 709 # to the first ':' 710 # 711 colonexp = "(?:" + quoteexp + "|([^:]+))" |
712 |
713 # 714 # Match various manditory parts of the KTR string this is 715 # fairly inflexible until you get to attributes to make 716 # parsing faster. 717 # 718 hdrexp = "\s*(\d+)\s+(\d+)\s+(\d+)\s+" 719 groupexp = "KTRGRAPH group:" + quoteexp + ", " 720 idexp = "id:" + quoteexp + ", " 721 typeexp = "([^:]+):" + commaexp + ", " 722 attribexp = "attributes: (.*)" |
723 |
724 # 725 # Matches optional attributes in the KTR string. This 726 # tolerates more variance as the users supply these values. 727 # 728 attrexp = colonexp + "\s*:\s*(?:" + commaexp + ", (.*)|" 729 attrexp += quoteexp +"|(.*))" |
730 |
731 # Precompile regexp 732 ktrre = re.compile(hdrexp + groupexp + idexp + typeexp + attribexp) 733 attrre = re.compile(attrexp) |
734 |
735 global lineno 736 lineno = 0 737 for line in ifp.readlines(): 738 lineno += 1 |
739 if ((lineno % 2048) == 0): |
740 status.startup("Parsing line " + str(lineno)) |
741 m = ktrre.match(line); |
742 if (m == None): |
743 print "Can't parse", lineno, line, 744 continue; 745 (index, cpu, timestamp, group, id, type, dat, dat1, attrstring) = m.groups(); 746 if (dat == None): 747 dat = dat1 748 if (self.checkstamp(timestamp) == 0): 749 print "Bad timestamp at", lineno, ":", line, 750 continue 751 # 752 # Build the table of optional attributes 753 # 754 attrs = [] 755 while (attrstring != None): 756 m = attrre.match(attrstring.strip()) 757 if (m == None): 758 break; 759 # 760 # Name may or may not be quoted. 761 # 762 # For val we have four cases: 763 # 1) quotes followed by comma and more 764 # attributes. 765 # 2) no quotes followed by comma and more 766 # attributes. 767 # 3) no more attributes or comma with quotes. 768 # 4) no more attributes or comma without quotes. 769 # 770 (name, name1, val, val1, attrstring, end, end1) = m.groups(); 771 if (name == None): 772 name = name1 773 if (end == None): 774 end = end1 775 if (val == None): 776 val = val1 777 if (val == None): 778 val = end 779 if (name == "stathz"): 780 self.setstathz(val, cpu) 781 attrs.append((name, val)) 782 args = (dat, cpu, timestamp, attrs) 783 e = self.makeevent(group, id, type, args) 784 if (e == None): 785 print "Unknown type", type, lineno, line, |
786 |
787 def makeevent(self, group, id, type, args): 788 e = None 789 source = self.makeid(group, id, type) 790 if (type == "state"): 791 e = StateEvent(source, *args) 792 elif (type == "counter"): 793 e = CountEvent(source, *args) 794 elif (type == "point"): 795 e = PointEvent(source, *args) 796 if (e != None): 797 source.addevent(e); 798 return e |
799 |
800 def setstathz(self, val, cpu): 801 self.stathz = int(val) |
802 cpu = int(cpu) 803 try: 804 ticks = self.ticks[cpu] 805 except: 806 self.ticks[cpu] = 0 807 self.ticks[cpu] += 1 |
808 |
809 def checkstamp(self, timestamp): 810 timestamp = int(timestamp) 811 if (self.timestamp_f == None): 812 self.timestamp_f = timestamp; 813 if (self.timestamp_l != None and timestamp > self.timestamp_l): 814 return (0) 815 self.timestamp_l = timestamp; 816 return (1) |
817 |
818 def makeid(self, group, id, type): 819 for source in sources: 820 if (source.name == id and source.group == group): 821 return source 822 if (type == "counter"): 823 source = Counter(group, id) 824 else: 825 source = EventSource(group, id) 826 sources.append(source) 827 return (source) |
828 |
829 def findid(self, id): 830 for source in sources: 831 if (source.name == id): 832 return source 833 return (None) |
834 |
835 def timespan(self): 836 return (self.timestamp_f - self.timestamp_l); |
837 |
838 def ticksps(self): 839 oneghz = 1000000000 840 # Use user supplied clock first 841 if (clockfreq != None): 842 return int(clockfreq * oneghz) |
843 |
844 # Check for a discovered clock 845 if (self.stathz != None): 846 return (self.timespan() / self.ticks[0]) * int(self.stathz) 847 # Pretend we have a 1ns clock 848 print "WARNING: No clock discovered and no frequency ", 849 print "specified via the command line." 850 print "Using fake 1ghz clock" 851 return (oneghz); |
852 |
853 def fixup(self): 854 for source in sources: 855 e = PadEvent(source, -1, self.timestamp_l) 856 source.addevent(e) 857 e = PadEvent(source, -1, self.timestamp_f, last=1) 858 source.addlastevent(e) 859 source.fixup() 860 sources.sort() |
861 |
862class SchedNames(Canvas): 863 def __init__(self, master, display): 864 self.display = display 865 self.parent = master 866 self.bdheight = master.bdheight 867 self.items = {} 868 self.ysize = 0 869 self.lines = [] 870 Canvas.__init__(self, master, width=120, 871 height=display["height"], bg='grey', 872 scrollregion=(0, 0, 50, 100)) |
873 |
874 def moveline(self, cur_y, y): 875 for line in self.lines: 876 (x0, y0, x1, y1) = self.coords(line) 877 if (cur_y != y0): 878 continue 879 self.move(line, 0, y) |
880 return |
881 |
882 def draw(self): 883 status.startup("Drawing names") 884 ypos = 0 885 self.configure(scrollregion=(0, 0, 886 self["width"], self.display.ysize())) 887 for source in sources: 888 l = self.create_line(0, ypos, self["width"], ypos, 889 width=1, fill="black", tags=("all","sources")) 890 self.lines.append(l) 891 ypos += self.bdheight 892 ypos += source.ysize() 893 t = source.drawname(self, ypos) 894 self.items[t] = source 895 ypos += self.bdheight 896 self.ysize = ypos 897 self.create_line(0, ypos, self["width"], ypos, 898 width=1, fill="black", tags=("all",)) 899 self.bind("<Button-1>", self.master.mousepress); 900 self.bind("<ButtonRelease-1>", self.master.mouserelease); 901 self.bind("<B1-Motion>", self.master.mousemotion); |
902 |
903 |
904class SchedDisplay(Canvas): 905 def __init__(self, master): 906 self.ratio = 1 |
907 self.parent = master |
908 self.bdheight = master.bdheight 909 self.items = {} 910 self.lines = [] |
911 Canvas.__init__(self, master, width=800, height=500, bg='grey', 912 scrollregion=(0, 0, 800, 500)) 913 |
914 def prepare(self): 915 # |
916 # Compute a ratio to ensure that the file's timespan fits into 917 # 2^31. Although python may handle larger values for X 918 # values, the Tk internals do not. |
919 # |
920 self.ratio = (ktrfile.timespan() - 1) / 2**31 + 1 921 922 def draw(self): 923 ypos = 0 924 xsize = self.xsize() |
925 for source in sources: |
926 status.startup("Drawing " + source.name) |
927 l = self.create_line(0, ypos, xsize, ypos, |
928 width=1, fill="black", tags=("all",)) |
929 self.lines.append(l) |
930 ypos += self.bdheight 931 ypos += source.ysize() 932 source.draw(self, ypos) 933 ypos += self.bdheight 934 try: 935 self.tag_raise("point", "state") 936 self.tag_lower("cpuinfo", "all") 937 except: 938 pass 939 self.create_line(0, ypos, xsize, ypos, 940 width=1, fill="black", tags=("all",)) 941 self.tag_bind("event", "<Enter>", self.mouseenter) 942 self.tag_bind("event", "<Leave>", self.mouseexit) |
943 self.bind("<Button-1>", self.mousepress) |
944 self.bind("<Button-4>", self.wheelup) 945 self.bind("<Button-5>", self.wheeldown) |
946 self.bind("<ButtonRelease-1>", self.master.mouserelease); 947 self.bind("<B1-Motion>", self.master.mousemotion); |
948 |
949 def moveline(self, cur_y, y): 950 for line in self.lines: 951 (x0, y0, x1, y1) = self.coords(line) 952 if (cur_y != y0): 953 continue 954 self.move(line, 0, y) 955 return 956 |
957 def mouseenter(self, event): 958 item, = self.find_withtag(CURRENT) |
959 self.items[item].mouseenter(self) |
960 961 def mouseexit(self, event): 962 item, = self.find_withtag(CURRENT) |
963 self.items[item].mouseexit(self) |
964 965 def mousepress(self, event): |
966 # Find out what's beneath us 967 items = self.find_withtag(CURRENT) 968 if (len(items) == 0): 969 self.master.mousepress(event) 970 return 971 # Only grab mouse presses for things with event tags. 972 item = items[0] 973 tags = self.gettags(item) 974 for tag in tags: 975 if (tag == "event"): 976 self.items[item].mousepress(self) 977 return 978 # Leave the rest to the master window 979 self.master.mousepress(event) |
980 981 def wheeldown(self, event): 982 self.parent.display_yview("scroll", 1, "units") 983 984 def wheelup(self, event): 985 self.parent.display_yview("scroll", -1, "units") 986 |
987 def xsize(self): |
988 return ((ktrfile.timespan() / self.ratio) + 20) |
989 990 def ysize(self): 991 ysize = 0 |
992 for source in sources: |
993 ysize += source.ysize() + (self.bdheight * 2) |
994 return ysize |
995 996 def scaleset(self, ratio): |
997 if (ktrfile == None): |
998 return 999 oldratio = self.ratio |
1000 xstart, xend = self.xview() 1001 midpoint = xstart + ((xend - xstart) / 2) |
1002 1003 self.ratio = ratio 1004 self.configure(scrollregion=(0, 0, self.xsize(), self.ysize())) 1005 self.scale("all", 0, 0, float(oldratio) / ratio, 1) 1006 |
1007 xstart, xend = self.xview() 1008 xsize = (xend - xstart) / 2 1009 self.xview_moveto(midpoint - xsize) |
1010 1011 def scaleget(self): 1012 return self.ratio 1013 |
1014 def getcolor(self, tag): 1015 return self.itemcget(tag, "fill") 1016 1017 def getstate(self, tag): 1018 return self.itemcget(tag, "state") 1019 |
1020 def setcolor(self, tag, color): 1021 self.itemconfigure(tag, state="normal", fill=color) 1022 1023 def hide(self, tag): 1024 self.itemconfigure(tag, state="hidden") 1025 1026class GraphMenu(Frame): 1027 def __init__(self, master): 1028 Frame.__init__(self, master, bd=2, relief=RAISED) 1029 self.view = Menubutton(self, text="Configure") 1030 self.viewmenu = Menu(self.view, tearoff=0) |
1031 self.viewmenu.add_command(label="Event Colors", |
1032 command=self.econf) |
1033 self.viewmenu.add_command(label="CPU Colors", 1034 command=self.cconf) |
1035 self.view["menu"] = self.viewmenu 1036 self.view.pack(side=LEFT) 1037 1038 def econf(self): |
1039 ColorConfigure(eventcolors, "Event Display Configuration") |
1040 |
1041 def cconf(self): 1042 ColorConfigure(cpucolors, "CPU Background Colors") |
1043 |
1044 |
1045class SchedGraph(Frame): 1046 def __init__(self, master): 1047 Frame.__init__(self, master) 1048 self.menu = None 1049 self.names = None 1050 self.display = None 1051 self.scale = None 1052 self.status = None |
1053 self.bdheight = 10 1054 self.clicksource = None 1055 self.lastsource = None |
1056 self.pack(expand=1, fill="both") 1057 self.buildwidgets() 1058 self.layout() |
1059 1060 def buildwidgets(self): 1061 global status 1062 self.menu = GraphMenu(self) 1063 self.display = SchedDisplay(self) |
1064 self.names = SchedNames(self, self.display) |
1065 self.scale = Scaler(self, self.display) 1066 status = self.status = Status(self) 1067 self.scrollY = Scrollbar(self, orient="vertical", 1068 command=self.display_yview) 1069 self.display.scrollX = Scrollbar(self, orient="horizontal", 1070 command=self.display.xview) 1071 self.display["xscrollcommand"] = self.display.scrollX.set 1072 self.display["yscrollcommand"] = self.scrollY.set --- 6 unchanged lines hidden (view full) --- 1079 self.names.grid(row=1, column=0, sticky=N+S) 1080 self.display.grid(row=1, column=1, sticky=W+E+N+S) 1081 self.scrollY.grid(row=1, column=2, sticky=N+S) 1082 self.display.scrollX.grid(row=2, column=0, columnspan=2, 1083 sticky=E+W) 1084 self.scale.grid(row=3, column=0, columnspan=3, sticky=E+W) 1085 self.status.grid(row=4, column=0, columnspan=3, sticky=E+W) 1086 |
1087 def draw(self): |
1088 self.master.update() |
1089 self.display.prepare() 1090 self.names.draw() |
1091 self.display.draw() |
1092 self.status.startup("") |
1093 self.scale.set(250000) 1094 self.display.xview_moveto(0) 1095 |
1096 def mousepress(self, event): 1097 self.clicksource = self.sourceat(event.y) 1098 1099 def mouserelease(self, event): 1100 if (self.clicksource == None): 1101 return 1102 newsource = self.sourceat(event.y) 1103 if (self.clicksource != newsource): 1104 self.sourceswap(self.clicksource, newsource) 1105 self.clicksource = None 1106 self.lastsource = None 1107 1108 def mousemotion(self, event): 1109 if (self.clicksource == None): 1110 return 1111 newsource = self.sourceat(event.y) 1112 # 1113 # If we get a None source they moved off the page. 1114 # swapsource() can't handle moving multiple items so just 1115 # pretend we never clicked on anything to begin with so the 1116 # user can't mouseover a non-contiguous area. 1117 # 1118 if (newsource == None): 1119 self.clicksource = None 1120 self.lastsource = None 1121 return 1122 if (newsource == self.lastsource): 1123 return; 1124 self.lastsource = newsource 1125 if (newsource != self.clicksource): 1126 self.sourceswap(self.clicksource, newsource) 1127 1128 # These are here because this object controls layout 1129 def sourcestart(self, source): 1130 return source.y - self.bdheight - source.ysize() 1131 1132 def sourceend(self, source): 1133 return source.y + self.bdheight 1134 1135 def sourcesize(self, source): 1136 return (self.bdheight * 2) + source.ysize() 1137 1138 def sourceswap(self, source1, source2): 1139 # Sort so we always know which one is on top. 1140 if (source2.y < source1.y): 1141 swap = source1 1142 source1 = source2 1143 source2 = swap 1144 # Only swap adjacent sources 1145 if (self.sourceend(source1) != self.sourcestart(source2)): 1146 return 1147 # Compute start coordinates and target coordinates 1148 y1 = self.sourcestart(source1) 1149 y2 = self.sourcestart(source2) 1150 y1targ = y1 + self.sourcesize(source2) 1151 y2targ = y1 1152 # 1153 # If the sizes are not equal, adjust the start of the lower 1154 # source to account for the lost/gained space. 1155 # 1156 if (source1.ysize() != source2.ysize()): 1157 diff = source2.ysize() - source1.ysize() 1158 self.names.moveline(y2, diff); 1159 self.display.moveline(y2, diff) 1160 source1.move(self.display, 0, y1targ - y1) 1161 source2.move(self.display, 0, y2targ - y2) 1162 source1.movename(self.names, 0, y1targ - y1) 1163 source2.movename(self.names, 0, y2targ - y2) 1164 1165 def sourceat(self, ypos): 1166 (start, end) = self.names.yview() 1167 starty = start * float(self.names.ysize) 1168 ypos += starty 1169 for source in sources: 1170 yend = self.sourceend(source) 1171 ystart = self.sourcestart(source) 1172 if (ypos >= ystart and ypos <= yend): 1173 return source 1174 return None 1175 |
1176 def display_yview(self, *args): 1177 self.names.yview(*args) 1178 self.display.yview(*args) 1179 1180 def setcolor(self, tag, color): 1181 self.display.setcolor(tag, color) 1182 1183 def hide(self, tag): 1184 self.display.hide(tag) 1185 |
1186 def getcolor(self, tag): 1187 return self.display.getcolor(tag) 1188 1189 def getstate(self, tag): 1190 return self.display.getstate(tag) 1191 1192if (len(sys.argv) != 2 and len(sys.argv) != 3): 1193 print "usage:", sys.argv[0], "<ktr file> [clock freq in ghz]" |
1194 sys.exit(1) 1195 |
1196if (len(sys.argv) > 2): 1197 clockfreq = float(sys.argv[2]) 1198 |
1199root = Tk() 1200root.title("Scheduler Graph") |
1201colormap = Colormap(eventcolors) 1202cpucolormap = Colormap(cpucolors) |
1203graph = SchedGraph(root) |
1204ktrfile = KTRFile(sys.argv[1]) 1205graph.draw() |
1206root.mainloop() |