diff --git a/tools/vipsprofile.py b/tools/vipsprofile.py index 533a115d..d64c06f1 100755 --- a/tools/vipsprofile.py +++ b/tools/vipsprofile.py @@ -4,8 +4,6 @@ import re import math import cairo -WIDTH, HEIGHT = 256, 256 - class ReadFile: def __init__(self, filename): self.filename = filename @@ -40,20 +38,34 @@ def read_times(rf): return times[::-1] -class Event: - def __init__(self, thread_name, gate_name, start, stop): +class Thread: + thread_number = 0 + + def __init__(self, thread_name): self.thread_name = thread_name + 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 self.gate_name = gate_name self.start = start self.stop = stop - if re.match(': work', gate_name): + self.work = False + self.wait = False + if re.match('.*: .*work.*', gate_name): self.work = True - if re.match(': wait', gate_name): + if re.match('.*: .*wait.*', gate_name): self.wait = True -events = [] + thread.events.append(self) + thread_id = 0 +threads = [] +n_events = 0 with ReadFile('vips-profile.txt') as rf: while rf: match = re.match('thread: (.*)', rf.line) @@ -61,6 +73,8 @@ with ReadFile('vips-profile.txt') as rf: print 'parse error line %d, expected "thread"' % rf.lineno thread_name = match.group(1) + " " + str(thread_id) thread_id += 1 + thread = Thread(thread_name) + threads.append(thread) rf.getnext() while True: @@ -88,54 +102,95 @@ with ReadFile('vips-profile.txt') as rf: print 'start and stop length mismatch' for a, b in zip(start, stop): - event = Event(thread_name, gate_name, a, b) - events.append(event) + Event(thread, gate_name, a, b) + n_events += 1 -events.sort(lambda x, y: cmp(x.start, y.start)) +for thread in threads: + thread.events.sort(lambda x, y: cmp(x.start, y.start)) -print 'loaded %d events' % len(events) +print 'loaded %d events' % n_events # normalise time axis to secs of computation ticks_per_sec = 1000000.0 -start_time = events[0].start -for event in events: - event.start = (event.start - start_time) / ticks_per_sec - event.stop = (event.stop - start_time) / ticks_per_sec -last_time = events[-1].stop +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 + + if event.stop > last_time: + last_time = event.stop + print 'last time =', last_time -# within each thread, allocate a Y position -threads = [] -for event in events: - if not event.thread_name in threads: - threads.append(event.thread_name) +# do two gates overlap? +def is_overlap(events, gate_name1, gate_name2): + for event1 in events: + if event1.gate_name != gate_name1: + continue + 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: + y = 1 + gate_positions = {} + for event in thread.events: + if event.work or event.wait: + gate_positions[event.gate_name] = 0 + elif not event.gate_name in gate_positions: + overlap = True + for gate_name in gate_positions: + if not is_overlap(thread.events, gate_name, event.gate_name): + gate_positions[event.gate_name] = gate_positions[gate_name] + overlap = False + break + + if overlap: + gate_positions[event.gate_name] = y + y += 1 + + event.y = gate_positions[event.gate_name] + event.total_y = total_y + y + + total_y += y + +PIXELS_PER_SECOND = 1000 +PIXELS_PER_GATE = 20 +WIDTH = int(last_time * PIXELS_PER_SECOND) +HEIGHT = int(total_y * PIXELS_PER_GATE) surface = cairo.ImageSurface (cairo.FORMAT_ARGB32, WIDTH, HEIGHT) ctx = cairo.Context (surface) -ctx.scale (WIDTH, HEIGHT) # Normalizing the canvas +ctx.scale (PIXELS_PER_SECOND, PIXELS_PER_GATE) -pat = cairo.LinearGradient (0.0, 0.0, 0.0, 1.0) -pat.add_color_stop_rgba (1, 0.7, 0, 0, 0.5) # First stop, 50% opacity -pat.add_color_stop_rgba (0, 0.9, 0.7, 0.2, 1) # Last stop, 100% opacity +for thread in threads: + for event in thread.events: + ctx.move_to (event.start, event.total_y) + ctx.line_to (event.stop, event.total_y) + ctx.close_path () + ctx.set_line_width (0.5) -ctx.rectangle (0, 0, 1, 1) # Rectangle(x0, y0, x1, y1) -ctx.set_source (pat) -ctx.fill () + 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.translate (0.1, 0.1) # Changing the current transformation matrix - -ctx.move_to (0, 0) -ctx.arc (0.2, 0.1, 0.1, -math.pi/2, 0) # Arc(cx, cy, radius, start_angle, stop_angle) -ctx.line_to (0.5, 0.1) # Line to (x,y) -ctx.curve_to (0.5, 0.2, 0.5, 0.4, 0.2, 0.8) # Curve(x1, y1, x2, y2, x3, y3) -ctx.close_path () - -ctx.set_source_rgb (0.3, 0.2, 0.5) # Solid color -ctx.set_line_width (0.02) -ctx.stroke () + ctx.stroke () surface.write_to_png ("example.png") # Output to PNG