Source

tracevis_adds_legends_to_graph_and_refactors / scripts / vis.py

Full commit
import os, struct, sys
import Image, ImageDraw, ImageFont

from config import *
import acts
from acts import *
from binlog import parse_input, collect
from progressbar import ProgressBar
from time import clock
import legendimgs

BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
font = ImageFont.truetype('LiberationSans-Bold.ttf', 16)

W, H, HA, HB, H_LEGENDS = 1600, 256, 64, 64, 64
H_OFFSET_TIMELINES = H + 4 + HA + 4 + HB
H_OFFSET_LEGENDS   = H + 4 + HA + 4 + HB + 30
HZ =          H + 4 + HA + 4 + HB + H_LEGENDS

states = [
    (0, 0, 0),
    (225, 225, 225), #(170, 20, 10),
    (225, 30, 12),
    (140, 0, 255),
    (0, 0, 255),
    (180, 180, 30),
    (0, 160, 30),
]

assert len(states) == len(acts.states), 'Number of states in vis.py and acts.py should be the same'

timeslices_ms = [
    1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000 ]

class Data(object):
    def __init__(self, duration, summary, transitions):
        self.duration = duration
        self.summary = summary
        self.transitions = transitions

def parse_raw(filename):
    hist = collect(parse_input(filename), True)

    t0 = hist.timeline[0].t
    ts = []
    for x in hist.timeline:
        ts.append((x.s0, x.r, x.t-t0))
    total = ts[-1][2] - ts[0][2]

    return Data(total, hist.state_summary, ts)

def parse_cooked(filename):
    f = open(filename)
    header = f.read(20)
    if header != 'TraceVis-History0001':
        print "Invalid header"
        sys.exit(1)

    duration = struct.unpack_from('Q', f.read(8))[0]
    summary = []
    state_count = struct.unpack_from('I', f.read(4))[0]
    for i in range(state_count):
        summary.append(struct.unpack_from('Q', f.read(8))[0])

    # This method is actually faster than reading it all into a buffer and decoding
    # from there.
    pos = f.tell()
    f.seek(0, os.SEEK_END)
    n = (f.tell() - pos) / 10
    f.seek(pos)

    pb = ProgressBar('read input', n)
    blip = n / 100

    transitions = []
    for i in range(n):
        if i % blip == 0:
            pb.update(i)
        raw = f.read(10)
        s = struct.unpack_from('B', raw)[0]
        r = struct.unpack_from('B', raw, 1)[0]
        t = struct.unpack_from('Q', raw, 2)[0]
        transitions.append((s, r, t))
    pb.finish()
    
    return Data(duration, summary, transitions)


def filter_data(ts):
    if 0:
        print 'Filtering data...', 
        a, b = 10*2.2e9, 12*2.2e9
        ts = [(s, t-a) for s, t in ts if a <= t <= b ]
        print 'Done'
    return ts
def draw_speedup_line(eps, d):
    d.rectangle((0, H + 4, W, H + 4 + HA), (255, 255, 255))

    epmax = 2.5*H

    d.line((0, H+4+HA-1.0*H/epmax*HA, W,  H+4+HA-1.0*H/epmax*HA), fill=(192, 192, 192))
    d.line((0, H+4+HA-1.0*H/epmax*HA, W,  H+4+HA-1.0*H/epmax*HA), fill=(192, 192, 192))
    d.line((0, H+4+HA-2.5*H/epmax*HA, W,  H+4+HA-2.5*H/epmax*HA), fill=(192, 192, 192))    
    
    last = None
    for i in range(W):
        n = 0
        sm = 0
        for j in range(max(0, i-3), min(W-1, i+3)):
            sm += eps[j]
            n += 1
        ep = sm / n

        ep_scaled = 1.0 * ep / epmax * HA
        px = (i, H + 4 + HA - int(ep_scaled))
        if last:
            d.line((px, last), fill=(0, 0, 0))
        last = px
        #d.point((i, H + 4 + HA - int(ep_scaled)), fill=(0, 0, 0))

