pycallgraph-1.0.1/0000775000175000017500000000000012216026765013356 5ustar gakgak00000000000000pycallgraph-1.0.1/test/0000775000175000017500000000000012216026765014335 5ustar gakgak00000000000000pycallgraph-1.0.1/test/test_color.py0000664000175000017500000000106512216012716017055 0ustar gakgak00000000000000from helpers import * def test_bad_range(): with pytest.raises(ColorException): Color(0, 5, 0, 0) with pytest.raises(ColorException): Color(0, 0, -1, 0) def test_hsv(): c = Color.hsv(0.1, 0.5, 0.75, 0.25) assert c.r is 0.75 assert abs(c.g - 0.6) < 0.1 # Floating point comparison inaccurate assert abs(c.b - 0.375) < 0.1 assert c.a is 0.25 def test_rgb_csv(): assert Color(0.3, 0.4, 0.5, 0.6).rgb_csv() == '76,102,127' def test_str(): assert str(Color(0.071, 0.204, 0.338, 0.471)) == '' pycallgraph-1.0.1/test/test_gephi.py0000664000175000017500000000101412216012716017025 0ustar gakgak00000000000000from helpers import * from calls import * @pytest.fixture def gephi(temp): g = GephiOutput() g.output_file = temp return g def test_simple(gephi): with PyCallGraph(output=gephi): one_nop() generated = open(gephi.output_file).read() os.unlink(gephi.output_file) assert 'nodedef> name VARCHAR' in generated assert 'edgedef> node1 VARCHAR, node2 VARCHAR' in generated assert 'calls.one_nop,calls.one_nop,calls,1' in generated assert 'calls.one_nop,calls.nop,1' in generated pycallgraph-1.0.1/test/test_pycallgraph.py0000664000175000017500000000071212216012716020243 0ustar gakgak00000000000000from helpers import * def test_start_no_outputs(pycg): with pytest.raises(PyCallGraphException): pycg.start() def test_with_block_no_outputs(pycg): with pytest.raises(PyCallGraphException): with pycg: pass def test_get_tracer_class(pycg): pycg.config.threaded = True assert pycg.get_tracer_class() == AsyncronousTracer pycg.config.threaded = False assert pycg.get_tracer_class() == SyncronousTracer pycallgraph-1.0.1/test/test_graphviz.py0000664000175000017500000000063612216012716017574 0ustar gakgak00000000000000from helpers import * from calls import * @pytest.fixture def graphviz(temp): g = GraphvizOutput() g.output_file = temp g.output_type = 'dot' return g def test_simple(graphviz): with PyCallGraph(output=graphviz): one_nop() dot = open(graphviz.output_file).read() os.unlink(graphviz.output_file) assert 'digraph G' in dot assert '__main__ -> "calls.one_nop"' in dot pycallgraph-1.0.1/test/test_script.py0000664000175000017500000000055512216012716017246 0ustar gakgak00000000000000import subprocess from helpers import * def execute(arguments): command = 'PYTHONPATH=. scripts/pycallgraph ' + arguments return subprocess.check_output(command, shell=True).decode('utf-8') def test_help(): assert 'Python Call Graph' in execute('--help') def test_graphviz_help(): assert '--font-name FONT_NAME' in execute('graphviz --help') pycallgraph-1.0.1/test/test_trace_processor.py0000664000175000017500000000240712216012716021135 0ustar gakgak00000000000000import re import sys from helpers import * import calls from pycallgraph.tracer import TraceProcessor @pytest.fixture def trace_processor(config): return TraceProcessor([], config) def test_empty(trace_processor): sys.settrace(trace_processor.process) sys.settrace(None) assert trace_processor.call_dict == {} def test_nop(trace_processor): sys.settrace(trace_processor.process) calls.nop() sys.settrace(None) assert trace_processor.call_dict == { '__main__': { 'calls.nop': 1 } } def test_one_nop(trace_processor): sys.settrace(trace_processor.process) calls.one_nop() sys.settrace(None) assert trace_processor.call_dict == { '__main__': {'calls.one_nop': 1}, 'calls.one_nop': {'calls.nop': 1}, } def stdlib_trace(trace_processor, include_stdlib): trace_processor.config = Config(include_stdlib=include_stdlib) sys.settrace(trace_processor.process) re.match("asdf", "asdf") calls.one_nop() sys.settrace(None) return trace_processor.call_dict def test_no_stdlib(trace_processor): assert 're.match' not in stdlib_trace(trace_processor, False) def test_yes_stdlib(trace_processor): assert 're.match' in stdlib_trace(trace_processor, True) pycallgraph-1.0.1/test/test_config.py0000664000175000017500000000016412216012716017203 0ustar gakgak00000000000000from helpers import * def test_init(): assert Config().groups assert Config(groups=False).groups is False pycallgraph-1.0.1/test/test_output.py0000664000175000017500000000015512216026610017274 0ustar gakgak00000000000000from helpers import * def test_set_config(): '''Should not raise!''' Output().set_config(Config()) pycallgraph-1.0.1/test/test_util.py0000664000175000017500000000100112216012716016702 0ustar gakgak00000000000000from helpers import * def test_human_readable_biyte(): hrb = Util.human_readable_bibyte assert hrb(0) == '0.0B' assert hrb(1024) == '1.0KiB' assert hrb(1024 * 5.2) == '5.2KiB' assert hrb(1024 * 1024 * 5.2) == '5.2MiB' assert hrb(1024 * 1024 * 1024 * 5.2) == '5.2GiB' assert hrb(1024 * 1024 * 1024 * 1024 * 5.2) == '5.2TiB' assert hrb(1024 * 1024 * 1024 * 1024 * 1024 * 5.2) == '5324.8TiB' assert hrb(-1024 * 1024 * 1024 * 5.2) == '-5.2GiB' assert hrb(-1024) == '-1.0KiB' pycallgraph-1.0.1/pycallgraph.egg-info/0000775000175000017500000000000012216026765017356 5ustar gakgak00000000000000pycallgraph-1.0.1/pycallgraph.egg-info/dependency_links.txt0000664000175000017500000000000112216026765023424 0ustar gakgak00000000000000 pycallgraph-1.0.1/pycallgraph.egg-info/SOURCES.txt0000664000175000017500000000151512216026765021244 0ustar gakgak00000000000000LICENSE MANIFEST.in Makefile README.rst setup.py man/pycallgraph.1 pycallgraph/__init__.py pycallgraph/color.py pycallgraph/config.py pycallgraph/exceptions.py pycallgraph/globbing_filter.py pycallgraph/memory_profiler.py pycallgraph/metadata.py pycallgraph/pycallgraph.py pycallgraph/tracer.py pycallgraph/util.py pycallgraph.egg-info/PKG-INFO pycallgraph.egg-info/SOURCES.txt pycallgraph.egg-info/dependency_links.txt pycallgraph.egg-info/top_level.txt pycallgraph/output/__init__.py pycallgraph/output/gephi.py pycallgraph/output/graphviz.py pycallgraph/output/output.py pycallgraph/output/pickle.py pycallgraph/output/ubigraph.py scripts/pycallgraph test/test_color.py test/test_config.py test/test_gephi.py test/test_graphviz.py test/test_output.py test/test_pycallgraph.py test/test_script.py test/test_trace_processor.py test/test_util.pypycallgraph-1.0.1/pycallgraph.egg-info/top_level.txt0000664000175000017500000000001412216026765022103 0ustar gakgak00000000000000pycallgraph pycallgraph-1.0.1/pycallgraph.egg-info/PKG-INFO0000664000175000017500000004750312216026765020464 0ustar gakgak00000000000000Metadata-Version: 1.1 Name: pycallgraph Version: 1.0.1 Summary: Python Call Graph is a library and command line tool that visualises the flow of your Python application. See http://pycallgraph.slowchop.com/ for more information. Home-page: http://pycallgraph.slowchop.com/ Author: Gerald Kaszuba Author-email: pycallgraph@gakman.com License: GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS Download-URL: http://pycallgraph.slowchop.com/files/download/pycallgraph-1.0.1.tar.gz Description: 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 :target: https://travis-ci.org/gak/pycallgraph .. image:: https://coveralls.io/repos/gak/pycallgraph/badge.png?branch=develop :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 :target: https://crate.io/packages/pycallgraph/ Screenshots =========== Click on the images below to see a larger version and the source code that generated them. .. image:: http://pycallgraph.slowchop.com/en/develop/_images/basic_thumb.png :target: http://pycallgraph.slowchop.com/en/develop/examples/basic.html .. image:: http://pycallgraph.slowchop.com/en/develop/_images/regexp_grouped_thumb.png :target: http://pycallgraph.slowchop.com/en/develop/examples/regexp_grouped.html .. image:: http://pycallgraph.slowchop.com/en/develop/_images/regexp_ungrouped_thumb.png :target: http://pycallgraph.slowchop.com/en/develop/examples/regexp_ungrouped.html 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 `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 `_. The documentation needs some work stiil. Feel free to contribute :) Features ======== * Support for Python 2.7+ and Python 3.3+. * Static visualizations of the call graph using various tools such as Graphviz and Gephi. * Execute pycallgraph from the command line or import it in your code. * Customisable colors. You can programatically set the colors based on number of calls, time taken, memory usage, etc. * Modules can be visually grouped together. * Easily extendable to create your own output formats. Quick Start =========== Installation is easy as:: pip install pycallgraph You can either use the `command-line interface `_ for a quick visualization of your Python script, or the `pycallgraph module `_ for more fine-grained settings. The following examples specify graphviz as the outputter, so it's required to be installed. They will generate a file called **pycallgraph.png**. The command-line method of running pycallgraph is:: $ pycallgraph graphviz -- ./mypythonscript.py A simple use of the API is:: from pycallgraph import PyCallGraph from pycallgraph.output import GraphvizOutput with PyCallGraph(output=GraphvizOutput()): code_to_profile() Documentation ============= Feel free to browse the `documentation of pycallgraph `_ for the `usage guide `_ and `API reference `_. Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: GNU General Public License (GPL) Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: Software Development :: Testing Classifier: Topic :: Software Development :: Debuggers pycallgraph-1.0.1/setup.cfg0000664000175000017500000000007312216026765015177 0ustar gakgak00000000000000[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 pycallgraph-1.0.1/LICENSE0000664000175000017500000003542212216012716014360 0ustar gakgak00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS pycallgraph-1.0.1/pycallgraph/0000775000175000017500000000000012216026765015664 5ustar gakgak00000000000000pycallgraph-1.0.1/pycallgraph/output/0000775000175000017500000000000012216026765017224 5ustar gakgak00000000000000pycallgraph-1.0.1/pycallgraph/output/gephi.py0000664000175000017500000000530512216012716020664 0ustar gakgak00000000000000import math from .output import Output class GephiOutput(Output): def __init__(self, **kwargs): self.fp = None self.output_file = 'pycallgraph.gdf' Output.__init__(self, **kwargs) @classmethod def add_arguments(cls, subparsers, parent_parser, usage): defaults = cls() subparser = subparsers.add_parser( 'gephi', help='Gephi GDF generation', parents=[parent_parser], usage=usage, ) cls.add_output_file( subparser, defaults, 'The generated Gephi GDF file' ) def generate(self): '''Returns a string with the contents of a GDF file.''' return u'\n'.join([ self.generate_nodes(), self.generate_edges(), ]) + '\n' def generate_nodes(self): output = [] fields = u', '.join([ u'name VARCHAR', u'label VARCHAR', u'group VARCHAR', u'calls INTEGER', u'time DOUBLE', u'memory_in INTEGER', u'memory_out INTEGER', u'color VARCHAR', u'width DOUBLE', ]) output.append(u'nodedef> {}'.format(fields)) for node in self.processor.nodes(): fields = u','.join([str(a) for a in [ node.name, node.name, node.group, node.calls.value, node.time.value, node.memory_in.value, node.memory_out.value, u"'{}'".format(self.node_color_func(node).rgb_csv()), self.node_size(node), ]]) output.append(fields) return '\n'.join(output) def node_size(self, node): return math.log(node.time.fraction * (math.e - 1) + 1) * 2 + 1 def generate_edges(self): output = [] fields = u', '.join([ u'node1 VARCHAR', u'node2 VARCHAR', u'label VARCHAR', u'labelvisible VARCHAR', u'directed BOOLEAN', u'color VARCHAR', u'width DOUBLE', ]) output.append(u'edgedef> {}'.format(fields)) for edge in self.processor.edges(): fields = u','.join([str(a) for a in [ edge.src_func, edge.dst_func, self.edge_label(edge), 'true', 'true', u"'{}'".format(self.edge_color_func(edge).rgb_csv()), edge.calls.fraction * 2, ]]) output.append(fields) return '\n'.join(output) def done(self): source = self.generate() f = open(self.output_file, 'w') f.write(source) f.close() pycallgraph-1.0.1/pycallgraph/output/pickle.py0000664000175000017500000000156212216012716021040 0ustar gakgak00000000000000try: import cPickle as pickle except ImportError: import pickle from .output import Output class PickleOutput(Output): def __init__(self, **kwargs): self.fp = None self.output_file = 'pycallgraph.dot' Output.__init__(self, **kwargs) @classmethod def add_arguments(cls, subparsers, parent_parser, usage): defaults = cls() subparser = subparsers.add_parser( 'pickle', help='Dump to a cPickle file for generation later', parents=[parent_parser], usage=usage, ) subparser.add_argument( '-o', '--output-file', type=str, default=defaults.output_file, help='The generated cPickle file', ) return subparser def done(self): self.prepare_output_file() pickle.dump(self.tracer, self.fp, pickle.HIGHEST_PROTOCOL) pycallgraph-1.0.1/pycallgraph/output/graphviz.py0000664000175000017500000001414412216012716021423 0ustar gakgak00000000000000from __future__ import division import tempfile import os import textwrap from ..metadata import __version__ from ..exceptions import PyCallGraphException from ..color import Color from .output import Output class GraphvizOutput(Output): def __init__(self, **kwargs): self.tool = 'dot' self.output_file = 'pycallgraph.png' self.output_type = 'png' self.font_name = 'Verdana' self.font_size = 7 self.group_font_size = 10 self.group_border_color = Color(0, 0, 0, 0.8) Output.__init__(self, **kwargs) self.prepare_graph_attributes() @classmethod def add_arguments(cls, subparsers, parent_parser, usage): defaults = cls() subparser = subparsers.add_parser( 'graphviz', help='Graphviz generation', parents=[parent_parser], usage=usage, ) subparser.add_argument( '-l', '--tool', dest='tool', default=defaults.tool, help='The tool from Graphviz to use, e.g. dot, neato, etc.', ) cls.add_output_file( subparser, defaults, 'The generated Graphviz file' ) subparser.add_argument( '-f', '--output-format', type=str, default=defaults.output_type, help='Image format to produce, e.g. png, ps, dot, etc. ' 'See http://www.graphviz.org/doc/info/output.html for more.', ) subparser.add_argument( '--font-name', type=str, default=defaults.font_name, help='Name of the font to be used', ) subparser.add_argument( '--font-size', type=int, default=defaults.font_size, help='Size of the font to be used', ) def sanity_check(self): self.ensure_binary(self.tool) def prepare_graph_attributes(self): generated_message = '\\n'.join([ r'Generated by Python Call Graph v%s' % __version__, r'http://pycallgraph.slowchop.com', ]) self.graph_attributes = { 'graph': { 'overlap': 'scalexy', 'fontname': self.font_name, 'fontsize': self.font_size, 'fontcolor': Color(0, 0, 0, 0.5).rgba_web(), 'label': generated_message, }, 'node': { 'fontname': self.font_name, 'fontsize': self.font_size, 'fontcolor': Color(0, 0, 0).rgba_web(), 'style': 'filled', 'shape': 'rect', }, 'edge': { 'fontname': self.font_name, 'fontsize': self.font_size, 'fontcolor': Color(0, 0, 0).rgba_web(), } } def done(self): source = self.generate() self.debug(source) fd, temp_name = tempfile.mkstemp() with os.fdopen(fd, 'w') as f: f.write(source) cmd = '{} -T{} -o{} {}'.format( self.tool, self.output_type, self.output_file, temp_name ) self.verbose('Executing: {}'.format(cmd)) try: ret = os.system(cmd) if ret: raise PyCallGraphException( 'The command "%(cmd)s" failed with error ' 'code %(ret)i.' % locals()) finally: os.unlink(temp_name) self.verbose('Generated {} with {} nodes.'.format( self.output_file, len(self.processor.func_count), )) def generate(self): '''Returns a string with the contents of a DOT file for Graphviz to parse. ''' indent_join = '\n' + ' ' * 12 return textwrap.dedent('''\ digraph G {{ // Attributes {} // Groups {} // Nodes {} // Edges {} }} '''.format( indent_join.join(self.generate_attributes()), indent_join.join(self.generate_groups()), indent_join.join(self.generate_nodes()), indent_join.join(self.generate_edges()), )) def attrs_from_dict(self, d): output = [] for attr, val in d.iteritems(): output.append('%s = "%s"' % (attr, val)) return ', '.join(output) def node(self, key, attr): return '"{}" [{}];'.format( key, self.attrs_from_dict(attr), ) def edge(self, edge, attr): return '"{0.src_func}" -> "{0.dst_func}" [{1}];'.format( edge, self.attrs_from_dict(attr), ) def generate_attributes(self): output = [] for section, attrs in self.graph_attributes.iteritems(): output.append('{} [ {} ];'.format( section, self.attrs_from_dict(attrs), )) return output def generate_groups(self): if not self.processor.config.groups: return '' output = [] for group, nodes in self.processor.groups(): funcs = [node.name for node in nodes] funcs = '" "'.join(funcs) group_color = self.group_border_color.rgba_web() group_font_size = self.group_font_size output.append( 'subgraph "cluster_{group}" {{ ' '"{funcs}"; ' 'label = "{group}"; ' 'fontsize = "{group_font_size}"; ' 'fontcolor = "black"; ' 'style = "bold"; ' 'color="{group_color}"; }}'.format(**locals())) return output def generate_nodes(self): output = [] for node in self.processor.nodes(): attr = { 'color': self.node_color_func(node).rgba_web(), 'label': self.node_label_func(node), } output.append(self.node(node.name, attr)) return output def generate_edges(self): output = [] for edge in self.processor.edges(): attr = { 'color': self.edge_color_func(edge).rgba_web(), 'label': self.edge_label_func(edge), } output.append(self.edge(edge, attr)) return output pycallgraph-1.0.1/pycallgraph/output/output.py0000664000175000017500000000704512216026610021131 0ustar gakgak00000000000000import re import os from distutils.spawn import find_executable from ..exceptions import PyCallGraphException from ..color import Color class Output(object): '''Base class for all outputters.''' def __init__(self, **kwargs): self.node_color_func = self.node_color self.edge_color_func = self.edge_color self.node_label_func = self.node_label self.edge_label_func = self.edge_label # Update the defaults with anything from kwargs [setattr(self, k, v) for k, v in kwargs.iteritems()] def set_config(self, config): ''' This is a quick hack to move the config variables set in Config into the output module config variables. ''' for k, v in config.__dict__.iteritems(): if hasattr(self, k) and callable(getattr(self, k)): continue setattr(self, k, v) def node_color(self, node): value = float(node.time.fraction * 2 + node.calls.fraction) / 3 return Color.hsv(value / 2 + .5, value, 0.9) def edge_color(self, edge): value = float(edge.time.fraction * 2 + edge.calls.fraction) / 3 return Color.hsv(value / 2 + .5, value, 0.7) def node_label(self, node): parts = [ '{0.name}', 'calls: {0.calls.value:n}', 'time: {0.time.value:f}s', ] if self.processor.config.memory: parts += [ 'memory in: {0.memory_in.value_human_bibyte}', 'memory out: {0.memory_out.value_human_bibyte}', ] return r'\n'.join(parts).format(node) def edge_label(self, edge): return '{}'.format(edge.calls.value) def sanity_check(self): '''Basic checks for certain libraries or external applications. Raise or warn if there is a problem. ''' pass @classmethod def add_arguments(cls, subparsers): pass def reset(self): pass def set_processor(self, processor): self.processor = processor def start(self): '''Initialise variables after initial configuration.''' pass def update(self): '''Called periodically during a trace, but only when should_update is set to True. ''' raise NotImplementedError('update') def should_update(self): '''Return True if the update method should be called periodically.''' return False def done(self): '''Called when the trace is complete and ready to be saved.''' raise NotImplementedError('done') def ensure_binary(self, cmd): if find_executable(cmd): return raise PyCallGraphException( 'The command "{}" is required to be in your path.'.format(cmd)) def normalize_path(self, path): regex_user_expand = re.compile('\A~') if regex_user_expand.match(path): path = os.path.expanduser(path) else: path = os.path.expandvars(path) # expand, just in case return path def prepare_output_file(self): if self.fp is None: self.output_file = self.normalize_path(self.output_file) self.fp = open(self.output_file, 'wb') def verbose(self, text): self.processor.config.log_verbose(text) def debug(self, text): self.processor.config.log_debug(text) @classmethod def add_output_file(cls, subparser, defaults, help): subparser.add_argument( '-o', '--output-file', type=str, default=defaults.output_file, help=help, ) pycallgraph-1.0.1/pycallgraph/output/ubigraph.py0000664000175000017500000000236112216012716021370 0ustar gakgak00000000000000try: from xmlrpclib import Server except ImportError: from xmlrpc.client import Server # from ..exceptions import PyCallGraphException from .output import Output class UbigraphOutput(Output): def __init__(self, **kwargs): self.fp = None self.server_url = 'http://127.0.0.1:20738/RPC2' Output.__init__(self, **kwargs) def start(self): server = Server(self.server_url) self.graph = server.ubigraph # Create a graph for i in range(0, 10): self.graph.new_vertex_w_id(i) # Make some edges for i in range(0, 10): self.graph.new_edge(i, (i + 1) % 10) def should_update(self): return True def update(self): pass @classmethod def add_arguments(cls, subparsers, parent_parser, usage): defaults = cls() subparser = subparsers.add_parser( 'ubigraph', help='Update an Ubigraph visualization in real time', parents=[parent_parser], usage=usage, ) subparser.add_argument( '-s', '--server-url', type=str, default=defaults.server_url, help='The Ubigraph server', ) return subparser def done(self): pass pycallgraph-1.0.1/pycallgraph/output/__init__.py0000664000175000017500000000050712216012716021326 0ustar gakgak00000000000000import collections from .output import Output from .graphviz import GraphvizOutput from .gephi import GephiOutput from .ubigraph import UbigraphOutput from .pickle import PickleOutput outputters = collections.OrderedDict([ ('graphviz', GraphvizOutput), ('gephi', GephiOutput), # ('ubigraph', UbigraphOutput), ]) pycallgraph-1.0.1/pycallgraph/exceptions.py0000664000175000017500000000006012216012716020402 0ustar gakgak00000000000000class PyCallGraphException(Exception): pass pycallgraph-1.0.1/pycallgraph/globbing_filter.py0000664000175000017500000000156112216012716021360 0ustar gakgak00000000000000from fnmatch import fnmatch 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): 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 def __call__(self, full_name=None): 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 pycallgraph-1.0.1/pycallgraph/color.py0000664000175000017500000000271712216012716017352 0ustar gakgak00000000000000import colorsys class ColorException(Exception): pass class Color(object): def __init__(self, r, g, b, a=1): self.r = r self.g = g self.b = b self.a = a self.validate_all() @classmethod def hsv(cls, h, s, v, a=1): r, g, b = colorsys.hsv_to_rgb(h, s, v) return cls(r, g, b, a) def __str__(self): return ''.format(self.rgba_web()) def validate_all(self): self.validate('r') self.validate('g') self.validate('b') self.validate('a') def validate(self, attr): v = getattr(self, attr) if not 0 <= v <= 1: raise ColorException('{} out of range 0 to 1: {}'.format(attr, v)) @property def r255(self): return int(self.r * 255) @property def g255(self): return int(self.g * 255) @property def b255(self): return int(self.b * 255) @property def a255(self): return int(self.a * 255) def rgb_web(self): '''Returns a string with the RGB components as a HTML hex string.''' return '#{0.r255:02x}{0.g255:02x}{0.b255:02x}'.format(self) def rgba_web(self): '''Returns a string with the RGBA components as a HTML hex string.''' return '{0}{1.a255:02x}'.format(self.rgb_web(), self) def rgb_csv(self): '''Returns a string with the RGB components as CSV.''' return '{0.r255},{0.g255},{0.b255}'.format(self) pycallgraph-1.0.1/pycallgraph/pycallgraph.py0000664000175000017500000000511312216012716020533 0ustar gakgak00000000000000import locale from .output import Output from .config import Config from .tracer import AsyncronousTracer, SyncronousTracer from .exceptions import PyCallGraphException 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()) ''' locale.setlocale(locale.LC_ALL, '') if output is None: self.output = [] elif isinstance(output, Output): self.output = [output] else: self.output = output self.config = config or Config() configured_ouput = self.config.get_output() if configured_ouput: self.output.append(configured_ouput) self.reset() def __enter__(self): self.start() def __exit__(self, type, value, traceback): self.done() def get_tracer_class(self): if self.config.threaded: return AsyncronousTracer else: return SyncronousTracer def reset(self): '''Resets all collected statistics. This is run automatically by start(reset=True) and when the class is initialized. ''' self.tracer = self.get_tracer_class()(self.output, config=self.config) for output in self.output: self.prepare_output(output) def start(self, reset=True): '''Begins a trace. Setting reset to True will reset all previously recorded trace data. ''' if not self.output: raise PyCallGraphException( 'No outputs declared. Please see the ' 'examples in the online documentation.' ) if reset: self.reset() for output in self.output: output.start() self.tracer.start() def stop(self): '''Stops the currently running trace, if any.''' self.tracer.stop() def done(self): '''Stops the trace and tells the outputters to generate their output. ''' self.stop() self.generate() def generate(self): # If in threaded mode, wait for the processor thread to complete self.tracer.done() for output in self.output: output.done() def add_output(self, output): self.output.append(output) self.prepare_output(output) def prepare_output(self, output): output.sanity_check() output.set_processor(self.tracer.processor) output.reset() pycallgraph-1.0.1/pycallgraph/util.py0000664000175000017500000000046412216012716017206 0ustar gakgak00000000000000class Util(object): @staticmethod def human_readable_bibyte(num): num = float(num) for x in ['B', 'KiB', 'MiB', 'GiB']: if num < 1024 and num > -1024: return '{:3.1f}{}'.format(num, x) num /= 1024 return '{:3.1f}{}'.format(num, 'TiB') pycallgraph-1.0.1/pycallgraph/__init__.py0000664000175000017500000000120312216012716017760 0ustar gakgak00000000000000''' Python Call Graph is a library and command line tool that visualises the flow of your Python application. See http://pycallgraph.slowchop.com/ for more information. ''' from .metadata import __version__ from .metadata import __copyright__ from .metadata import __license__ from .metadata import __author__ from .metadata import __email__ from .metadata import __url__ from .metadata import __credits__ from .pycallgraph import PyCallGraph from .exceptions import PyCallGraphException from .config import Config from .globbing_filter import GlobbingFilter from .util import Util from .color import Color from .color import ColorException pycallgraph-1.0.1/pycallgraph/memory_profiler.py0000664000175000017500000004733212216012716021450 0ustar gakgak00000000000000"""Profile the memory usage of a Python program""" __version__ = '0.25' _CMD_USAGE = "python -m memory_profiler script_file.py" import time, sys, os, pdb import warnings import linecache import inspect import subprocess # TODO: provide alternative when multprocessing is not available try: from multiprocessing import Process, Pipe except ImportError: from multiprocessing.dummy import Process, Pipe try: import psutil def _get_memory(pid): process = psutil.Process(pid) try: mem = float(process.get_memory_info()[0]) / (1024 ** 2) except psutil.AccessDenied: mem = -1 return mem except ImportError: warnings.warn("psutil module not found. memory_profiler will be slow") if os.name == 'posix': def _get_memory(pid): # .. # .. memory usage in MB .. # .. this should work on both Mac and Linux .. # .. subprocess.check_output appeared in 2.7, using Popen .. # .. for backwards compatibility .. out = subprocess.Popen(['ps', 'v', '-p', str(pid)], stdout=subprocess.PIPE).communicate()[0].split(b'\n') try: vsz_index = out[0].split().index(b'RSS') return float(out[1].split()[vsz_index]) / 1024 except: return -1 else: raise NotImplementedError('The psutil module is required for non-unix ' 'platforms') class Timer(Process): """ Fetch memory consumption from over a time interval """ def __init__(self, monitor_pid, interval, pipe, *args, **kw): self.monitor_pid = monitor_pid self.interval = interval self.pipe = pipe self.cont = True super(Timer, self).__init__(*args, **kw) def run(self): m = _get_memory(self.monitor_pid) timings = [m] self.pipe.send(0) # we're ready while not self.pipe.poll(self.interval): m = _get_memory(self.monitor_pid) timings.append(m) self.pipe.send(timings) def memory_usage(proc=-1, interval=0.0, timeout=None): """ Return the memory usage of a process or piece of code Parameters ---------- proc : {int, string, tuple, subprocess.Popen}, optional The process to monitor. Can be given by an integer/string representing a PID, by a Popen object or by a tuple representing a Python function. The tuple contains three values (f, args, kw) and specifies to run the function f(*args, **kw). Set to -1 (default) for current process. interval : float, optional Interval at which measurements are collected. timeout : float, optional Maximum amount of time (in seconds) to wait before returning. Returns ------- mem_usage : list of floating-poing values memory usage, in MB. It's length is always < timeout / interval """ ret = [] if timeout is not None: max_iter = int(timeout / interval) elif isinstance(proc, int): # external process and no timeout max_iter = 1 else: # for a Python function wait until it finishes max_iter = float('inf') if hasattr(proc, '__call__'): proc = (proc, (), {}) if isinstance(proc, (list, tuple)): if len(proc) == 1: f, args, kw = (proc[0], (), {}) elif len(proc) == 2: f, args, kw = (proc[0], proc[1], {}) elif len(proc) == 3: f, args, kw = (proc[0], proc[1], proc[2]) else: raise ValueError aspec = inspect.getargspec(f) n_args = len(aspec.args) if aspec.defaults is not None: n_args -= len(aspec.defaults) if n_args != len(args): raise ValueError( 'Function expects %s value(s) but %s where given' % (n_args, len(args))) child_conn, parent_conn = Pipe() # this will store Timer's results p = Timer(os.getpid(), interval, child_conn) p.start() parent_conn.recv() # wait until we start getting memory f(*args, **kw) parent_conn.send(0) # finish timing ret = parent_conn.recv() p.join(5 * interval) elif isinstance(proc, subprocess.Popen): # external process, launched from Python while True: ret.append(_get_memory(proc.pid)) time.sleep(interval) if timeout is not None: max_iter -= 1 if max_iter == 0: break if proc.poll() is not None: break else: # external process if proc == -1: proc = os.getpid() if max_iter == -1: max_iter = 1 counter = 0 while counter < max_iter: counter += 1 ret.append(_get_memory(proc)) time.sleep(interval) return ret # .. # .. utility functions for line-by-line .. def _find_script(script_name): """ Find the script. If the input is not a file, then $PATH will be searched. """ if os.path.isfile(script_name): return script_name path = os.getenv('PATH', os.defpath).split(os.pathsep) for folder in path: if folder == '': continue fn = os.path.join(folder, script_name) if os.path.isfile(fn): return fn sys.stderr.write('Could not find script {0}\n'.format(script_name)) raise SystemExit(1) class LineProfiler: """ A profiler that records the amount of memory for each line """ def __init__(self, **kw): self.functions = list() self.code_map = {} self.enable_count = 0 self.max_mem = kw.get('max_mem', None) def __call__(self, func): self.add_function(func) f = self.wrap_function(func) f.__module__ = func.__module__ f.__name__ = func.__name__ f.__doc__ = func.__doc__ f.__dict__.update(getattr(func, '__dict__', {})) return f def add_function(self, func): """ Record line profiling information for the given Python function. """ try: # func_code does not exist in Python3 code = func.__code__ except AttributeError: import warnings warnings.warn("Could not extract a code object for the object %r" % (func,)) return if code not in self.code_map: self.code_map[code] = {} self.functions.append(func) def wrap_function(self, func): """ Wrap a function to profile it. """ def f(*args, **kwds): self.enable_by_count() try: result = func(*args, **kwds) finally: self.disable_by_count() return result return f def run(self, cmd): """ Profile a single executable statment in the main namespace. """ import __main__ main_dict = __main__.__dict__ return self.runctx(cmd, main_dict, main_dict) def runctx(self, cmd, globals, locals): """ Profile a single executable statement in the given namespaces. """ self.enable_by_count() try: exec(cmd, globals, locals) finally: self.disable_by_count() return self def runcall(self, func, *args, **kw): """ Profile a single function call. """ # XXX where is this used ? can be removed ? self.enable_by_count() try: return func(*args, **kw) finally: self.disable_by_count() def enable_by_count(self): """ Enable the profiler if it hasn't been enabled before. """ if self.enable_count == 0: self.enable() self.enable_count += 1 def disable_by_count(self): """ Disable the profiler if the number of disable requests matches the number of enable requests. """ if self.enable_count > 0: self.enable_count -= 1 if self.enable_count == 0: self.disable() def trace_memory_usage(self, frame, event, arg): """Callback for sys.settrace""" if event in ('line', 'return') and frame.f_code in self.code_map: lineno = frame.f_lineno if event == 'return': lineno += 1 entry = self.code_map[frame.f_code].setdefault(lineno, []) entry.append(_get_memory(os.getpid())) return self.trace_memory_usage def trace_max_mem(self, frame, event, arg): # run into PDB as soon as memory is higher than MAX_MEM if event in ('line', 'return') and frame.f_code in self.code_map: c = _get_memory(os.getpid()) if c >= self.max_mem: t = 'Current memory {0:.2f} MB exceeded the maximum '.format(c) + \ 'of {0:.2f} MB\n'.format(self.max_mem) sys.stdout.write(t) sys.stdout.write('Stepping into the debugger \n') frame.f_lineno -= 2 p = pdb.Pdb() p.quitting = False p.stopframe = frame p.returnframe = None p.stoplineno = frame.f_lineno - 3 p.botframe = None return p.trace_dispatch return self.trace_max_mem def __enter__(self): self.enable_by_count() def __exit__(self, exc_type, exc_val, exc_tb): self.disable_by_count() def enable(self): if self.max_mem is not None: sys.settrace(self.trace_max_mem) else: sys.settrace(self.trace_memory_usage) def disable(self): self.last_time = {} sys.settrace(None) def show_results(prof, stream=None, precision=3): if stream is None: stream = sys.stdout template = '{0:>6} {1:>12} {2:>12} {3:<}' for code in prof.code_map: lines = prof.code_map[code] if not lines: # .. measurements are empty .. continue filename = code.co_filename if filename.endswith((".pyc", ".pyo")): filename = filename[:-1] stream.write('Filename: ' + filename + '\n\n') if not os.path.exists(filename): stream.write('ERROR: Could not find file ' + filename + '\n') if filename.startswith("ipython-input") or filename.startswith(" The given statement (which doesn't require quote marks) is run via the LineProfiler. Profiling is enabled for the functions specified by the -f options. The statistics will be shown side-by-side with the code through the pager once the statement has completed. Options: -f : LineProfiler only profiles functions and methods it is told to profile. This option tells the profiler about these functions. Multiple -f options may be used. The argument may be any expression that gives a Python function or method object. However, one must be careful to avoid spaces that may confuse the option parser. Additionally, functions defined in the interpreter at the In[] prompt or via %run currently cannot be displayed. Write these functions out to a separate file and import them. One or more -f options are required to get any useful results. -T : dump the text-formatted statistics with the code side-by-side out to a text file. -r: return the LineProfiler object after it has completed profiling. """ try: from StringIO import StringIO except ImportError: # Python 3.x from io import StringIO # Local imports to avoid hard dependency. from distutils.version import LooseVersion import IPython ipython_version = LooseVersion(IPython.__version__) if ipython_version < '0.11': from IPython.genutils import page from IPython.ipstruct import Struct from IPython.ipapi import UsageError else: from IPython.core.page import page from IPython.utils.ipstruct import Struct from IPython.core.error import UsageError # Escape quote markers. opts_def = Struct(T=[''], f=[]) parameter_s = parameter_s.replace('"', r'\"').replace("'", r"\'") opts, arg_str = self.parse_options(parameter_s, 'rf:T:', list_all=True) opts.merge(opts_def) global_ns = self.shell.user_global_ns local_ns = self.shell.user_ns # Get the requested functions. funcs = [] for name in opts.f: try: funcs.append(eval(name, global_ns, local_ns)) except Exception as e: raise UsageError('Could not find function %r.\n%s: %s' % (name, e.__class__.__name__, e)) profile = LineProfiler() for func in funcs: profile(func) # Add the profiler to the builtins for @profile. try: import builtins except ImportError: # Python 3x import __builtin__ as builtins if 'profile' in builtins.__dict__: had_profile = True old_profile = builtins.__dict__['profile'] else: had_profile = False old_profile = None builtins.__dict__['profile'] = profile try: try: profile.runctx(arg_str, global_ns, local_ns) message = '' except SystemExit: message = "*** SystemExit exception caught in code being profiled." except KeyboardInterrupt: message = ("*** KeyboardInterrupt exception caught in code being " "profiled.") finally: if had_profile: builtins.__dict__['profile'] = old_profile # Trap text output. stdout_trap = StringIO() show_results(profile, stdout_trap) output = stdout_trap.getvalue() output = output.rstrip() if ipython_version < '0.11': page(output, screen_lines=self.shell.rc.screen_length) else: page(output) print(message,) text_file = opts.T[0] if text_file: with open(text_file, 'w') as pfile: pfile.write(output) print('\n*** Profile printout saved to text file %s. %s' % (text_file, message)) return_value = None if 'r' in opts: return_value = profile return return_value def _func_exec(stmt, ns): # helper for magic_memit, just a function proxy for the exec # statement exec(stmt, ns) # a timeit-style %memit magic for IPython def magic_memit(self, line=''): """Measure memory usage of a Python statement Usage, in line mode: %memit [-rt] statement Options: -r: repeat the loop iteration times and take the best result. Default: 1 -t: timeout after seconds. Unused if `-i` is active. Default: None Examples -------- :: In [1]: import numpy as np In [2]: %memit np.zeros(1e7) maximum of 1: 76.402344 MB per loop In [3]: %memit np.ones(1e6) maximum of 1: 7.820312 MB per loop In [4]: %memit -r 10 np.empty(1e8) maximum of 10: 0.101562 MB per loop In [5]: memit -t 3 while True: pass; Subprocess timed out. Subprocess timed out. Subprocess timed out. ERROR: all subprocesses exited unsuccessfully. Try again with the `-i` option. maximum of 1: -inf MB per loop """ opts, stmt = self.parse_options(line, 'r:t:i', posix=False, strict=False) repeat = int(getattr(opts, 'r', 1)) if repeat < 1: repeat == 1 timeout = int(getattr(opts, 't', 0)) if timeout <= 0: timeout = None mem_usage = [] for _ in range(repeat): tmp = memory_usage((_func_exec, (stmt, self.shell.user_ns)), timeout=timeout) mem_usage.extend(tmp) if mem_usage: print('maximum of %d: %f MB per loop' % (repeat, max(mem_usage))) else: print('ERROR: could not read memory usage, try with a lower interval or more iterations') def load_ipython_extension(ip): """This is called to load the module as an IPython extension.""" ip.define_magic('mprun', magic_mprun) ip.define_magic('memit', magic_memit) def profile(func, stream=None): """ Decorator that will run the function and print a line-by-line profile """ def wrapper(*args, **kwargs): prof = LineProfiler() val = prof(func)(*args, **kwargs) show_results(prof, stream=stream) return val return wrapper if __name__ == '__main__': from optparse import OptionParser parser = OptionParser(usage=_CMD_USAGE, version=__version__) parser.disable_interspersed_args() parser.add_option("--pdb-mmem", dest="max_mem", metavar="MAXMEM", type="float", action="store", help="step into the debugger when memory exceeds MAXMEM") parser.add_option('--precision', dest="precision", type="int", action="store", default=3, help="precision of memory output in number of significant digits") if not sys.argv[1:]: parser.print_help() sys.exit(2) (options, args) = parser.parse_args() prof = LineProfiler(max_mem=options.max_mem) __file__ = _find_script(args[0]) try: if sys.version_info[0] < 3: import __builtin__ __builtin__.__dict__['profile'] = prof ns = locals() ns['profile'] = prof # shadow the profile decorator defined above execfile(__file__, ns, ns) else: import builtins builtins.__dict__['profile'] = prof ns = locals() ns['profile'] = prof # shadow the profile decorator defined above exec(compile(open(__file__).read(), __file__, 'exec'), ns, globals()) finally: show_results(prof, precision=options.precision) pycallgraph-1.0.1/pycallgraph/metadata.py0000664000175000017500000000050212216026615020005 0ustar gakgak00000000000000# A different file to pycallgraph.py because of circular import problem __version__ = '1.0.1' __copyright__ = 'Copyright Gerald Kaszuba 2007-2013' __license__ = 'GPLv2' __author__ = 'Gerald Kaszuba' __email__ = 'pycallgraph@gakman.com' __url__ = 'http://pycallgraph.slowchop.com/' __credits__ = [ 'Gerald Kaszuba', ] pycallgraph-1.0.1/pycallgraph/tracer.py0000664000175000017500000002660112216012716017512 0ustar gakgak00000000000000from __future__ import division import inspect import sys import os import time from distutils import sysconfig from collections import defaultdict from threading import Thread try: from Queue import Queue, Empty except ImportError: from queue import Queue, Empty from .util import Util class SyncronousTracer(object): def __init__(self, outputs, config): self.processor = TraceProcessor(outputs, config) self.config = config def tracer(self, frame, event, arg): self.processor.process(frame, event, arg, self.memory()) return self.tracer def memory(self): if self.config.memory: from .memory_profiler import memory_usage return int(memory_usage(-1, 0)[0] * 1000000) def start(self): sys.settrace(self.tracer) def stop(self): sys.settrace(None) def done(self): pass class AsyncronousTracer(SyncronousTracer): def start(self): self.processor.start() SyncronousTracer.start(self) def tracer(self, frame, event, arg): self.processor.queue(frame, event, arg, self.memory()) return self.tracer def done(self): self.processor.done() self.processor.join() class TraceProcessor(Thread): ''' Contains a callback used by sys.settrace, which collects information about function call count, time taken, etc. ''' def __init__(self, outputs, config): Thread.__init__(self) self.trace_queue = Queue() self.keep_going = True self.outputs = outputs self.config = config self.updatables = [a for a in self.outputs if a.should_update()] self.init_trace_data() self.init_libpath() def init_trace_data(self): self.previous_event_return = False # A mapping of which function called which other function self.call_dict = defaultdict(lambda: defaultdict(int)) # Current call stack self.call_stack = ['__main__'] # Counters for each function self.func_count = defaultdict(int) self.func_count_max = 0 self.func_count['__main__'] = 1 # Accumulative time per function self.func_time = defaultdict(float) self.func_time_max = 0 # Accumulative memory addition per function self.func_memory_in = defaultdict(int) self.func_memory_in_max = 0 # Accumulative memory addition per function once exited self.func_memory_out = defaultdict(int) self.func_memory_out_max = 0 # Keeps track of the start time of each call on the stack self.call_stack_timer = [] self.call_stack_memory_in = [] self.call_stack_memory_out = [] def init_libpath(self): self.lib_path = sysconfig.get_python_lib() path = os.path.split(self.lib_path) if path[1] == 'site-packages': self.lib_path = path[0] self.lib_path = self.lib_path.lower() def queue(self, frame, event, arg, memory): data = { 'frame': frame, 'event': event, 'arg': arg, 'memory': memory, } self.trace_queue.put(data) def run(self): while self.keep_going: try: data = self.trace_queue.get(timeout=0.1) except Empty: pass self.process(**data) def done(self): while not self.trace_queue.empty(): time.sleep(0.1) self.keep_going = False def process(self, frame, event, arg, memory=None): '''This function processes a trace result. Keeps track of relationships between calls. ''' if memory is not None and self.previous_event_return: # Deal with memory when function has finished so local variables # can be cleaned up self.previous_event_return = False if self.call_stack_memory_out: full_name, m = self.call_stack_memory_out.pop(-1) else: full_name, m = (None, None) # NOTE: Call stack is no longer the call stack that may be # expected. Potentially need to store a copy of it. if full_name and m: call_memory = memory - m self.func_memory_out[full_name] += call_memory self.func_memory_out_max = max( self.func_memory_out_max, self.func_memory_out[full_name] ) 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 self.config.include_stdlib \ and self.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) if len(self.call_stack) > self.config.max_depth: keep = False # Load the trace filter, if any. 'keep' determines if we should # ignore this call if keep and self.config.trace_filter: keep = self.config.trace_filter(full_name) # Store the call information if keep: if self.call_stack: src_func = self.call_stack[-1] else: src_func = None self.call_dict[src_func][full_name] += 1 self.func_count[full_name] += 1 self.func_count_max = max( self.func_count_max, self.func_count[full_name] ) self.call_stack.append(full_name) self.call_stack_timer.append(time.time()) if memory is not None: self.call_stack_memory_in.append(memory) self.call_stack_memory_out.append([full_name, memory]) else: self.call_stack.append('') self.call_stack_timer.append(None) if event == 'return': self.previous_event_return = True if self.call_stack: full_name = self.call_stack.pop(-1) if self.call_stack_timer: start_time = self.call_stack_timer.pop(-1) else: start_time = None if start_time: call_time = time.time() - start_time self.func_time[full_name] += call_time self.func_time_max = max( self.func_time_max, self.func_time[full_name] ) if memory is not None: if self.call_stack_memory_in: start_mem = self.call_stack_memory_in.pop(-1) else: start_mem = None if start_mem: call_memory = memory - start_mem self.func_memory_in[full_name] += call_memory self.func_memory_in_max = max( self.func_memory_in_max, self.func_memory_in[full_name], ) def is_module_stdlib(self, file_name): ''' Returns True if the file_name is in the lib directory. Used to check if a function is in the standard library or not. ''' return file_name.lower().startswith(self.lib_path) def __getstate__(self): '''Used for when creating a pickle. Certain instance variables can't pickled and aren't used anyway. ''' odict = self.__dict__.copy() dont_keep = [ 'outputs', 'config', 'updatables', 'lib_path', ] for key in dont_keep: del odict[key] 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) 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.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( self.func_memory_in.get(func, 0), self.func_memory_in_max ) stat_group.memory_out = Stat( self.func_memory_in.get(func, 0), self.func_memory_in_max ) return stat_group def nodes(self): for func, calls in self.func_count.iteritems(): yield self.stat_group_from_func(func, calls) def edges(self): for src_func, dests in self.call_dict.iteritems(): if not src_func: continue for dst_func, calls in dests.iteritems(): edge = self.stat_group_from_func(dst_func, calls) edge.src_func = src_func edge.dst_func = dst_func yield edge class Stat(object): '''Stores a "statistic" value, e.g. "time taken" along with the maximum possible value of the value, which is used to calculate the fraction of 1. The fraction is used for choosing colors. ''' def __init__(self, value, total): self.value = value self.total = total try: self.fraction = value / total except ZeroDivisionError: self.fraction = 0 @property def value_human_bibyte(self): '''Mebibyte of the value in human readable a form.''' return Util.human_readable_bibyte(self.value) class StatGroup(object): pass 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 inspect.getmodule = simple_memoize(inspect.getmodule) pycallgraph-1.0.1/pycallgraph/config.py0000775000175000017500000001226412216012716017502 0ustar gakgak00000000000000import argparse import sys from .output import outputters from .globbing_filter import GlobbingFilter class Config(object): '''Handles configuration settings for pycallgraph, tracer, and each output module. It also handles command line arguments. ''' def __init__(self, **kwargs): ''' You can set defaults in the constructor, e.g. Config(verbose=True) ''' self.output = None self.verbose = False self.debug = False self.groups = True self.threaded = False self.memory = False # Filtering self.include_stdlib = False self.include_pycallgraph = False self.max_depth = 99999 self.trace_filter = GlobbingFilter( exclude=['pycallgraph.*'], include=['*'], ) self.did_init = True # Update the defaults with anything from kwargs [setattr(self, k, v) for k, v in kwargs.iteritems()] self.create_parser() def log_verbose(self, text): if self.verbose: print(text) def log_debug(self, text): if self.debug: print(text) def add_module_arguments(self, usage): subparsers = self.parser.add_subparsers( help='OUTPUT_TYPE', dest='output') parent_parser = self.create_parent_parser() for name, cls in outputters.items(): cls.add_arguments(subparsers, parent_parser, usage) def get_output(self): if not self.output: return output = outputters[self.output]() output.set_config(self) return output def parse_args(self, args=None): self.parser.parse_args(args, namespace=self) self.convert_filter_args() def strip_argv(self): sys.argv = [self.command] + self.command_args def convert_filter_args(self): if not self.include: self.include = ['*'] if not self.include_pycallgraph: self.exclude.append('pycallgraph.*') self.trace_filter = GlobbingFilter( include=self.include, exclude=self.exclude, ) def create_parser(self): '''Used by the pycallgraph command line interface to parse arguments. ''' usage = 'pycallgraph [options] OUTPUT_TYPE [output_options] -- ' \ 'SCRIPT.py [ARG ...]' self.parser = argparse.ArgumentParser( description='Python Call Graph profiles a Python script and ' 'generates a call graph visualization.', usage=usage, ) self.add_ungrouped_arguments() self.add_filter_arguments() self.add_module_arguments(usage) def create_parent_parser(self): '''Mixing subparsers with positional arguments can be done with a parents option. Found via: http://stackoverflow.com/a/11109863/11125 ''' parent_parser = argparse.ArgumentParser(add_help=False) parent_parser.add_argument( 'command', metavar='SCRIPT', help='The Python script file to profile', ) parent_parser.add_argument( 'command_args', metavar='ARG', nargs='*', help='Python script arguments.' ) return parent_parser def add_ungrouped_arguments(self): self.parser.add_argument( '-v', '--verbose', action='store_true', default=self.verbose, help='Display informative messages while running') self.parser.add_argument( '-d', '--debug', action='store_true', default=self.debug, help='Display debugging messages while running') self.parser.add_argument( '-t', '--threaded', action='store_true', default=self.threaded, help='Process traces asyncronously (Experimental)') self.parser.add_argument( '-ng', '--no-groups', dest='groups', action='store_false', default=self.groups, help='Do not group functions by module') self.parser.add_argument( '-s', '--stdlib', dest='include_stdlib', action='store_true', default=self.include_stdlib, help='Include standard library functions in the trace') self.parser.add_argument( '-m', '--memory', action='store_true', default=self.memory, help='(Experimental) Track memory usage') def add_filter_arguments(self): group = self.parser.add_argument_group('filtering') group.add_argument( '-i', '--include', default=[], action='append', help='Wildcard pattern of modules to include in the output. ' 'You can have multiple include arguments.' ) group.add_argument( '-e', '--exclude', default=[], action='append', help='Wildcard pattern of modules to exclude in the output. ' 'You can have multiple exclude arguments.' ) group.add_argument( '--include-pycallgraph', default=self.include_pycallgraph, action='store_true', help='Do not automatically filter out pycallgraph', ) group.add_argument( '--max-depth', default=self.max_depth, type=int, help='Maximum stack depth to trace', ) pycallgraph-1.0.1/MANIFEST.in0000664000175000017500000000022312216012716015100 0ustar gakgak00000000000000include Makefile include LICENSE include README.rst include requirements include test include docs include examples/*.py include man/pycallgraph.1 pycallgraph-1.0.1/README.rst0000664000175000017500000000667112216026615015051 0ustar gakgak00000000000000Python 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 :target: https://travis-ci.org/gak/pycallgraph .. image:: https://coveralls.io/repos/gak/pycallgraph/badge.png?branch=develop :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 :target: https://crate.io/packages/pycallgraph/ Screenshots =========== Click on the images below to see a larger version and the source code that generated them. .. image:: http://pycallgraph.slowchop.com/en/develop/_images/basic_thumb.png :target: http://pycallgraph.slowchop.com/en/develop/examples/basic.html .. image:: http://pycallgraph.slowchop.com/en/develop/_images/regexp_grouped_thumb.png :target: http://pycallgraph.slowchop.com/en/develop/examples/regexp_grouped.html .. image:: http://pycallgraph.slowchop.com/en/develop/_images/regexp_ungrouped_thumb.png :target: http://pycallgraph.slowchop.com/en/develop/examples/regexp_ungrouped.html 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 `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 `_. The documentation needs some work stiil. Feel free to contribute :) Features ======== * Support for Python 2.7+ and Python 3.3+. * Static visualizations of the call graph using various tools such as Graphviz and Gephi. * Execute pycallgraph from the command line or import it in your code. * Customisable colors. You can programatically set the colors based on number of calls, time taken, memory usage, etc. * Modules can be visually grouped together. * Easily extendable to create your own output formats. Quick Start =========== Installation is easy as:: pip install pycallgraph You can either use the `command-line interface `_ for a quick visualization of your Python script, or the `pycallgraph module `_ for more fine-grained settings. The following examples specify graphviz as the outputter, so it's required to be installed. They will generate a file called **pycallgraph.png**. The command-line method of running pycallgraph is:: $ pycallgraph graphviz -- ./mypythonscript.py A simple use of the API is:: from pycallgraph import PyCallGraph from pycallgraph.output import GraphvizOutput with PyCallGraph(output=GraphvizOutput()): code_to_profile() Documentation ============= Feel free to browse the `documentation of pycallgraph `_ for the `usage guide `_ and `API reference `_. pycallgraph-1.0.1/Makefile0000664000175000017500000000150212216012724015002 0ustar gakgak00000000000000export PYTHONPATH=$(shell pwd) all: deps tests doc pypi: python setup.py sdist upload clean: make -C docs clean -rm .coverage run_examples: cd examples/graphviz; ./all.py cd examples/gephi; ./all.py deps: pip install -r requirements/development.txt pip install -r requirements/documentation.txt tests: py.test \ --pep8 \ --ignore=pycallgraph/memory_profiler.py \ test pycallgraph examples coverage run --source pycallgraph,scripts -m py.test coverage report -m flake8 --exclude=__init__.py,memory_profiler.py pycallgraph flake8 --ignore=F403 test flake8 examples doc: cd docs/examples && ./generate.py cd docs/guide/filtering && ./generate.py make -C docs html man cp docs/_build/man/pycallgraph.1 man/ docs/update_readme.py 2to3: for a in pycallgraph test examples scripts; do 2to3 -wn $$a; done pycallgraph-1.0.1/setup.py0000775000175000017500000000401612216012716015063 0ustar gakgak00000000000000#!/usr/bin/env python from os import path from setuptools import setup import sys from setuptools.command.test import test as TestCommand import pycallgraph # Only install the man page if the correct directory exists # XXX: Commented because easy_install doesn't like it #man_path = '/usr/share/man/man1/' #if path.exists(man_path): # data_files=[['/usr/share/man/man1/', ['man/pycallgraph.1']]] #else: # data_files=None data_files=None class PyTest(TestCommand): def finalize_options(self): TestCommand.finalize_options(self) self.test_args = [] self.test_suite = True def run_tests(self): import pytest errno = pytest.main(self.test_args) sys.exit(errno) setup( name='pycallgraph', version=pycallgraph.__version__, description=pycallgraph.__doc__.strip().replace('\n', ' '), long_description=open('README.rst').read(), author=pycallgraph.__author__, author_email=pycallgraph.__email__, license=open('LICENSE').read(), url=pycallgraph.__url__, packages=['pycallgraph', 'pycallgraph.output'], scripts=['scripts/pycallgraph'], data_files=data_files, use_2to3=True, # TODO: Update download_url download_url = 'http://pycallgraph.slowchop.com/files/download/pycallgraph-%s.tar.gz' % \ pycallgraph.__version__, # Testing tests_require=['pytest'], cmdclass = {'test': PyTest}, classifiers = [ 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', 'License :: OSI Approved :: GNU General Public License (GPL)', 'Natural Language :: English', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.3', 'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Software Development :: Testing', 'Topic :: Software Development :: Debuggers', ], ) pycallgraph-1.0.1/man/0000775000175000017500000000000012216026765014131 5ustar gakgak00000000000000pycallgraph-1.0.1/man/pycallgraph.10000664000175000017500000001137112216026615016516 0ustar gakgak00000000000000.\" Man page generated from reStructuredText. . .TH "PYCALLGRAPH" "1" "September 17, 2013" "1.0.1" "Python Call Graph" .SH NAME pycallgraph \- Python Call Graph . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .SH SYNOPSIS .sp pycallgraph [\fIOPTION\fP]... \fIOUTPUT_MODE\fP [\fIOUTPUT_OPTIONS\fP] \fIpython_file.py\fP .SH DESCRIPTION .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. .SH GENERAL ARGUMENTS .INDENT 0.0 .TP .B A choice of graphviz, gephi and json. .UNINDENT .INDENT 0.0 .TP .B \-h, \-\-help Shows a list of possible options for the command line. .UNINDENT .INDENT 0.0 .TP .B \-v, \-\-verbose Turns on verbose mode which will print out information of pycallgraph\(aqs state and processing. .UNINDENT .INDENT 0.0 .TP .B \-d, \-\-debug Turns on debug mode which will print out debugging information such as the raw Graphviz generated files. .UNINDENT .INDENT 0.0 .TP .B \-ng, \-\-no\-groups Do not group modules in the results. By default this is turned on and will visually group together methods of the same module. The technique of grouping does rely on the type of output used. .UNINDENT .INDENT 0.0 .TP .B \-s, \-\-stdlib When running a trace, also include the Python standard library. .UNINDENT .INDENT 0.0 .TP .B \-m, \-\-memory An experimental option which includes memory tracking in the trace. .UNINDENT .INDENT 0.0 .TP .B \-t, \-\-threaded An experimental option which processes the trace in another thread. This may or may not be faster. .UNINDENT .SH FILTERING ARGUMENTS .INDENT 0.0 .TP .B \-i, \-\-include Wildcard pattern of modules to include in the output. You can have multiple include arguments. .UNINDENT .INDENT 0.0 .TP .B \-e, \-\-exclude Wildcard pattern of modules to exclude in the output. You can have multiple include arguments. .UNINDENT .INDENT 0.0 .TP .B \-\-include\-pycallgraph By default pycallgraph filters itself out of the trace. Enabling this will include pycallgraph in the trace. .UNINDENT .INDENT 0.0 .TP .B \-\-max\-depth Maximum stack depth to trace. Any calls made past this stack depth are not included in the trace. .UNINDENT .SH GRAPHVIZ ARGUMENTS .INDENT 0.0 .TP .B \-l , \-\-tool Modify the default Graphviz tool used by pycallgraph. It uses "dot", but can be changed to either neato, fdp, sfdp, twopi, or circo. .UNINDENT .SH EXAMPLES .sp Create a call graph image called pycallgraph.png on myprogram.py: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C pycallgraph graphviz \-\- ./myprogram.py .ft P .fi .UNINDENT .UNINDENT .sp Create a call graph of a standard Python installation script with command line parameters: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C pycallgraph graphviz \-\-output\-file=setup.png \-\- setup.py \-\-dry\-run install .ft P .fi .UNINDENT .UNINDENT .sp Run Django\(aqs \fImanage.py\fP script, but since there are many calls within Django, and will cause a massively sized generated image, we can filter it to only trace the core Django modules: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C pycallgraph \-v \-\-stdlib \-\-include "django.core.*" graphviz \-\- ./manage.py syncdb \-\-noinput .ft P .fi .UNINDENT .UNINDENT .SH AUTHOR pycallgraph was written by Gerald Kaszuba . This manual page was originally written by Jan Alonzo , for the Debian GNU/Linux system. .SH COPYRIGHT 2007-2013 Gerald Kaszuba, et al. .\" Generated by docutils manpage writer. . pycallgraph-1.0.1/scripts/0000775000175000017500000000000012216026765015045 5ustar gakgak00000000000000pycallgraph-1.0.1/scripts/pycallgraph0000775000175000017500000000110612216012716017266 0ustar gakgak00000000000000#!/usr/bin/env python """ pycallgraph This script is the command line interface to the pycallgraph Python library. See http://pycallgraph.slowchop.com/ for more information. """ import sys import os import pycallgraph as __pycallgraph # Inject the current working directory so modules can be imported. sys.path.insert(0, os.getcwd()) __config = __pycallgraph.Config() __config.parse_args() __config.strip_argv() globals()['__file__'] = __config.command __file_content = open(__config.command).read() with __pycallgraph.PyCallGraph(config=__config): exec(__file_content) pycallgraph-1.0.1/PKG-INFO0000664000175000017500000004750312216026765014464 0ustar gakgak00000000000000Metadata-Version: 1.1 Name: pycallgraph Version: 1.0.1 Summary: Python Call Graph is a library and command line tool that visualises the flow of your Python application. See http://pycallgraph.slowchop.com/ for more information. Home-page: http://pycallgraph.slowchop.com/ Author: Gerald Kaszuba Author-email: pycallgraph@gakman.com License: GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS Download-URL: http://pycallgraph.slowchop.com/files/download/pycallgraph-1.0.1.tar.gz Description: 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 :target: https://travis-ci.org/gak/pycallgraph .. image:: https://coveralls.io/repos/gak/pycallgraph/badge.png?branch=develop :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 :target: https://crate.io/packages/pycallgraph/ Screenshots =========== Click on the images below to see a larger version and the source code that generated them. .. image:: http://pycallgraph.slowchop.com/en/develop/_images/basic_thumb.png :target: http://pycallgraph.slowchop.com/en/develop/examples/basic.html .. image:: http://pycallgraph.slowchop.com/en/develop/_images/regexp_grouped_thumb.png :target: http://pycallgraph.slowchop.com/en/develop/examples/regexp_grouped.html .. image:: http://pycallgraph.slowchop.com/en/develop/_images/regexp_ungrouped_thumb.png :target: http://pycallgraph.slowchop.com/en/develop/examples/regexp_ungrouped.html 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 `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 `_. The documentation needs some work stiil. Feel free to contribute :) Features ======== * Support for Python 2.7+ and Python 3.3+. * Static visualizations of the call graph using various tools such as Graphviz and Gephi. * Execute pycallgraph from the command line or import it in your code. * Customisable colors. You can programatically set the colors based on number of calls, time taken, memory usage, etc. * Modules can be visually grouped together. * Easily extendable to create your own output formats. Quick Start =========== Installation is easy as:: pip install pycallgraph You can either use the `command-line interface `_ for a quick visualization of your Python script, or the `pycallgraph module `_ for more fine-grained settings. The following examples specify graphviz as the outputter, so it's required to be installed. They will generate a file called **pycallgraph.png**. The command-line method of running pycallgraph is:: $ pycallgraph graphviz -- ./mypythonscript.py A simple use of the API is:: from pycallgraph import PyCallGraph from pycallgraph.output import GraphvizOutput with PyCallGraph(output=GraphvizOutput()): code_to_profile() Documentation ============= Feel free to browse the `documentation of pycallgraph `_ for the `usage guide `_ and `API reference `_. Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: GNU General Public License (GPL) Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: Software Development :: Testing Classifier: Topic :: Software Development :: Debuggers