Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/tools/sched/schedgraph.py
39475 views
1
#!/usr/local/bin/python
2
3
# Copyright (c) 2002-2003, 2009, Jeffrey Roberson <[email protected]>
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
28
from __future__ import print_function
29
import sys
30
import re
31
import random
32
from operator import attrgetter, itemgetter
33
from functools import total_ordering
34
from 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
73
eventcolors = [
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
98
cpucolors = [
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
110
colors = [
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
]
121
colors.sort()
122
123
ticksps = None
124
status = None
125
colormap = None
126
ktrfile = None
127
clockfreq = None
128
sources = []
129
lineno = -1
130
131
Y_BORDER = 10
132
X_BORDER = 10
133
Y_COUNTER = 80
134
Y_EVENTSOURCE = 10
135
XY_POINT = 4
136
137
class 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
154
def 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
169
class 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
199
class 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
216
class 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
277
class 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
323
class 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
363
class 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
450
class 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
503
class 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
528
class 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
625
class 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
710
class 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
725
class 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
750
class 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
778
class 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
797
class 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
884
class 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
915
class 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
1117
class 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
1164
class 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
1289
class 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
1312
class 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
1628
if (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
1632
if (len(sys.argv) > 2):
1633
clockfreq = float(sys.argv[2])
1634
1635
root = Tk()
1636
root.title("SchedGraph")
1637
colormap = Colormap(eventcolors)
1638
cpucolormap = Colormap(cpucolors)
1639
graph = SchedGraph(root)
1640
ktrfile = KTRFile(sys.argv[1])
1641
graph.draw()
1642
root.mainloop()
1643
1644