def draw_timelines(total_ms, pc_us, d):
    i = 0
    while True:
        divs = total_ms / timeslices_ms[i]
        if divs < 15: break
        if i == len(timeslices_ms) - 1: break
        i += 1

    timeslice = timeslices_ms[i]
    t = timeslice
    c = (128, 128, 128)
    while t < total_ms:
        x = t / total_ms * W
        y = 0
        while y < H_OFFSET_TIMELINES:
            d.line((x, y, x, y + 8), fill=c)
            y += 12
        text = '%d ms'%t
        w, h = font.getsize(text)
        d.text((x-w/2, H_OFFSET_TIMELINES), text, fill=BLACK, font=font)
        t += timeslice

    d.text((0, H_OFFSET_TIMELINES), '[%.1fus]'%pc_us, fill=BLACK)


def draw_summary_graph(summary, states, total, d):
    
    t = 0
    ppt = 1.0 * W / total
    
    for dt, c in zip(summary, states):
        x1 = int(t * ppt)
        y1 = H + 4 + HA + 5 
        x2 = int((t+dt) * ppt)
        y2 = H + 4 + HA + 4 + HB
        d.rectangle((x1, y1, x2, y2), fill=c)
        t += dt

def draw_main_graph(pp, ts, pb, d):
    eps = []

    # estimated progress of program run 
    ep = 0
    
    blip = W*H//50


    # Flush events. We need to save them and draw them after filling the main area
    # because otherwise they will be overwritten. Format is (x, y, r) to plot flush at,
    # where r is reason code.
    events = []

    ti = 0
    for i in range(W*H):
        if i % blip == 0:
            pb.update(i)
            pass
        x, y = i/H, i % H

        t0 = pp * i
        t1 = t0 + pp
        color = [ 0, 0, 0 ]
        tx = t0
        while ti < len(ts):
            state, reason, time = ts[ti]
            # States past this limit are actually events rather than state transitions
            if state >= event_start:
                events.append((x, y, reason))
                ti += 1
                continue

            q = (min(time, t1)-tx)/pp
            c = states[state]
            color[0] += q * c[0]
            color[1] += q * c[1]
            color[2] += q * c[2]
            ep += q * speedups[state]
            if time > t1:
                break
            tx = time
            ti += 1
        
        color[0] = int(color[0])
        color[1] = int(color[1])
        color[2] = int(color[2])

        d.point((x, y), fill=tuple(color))

        if i % H == H - 1:
            eps.append(ep)
            ep = 0
            
    return eps, events
    
def draw_events(events, flush_reasons, d):
    for x, y, r in events:
        d.ellipse((x-5, y-5, x+5, y+5), fill=(255, 222, 222), outline=(160, 10, 10))
        ch = flush_reasons[r]
        d.text((x-2, y-5), ch, fill=BLACK, font=font)

def draw_legends(d):
    fill_colors = states[1:]
    names = acts.states[1:]

    legendimgs.draw_lengends(d, fill_colors, BLACK, 32, H_OFFSET_LEGENDS, names)

def draw(data, outfile):
    total, summary, ts = data.duration, data.summary, data.transitions
    
    im = Image.new('RGBA', (W, HZ + 20))
    d = ImageDraw.Draw(im)
    
    ts = filter_data(ts)

    total_ms = total / CPU_SPEED * 1000
    pp = 1.0 * total / (W*H)
    pc_us = 1.0 * total / CPU_SPEED / W * 1e6
    print "Parsed %d items, t=%f, t/pixel=%f"%(len(ts), total/CPU_SPEED, pp/CPU_SPEED*1000)
    
    # text progress indicator for graph generation
    pb = ProgressBar('draw main', W*H)
    eps, events = draw_main_graph(pp, ts, pb, d)    
    pb.finish()

    draw_events(events, flush_reasons, d)
    draw_speedup_line(eps, d)
    draw_summary_graph(summary, states, total, d)
    draw_timelines(total_ms, pc_us, d)
    draw_legends(d)

    im.save(outfile, "PNG")

if __name__ == '__main__':
    from optparse import OptionParser
    parser = OptionParser()
    parser.add_option('-c', action='store_true', dest='cooked', default=False,
                      help='process cooked format input file')
    options, args = parser.parse_args()

    if len(args) == 0 or len(args) > 2:
        print >> sys.stderr, 'need output filename argument'
        sys.exit(1)
    if len(args) == 1:
        outfile = args[0]
        filename = None
    elif len(args) == 2:
        filename, outfile = args

    if options.cooked:
        data = parse_cooked(filename)
    else:
        data = parse_raw(filename)

    draw(data, outfile)