1. Matthew Turk
  2. pyprof2html

Source

pyprof2html / pyprof2html.py

#!/usr/bin/env python

"""pyprof2html - Profile data to HTML output tool
"""

import os
import time
from pprint import *
from hotshot import log, stats
from pstats import Stats
from jinja2 import Template

__version__ = '0.0.4'
__author__ = 'Hideo Hattori <syobosyobo@gmail.com>'
__licence__ = "NewBSDLicense"


XHTML_TEMPLATE = """\
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>

<style type="text/css">
body {
    font-size: 120%;
    line-height: 1.5;
    margin: 0px;
    padding:0px;
}

div.header {
    font-family: Georgia;
    background-color: #eae0d5;
    text-align: right;
    padding: 20px 0px 20px 0px;
    width: 100%;
}
span.header-title {
    font-weight: bold;
    font-size: 250%;
    margin-left: 1em;
    float: left;
}
span.header-time {
    /*font-family: Arial, Verdana;*/
    text-align: right;
    width: 50%;
    margin-right: 1em;
}
div.content {
    clear: both;
    font-family: Arial, Verdana;
}
table.prof {
    border-collapse: collapse;
    border: 1px #cccccc solid;
}
td.prof {
    border-collapse: collapse;
    border: 1px #cccccc solid;
}
span.tdmg {
  margin-left: 5px;
  margin-right: 5px;
}
th.prof {
    border-collapse: collapse;
    border: 1px #cccccc solid;
    background-color: #eae0d5;
}
th.procheader {
    align: center;
    background: #a294c8;
}

#sidebar {
    position: fixed;
    padding-top: 30px;
    padding-left:10px;
    padding-right:10px;
    right: 0;
    top  : 0;
    width: 160px;
}

#content {
    margin-left : 1em;
    margin-right: 1em;
    text-align: left;
}

#footer {
    clear     : both;
    text-align: center;
    font-size : small;
    padding   : 10px 0px 10px 0px;
    margin-top:2em;
    margin-bottom: -1em;
    background-color: #eae0d5;
}

h1.blog-title {
    margin-top : 0px;
}

</style>

<title>{{ title }}</title>
</head>

<body>
    <div class="header">
        <span class="header-title">Profile Report</span>
        <span class="header-time">Profile Time on {{ proftime }}</span><br/>
        <span class="header-time">Reported Time on {{ reporttime }}</span>
    </div>

    <div id="sidebar">
    </div>

    <div id="content">
    <p>Total Time     : <span style="font-weight:bold">{{ profdata['totaltime'] }}</span> CPU second<br/>
       Total Func Call: <span style="font-weight:bold">{{ profdata['totalcalls'] }}</span> function calls</p>
    <table class="prof">
        <tr>
        <th class="procheader" align="center" colspan="6">Top {{ profdata['data']|length }} functions data</th>
        </tr>
        <tr align="right">
        <th class="prof"><span class="tdmg">ncalls</span></th>
        <th class="prof"><span class="tdmg">tottime</span></th>
        <th class="prof"><span class="tdmg">percall</span></th>
        <th class="prof"><span class="tdmg">cumtime</span></th>
        <th class="prof"><span class="tdmg">percall</span></th>
        <th class="prof" align="center"><span class="tdmg">filename:lineno(function)</span></th>
        </tr>
        {% for item in profdata['data'] %}
            <tr>
            <td class="prof" align="right"><span class="tdmg">{{ item['ncalls'] }}</span></td>
            <td class="prof" style="background-color:{{ item['tottimelevel'] }}" align="right">
              <span class="tdmg">{{ item['tottime'] }}</span></td>
            <td class="prof" style="background-color:{{ item['totcalllevel'] }}" align="right">
              <span class="tdmg">{{ item['totpercall'] }}</span></td>
            <td class="prof" style="background-color:{{ item['cumtimelevel'] }}" align="right">
              <span class="tdmg">{{ item['cumtime'] }}</span></td>
            <td class="prof" style="background-color:{{ item['cumcalllevel'] }}" align="right">
              <span class="tdmg">{{ item['cumpercall'] }}</span></td>
            {% if item['func'][0] == '~' %}
                <td class="prof"><span class="tdmg">{{ item['func'][2]|escape }}</span></td>
            {% else %}
                <td class="prof"><span class="tdmg">{{ item['func'][0]|escape }} : 
                {{ item['func'][1] }} ({{ item['func'][2]|escape }})</span></td>
            {% endif %}
            </tr>
        {% endfor %}
    </table>
    </div>

    <div id="footer">
    <p>
    Powered by <a href="http://www.python.org/">Python</a> and 
    <a href="http://jinja.pocoo.org/2/">Jinja2</a>&nbsp;&nbsp;&nbsp;
    pyprof2html:ver{{ version }}
    </p>
    </div>
</body></html>
"""

