libvips/tools/vipsprofile

321 lines
8.8 KiB
Plaintext
Raw Normal View History

2013-11-19 11:13:38 +01:00
#!/usr/bin/python
import re
2013-11-21 15:58:44 +01:00
import math
import cairo
2013-11-19 11:13:38 +01:00
class ReadFile:
def __init__(self, filename):
self.filename = filename
def __enter__(self):
self.f = open(self.filename, 'r')
self.lineno = 0
self.getnext();
return self
def __exit__(self, type, value, traceback):
self.f.close()
def __nonzero__(self):
return self.line != ""
def getnext(self):
self.lineno += 1
2013-11-25 15:45:06 +01:00
self.line = self.f.readline()
2013-11-19 11:13:38 +01:00
def read_times(rf):
times = []
while True:
match = re.match('[0-9]+ ', rf.line)
if not match:
break
2013-11-20 22:35:18 +01:00
times += [int(x) for x in re.split(' ', rf.line.rstrip())]
2013-11-19 11:13:38 +01:00
rf.getnext()
return times[::-1]
2013-11-25 11:36:37 +01:00
class Thread:
thread_number = 0
def __init__(self, thread_name):
# no one cares about the thread address
match = re.match('(.*) \(0x.*?\) (.*)', thread_name)
if match:
thread_name = match.group(1) + " " + match.group(2)
2013-11-19 15:59:40 +01:00
self.thread_name = thread_name
2013-11-25 11:36:37 +01:00
self.thread_number = Thread.thread_number
self.events = []
Thread.thread_number += 1
class Event:
def __init__(self, thread, gate_name, start, stop):
self.thread = thread
2013-11-19 15:59:40 +01:00
self.gate_name = gate_name
self.start = start
2013-11-20 22:35:18 +01:00
self.stop = stop
2013-11-19 15:59:40 +01:00
2013-11-25 11:36:37 +01:00
self.work = False
self.wait = False
2013-11-27 18:31:07 +01:00
if re.match('.*work.*', gate_name):
2013-11-19 15:59:40 +01:00
self.work = True
2013-11-27 18:31:07 +01:00
if re.match('.*wait.*', gate_name):
2013-11-19 15:59:40 +01:00
self.wait = True
2013-11-25 11:36:37 +01:00
thread.events.append(self)
2013-11-25 15:45:06 +01:00
input_filename = 'vips-profile.txt'
2013-11-21 15:58:44 +01:00
thread_id = 0
2013-11-25 11:36:37 +01:00
threads = []
n_events = 0
2013-11-25 15:45:06 +01:00
print 'reading from', input_filename
with ReadFile(input_filename) as rf:
2013-11-19 11:13:38 +01:00
while rf:
2013-11-25 15:45:06 +01:00
if rf.line.rstrip() == "":
rf.getnext()
continue
if rf.line[0] == "#":
rf.getnext()
continue
2013-11-21 15:58:44 +01:00
match = re.match('thread: (.*)', rf.line)
2013-11-19 11:13:38 +01:00
if not match:
print 'parse error line %d, expected "thread"' % rf.lineno
2013-11-21 15:58:44 +01:00
thread_name = match.group(1) + " " + str(thread_id)
thread_id += 1
2013-11-25 11:36:37 +01:00
thread = Thread(thread_name)
threads.append(thread)
2013-11-19 11:13:38 +01:00
rf.getnext()
while True:
2013-11-27 18:31:07 +01:00
match = re.match('gate: (.*?): (.*)', rf.line)
2013-11-19 11:13:38 +01:00
if not match:
break
2013-11-27 18:31:07 +01:00
gate_name = match.group(2)
2013-11-19 11:13:38 +01:00
rf.getnext()
match = re.match('start:', rf.line)
if not match:
continue
rf.getnext()
2013-11-19 15:59:40 +01:00
start = read_times(rf)
2013-11-19 11:13:38 +01:00
match = re.match('stop:', rf.line)
if not match:
continue
rf.getnext()
2013-11-19 15:59:40 +01:00
stop = read_times(rf)
if len(start) != len(stop):
print 'start and stop length mismatch'
2013-11-20 22:35:18 +01:00
for a, b in zip(start, stop):
2013-11-25 11:36:37 +01:00
Event(thread, gate_name, a, b)
n_events += 1
2013-11-20 22:35:18 +01:00
2013-11-25 11:36:37 +01:00
for thread in threads:
thread.events.sort(lambda x, y: cmp(x.start, y.start))
2013-11-20 22:35:18 +01:00
2013-11-25 11:36:37 +01:00
print 'loaded %d events' % n_events
2013-11-21 15:58:44 +01:00
# normalise time axis to secs of computation
ticks_per_sec = 1000000.0
2013-11-25 11:36:37 +01:00
start_time = threads[0].events[0].start
last_time = 0
for thread in threads:
for event in thread.events:
event.start = (event.start - start_time) / ticks_per_sec
event.stop = (event.stop - start_time) / ticks_per_sec
2013-11-21 15:58:44 +01:00
2013-11-25 11:36:37 +01:00
if event.stop > last_time:
last_time = event.stop
2013-11-21 15:58:44 +01:00
2013-11-25 11:36:37 +01:00
print 'last time =', last_time
2013-11-21 15:58:44 +01:00
# calculate some simple stats
for thread in threads:
thread.start = last_time
thread.stop = 0
thread.wait = 0
thread.work = 0
for event in thread.events:
if event.start < thread.start:
thread.start = event.start
if event.stop > thread.stop:
thread.stop = event.stop
if event.wait:
thread.wait += event.stop - event.start
2013-11-26 18:53:44 +01:00
if event.work:
thread.work += event.stop - event.start
thread.alive = thread.stop - thread.start
# hide very short-lived threads
thread.hide = thread.alive < 0.001
print 'name\t\talive\twait%\twork%\tunknown%'
for thread in threads:
if thread.hide:
continue
wait_percent = 100 * thread.wait / thread.alive
work_percent = 100 * thread.work / thread.alive
unkn_percent = 100 - 100 * (thread.work + thread.wait) / thread.alive
print '%13s\t%6.2g\t' % (thread.thread_name, thread.alive),
print '%.3g\t%.3g\t%.3g' % (wait_percent, work_percent, unkn_percent)
2013-11-25 11:36:37 +01:00
# do two gates overlap?
def is_overlap(events, gate_name1, gate_name2):
for event1 in events:
if event1.gate_name != gate_name1:
continue
2013-11-21 15:58:44 +01:00
2013-11-25 11:36:37 +01:00
for event2 in events:
if event2.gate_name != gate_name2:
continue
# if either endpoint of 1 is within 2
if event1.start > event2.start and event1.stop < event2.stop:
return True
if event1.stop > event2.start and event1.stop < event2.stop:
return True
return False
# allocate a y position for each gate
total_y = 0
for thread in threads:
if thread.hide:
continue
2013-11-25 15:45:06 +01:00
thread.total_y = total_y
2013-11-26 18:53:44 +01:00
# first pass .. move work and wait events to y == 0
2013-11-25 11:36:37 +01:00
gate_positions = {}
for event in thread.events:
2013-11-26 18:53:44 +01:00
if not event.work and not event.wait:
continue
# no works and waits must overlap
if not event.gate_name in gate_positions:
for gate_name in gate_positions:
if is_overlap(thread.events, event.gate_name, gate_name):
print 'gate', event.gate_name, 'and', gate_name, 'overlap'
break
2013-11-25 11:36:37 +01:00
gate_positions[event.gate_name] = 0
2013-11-26 18:53:44 +01:00
event.y = gate_positions[event.gate_name]
event.total_y = total_y + event.y
# second pass: move all other events to non-overlapping ys
y = 1
for event in thread.events:
if event.work or event.wait:
continue
if not event.gate_name in gate_positions:
2013-11-28 11:45:31 +01:00
# look at all the ys we've allocated previously and see if we can
# add this gate to one of them
for gate_y in range(1, y):
found_overlap = False
for gate_name in gate_positions:
if gate_positions[gate_name] != gate_y:
continue
if is_overlap(thread.events, event.gate_name, gate_name):
found_overlap = True
break
if not found_overlap:
gate_positions[event.gate_name] = gate_y
2013-11-25 11:36:37 +01:00
break
2013-11-28 11:45:31 +01:00
# failure? add a new y
if not event.gate_name in gate_positions:
2013-11-25 11:36:37 +01:00
gate_positions[event.gate_name] = y
y += 1
event.y = gate_positions[event.gate_name]
2013-11-28 11:45:31 +01:00
# third pass: flip the order of the ys to get the lowest-level ones at the
# top, next to the wait/work line
for event in thread.events:
if event.work or event.wait:
continue
event.y = y - event.y
2013-11-25 15:45:06 +01:00
event.total_y = total_y + event.y
2013-11-25 11:36:37 +01:00
total_y += y
PIXELS_PER_SECOND = 1000
PIXELS_PER_GATE = 20
LEFT_BORDER = 130
2013-11-25 15:45:06 +01:00
BAR_HEIGHT = 5
WIDTH = int(LEFT_BORDER + last_time * PIXELS_PER_SECOND) + 20
2013-11-25 15:45:06 +01:00
HEIGHT = int((total_y + 1) * PIXELS_PER_GATE)
output_filename = "vips-profile.svg"
print 'writing to', output_filename
surface = cairo.SVGSurface(output_filename, WIDTH, HEIGHT)
2013-11-25 15:45:06 +01:00
ctx = cairo.Context(surface)
ctx.select_font_face('Sans')
ctx.set_font_size(15)
ctx.rectangle(0, 0, WIDTH, HEIGHT)
ctx.set_source_rgba(0.0, 0.0, 0.3, 1.0)
ctx.fill()
2013-11-25 15:45:06 +01:00
def draw_event(ctx, event):
left = event.start * PIXELS_PER_SECOND + LEFT_BORDER
top = event.total_y * PIXELS_PER_GATE + BAR_HEIGHT / 2
width = (event.stop - event.start) * PIXELS_PER_SECOND
2013-11-25 15:45:06 +01:00
height = BAR_HEIGHT
ctx.rectangle(left, top, width, height)
if event.wait:
ctx.set_source_rgb(0.9, 0.1, 0.1)
elif event.work:
ctx.set_source_rgb(0.1, 0.9, 0.1)
else:
ctx.set_source_rgb(0.1, 0.1, 0.9)
ctx.fill()
if not event.wait and not event.work:
xbearing, ybearing, twidth, theight, xadvance, yadvance = \
ctx.text_extents(event.gate_name)
2013-11-29 10:40:54 +01:00
ctx.move_to(left + width / 2 - twidth / 2, top + 3 * BAR_HEIGHT)
2013-11-25 15:45:06 +01:00
ctx.set_source_rgb(1.00, 0.83, 0.00)
ctx.show_text(event.gate_name)
2013-11-21 15:58:44 +01:00
2013-11-25 11:36:37 +01:00
for thread in threads:
if thread.hide:
continue
ctx.rectangle(0, thread.total_y * PIXELS_PER_GATE, WIDTH, 1)
ctx.set_source_rgb(1.00, 1.00, 1.00)
ctx.fill()
2013-11-25 15:45:06 +01:00
xbearing, ybearing, twidth, theight, xadvance, yadvance = \
ctx.text_extents(thread.thread_name)
ctx.move_to(0, theight + thread.total_y * PIXELS_PER_GATE + BAR_HEIGHT / 2)
2013-11-25 15:45:06 +01:00
ctx.set_source_rgb(1.00, 1.00, 1.00)
ctx.show_text(thread.thread_name)
2013-11-25 11:36:37 +01:00
for event in thread.events:
2013-11-25 15:45:06 +01:00
draw_event(ctx, event)
2013-11-25 12:01:09 +01:00
surface.finish()