Esempio n. 1
0
def stats_to_markdownstr(p: pstats.Stats, prtRowNum: int) -> str:
    """convert pstats result to markdown table

    Args:
        p (pstats.Stats): pstats result
        prtRowNum (int): only print several functions

    Returns:
        str: the markdown string with table

    """
    s = io.StringIO()
    p.stream = s
    p.print_stats(prtRowNum)
    sumlines = []
    _line = ''
    flag = True
    fp = 0
    s.seek(0)
    while flag:
        fp = s.tell()
        _line = s.readline()
        _pl = _line.rstrip()
        flag = _line.find("   ncalls  tottime") != 0 and _line
        if _pl and flag:
            sumlines.append(_pl)
    s.seek(fp)  # rewind before table
    width, fncs = p.get_print_list((prtRowNum, ))
    results = []
    for func in fncs:
        cc, nc, tt, ct, callers = p.stats[func]

        results.append(
            (nc, cc, tt, tt / nc, ct, ct / nc, pstats.func_std_string(func)))
    headers = [
        'ncalls', 'pcalls', 'tottime', 'tt percall', 'cumtime', 'ct percall',
        'filename:lineno(function)'
    ]
    sumstr = '\n'.join(sumlines)
    tablestr = tabulate.tabulate(results,
                                 headers=headers,
                                 floatfmt='.3g',
                                 tablefmt='pipe')  # for markdown
    resultstr = sumstr + '\n' + tablestr
    return resultstr
