Source code for linesman
# This file is part of linesman.
#
# linesman is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# linesman is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along
# with linesman. If not, see <http://www.gnu.org/licenses/>.
#
import logging
import uuid
from inspect import getmodule
import networkx as nx
log = logging.getLogger(__name__)
def _generate_key(stat):
code = stat.code
# First, check if its built-in (i.e., code is a string)
if isinstance(code, str):
return code
# If we have a module, generate the module name (a.b.c, etc..)
module = getmodule(code)
if module:
return ".".join((module.__name__, code.co_name))
# Otherwise, return a path based on the filename and function name.
return "%s.%s" % (code.co_filename, code.co_name)
[docs]def draw_graph(graph, output_path):
"""
Draws a networkx graph to disk using the `dot` format.
``graph``:
networkx graph
``output_path``:
Location to store the rendered output.
"""
nx.to_agraph(graph).draw(output_path, prog="dot")
log.info("Wrote output to `%s'" % output_path)
[docs]def create_graph(stats):
"""
Given an instance of :class:`pstats.Pstats`, this will use the generated
call data to create a graph using the :mod:`networkx` library. Node
and edge information is stored in the graph itself, so that the stats
object itself--which can't be pickled--does not need to be kept around.
``stats``:
An instance of :class:`pstats.Pstats`, usually retrieved by calling
:func:`~cProfile.Profile.getstats()` on a cProfile object.
Returns a :class:`networkx.DiGraph` containing the callgraph.
"""
# Create a graph; dot graphs need names, so just use `G' so that
# pygraphviz doesn't complain when we render it.
g = nx.DiGraph(name="G")
# Iterate through stats to add the original nodes. The will add ALL
# the nodes, even ones which might be pruned later. This is so that
# the library will always have the original callgraph, which can be
# manipulated for display purposes later.
duration = max([stat.totaltime for stat in stats])
for stat in stats:
caller_key = _generate_key(stat)
attrs = {
'callcount': stat.callcount,
'inlinetime': stat.inlinetime,
'reccallcount': stat.reccallcount,
'totaltime': stat.totaltime,
}
g.add_node(caller_key, attr_dict=attrs)
# Add all the calls as edges
for call in stat.calls or []:
callee_key = _generate_key(call)
call_attrs = {
'callcount': call.callcount,
'inlinetime': call.inlinetime,
'reccallcount': call.reccallcount,
'totaltime': call.totaltime,
}
# Add the edge using a weight and label
g.add_edge(caller_key, callee_key,
weight=call.totaltime,
label=call.totaltime,
attr_dict=call_attrs)
return g
[docs]class ProfilingSession(object):
"""
The profiling session is used to store long-term information about the
profiled call, including generating the complete callgraph based on
cProfile's stats output.
Of special interest is the ``uuid`` that it generates to uniquely track
this request.
``stats``:
An instance of :class:`pstats.Pstats`, usually retrieved by calling
:func:`~cProfile.Profile.getstats()` on a cProfile object.
``environ``:
If specified, this Session will store `environ` data. This should be
the environment setup by the WSGI server (i.e., :mod:`paster`).
``timestamp``:
If specified, this should mark the beginning of the profiling
session--i.e., when Profile.run() was called. This can be any format
if your choosing; however, the default templates simply invoke
``timestamp.__repr__``, so whatever object you use should provide at
_least_ that function.
"""
def __init__(self, stats, environ={}, timestamp=None):
self._graph = None
self._uuid = uuid.uuid1()
# Save some environment variables (if available)
self.path = environ.get('PATH_INFO')
# Some profiling session attributes need to be calculated
self.duration = max([stat.totaltime for stat in stats])
self.timestamp = timestamp
self._graph = create_graph(stats)
@property
[docs] def uuid(self):
""" See :term:`session_uuid`. """
return str(self._uuid)