remove external unused file
This commit is contained in:
parent
16385e8a52
commit
b8edf8ed64
|
@ -1,410 +0,0 @@
|
|||
"""
|
||||
pycallgraph
|
||||
|
||||
U{http://pycallgraph.slowchop.com/}
|
||||
|
||||
Copyright Gerald Kaszuba 2007
|
||||
|
||||
This program 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 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program 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 this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
"""
|
||||
|
||||
__version__ = '0.4.1'
|
||||
__author__ = 'Gerald Kaszuba'
|
||||
|
||||
import inspect
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import tempfile
|
||||
import time
|
||||
from distutils import sysconfig
|
||||
|
||||
# Initialise module variables.
|
||||
# TODO Move these into settings
|
||||
trace_filter = None
|
||||
time_filter = None
|
||||
|
||||
|
||||
def colourize_node(calls, total_time):
|
||||
value = float(total_time * 2 + calls) / 3
|
||||
return '%f %f %f' % (value / 2 + .5, value, 0.9)
|
||||
|
||||
|
||||
def colourize_edge(calls, total_time):
|
||||
value = float(total_time * 2 + calls) / 3
|
||||
return '%f %f %f' % (value / 2 + .5, value, 0.7)
|
||||
|
||||
|
||||
def reset_settings():
|
||||
global settings
|
||||
global graph_attributes
|
||||
global __version__
|
||||
|
||||
settings = {
|
||||
'node_attributes': {
|
||||
'label': r'%(func)s\ncalls: %(hits)i\ntotal time: %(total_time)f',
|
||||
'color': '%(col)s',
|
||||
},
|
||||
'node_colour': colourize_node,
|
||||
'edge_colour': colourize_edge,
|
||||
'dont_exclude_anything': False,
|
||||
'include_stdlib': True,
|
||||
}
|
||||
|
||||
# TODO: Move this into settings
|
||||
graph_attributes = {
|
||||
'graph': {
|
||||
'fontname': 'Verdana',
|
||||
'fontsize': 7,
|
||||
'fontcolor': '0 0 0.5',
|
||||
'label': r'Generated by Python Call Graph v%s\n' \
|
||||
r'http://pycallgraph.slowchop.com' % __version__,
|
||||
},
|
||||
'node': {
|
||||
'fontname': 'Verdana',
|
||||
'fontsize': 7,
|
||||
'color': '.5 0 .9',
|
||||
'style': 'filled',
|
||||
'shape': 'rect',
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def reset_trace():
|
||||
"""Resets all collected statistics. This is run automatically by
|
||||
start_trace(reset=True) and when the module is loaded.
|
||||
"""
|
||||
global call_dict
|
||||
global call_stack
|
||||
global func_count
|
||||
global func_count_max
|
||||
global func_time
|
||||
global func_time_max
|
||||
global call_stack_timer
|
||||
|
||||
call_dict = {}
|
||||
|
||||
# current call stack
|
||||
call_stack = ['__main__']
|
||||
|
||||
# counters for each function
|
||||
func_count = {}
|
||||
func_count_max = 0
|
||||
|
||||
# accumative time per function
|
||||
func_time = {}
|
||||
func_time_max = 0
|
||||
|
||||
# keeps track of the start time of each call on the stack
|
||||
call_stack_timer = []
|
||||
|
||||
|
||||
class PyCallGraphException(Exception):
|
||||
"""Exception used for pycallgraph"""
|
||||
pass
|
||||
|
||||
|
||||
class GlobbingFilter(object):
|
||||
"""Filter module names using a set of globs.
|
||||
|
||||
Objects are matched against the exclude list first, then the include list.
|
||||
Anything that passes through without matching either, is excluded.
|
||||
"""
|
||||
|
||||
def __init__(self, include=None, exclude=None, max_depth=None,
|
||||
min_depth=None):
|
||||
if include is None and exclude is None:
|
||||
include = ['*']
|
||||
exclude = []
|
||||
elif include is None:
|
||||
include = ['*']
|
||||
elif exclude is None:
|
||||
exclude = []
|
||||
self.include = include
|
||||
self.exclude = exclude
|
||||
self.max_depth = max_depth or 9999
|
||||
self.min_depth = min_depth or 0
|
||||
|
||||
def __call__(self, stack, module_name=None, class_name=None,
|
||||
func_name=None, full_name=None):
|
||||
from fnmatch import fnmatch
|
||||
if len(stack) > self.max_depth:
|
||||
return False
|
||||
if len(stack) < self.min_depth:
|
||||
return False
|
||||
for pattern in self.exclude:
|
||||
if fnmatch(full_name, pattern):
|
||||
return False
|
||||
for pattern in self.include:
|
||||
if fnmatch(full_name, pattern):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def is_module_stdlib(file_name):
|
||||
"""Returns True if the file_name is in the lib directory."""
|
||||
# TODO: Move these calls away from this function so it doesn't have to run
|
||||
# every time.
|
||||
lib_path = sysconfig.get_python_lib()
|
||||
path = os.path.split(lib_path)
|
||||
if path[1] == 'site-packages':
|
||||
lib_path = path[0]
|
||||
return file_name.lower().startswith(lib_path.lower())
|
||||
|
||||
|
||||
def start_trace(reset=True, filter_func=None, time_filter_func=None):
|
||||
"""Begins a trace. Setting reset to True will reset all previously recorded
|
||||
trace data. filter_func needs to point to a callable function that accepts
|
||||
the parameters (call_stack, module_name, class_name, func_name, full_name).
|
||||
Every call will be passed into this function and it is up to the function
|
||||
to decide if it should be included or not. Returning False means the call
|
||||
will be filtered out and not included in the call graph.
|
||||
"""
|
||||
global trace_filter
|
||||
global time_filter
|
||||
if reset:
|
||||
reset_trace()
|
||||
|
||||
if filter_func:
|
||||
trace_filter = filter_func
|
||||
else:
|
||||
trace_filter = GlobbingFilter(exclude=['pycallgraph.*'])
|
||||
|
||||
if time_filter_func:
|
||||
time_filter = time_filter_func
|
||||
else:
|
||||
time_filter = GlobbingFilter()
|
||||
|
||||
sys.settrace(tracer)
|
||||
|
||||
|
||||
def stop_trace():
|
||||
"""Stops the currently running trace, if any."""
|
||||
sys.settrace(None)
|
||||
|
||||
|
||||
def tracer(frame, event, arg):
|
||||
"""This is an internal function that is called every time a call is made
|
||||
during a trace. It keeps track of relationships between calls.
|
||||
"""
|
||||
global func_count_max
|
||||
global func_count
|
||||
global trace_filter
|
||||
global time_filter
|
||||
global call_stack
|
||||
global func_time
|
||||
global func_time_max
|
||||
|
||||
if event == 'call':
|
||||
keep = True
|
||||
code = frame.f_code
|
||||
|
||||
# Stores all the parts of a human readable name of the current call.
|
||||
full_name_list = []
|
||||
|
||||
# Work out the module name
|
||||
module = inspect.getmodule(code)
|
||||
if module:
|
||||
module_name = module.__name__
|
||||
module_path = module.__file__
|
||||
if not settings['include_stdlib'] \
|
||||
and is_module_stdlib(module_path):
|
||||
keep = False
|
||||
if module_name == '__main__':
|
||||
module_name = ''
|
||||
else:
|
||||
module_name = ''
|
||||
if module_name:
|
||||
full_name_list.append(module_name)
|
||||
|
||||
# Work out the class name.
|
||||
try:
|
||||
class_name = frame.f_locals['self'].__class__.__name__
|
||||
full_name_list.append(class_name)
|
||||
except (KeyError, AttributeError):
|
||||
class_name = ''
|
||||
|
||||
# Work out the current function or method
|
||||
func_name = code.co_name
|
||||
if func_name == '?':
|
||||
func_name = '__main__'
|
||||
full_name_list.append(func_name)
|
||||
|
||||
# Create a readable representation of the current call
|
||||
full_name = '.'.join(full_name_list)
|
||||
|
||||
# Load the trace filter, if any. 'keep' determines if we should ignore
|
||||
# this call
|
||||
if keep and trace_filter:
|
||||
keep = trace_filter(call_stack, module_name, class_name,
|
||||
func_name, full_name)
|
||||
|
||||
# Store the call information
|
||||
if keep:
|
||||
|
||||
fr = call_stack[-1]
|
||||
if fr not in call_dict:
|
||||
call_dict[fr] = {}
|
||||
if full_name not in call_dict[fr]:
|
||||
call_dict[fr][full_name] = 0
|
||||
call_dict[fr][full_name] += 1
|
||||
|
||||
if full_name not in func_count:
|
||||
func_count[full_name] = 0
|
||||
func_count[full_name] += 1
|
||||
if func_count[full_name] > func_count_max:
|
||||
func_count_max = func_count[full_name]
|
||||
|
||||
call_stack.append(full_name)
|
||||
call_stack_timer.append(time.time())
|
||||
|
||||
else:
|
||||
call_stack.append('')
|
||||
call_stack_timer.append(None)
|
||||
|
||||
if event == 'return':
|
||||
if call_stack:
|
||||
full_name = call_stack.pop(-1)
|
||||
t = call_stack_timer.pop(-1)
|
||||
if t and time_filter(stack=call_stack, full_name=full_name):
|
||||
if full_name not in func_time:
|
||||
func_time[full_name] = 0
|
||||
call_time = (time.time() - t)
|
||||
func_time[full_name] += call_time
|
||||
if func_time[full_name] > func_time_max:
|
||||
func_time_max = func_time[full_name]
|
||||
|
||||
return tracer
|
||||
|
||||
|
||||
def get_dot(stop=True):
|
||||
"""Returns a string containing a DOT file. Setting stop to True will cause
|
||||
the trace to stop.
|
||||
"""
|
||||
global func_time_max
|
||||
|
||||
def frac_calculation(func, count):
|
||||
global func_count_max
|
||||
global func_time
|
||||
global func_time_max
|
||||
calls_frac = float(count) / func_count_max
|
||||
try:
|
||||
total_time = func_time[func]
|
||||
except KeyError:
|
||||
total_time = 0
|
||||
if func_time_max:
|
||||
total_time_frac = float(total_time) / func_time_max
|
||||
else:
|
||||
total_time_frac = 0
|
||||
return calls_frac, total_time_frac, total_time
|
||||
|
||||
if stop:
|
||||
stop_trace()
|
||||
ret = ['digraph G {', ]
|
||||
for comp, comp_attr in graph_attributes.items():
|
||||
ret.append('%s [' % comp)
|
||||
for attr, val in comp_attr.items():
|
||||
ret.append('%(attr)s = "%(val)s",' % locals())
|
||||
ret.append('];')
|
||||
for func, hits in func_count.items():
|
||||
calls_frac, total_time_frac, total_time = frac_calculation(func, hits)
|
||||
col = settings['node_colour'](calls_frac, total_time_frac)
|
||||
attribs = ['%s="%s"' % a for a in settings['node_attributes'].items()]
|
||||
node_str = '"%s" [%s];' % (func, ','.join(attribs))
|
||||
ret.append(node_str % locals())
|
||||
for fr_key, fr_val in call_dict.items():
|
||||
if fr_key == '':
|
||||
continue
|
||||
for to_key, to_val in fr_val.items():
|
||||
calls_frac, total_time_frac, totla_time = \
|
||||
frac_calculation(to_key, to_val)
|
||||
col = settings['edge_colour'](calls_frac, total_time_frac)
|
||||
edge = '[ color = "%s" ]' % col
|
||||
ret.append('"%s"->"%s" %s' % (fr_key, to_key, edge))
|
||||
ret.append('}')
|
||||
ret = '\n'.join(ret)
|
||||
return ret
|
||||
|
||||
|
||||
def save_dot(filename):
|
||||
"""Generates a DOT file and writes it into filename."""
|
||||
open(filename, 'w').write(get_dot())
|
||||
|
||||
|
||||
def make_graph(filename, format=None, tool=None, stop=None):
|
||||
"""This has been changed to make_dot_graph."""
|
||||
raise PyCallGraphException( \
|
||||
'make_graph is depricated. Please use make_dot_graph')
|
||||
|
||||
|
||||
def make_dot_graph(filename, format='png', tool='dot', stop=True):
|
||||
"""Creates a graph using a Graphviz tool that supports the dot language. It
|
||||
will output into a file specified by filename with the format specified.
|
||||
Setting stop to True will stop the current trace.
|
||||
"""
|
||||
if stop:
|
||||
stop_trace()
|
||||
|
||||
# create a temporary file to be used for the dot data
|
||||
fd, tempname = tempfile.mkstemp()
|
||||
f = os.fdopen(fd, 'w')
|
||||
f.write(get_dot())
|
||||
f.close()
|
||||
|
||||
# normalize filename
|
||||
regex_user_expand = re.compile('\A~')
|
||||
if regex_user_expand.match(filename):
|
||||
filename = os.path.expanduser(filename)
|
||||
else:
|
||||
filename = os.path.expandvars(filename) # expand, just in case
|
||||
|
||||
cmd = '%(tool)s -T%(format)s -o%(filename)s %(tempname)s' % locals()
|
||||
try:
|
||||
ret = os.system(cmd)
|
||||
if ret:
|
||||
raise PyCallGraphException( \
|
||||
'The command "%(cmd)s" failed with error ' \
|
||||
'code %(ret)i.' % locals())
|
||||
finally:
|
||||
os.unlink(tempname)
|
||||
|
||||
|
||||
def simple_memoize(callable_object):
|
||||
"""Simple memoization for functions without keyword arguments.
|
||||
|
||||
This is useful for mapping code objects to module in this context.
|
||||
inspect.getmodule() requires a number of system calls, which may slow down
|
||||
the tracing considerably. Caching the mapping from code objects (there is
|
||||
*one* code object for each function, regardless of how many simultaneous
|
||||
activations records there are).
|
||||
|
||||
In this context we can ignore keyword arguments, but a generic memoizer
|
||||
ought to take care of that as well.
|
||||
"""
|
||||
|
||||
cache = dict()
|
||||
def wrapper(*rest):
|
||||
if rest not in cache:
|
||||
cache[rest] = callable_object(*rest)
|
||||
return cache[rest]
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
settings = {}
|
||||
graph_attributes = {}
|
||||
reset_settings()
|
||||
reset_trace()
|
||||
inspect.getmodule = simple_memoize(inspect.getmodule)
|
Loading…
Reference in New Issue