Esempio n. 2
0
class Converter(object):
    """output to HTML from profile data
    """
    def __init__(self, filename):
        self.outfile = None
        self.output_dir = 'html'
        self.output_htmlfile = 'index.html'
        self.callers_htmlfile = 'callers.html'
        self.callers_data = []
        with open(filename, 'rb') as fp:
            dump = fp.read()
        self.tmpl = ENVIRON.get_template('main.html')
        self.callers_tmpl = ENVIRON.get_template('callers.html')
        if check_hotshot(dump[:20]):
            if not PY2:
                # TODO: xxx
                raise Exception('Not support for hotshot on Python3')
            self.prof = stats.load(filename)
            self.profile_datatype = "hotshot"
            if check_hotlinetimings(dump[102:108]):
                self.profline = log.LogReader(filename)
                self.tmplline = ENVIRON.get_template('hotshot-line.html')
                self.profile_datatype = "hotshot(line)"
        else:
            try:
                self.prof = Stats(filename)
            except ValueError as e:
                # TODO: xxx
                raise ValueError(filename)
            if check_cprofile(dump):
                self.profile_datatype = "cProfile"
            else:
                self.profile_datatype = "profile"
        self.filename = filename
        self.proftime = time.ctime(os.stat(filename).st_mtime)
        self.reporttime = time.ctime()
        self.outputtype = 'html'
        self.functions_number = 20
        self.profiledata_count = 0

    def _printhtml_source(self, filename, profs, outputfile):
        """printing one file profile line. return value is html render
        strings.
        """
        result = []
        if not os.path.exists(filename):
            return ""
        filecodec = detect_filecodec(open(filename).readlines())
        if filecodec is None:
            fileobj = open(filename)
        else:
            fileobj = codecs.open(filename, 'r', filecodec)
        for num, line in enumerate(fileobj, 1):
            if num in profs:
                result.append({
                    'sec': profs[num]['sec'],
                    'cnt': profs[num]['cnt'],
                    'line': line
                })
            else:
                result.append({'sec': 'None', 'line': line})
        titletext = "pyprof2html - %s" % filename
        renderdict = {
            'title': titletext,
            'proftime': self.proftime,
            'reporttime': self.reporttime,
            'filename': filename,
            'profile_datatype': self.profile_datatype,
            'version': pyprof2html.__version__,
            'profdata': {
                'data': result,
                'totaltime': "%8.4lf" % self.prof.total_tt,
                'totalcalls': self.prof.total_calls
            }
        }
        if filecodec is None:
            outputfile.write(self.tmplline.render(renderdict))
        else:
            self.tmplline.stream(renderdict).dump(outputfile, CODEC)

    def _print_source(self, filename, profs):
        """printing one file profile line. return value is text strings.
        """
        result = []
        if not os.path.exists(filename):
            return ""
        result.append("=" * 60 + "\n")
        result.append(filename + "\n")
        result.append("=" * 60 + "\n")
        for num, line in enumerate(open(filename).readlines(), 1):
            if num in profs:
                result.append(" %3.4lfs | %7dn | %s" %
                              (profs[num]['sec'], profs[num]['cnt'], line))
            else:
                result.append("         |          | %s" % (line))
        return ''.join(result)

    def _print_sources(self, profs):
        """wrapper of _print_source() and _printhtml_source() method.
        """
        ret = []
        sources = []
        info = {}
        filecnt = 0
        for i, prof in enumerate(profs):
            filename = prof[0]
            if (not sources.count(filename)) or (len(profs) == (i + 1)):
                if info != {}:
                    if self.outputtype == 'html':
                        out_filename = "%s/%s.html" % (
                            self.output_dir, sources[filecnt].replace(
                                '/', '_'))
                        outputfile = open(out_filename, 'w')
                        self._printhtml_source(sources[filecnt], info,
                                               outputfile)
                        outputfile.close()
                    else:
                        ret.append(self._print_source(sources[filecnt], info))
                    filecnt += 1
                info = {}
                sources.append(filename)
            info[prof[1]] = {'sec': prof[2], 'cnt': prof[3]}
        return "".join(ret)

    def _analyze_profline(self):
        """analyzed to hotshot linetimings data.
        """
        profset = dict()
        for i in self.profline:
            if i[0] == 0 or i[0] == 1:  # WHAT_ENTER or WHAT_EXIT
                continue
            if i[1] not in profset:
                profset[i[1]] = [float(int(i[2]) / 1000000.), i[2]]
            else:
                profset[i[1]][0] += float(int(i[2]) / 1000000.)
                profset[i[1]][1] += i[2]
        profs = [(p[0], p[1], profset[p][0], profset[p][1]) for p in profset]
        profs.sort()
        return profs

    def _collect_callers_data(self, source, call_dict):
        """collect to similar of pstats.Stats.print_callers() data in
        self.callers_data.

        It refers to pstats.Stats.print_call_line() method.
        """
        clist = list(call_dict.keys())
        clist.sort()
        tempdata = []
        for cnt, func in enumerate(clist):
            name = func_std_string(func)
            value = call_dict[func]
            if isinstance(value, tuple):
                nc, cc, tt, ct = value
                del (cc)
            else:
                nc = value
                tt = self.prof.stats[func][2]
                ct = self.prof.stats[func][3]
            ncl = mapping_table(nc, self.prof.total_calls)
            ttl = mapping_table(tt, self.prof.total_tt)
            ctl = mapping_table(ct, self.prof.total_tt)
            if cnt:
                dst_function = ""
            else:
                dst_function = func_std_string(source)
            callers_link = hashlib.md5(
                func_std_string(source).encode()).hexdigest()
            tempdata.append({
                'dst_function': dst_function,
                'callers_link': callers_link,
                'ncalls': nc,
                'ncallslevel': ncl,
                'tottime': "%8.4f" % (tt),
                'tottimelevel': ttl,
                'cumtime': "%8.4f" % (ct),
                'cumtimelevel': ctl,
                'org_function': name
            })
        self.callers_data.append(tempdata)

    def _analyzed_prof(self):
        """analyzed to not linetimings profile data.
        """
        self.prof.sort_stats('time', 'calls')
        self.prof.stream = open(os.devnull, 'w')  # darty hack
        backstream = self.prof.stream
        funclist = self.prof.get_print_list(())[1]
        self.prof.stream = backstream
        self.prof.stream.close()
        datalist = list()
        self.profiledata_count = len(funclist)
        for cnt, func in enumerate(funclist):
            if cnt >= self.functions_number:
                break
            stat = self.prof.stats[func]
            ncalls = stat[0]
            if not int(ncalls):
                ## skip to 0calls function
                self.profiledata_count -= 1
                continue
            self._collect_callers_data(func, self.prof.stats[func][4])
            #tottime = convert_unit(stat[2])
            tottime = "%8.4lf" % stat[2]
            try:
                totpercall = "%8.4lf" % float(stat[2] / stat[0])
            except ZeroDivisionError:
                totpercall = "0.0000"
            cumtime = "%8.4lf" % stat[3]
            try:
                cumpercall = "%8.4lf" % float(stat[3] / stat[0])
            except ZeroDivisionError:
                cumpercall = "0.0000"
            ncallslevel = mapping_table(ncalls, self.prof.total_calls)
            tottlevel = mapping_table(stat[2], self.prof.total_tt)
            totclevel = mapping_table(float(totpercall), self.prof.total_tt)
            cumtlevel = mapping_table(stat[3], self.prof.total_tt)
            cumclevel = mapping_table(float(cumpercall), self.prof.total_tt)
            linelink = "%s.html" % func[0].replace('/', '_')
            callers_link = hashlib.md5(
                func_std_string(func).encode()).hexdigest()
            data = {
                'func': func,
                'linelink': linelink,
                'ncalls': ncalls,
                'tottime': tottime,
                'cumtime': cumtime,
                'totpercall': totpercall,
                'cumpercall': cumpercall,
                'ncallslevel': ncallslevel,
                'cumtimelevel': cumtlevel,
                'tottimelevel': tottlevel,
                'totcalllevel': totclevel,
                'cumcalllevel': cumclevel,
                'callers_link': callers_link,
            }
            datalist.append(data)
        profdata = {
            'totaltime': "%8.4lf" % self.prof.total_tt,
            'totalcalls': self.prof.total_calls,
            'data': datalist,
        }
        return profdata

    def _hookraw(self):
        """hook rawtext print
        """
        if self.profile_datatype == 'hotshot(line)':
            profs = self._analyze_profline()
            print(self._print_sources(profs))
        else:
            self.prof.sort_stats('time', 'calls')
            self.prof.print_stats()

    def _hookhtml(self):
        """hook html print
        """
        if not os.path.exists(self.output_dir):
            os.makedirs(self.output_dir)
        filepath = "%s/%s" % (self.output_dir, self.output_htmlfile)
        self.outfile = open(filepath, 'w')
        profdata = self._analyzed_prof()
        titletext = "pyprof2html - %s" % self.filename
        create_stylefile(self.output_dir)
        if self.profile_datatype == 'hotshot(line)':
            profs = self._analyze_profline()
            self._print_sources(profs)
        self.outfile.write(
            self.tmpl.render(title=titletext,
                             proftime=self.proftime,
                             reporttime=self.reporttime,
                             profdata=profdata,
                             profiledata_count=self.profiledata_count,
                             thisname=self.output_htmlfile,
                             version=pyprof2html.__version__,
                             profile_datatype=self.profile_datatype))
        self.outfile.close()
        filepath = "%s/%s" % (self.output_dir, self.callers_htmlfile)
        self.outfile = open(filepath, 'w')
        render_data = dict(title=titletext,
                           proftime=self.proftime,
                           reporttime=self.reporttime,
                           profile_datatype=self.profile_datatype,
                           callers_data=self.callers_data,
                           profdata=profdata,
                           profiledata_count=self.profiledata_count,
                           thisname=self.callers_htmlfile,
                           version=pyprof2html.__version__)
        self.outfile.write(self.callers_tmpl.render(render_data))
        self.outfile.close()

    def printout(self,
                 filetype='html',
                 output_directory='html',
                 output_htmlfile='index.html',
                 functions_number=20):
        """print to html or text.
        """
        self.outputtype = filetype
        self.output_dir = output_directory
        self.output_htmlfile = output_htmlfile
        self.functions_number = functions_number
        if filetype == 'raw':
            self._hookraw()
        else:
            self._hookhtml()