class HTMLStats:

    def __init__(self, filename):
        self.proftype = None
        dump = open(filename).read()
        if self.check_hotshot(dump[:20]):
            if self.check_hotlinetimings(dump[102:108]):
                self.prof = log.LogReader(filename)
                self.proftype = 'log'
            else:
                self.prof = stats.load(filename)
        else:
            self.prof = Stats(filename)
        self.filename = filename
        self.outputtype = 'html'
        self.proftime = time.ctime(os.stat(filename).st_mtime)
        self.tmpl = Template(XHTML_TEMPLATE)
        self.functions_number = 20
        self.levelmap = {
            0: '',
            1: '#fa9cb8',
            2: '#e95464',
            3: '#e2041b',
        }

    def check_hotlinetimings(self, dump):
        SIGNATURE = "yes"
        return SIGNATURE in dump

    def check_hotshot(self, dump):
        SIGNATURE = "hotshot-version"
        return SIGNATURE in dump

    def _hookraw(self):
        self.prof.sort_stats('time', 'calls')
        self.prof.print_stats()

    def print_onesource(self, filename, profinfo):
        num = 0
        try:
            lines = open(filename).readlines()
        except IOError:
            return
        print 
        print "="*60
        print filename
        print "="*60
        for l in lines:
            num += 1
            if profinfo.has_key(num):
                print " %3.3lfsec | %s" % (profinfo[num], l),
            else:
                print "          | %s" % (l),

    def print_source(self, profinfo):
        source_table = []
        info = {}
        filename = None
        filecnt  = 0
        for prof in profinfo:
            filename = prof[0]
            if not source_table.count(filename):
                if info != {}:
                    filecnt += 1
                    self.print_onesource(source_table[filecnt-1], info)
                info = {}
                source_table.append(filename)
            info[prof[1]] = prof[3]
        self.print_onesource(filename, info)

    def hookhtmlline(self):
        profset = dict()
        for i in self.prof:
            if not profset.has_key(i[1]):
                profset[i[1]] = float(int(i[2])/1000000.)
            else:
                profset[i[1]] += float(int(i[2])/1000000.)
        proflist = list()
        for p in profset:
            proflist.append([p[0], p[1], p[2], profset[p]])
        proflist.sort()
        self.print_source(proflist)

    def hookhtml(self):
        self.prof.sort_stats('time', 'calls')
        ## darty hack (order output block)
        null = open('/dev/null', 'w')
        self.prof.stream = null
        backstream = self.prof.stream
        w, funclist = self.prof.get_print_list(())
        self.prof.stream = backstream
        null.close()

        d = list()
        cnt = 0
        for func in funclist:
            if cnt >= self.functions_number:
                break
            cnt += 1
            t = self.prof.stats[func]
            ncalls = t[0]
            if not int(ncalls):
                ## skip to 0calls function
                continue
            tottime = "%8.4lf" % t[2]
            try:
                totpercall = "%8.4lf" % float(t[2]/t[0])
            except ZeroDivisionError:
                totpercall = "0.0000"
            cumtime = "%8.4lf" % t[3]
            try:
                cumpercall = "%8.4lf" % float(t[3]/t[0])
            except ZeroDivisionError:
                cumpercall = "0.0000"
            tottlevel = self.levelmap[int((t[2]/self.prof.total_tt)*3.33)]
            totclevel = self.levelmap[int((float(totpercall)/self.prof.total_tt)*3.33)]
            cumtlevel = self.levelmap[int((t[3]/self.prof.total_tt)*3.33)]
            cumclevel = self.levelmap[int((float(cumpercall)/self.prof.total_tt)*3.33)]
            data = {
                'ncalls'        : ncalls,
                'tottime'       : tottime,
                'cumtime'       : cumtime,
                'totpercall'    : totpercall,
                'cumpercall'    : cumpercall,
                'cumtimelevel'  : cumtlevel,
                'tottimelevel'  : tottlevel,
                'totcalllevel'  : totclevel,
                'cumcalllevel'  : cumclevel,
                'func'          : func,
            }
            d.append(data)

        profdata = {
            'totaltime': "%8.4lf" % self.prof.total_tt,
            'totalcalls': self.prof.total_calls,
            'data': d
        }
        print self.tmpl.render(title="pyprof2html - %s" % self.filename,
                               proftime=self.proftime,
                               reporttime=time.ctime(),
                               profdata=profdata,
                               version=__version__)

    def printout(self, type='html'):
        self.outputtype = type
        if type == 'raw':
            self._hookraw()
        elif type == 'html':
            if self.proftype == 'log':
                self.hookhtmlline()
            else:
                self.hookhtml()


def main():
    from optparse import OptionParser
    p = OptionParser(version="%s version:%s" % (__file__, __version__),
                     usage="Usage: pyprof2html [options] arg1")
    p.add_option('-r', '--raw', action='store_true',
                 help='raw print mode')
    p.add_option('-x', '--xhtml', action='store_true',
                 help='html print mode (default)')
    p.add_option('-t', '--template', dest='template_file',
                 help='jinja2 template file')
    p.add_option('-n', '--num', type='int', dest='print_functions',
                 default=20, help='print to N funcs.(default 20)')
    opts, args = p.parse_args()
    if not len(args):
        p.parse_args(['-h'])
        return
    p2h = HTMLStats(args[0])
    p2h.functions_number = opts.print_functions
    if opts.template_file:
        p2h.tmpl = Template(open(opts.template_file).read())
    if opts.raw:
        outmode = 'raw'
    elif opts.xhtml:
        outmode = 'html'
    else:
        outmode = 'html'
    p2h.printout(outmode)


if __name__ == '__main__':
    main()