From fbd8fae87a9dc918d4506cc02f31043efb801089 Mon Sep 17 00:00:00 2001 From: Gerald Kaszuba Date: Tue, 17 Sep 2013 20:30:31 +1000 Subject: [PATCH 01/27] Potential fix for 2to3 too long line --- pycallgraph/output/output.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pycallgraph/output/output.py b/pycallgraph/output/output.py index 9660d58..8824004 100644 --- a/pycallgraph/output/output.py +++ b/pycallgraph/output/output.py @@ -24,7 +24,8 @@ def set_config(self, config): the output module config variables. ''' for k, v in config.__dict__.iteritems(): - if hasattr(self, k) and callable(getattr(self, k)): + if hasattr(self, k) and \ + callable(getattr(self, k)): continue setattr(self, k, v) From 35d64654148514a0b30d7a7b2b012b18657f611e Mon Sep 17 00:00:00 2001 From: Gerald Kaszuba Date: Tue, 17 Sep 2013 20:32:29 +1000 Subject: [PATCH 02/27] Removed pypy from travis tests because it kept segfaulting --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 855cc08..be55292 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,6 @@ language: python: - "2.7" - "3.3" - - "pypy" env: global: From 22dbcc5e5946770a558c14a9d4cdf3a07fd47c3d Mon Sep 17 00:00:00 2001 From: sMAshdot Date: Wed, 19 Feb 2014 16:00:29 +0000 Subject: [PATCH 03/27] add module to let you group objects arbitrarily --- pycallgraph/__init__.py | 1 + pycallgraph/config.py | 4 ++++ pycallgraph/grouper.py | 25 +++++++++++++++++++++++++ pycallgraph/tracer.py | 7 ++----- 4 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 pycallgraph/grouper.py diff --git a/pycallgraph/__init__.py b/pycallgraph/__init__.py index fa03ac3..7ac4695 100644 --- a/pycallgraph/__init__.py +++ b/pycallgraph/__init__.py @@ -16,6 +16,7 @@ from .exceptions import PyCallGraphException from .config import Config from .globbing_filter import GlobbingFilter +from .grouper import Grouper from .util import Util from .color import Color from .color import ColorException diff --git a/pycallgraph/config.py b/pycallgraph/config.py index 5911fef..2bce930 100755 --- a/pycallgraph/config.py +++ b/pycallgraph/config.py @@ -3,6 +3,7 @@ from .output import outputters from .globbing_filter import GlobbingFilter +from .grouper import Grouper class Config(object): @@ -31,6 +32,9 @@ def __init__(self, **kwargs): include=['*'], ) + # Grouping + self.trace_grouper = Grouper() + self.did_init = True # Update the defaults with anything from kwargs diff --git a/pycallgraph/grouper.py b/pycallgraph/grouper.py new file mode 100644 index 0000000..7e97b08 --- /dev/null +++ b/pycallgraph/grouper.py @@ -0,0 +1,25 @@ +from fnmatch import fnmatch + +class Grouper(object): + '''Group module names. + + By default, objects are grouped by their top-level module name. Additional + groups can be specified with the groups list and all objects will be matched + against it. + ''' + + def __init__(self, groups=None): + if groups is None: + groups = [] + + self.groups = groups + + def __call__(self, full_name=None): + for pattern in self.groups: + if fnmatch(full_name, pattern): + if pattern[-2:] == ".*": + # a wilcard in the middle is probably meaningful, while at + # the end, it's only noise and can be removed + return pattern[:-2] + return pattern + return full_name.split('.', 1)[0] diff --git a/pycallgraph/tracer.py b/pycallgraph/tracer.py index 17e9286..ffcab15 100644 --- a/pycallgraph/tracer.py +++ b/pycallgraph/tracer.py @@ -290,20 +290,17 @@ def __getstate__(self): return odict - def group(self, name): - return name.split('.', 1)[0] - def groups(self): grp = defaultdict(list) for node in self.nodes(): - grp[self.group(node.name)].append(node) + grp[node.group].append(node) for g in grp.iteritems(): yield g def stat_group_from_func(self, func, calls): stat_group = StatGroup() stat_group.name = func - stat_group.group = self.group(func) + stat_group.group = self.config.trace_grouper(func) stat_group.calls = Stat(calls, self.func_count_max) stat_group.time = Stat(self.func_time.get(func, 0), self.func_time_max) stat_group.memory_in = Stat( From 61762563e9e550dcf51d8341593a2e9462f6089c Mon Sep 17 00:00:00 2001 From: sMAshdot Date: Wed, 19 Feb 2014 16:01:56 +0000 Subject: [PATCH 04/27] add example of custom grouping --- .../example_with_submodules/__init__.py | 1 + .../example_with_submodules.py | 12 +++ .../example_with_submodules/helpers.py | 2 + .../example_with_submodules/submodule_one.py | 6 ++ .../example_with_submodules/submodule_two.py | 8 ++ examples/graphviz/grouper.py | 73 +++++++++++++++++++ 6 files changed, 102 insertions(+) create mode 100644 examples/graphviz/example_with_submodules/__init__.py create mode 100644 examples/graphviz/example_with_submodules/example_with_submodules.py create mode 100644 examples/graphviz/example_with_submodules/helpers.py create mode 100644 examples/graphviz/example_with_submodules/submodule_one.py create mode 100644 examples/graphviz/example_with_submodules/submodule_two.py create mode 100755 examples/graphviz/grouper.py diff --git a/examples/graphviz/example_with_submodules/__init__.py b/examples/graphviz/example_with_submodules/__init__.py new file mode 100644 index 0000000..3a5bfd0 --- /dev/null +++ b/examples/graphviz/example_with_submodules/__init__.py @@ -0,0 +1 @@ +from .example_with_submodules import main diff --git a/examples/graphviz/example_with_submodules/example_with_submodules.py b/examples/graphviz/example_with_submodules/example_with_submodules.py new file mode 100644 index 0000000..b2f7060 --- /dev/null +++ b/examples/graphviz/example_with_submodules/example_with_submodules.py @@ -0,0 +1,12 @@ +from submodule_one import SubmoduleOne +from submodule_two import SubmoduleTwo + +def main(): + s1 = SubmoduleOne() + s1.report() + + s2 = SubmoduleTwo() + s2.report() + +if __name__ == "__main__": + main() diff --git a/examples/graphviz/example_with_submodules/helpers.py b/examples/graphviz/example_with_submodules/helpers.py new file mode 100644 index 0000000..6d8bd98 --- /dev/null +++ b/examples/graphviz/example_with_submodules/helpers.py @@ -0,0 +1,2 @@ +def helper(something): + return something diff --git a/examples/graphviz/example_with_submodules/submodule_one.py b/examples/graphviz/example_with_submodules/submodule_one.py new file mode 100644 index 0000000..99017cb --- /dev/null +++ b/examples/graphviz/example_with_submodules/submodule_one.py @@ -0,0 +1,6 @@ +class SubmoduleOne(object): + def __init__(self): + self.one = 1 + + def report(self): + return self.one diff --git a/examples/graphviz/example_with_submodules/submodule_two.py b/examples/graphviz/example_with_submodules/submodule_two.py new file mode 100644 index 0000000..d2688ed --- /dev/null +++ b/examples/graphviz/example_with_submodules/submodule_two.py @@ -0,0 +1,8 @@ +from helpers import helper + +class SubmoduleTwo(object): + def __init__(self): + self.two = 2 + + def report(self): + return helper(self.two) diff --git a/examples/graphviz/grouper.py b/examples/graphviz/grouper.py new file mode 100755 index 0000000..a699eee --- /dev/null +++ b/examples/graphviz/grouper.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python +''' +This example demonstrates the use of filtering. +''' +import time + +from pycallgraph import PyCallGraph +from pycallgraph import Config +from pycallgraph import GlobbingFilter +from pycallgraph import Grouper +from pycallgraph.output import GraphvizOutput +import example_with_submodules + + +def run(name, trace_grouper=None, config=None, comment=None): + if not config: + config = Config() + + config.trace_filter = GlobbingFilter() + + if trace_grouper is not None: + config.trace_grouper = trace_grouper + + graphviz = GraphvizOutput() + graphviz.output_file = 'grouper-{}.png'.format(name) + if comment: + graphviz.graph_attributes['graph']['label'] = comment + + with PyCallGraph(config=config, output=graphviz): + example_with_submodules.main() + + +def group_none(): + run( + 'without', + comment='Default grouping.' + ) + + +def group_some(): + trace_grouper = Grouper(groups=[ + 'example_with_submodules.submodule_one.*', + 'example_with_submodules.submodule_two.*', + 'example_with_submodules.helpers.*', + ]) + + run( + 'with', + trace_grouper=trace_grouper, + comment='Should assign groups to the two submodules.', + ) + + +def group_methods(): + trace_grouper = Grouper(groups=[ + 'example_with_submodules.*.report', + ]) + + run( + 'methods', + trace_grouper=trace_grouper, + comment='Should assign a group to the report methods.', + ) + + +def main(): + group_none() + group_some() + group_methods() + + +if __name__ == '__main__': + main() From 2c4ca0e292a4e66a957eb6f225b32a6b44abaf22 Mon Sep 17 00:00:00 2001 From: sMAshdot Date: Wed, 19 Feb 2014 16:03:50 +0000 Subject: [PATCH 05/27] fix oversight in docstring --- examples/graphviz/grouper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/graphviz/grouper.py b/examples/graphviz/grouper.py index a699eee..9bdb711 100755 --- a/examples/graphviz/grouper.py +++ b/examples/graphviz/grouper.py @@ -1,6 +1,6 @@ #!/usr/bin/env python ''' -This example demonstrates the use of filtering. +This example demonstrates the use of grouping. ''' import time From 24085f1295bd6b16c022f014f2c1b42cf5e47f53 Mon Sep 17 00:00:00 2001 From: sMAshdot Date: Wed, 19 Feb 2014 16:47:49 +0000 Subject: [PATCH 06/27] make changes PEP-8 compliant --- .../example_with_submodules/example_with_submodules.py | 1 + examples/graphviz/example_with_submodules/submodule_two.py | 1 + pycallgraph/grouper.py | 4 ++-- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/graphviz/example_with_submodules/example_with_submodules.py b/examples/graphviz/example_with_submodules/example_with_submodules.py index b2f7060..2fb6d04 100644 --- a/examples/graphviz/example_with_submodules/example_with_submodules.py +++ b/examples/graphviz/example_with_submodules/example_with_submodules.py @@ -1,6 +1,7 @@ from submodule_one import SubmoduleOne from submodule_two import SubmoduleTwo + def main(): s1 = SubmoduleOne() s1.report() diff --git a/examples/graphviz/example_with_submodules/submodule_two.py b/examples/graphviz/example_with_submodules/submodule_two.py index d2688ed..d3c0ac6 100644 --- a/examples/graphviz/example_with_submodules/submodule_two.py +++ b/examples/graphviz/example_with_submodules/submodule_two.py @@ -1,5 +1,6 @@ from helpers import helper + class SubmoduleTwo(object): def __init__(self): self.two = 2 diff --git a/pycallgraph/grouper.py b/pycallgraph/grouper.py index 7e97b08..cb5a757 100644 --- a/pycallgraph/grouper.py +++ b/pycallgraph/grouper.py @@ -4,8 +4,8 @@ class Grouper(object): '''Group module names. By default, objects are grouped by their top-level module name. Additional - groups can be specified with the groups list and all objects will be matched - against it. + groups can be specified with the groups list and all objects will be + matched against it. ''' def __init__(self, groups=None): From 4c74bb45562f34ef7e434b461862f20dd29c5d18 Mon Sep 17 00:00:00 2001 From: sMAshdot Date: Wed, 19 Feb 2014 16:57:32 +0000 Subject: [PATCH 07/27] add a blank line to pycallgraph/grouper.py --- pycallgraph/grouper.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pycallgraph/grouper.py b/pycallgraph/grouper.py index cb5a757..6fb6035 100644 --- a/pycallgraph/grouper.py +++ b/pycallgraph/grouper.py @@ -1,5 +1,6 @@ from fnmatch import fnmatch + class Grouper(object): '''Group module names. From 9a2a76bf1ebc80ff283888860280381576d06077 Mon Sep 17 00:00:00 2001 From: sMAshdot Date: Wed, 19 Feb 2014 17:18:30 +0000 Subject: [PATCH 08/27] add main to __all__ so that flake8 won't complain about unused import (it gets imported by the examples/graphviz/grouper.py example later) --- examples/graphviz/example_with_submodules/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/graphviz/example_with_submodules/__init__.py b/examples/graphviz/example_with_submodules/__init__.py index 3a5bfd0..22f9d83 100644 --- a/examples/graphviz/example_with_submodules/__init__.py +++ b/examples/graphviz/example_with_submodules/__init__.py @@ -1 +1,3 @@ from .example_with_submodules import main + +__all__ = [main] From 5c51c51b27e7eacd187ab6964e4d3abb54359a97 Mon Sep 17 00:00:00 2001 From: sMAshdot Date: Wed, 19 Feb 2014 17:19:25 +0000 Subject: [PATCH 09/27] remove unused import (was copy and pasted from filter example) --- examples/graphviz/grouper.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/graphviz/grouper.py b/examples/graphviz/grouper.py index 9bdb711..312fad2 100755 --- a/examples/graphviz/grouper.py +++ b/examples/graphviz/grouper.py @@ -2,8 +2,6 @@ ''' This example demonstrates the use of grouping. ''' -import time - from pycallgraph import PyCallGraph from pycallgraph import Config from pycallgraph import GlobbingFilter From f437d43bbb30c4c0581d52221772b513db5fa714 Mon Sep 17 00:00:00 2001 From: Alex Brandt Date: Sat, 11 Oct 2014 19:35:25 -0500 Subject: [PATCH 10/27] Ensure all files in docs are part of the sdist. With the current MANIFEST.in the scripts for generating examples and documentation are not actually included in the sdist. This ensures the documentation and example sources are included. --- MANIFEST.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index db3080b..3c8de00 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -3,6 +3,6 @@ include LICENSE include README.rst include requirements include test -include docs +recursive-include docs * include examples/*.py include man/pycallgraph.1 From 99bf650df3b9cf524448ef90d5759c2ce4f80bae Mon Sep 17 00:00:00 2001 From: Alexander Todorov Date: Thu, 6 Nov 2014 10:59:49 +0200 Subject: [PATCH 11/27] Make .format() work with older Python versions --- pycallgraph/output/output.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pycallgraph/output/output.py b/pycallgraph/output/output.py index 8824004..662d563 100644 --- a/pycallgraph/output/output.py +++ b/pycallgraph/output/output.py @@ -53,7 +53,7 @@ def node_label(self, node): return r'\n'.join(parts).format(node) def edge_label(self, edge): - return '{}'.format(edge.calls.value) + return '{0}'.format(edge.calls.value) def sanity_check(self): '''Basic checks for certain libraries or external applications. Raise @@ -94,7 +94,7 @@ def ensure_binary(self, cmd): return raise PyCallGraphException( - 'The command "{}" is required to be in your path.'.format(cmd)) + 'The command "{0}" is required to be in your path.'.format(cmd)) def normalize_path(self, path): regex_user_expand = re.compile('\A~') From fad968c90306de15a8a6947af8e936b6c546cf00 Mon Sep 17 00:00:00 2001 From: Alexander Todorov Date: Thu, 6 Nov 2014 11:12:41 +0200 Subject: [PATCH 12/27] more format fixes --- pycallgraph/output/graphviz.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pycallgraph/output/graphviz.py b/pycallgraph/output/graphviz.py index 6f10049..cbd938b 100644 --- a/pycallgraph/output/graphviz.py +++ b/pycallgraph/output/graphviz.py @@ -99,11 +99,11 @@ def done(self): with os.fdopen(fd, 'w') as f: f.write(source) - cmd = '{} -T{} -o{} {}'.format( + cmd = '{0} -T{1} -o{2} {3}'.format( self.tool, self.output_type, self.output_file, temp_name ) - self.verbose('Executing: {}'.format(cmd)) + self.verbose('Executing: {0}'.format(cmd)) try: ret = os.system(cmd) if ret: @@ -113,7 +113,7 @@ def done(self): finally: os.unlink(temp_name) - self.verbose('Generated {} with {} nodes.'.format( + self.verbose('Generated {0} with {1} nodes.'.format( self.output_file, len(self.processor.func_count), )) @@ -127,16 +127,16 @@ def generate(self): digraph G {{ // Attributes - {} + {0} // Groups - {} + {1} // Nodes - {} + {2} // Edges - {} + {3} }} '''.format( @@ -153,7 +153,7 @@ def attrs_from_dict(self, d): return ', '.join(output) def node(self, key, attr): - return '"{}" [{}];'.format( + return '"{0}" [{1}];'.format( key, self.attrs_from_dict(attr), ) @@ -165,7 +165,7 @@ def edge(self, edge, attr): def generate_attributes(self): output = [] for section, attrs in self.graph_attributes.iteritems(): - output.append('{} [ {} ];'.format( + output.append('{0} [ {1} ];'.format( section, self.attrs_from_dict(attrs), )) return output From ea6563e0c600a0735fe979a65e723193f276c429 Mon Sep 17 00:00:00 2001 From: Rob Bednark Date: Tue, 18 Nov 2014 13:54:07 -0800 Subject: [PATCH 13/27] Removed "json" output mode from the docs, because it looks like json output is not implemented yet. --- docs/guide/command_line_usage.rst | 4 ++-- docs/guide/intro.rst | 2 +- man/pycallgraph.1 | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/guide/command_line_usage.rst b/docs/guide/command_line_usage.rst index 67e4fb9..4444a56 100644 --- a/docs/guide/command_line_usage.rst +++ b/docs/guide/command_line_usage.rst @@ -15,14 +15,14 @@ Description pycallgraph is a program that creates call graph visualization from Python scripts. -*OUTPUT_MODE* can be one of graphviz, gephi and json. *python_file.py* is a python script that will be traced and afterwards, a call graph visualization will be generated. +*OUTPUT_MODE* can be one of graphviz or gephi. *python_file.py* is a python script that will be traced and afterwards, a call graph visualization will be generated. General Arguments ----------------- .. cmdoption:: - A choice of graphviz, gephi and json. + A choice of graphviz or gephi. .. cmdoption:: -h, --help diff --git a/docs/guide/intro.rst b/docs/guide/intro.rst index 3b43d8e..85dedb6 100644 --- a/docs/guide/intro.rst +++ b/docs/guide/intro.rst @@ -3,7 +3,7 @@ Intro Python Call Graph was made to be a visual profiling tool for Python applications. It uses a debugging Python function called `sys.set_trace() `_ which makes a callback every time your code enters or leaves function. This allows Python Call Graph to track the name of every function called, as well as which function called which, the time taken within each function, number of calls, etc. -It is able to generate different types of :ref:`outputs and visualizations `. Initially Python Call Graph was only used to generate DOT files for `GraphViz `_, and as of version 1.0.0, it can also generate JSON files, and GDF files for Gephi. Creating :ref:`custom outputs ` is fairly easy by subclassing the :ref:`Output ` class. +It is able to generate different types of :ref:`outputs and visualizations `. Initially Python Call Graph was only used to generate DOT files for `GraphViz `_, and as of version 1.0.0, it can also generate GDF files for Gephi. Creating :ref:`custom outputs ` is fairly easy by subclassing the :ref:`Output ` class. You can either use the :ref:`command-line interface ` for a quick visualization of your Python script, or the :ref:`pycallgraph module ` for more fine-grained settings. diff --git a/man/pycallgraph.1 b/man/pycallgraph.1 index a46afa3..01a0876 100644 --- a/man/pycallgraph.1 +++ b/man/pycallgraph.1 @@ -64,12 +64,12 @@ pycallgraph [\fIOPTION\fP]... \fIOUTPUT_MODE\fP [\fIOUTPUT_OPTIONS\fP] \fIpython .sp pycallgraph is a program that creates call graph visualization from Python scripts. .sp -\fIOUTPUT_MODE\fP can be one of graphviz, gephi and json. \fIpython_file.py\fP is a python script that will be traced and afterwards, a call graph visualization will be generated. +\fIOUTPUT_MODE\fP can be one of graphviz or gephi. \fIpython_file.py\fP is a python script that will be traced and afterwards, a call graph visualization will be generated. .SH GENERAL ARGUMENTS .INDENT 0.0 .TP .B -A choice of graphviz, gephi and json. +A choice of graphviz or gephi. .UNINDENT .INDENT 0.0 .TP From c92539b5d6583312b69a74d65e28b3a6e2a40227 Mon Sep 17 00:00:00 2001 From: Vadim Markovtsev Date: Mon, 24 Nov 2014 13:32:19 +0300 Subject: [PATCH 14/27] Fix graphviz output format setting Setting graphivz output format is broken: -f / --output-format is ignored. This is because self.output_format is adjusted internally, not output_type. The fix is to set "dest" property of the corresponding argument. --- pycallgraph/output/graphviz.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pycallgraph/output/graphviz.py b/pycallgraph/output/graphviz.py index 6f10049..203c097 100644 --- a/pycallgraph/output/graphviz.py +++ b/pycallgraph/output/graphviz.py @@ -45,6 +45,7 @@ def add_arguments(cls, subparsers, parent_parser, usage): subparser.add_argument( '-f', '--output-format', type=str, default=defaults.output_type, + dest='output_type', help='Image format to produce, e.g. png, ps, dot, etc. ' 'See http://www.graphviz.org/doc/info/output.html for more.', ) From e201d73b8217595c52388830381f4bd023e06455 Mon Sep 17 00:00:00 2001 From: "David J. Felix" Date: Mon, 29 Dec 2014 15:51:23 -0500 Subject: [PATCH 15/27] Make Readme badges uniform and SVG using shields.io --- README.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index ad4919d..70322a4 100644 --- a/README.rst +++ b/README.rst @@ -3,14 +3,14 @@ Python Call Graph Welcome! Python Call Graph is a `Python `_ module that creates `call graph `_ visualizations for Python applications. -.. image:: https://travis-ci.org/gak/pycallgraph.png +.. image:: https://img.shields.io/travis/gak/pycallgraph.svg :target: https://travis-ci.org/gak/pycallgraph -.. image:: https://coveralls.io/repos/gak/pycallgraph/badge.png?branch=develop +.. image:: https://img.shields.io/coveralls/gak/pycallgraph/develop.svg :target: https://coveralls.io/r/gak/pycallgraph?branch=develop -.. image:: https://pypip.in/v/pycallgraph/badge.png - :target: https://crate.io/packages/pycallgraph/ -.. image:: https://pypip.in/d/pycallgraph/badge.png +.. image:: https://img.shields.io/pypi/v/pycallgraph.svg :target: https://crate.io/packages/pycallgraph/ +.. image:: https://img.shields.io/pypi/dm/pycallgraph.svg + :target: https://crate.io/packages/pycallgraph Screenshots =========== From 1d8f276102bbb54efb5328a6b0afff8f1d3fea7b Mon Sep 17 00:00:00 2001 From: Fabio Caritas Barrionuevo da Luz Date: Thu, 5 Mar 2015 10:05:42 -0300 Subject: [PATCH 16/27] decorators from @ivannotes --- .gitignore | 2 ++ pycallgraph/__init__.py | 1 + pycallgraph/decorators.py | 16 ++++++++++++++++ test/test_pycallgraph.py | 15 ++++++++++++++- 4 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 pycallgraph/decorators.py diff --git a/.gitignore b/.gitignore index 2523144..6b42ecb 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,5 @@ dist/ *.pyc __pycache__ +.idea + diff --git a/pycallgraph/__init__.py b/pycallgraph/__init__.py index 7ac4695..b1ecc15 100644 --- a/pycallgraph/__init__.py +++ b/pycallgraph/__init__.py @@ -14,6 +14,7 @@ from .pycallgraph import PyCallGraph from .exceptions import PyCallGraphException +from .decorators import pycall_profile from .config import Config from .globbing_filter import GlobbingFilter from .grouper import Grouper diff --git a/pycallgraph/decorators.py b/pycallgraph/decorators.py new file mode 100644 index 0000000..1db1a8e --- /dev/null +++ b/pycallgraph/decorators.py @@ -0,0 +1,16 @@ +import functools + +from .output import GraphvizOutput +from .pycallgraph import PyCallGraph + + +def pycall_profile(output=GraphvizOutput(), config=None): + + def inner(func): + + @functools.wraps(func) + def exec_func(*args, **kw_args): + with(PyCallGraph(output, config)): + return func(*args, **kw_args) + return exec_func + return inner diff --git a/test/test_pycallgraph.py b/test/test_pycallgraph.py index bdc7dc4..94679b3 100644 --- a/test/test_pycallgraph.py +++ b/test/test_pycallgraph.py @@ -1,5 +1,6 @@ from helpers import * - +from pycallgraph.output import GraphvizOutput +from pycallgraph import pycall_profile def test_start_no_outputs(pycg): with pytest.raises(PyCallGraphException): @@ -18,3 +19,15 @@ def test_get_tracer_class(pycg): pycg.config.threaded = False assert pycg.get_tracer_class() == SyncronousTracer + +@pycall_profile() +def print_something(): + print "hello" + + +def test_wrapper(): + print_something() + + +if __name__ == "__main__": + test_wrapper() \ No newline at end of file From ac293eb7482c23ca6d03e86214dc6c37517ec36b Mon Sep 17 00:00:00 2001 From: Fabio Caritas Barrionuevo da Luz Date: Thu, 5 Mar 2015 10:24:07 -0300 Subject: [PATCH 17/27] pep8 and add newline --- test/test_pycallgraph.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_pycallgraph.py b/test/test_pycallgraph.py index 94679b3..5fd3602 100644 --- a/test/test_pycallgraph.py +++ b/test/test_pycallgraph.py @@ -30,4 +30,4 @@ def test_wrapper(): if __name__ == "__main__": - test_wrapper() \ No newline at end of file + test_wrapper() From f751b95437cf8251ca46a97ad6f65ca70c82c3cb Mon Sep 17 00:00:00 2001 From: Fabio Caritas Barrionuevo da Luz Date: Thu, 5 Mar 2015 10:57:34 -0300 Subject: [PATCH 18/27] pep8 --- test/test_pycallgraph.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/test_pycallgraph.py b/test/test_pycallgraph.py index 5fd3602..d902c40 100644 --- a/test/test_pycallgraph.py +++ b/test/test_pycallgraph.py @@ -1,8 +1,9 @@ from helpers import * -from pycallgraph.output import GraphvizOutput from pycallgraph import pycall_profile + def test_start_no_outputs(pycg): + with pytest.raises(PyCallGraphException): pycg.start() @@ -20,6 +21,7 @@ def test_get_tracer_class(pycg): pycg.config.threaded = False assert pycg.get_tracer_class() == SyncronousTracer + @pycall_profile() def print_something(): print "hello" From db347c1112e286e85ad54dfd3b681c2c34c40019 Mon Sep 17 00:00:00 2001 From: "Fabio C. Barrionuevo da Luz" Date: Wed, 27 May 2015 22:20:41 -0300 Subject: [PATCH 19/27] renamed decorator from "pycall_profile" to "trace" set all "trace" decorator parameters to None by default moved "trace" decorator tests from test_pycallgraph.py to test_decorators.py add setup.cfg config to wheel package support --- pycallgraph/__init__.py | 2 +- pycallgraph/decorators.py | 7 +++---- pycallgraph/pycallgraph.py | 3 +-- setup.cfg | 2 ++ test/test_decorators.py | 39 ++++++++++++++++++++++++++++++++++++++ test/test_pycallgraph.py | 15 --------------- 6 files changed, 46 insertions(+), 22 deletions(-) create mode 100644 setup.cfg create mode 100644 test/test_decorators.py diff --git a/pycallgraph/__init__.py b/pycallgraph/__init__.py index b1ecc15..6bfdd1d 100644 --- a/pycallgraph/__init__.py +++ b/pycallgraph/__init__.py @@ -14,7 +14,7 @@ from .pycallgraph import PyCallGraph from .exceptions import PyCallGraphException -from .decorators import pycall_profile +from . import decorators from .config import Config from .globbing_filter import GlobbingFilter from .grouper import Grouper diff --git a/pycallgraph/decorators.py b/pycallgraph/decorators.py index 1db1a8e..78b3ce0 100644 --- a/pycallgraph/decorators.py +++ b/pycallgraph/decorators.py @@ -1,16 +1,15 @@ import functools -from .output import GraphvizOutput from .pycallgraph import PyCallGraph -def pycall_profile(output=GraphvizOutput(), config=None): - +def trace(output=None, config=None): def inner(func): - @functools.wraps(func) def exec_func(*args, **kw_args): with(PyCallGraph(output, config)): return func(*args, **kw_args) + return exec_func + return inner diff --git a/pycallgraph/pycallgraph.py b/pycallgraph/pycallgraph.py index 1a03a50..6af5428 100644 --- a/pycallgraph/pycallgraph.py +++ b/pycallgraph/pycallgraph.py @@ -7,12 +7,11 @@ class PyCallGraph(object): - def __init__(self, output=None, config=None): '''output can be a single Output instance or an iterable with many of them. Example usage: - PyCallGraph(config=Config(), output=GraphvizOutput()) + PyCallGraph(output=GraphvizOutput(), config=Config()) ''' locale.setlocale(locale.LC_ALL, '') diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..5e40900 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[wheel] +universal = 1 diff --git a/test/test_decorators.py b/test/test_decorators.py new file mode 100644 index 0000000..8392a4c --- /dev/null +++ b/test/test_decorators.py @@ -0,0 +1,39 @@ +import pytest + +import pycallgraph +from pycallgraph import PyCallGraphException +from pycallgraph.output import GephiOutput, GraphvizOutput + + +@pycallgraph.decorators.trace(output=GraphvizOutput()) +def print_something(): + print("hello") + + +@pycallgraph.decorators.trace(output=GephiOutput()) +def print_foo(): + print("foo") + + +@pycallgraph.decorators.trace() +def print_bar(): + print("bar") + + +def test_trace_decorator_graphviz_output(): + print_something() + + +def test_trace_decorator_gephi_output(): + print_foo() + + +def test_trace_decorator_parameter(): + with pytest.raises(PyCallGraphException): + print_bar() + + +if __name__ == "__main__": + test_trace_decorator_graphviz_output() + test_trace_decorator_gephi_output() + test_trace_decorator_parameter() diff --git a/test/test_pycallgraph.py b/test/test_pycallgraph.py index d902c40..bdc7dc4 100644 --- a/test/test_pycallgraph.py +++ b/test/test_pycallgraph.py @@ -1,9 +1,7 @@ from helpers import * -from pycallgraph import pycall_profile def test_start_no_outputs(pycg): - with pytest.raises(PyCallGraphException): pycg.start() @@ -20,16 +18,3 @@ def test_get_tracer_class(pycg): pycg.config.threaded = False assert pycg.get_tracer_class() == SyncronousTracer - - -@pycall_profile() -def print_something(): - print "hello" - - -def test_wrapper(): - print_something() - - -if __name__ == "__main__": - test_wrapper() From ee33d763bbb474edfc9c11b9f75b3942463d2af6 Mon Sep 17 00:00:00 2001 From: "Fabio C. Barrionuevo da Luz" Date: Wed, 27 May 2015 22:31:06 -0300 Subject: [PATCH 20/27] test with python 3.5-dev nightly builds and pypy --- .travis.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.travis.yml b/.travis.yml index be55292..e4bf85e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,16 @@ language: python: - "2.7" - "3.3" + - "3.4" + - "pypy" + - "nightly" + +matrix: + allow_failures: + - python: + - "pypy" + - python: + - "nigthly" env: global: From 475fbd75c71fcd4a476bf1b9c37fa1305dc66851 Mon Sep 17 00:00:00 2001 From: "Fabio C. Barrionuevo da Luz" Date: Wed, 27 May 2015 22:45:10 -0300 Subject: [PATCH 21/27] fix .travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e4bf85e..827be89 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,7 +26,7 @@ before_install: install: - "pip install -r requirements/development.txt --use-mirrors" - - "if [[ $TRAVIS_PYTHON_VERSION == '3.3' ]]; then make 2to3; fi" + - "if [[ $TRAVIS_PYTHON_VERSION == '3.3' ]] || [[ $TRAVIS_PYTHON_VERSION == '3.4' ]] || [[ $TRAVIS_PYTHON_VERSION == 'nightly' ]]; then make 2to3; fi" script: - make tests From 09ef7faa56af957eb3f0eaedfcee6287d6357507 Mon Sep 17 00:00:00 2001 From: "Fabio C. Barrionuevo da Luz" Date: Wed, 27 May 2015 22:58:16 -0300 Subject: [PATCH 22/27] fix .travis.yml --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 827be89..8ab1108 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,9 @@ python: - "3.3" - "3.4" - "pypy" - - "nightly" +# pytest does not support python 3.5 +# https://bitbucket.org/pytest-dev/pytest/pull-request/296/astcall-signature-changed-on-35 +# - "nightly" matrix: allow_failures: From d23f9a444fb1715403eb46e3c6295f205ed3b7c8 Mon Sep 17 00:00:00 2001 From: "robert.gomulka" Date: Fri, 12 Jun 2015 08:53:29 +0200 Subject: [PATCH 23/27] [Bugfix] Allow spaces in tool path Fix for #85. When using the following pycallgraph invocation: python c:\Python27\Scripts\pycallgraph graphviz -l "c:\Program Files (x86)\Graphviz2.36\bin\dot.exe" -- scapy_start_profile.py the tool could not be found. After the fix it's ok to use such paths. --- pycallgraph/output/graphviz.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pycallgraph/output/graphviz.py b/pycallgraph/output/graphviz.py index 6c38eb0..f09d5ef 100644 --- a/pycallgraph/output/graphviz.py +++ b/pycallgraph/output/graphviz.py @@ -100,7 +100,7 @@ def done(self): with os.fdopen(fd, 'w') as f: f.write(source) - cmd = '{0} -T{1} -o{2} {3}'.format( + cmd = '"{0}" -T{1} -o{2} {3}'.format( self.tool, self.output_type, self.output_file, temp_name ) From f552d41643e72d2d3929808a6592a0750a0cadf0 Mon Sep 17 00:00:00 2001 From: "Boris V." Date: Thu, 20 Aug 2015 14:07:07 -0700 Subject: [PATCH 24/27] Update readme with a typo correction. --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 70322a4..a3d0972 100644 --- a/README.rst +++ b/README.rst @@ -27,7 +27,7 @@ Click on the images below to see a larger version and the source code that gener Project Status ============== -The latest version is **1.0.1** which was released on 2013-09-17, and is a backwards incompatbile from the previous release. +The latest version is **1.0.1** which was released on 2013-09-17, and is a backwards incompatible from the previous release. The `project lives on GitHub `_, where you can `report issues `_, contribute to the project by `forking the project `_ then creating a `pull request `_, or just `browse the source code `_. From 613ce31507d20f505bce26af971e0797f09014c8 Mon Sep 17 00:00:00 2001 From: seperman Date: Thu, 27 Aug 2015 19:18:08 -0700 Subject: [PATCH 25/27] Proper way of running system calls http://stackoverflow.com/a/3791476/1497443 --- pycallgraph/output/graphviz.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pycallgraph/output/graphviz.py b/pycallgraph/output/graphviz.py index f09d5ef..e875e39 100644 --- a/pycallgraph/output/graphviz.py +++ b/pycallgraph/output/graphviz.py @@ -3,6 +3,7 @@ import tempfile import os import textwrap +import subprocess as sub from ..metadata import __version__ from ..exceptions import PyCallGraphException @@ -106,7 +107,8 @@ def done(self): self.verbose('Executing: {0}'.format(cmd)) try: - ret = os.system(cmd) + proc = sub.Popen(cmd, stdout=sub.PIPE, stderr=sub.PIPE, shell=True) + ret, output = proc.communicate() if ret: raise PyCallGraphException( 'The command "%(cmd)s" failed with error ' From 015ec1441c8ab1e53dd7a8837bd0d734a26c3ee8 Mon Sep 17 00:00:00 2001 From: rbubley Date: Wed, 15 Mar 2017 13:42:39 +0000 Subject: [PATCH 26/27] Update .travis.yaml `--use-mirrors` was removed as a `pip` option (https://github.com/pypa/pip/pull/1098) --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 8ab1108..d3c35bf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,7 +27,7 @@ before_install: - sudo apt-get install -qq graphviz install: - - "pip install -r requirements/development.txt --use-mirrors" + - "pip install -r requirements/development.txt" - "if [[ $TRAVIS_PYTHON_VERSION == '3.3' ]] || [[ $TRAVIS_PYTHON_VERSION == '3.4' ]] || [[ $TRAVIS_PYTHON_VERSION == 'nightly' ]]; then make 2to3; fi" script: From f395ce9224dba873ed65c33906d3959e6a9e50de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Agusti=CC=81n=20Benassi?= Date: Wed, 12 Jul 2017 23:50:36 -0300 Subject: [PATCH 27/27] Fix decorator implementation and import. Document use of decorators in README. --- README.rst | 28 ++++++++++++++++++++++++++++ pycallgraph/__init__.py | 5 ++++- pycallgraph/decorators.py | 7 ++++++- 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index a3d0972..8db8fcb 100644 --- a/README.rst +++ b/README.rst @@ -66,6 +66,34 @@ A simple use of the API is:: with PyCallGraph(output=GraphvizOutput()): code_to_profile() +Use decorators for an even more simple use of the API:: + + from pycallgraph.decorators import trace + + @trace("path/to/output.png") + def main(): + code_to_profile() + + main() + +Or decorate a specific function inside your code you want to profile:: + + from pycallgraph.decorators import trace + + @trace("path/to/output.png") + def function_1(): + do_stuff + + def function_2(): + do_stuff + + def code_to_profile(): + function_1() + function_2() + + code_to_profile() + + Documentation ============= diff --git a/pycallgraph/__init__.py b/pycallgraph/__init__.py index 6bfdd1d..644e4ea 100644 --- a/pycallgraph/__init__.py +++ b/pycallgraph/__init__.py @@ -14,7 +14,10 @@ from .pycallgraph import PyCallGraph from .exceptions import PyCallGraphException -from . import decorators +try: + from . import decorators +except Exception: + import decorators from .config import Config from .globbing_filter import GlobbingFilter from .grouper import Grouper diff --git a/pycallgraph/decorators.py b/pycallgraph/decorators.py index 78b3ce0..ad415b3 100644 --- a/pycallgraph/decorators.py +++ b/pycallgraph/decorators.py @@ -1,13 +1,18 @@ import functools from .pycallgraph import PyCallGraph +from .output import GraphvizOutput def trace(output=None, config=None): def inner(func): @functools.wraps(func) def exec_func(*args, **kw_args): - with(PyCallGraph(output, config)): + + graphviz = GraphvizOutput() + graphviz.output_file = output + + with(PyCallGraph(output=graphviz, config=config)): return func(*args, **kw_args) return exec_func