remove external unused file
This commit is contained in:
parent
b11524ea1d
commit
56acdd2b56
|
@ -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