#!/usr/bin/python import re import math import cairo 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 self.line = self.f.readline() def read_times(rf): times = [] while True: match = re.match('[0-9]+ ', rf.line) if not match: break times += [int(x) for x in re.split(' ', rf.line.rstrip())] rf.getnext() return times[::-1] 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) self.thread_name = thread_name self.thread_number = Thread.thread_number self.events = [] Thread.thread_number += 1 class Event: def __init__(self, thread, gate_location, gate_name, start, stop): self.thread = thread self.gate_location = gate_location self.gate_name = gate_name self.start = start self.stop = stop self.work = False self.wait = False if re.match('.*work.*', gate_name): self.work = True if re.match('.*wait.*', gate_name): self.wait = True thread.events.append(self) input_filename = 'vips-profile.txt' thread_id = 0 threads = [] n_events = 0 print 'reading from', input_filename with ReadFile(input_filename) as rf: while rf: if rf.line.rstrip() == "": rf.getnext() continue if rf.line[0] == "#": rf.getnext() continue match = re.match('thread: (.*)', rf.line) if not match: 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: match = re.match('gate: (.*?): (.*)', rf.line) if not match: break gate_location = match.group(1) gate_name = match.group(2) rf.getnext() match = re.match('start:', rf.line) if not match: continue rf.getnext() start = read_times(rf) match = re.match('stop:', rf.line) if not match: continue rf.getnext() stop = read_times(rf) if len(start) != len(stop): print 'start and stop length mismatch' for a, b in zip(start, stop): Event(thread, gate_location, gate_name, a, b) n_events += 1 for thread in threads: thread.events.sort(lambda x, y: cmp(x.start, y.start)) print 'loaded %d events' % n_events # normalise time axis to secs of computation ticks_per_sec = 1000000.0 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 # 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 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.01 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) # 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: if thread.hide: continue thread.total_y = total_y # first pass .. move work and wait events to y == 0 gate_positions = {} for event in thread.events: 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 overlap on thread', thread.thread_name print '\t', event.gate_location print '\t', event.gate_name print '\t', gate_name break gate_positions[event.gate_name] = 0 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: # 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 break # failure? add a new y if not event.gate_name in gate_positions: gate_positions[event.gate_name] = y y += 1 event.y = gate_positions[event.gate_name] # 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 event.total_y = total_y + event.y total_y += y PIXELS_PER_SECOND = 1000 PIXELS_PER_GATE = 20 LEFT_BORDER = 130 BAR_HEIGHT = 5 WIDTH = int(LEFT_BORDER + last_time * PIXELS_PER_SECOND) + 20 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) 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() 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 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) ctx.move_to(left + width / 2 - twidth / 2, top + 3 * BAR_HEIGHT) ctx.set_source_rgb(1.00, 0.83, 0.00) ctx.show_text(event.gate_name) 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() 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) ctx.set_source_rgb(1.00, 1.00, 1.00) ctx.show_text(thread.thread_name) for event in thread.events: draw_event(ctx, event) surface.finish()