# -*- Mode: Python -*-

import os
import re

# 08135174 00000041 T PyRun_SimpleString
fun_re = re.compile ('([0-9a-f]+) ([0-9a-f]+) [Tt] ([0-9A-Za-z_]+)\n')

def get_syms (path_exe):
    p = os.popen ('nm -S %s' % (path_exe,), 'r')
    d = {}
    while 1:
        line = p.readline()
        if not line:
            break
        else:
            probe = fun_re.match (line)
            if probe is not None:
                addr, size, name = probe.groups()
                d[int(addr,16)] = (name, int(size,16))
    return d

def get_sym (syms, addr):
    if syms.has_key (addr):
        return syms[addr][0]
    else:
        return hex(addr)

def read_profile (path_prof):
    graph = {}
    f = open (path_prof, 'rb')
    while 1:
        line = f.readline()
        if not line or line[0] == '-':
            break
        else:
            addr, count, ticks = line.split()
            addr = int (addr, 16)
            count = int (count)
            ticks = int (ticks)
            graph[addr] = (count, ticks, [])
    line = f.readline()
    while 1:
        # read fun address
        if not line:
            break
        else:
            caller = int(line,16)
            callees = graph[caller][2]
            while 1:
                line = f.readline()
                if not line:
                    break
                elif line[0] == ' ':
                    addr, count = line.split()
                    addr = int(addr, 16)
                    count = int (count)
                    callees.append ((addr, count))
                else:
                    break
    return graph

def print_profile (syms, graph, sortby='ticks'):
    # pull out ticks and counts
    profile = [(t, c, a) for (a, (c, t, ignore)) in graph.iteritems()]
    # convert addresses to names
    profile = [(t, c, get_sym(syms,a)) for (t, c, a) in profile]
    # repair zero-count entries
    profile = [(t, c or 1, n) for (t,c,n) in profile]
    # synthesize ticks/call
    profile = [(t, c, t/c, n) for (t,c,n) in profile]
    which = {'ticks':0, 'count':1, 'tpc':2, 'name':3}[sortby]
    profile.sort (lambda a,b: cmp(a[which],b[which]))
    sum_ticks = 0
    sum_calls = 0
    for ticks, count, per, name in profile:
        sum_ticks += ticks
        sum_calls += count
    sum_percent = 0
    for ticks, count, per, name in profile:
        percent = 100 * (ticks / float(sum_ticks))
        print '%15d %6.2f %12d %10d %s' % (
            ticks, percent, count, per, name
            )
        sum_percent += percent
    print '%15d %6.2f %12d %10s (totals)' % (
        sum_ticks, sum_percent, sum_calls, ''
        )

def print_graph (syms, graph):
    # we have the forward graph, build the reverse from it
    rg = {}
    for addr, (total_count, ticks, callees) in graph.iteritems():
        for callee, count in callees:
            if rg.has_key (callee):
                rg[callee].append ((addr, count))
            else:
                rg[callee] = [(addr, count)]
    # 
    gi = graph.items()
    gi.sort()
    for addr, (total_count, ticks, callees) in gi:
        print '------------------------------'
        if rg.has_key (addr):
            for caller, count in rg[addr]:
                print '  %8s %8d %s' % ('', count, get_sym (syms, caller))
        print '%8d %s' % (total_count, get_sym (syms, addr))
        for callee, count in callees:
            if (callee == addr):
                print '  %8s %8d [recursive]' % ('', count,)
            else:
                print '  %8s %8d %s' % ('', count, get_sym (syms, callee))

def print_profile_html (syms, graph):
    # pull out ticks and counts
    profile = [(t, c, a) for (a, (c, t, ignore)) in graph.iteritems()]
    # convert addresses to names
    profile = [(t, c, get_sym(syms,a)) for (t, c, a) in profile]
    # repair zero-count entries
    profile = [(t, c or 1, n) for (t,c,n) in profile]
    # synthesize ticks/call
    profile = [(t, c, t/c, n) for (t,c,n) in profile]
    # sortby == ticks
    which = 0
    profile.sort (lambda a,b: cmp(b[which],a[which]))
    sum_ticks = 0
    sum_calls = 0
    for ticks, count, per, name in profile:
        sum_ticks += ticks
        sum_calls += count
    print '<table border=1 cellpadding=2 cellspacing=0>'
    print '<tr><th>ticks</th><th>percent</th><th>calls</th><th>ticks/call</th><th>function</th>'
    i = 0
    for ticks, count, per, name in profile:
        percent = 100 * (ticks / float(sum_ticks))
        print (
            '<tr align=right>'
            '<td>%d</td>'
            '<td>%6.2f</td>'
            '<td>%d</td>'
            '<td>%d</td>'
            '<td align=left><a name="tt_%s"></a><a href="#cg_%s">%s</a></td>'
            '</tr>' % (
                ticks, percent, count, per, name, name, name
                )
            )
        i += 1
    print '<tr><td>%d</td><td>%6.2f</td><td></td><td>%d</td><td>(totals)</td></tr>' % (
        sum_ticks, 100.00, sum_calls
        )
    print '</table>'

def print_graph_html (syms, graph):
    # we have the forward graph, build the reverse from it
    rg = {}
    for addr, (total_count, ticks, callees) in graph.iteritems():
        for callee, count in callees:
            if rg.has_key (callee):
                rg[callee].append ((addr, count))
            else:
                rg[callee] = [(addr, count)]
    # 
    gi = [(get_sym (syms, x[0]), x) for x in graph.iteritems()]
    gi.sort()
    for name, (addr, (total_count, ticks, callees)) in gi:
        print '<hr>'
        print '<a name="cg_%s">%s</a> [%d]' % (name, name, ticks)
        print '<pre>'
        if rg.has_key (addr):
            l = []
            for caller, count in rg[addr]:
                cname = get_sym (syms, caller)
                tc, tt, cl = graph[addr]
                est_ticks = (ticks * count) / total_count
                l.append ((count, tc, est_ticks, cname))
            l.sort (lambda a,b:cmp(a[2],b[2]))
            for count, tc, est_ticks, cname in l:
                print '%10d/%-10d %15d <a href="#tt_%s">%s</a>' % (count, tc, est_ticks, cname, cname)
        print '%15d %15d <b>%s</b>' % (total_count, ticks, name)
        callees2 = []
        for callee, count in callees:
            cname = get_sym (syms, callee)
            tc, tt, cl = graph[callee]
            est_ticks = (tt / tc) * count
            callees2.append ((count, tc, est_ticks, cname))
        # sort by estimated ticks
        callees2.sort (lambda a,b: cmp (b[2], a[2]))
        for count, tc, est_ticks, cname in callees2:
            print '%10d/%-10d %15d <a href="#tt_%s">%s</a>' % (count, tc, est_ticks, cname, cname)
        print '</pre>'

def print_html (syms, graph):
    print '<html><head><title>tscprof results</title></head><body bgcolor=#ffffff>'
    print '<h1>tscprof results</h1>'
    print_profile_html (syms, graph)
    print_graph_html (syms, graph)
    print '</body></html>'

if __name__ == '__main__':
    import sys
    if '-html' in sys.argv:
        sys.argv.remove ('-html')
        html = True
    else:
        html = False

    if len(sys.argv) > 3:
        sortby = sys.argv[3]
    else:
        sortby = 'ticks'
    
    syms = get_syms (sys.argv[1])
    graph = read_profile (sys.argv[2])

    if html:
        print_html (syms, graph)
    else:
        print_profile (syms, graph, sortby)
        print_graph (syms, graph)
