tprof 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tprof-1.0.0/CHANGELOG.rst +8 -0
- tprof-1.0.0/LICENSE +21 -0
- tprof-1.0.0/MANIFEST.in +6 -0
- tprof-1.0.0/PKG-INFO +266 -0
- tprof-1.0.0/README.rst +241 -0
- tprof-1.0.0/pyproject.toml +149 -0
- tprof-1.0.0/setup.cfg +4 -0
- tprof-1.0.0/setup.py +15 -0
- tprof-1.0.0/src/tprof/__init__.py +5 -0
- tprof-1.0.0/src/tprof/__main__.py +6 -0
- tprof-1.0.0/src/tprof/api.py +208 -0
- tprof-1.0.0/src/tprof/main.py +86 -0
- tprof-1.0.0/src/tprof/py.typed +0 -0
- tprof-1.0.0/src/tprof/record.c +223 -0
- tprof-1.0.0/src/tprof/record.pyi +7 -0
- tprof-1.0.0/src/tprof.egg-info/PKG-INFO +266 -0
- tprof-1.0.0/src/tprof.egg-info/SOURCES.txt +19 -0
- tprof-1.0.0/src/tprof.egg-info/dependency_links.txt +1 -0
- tprof-1.0.0/src/tprof.egg-info/entry_points.txt +2 -0
- tprof-1.0.0/src/tprof.egg-info/requires.txt +1 -0
- tprof-1.0.0/src/tprof.egg-info/top_level.txt +1 -0
tprof-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Adam Johnson
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
tprof-1.0.0/MANIFEST.in
ADDED
tprof-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tprof
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A targeting profiler.
|
|
5
|
+
Author-email: Adam Johnson <me@adamj.eu>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Changelog, https://github.com/adamchainz/tprof/blob/main/CHANGELOG.rst
|
|
8
|
+
Project-URL: Funding, https://adamj.eu/books/
|
|
9
|
+
Project-URL: Repository, https://github.com/adamchainz/tprof
|
|
10
|
+
Keywords: performance,profiler,profiling,target
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Natural Language :: English
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Typing :: Typed
|
|
20
|
+
Requires-Python: >=3.12
|
|
21
|
+
Description-Content-Type: text/x-rst
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: rich>=14.2
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
=========
|
|
27
|
+
🎯 tprof
|
|
28
|
+
=========
|
|
29
|
+
|
|
30
|
+
.. image:: https://img.shields.io/github/actions/workflow/status/adamchainz/tprof/main.yml.svg?branch=main&style=for-the-badge
|
|
31
|
+
:target: https://github.com/adamchainz/tprof/actions?workflow=CI
|
|
32
|
+
|
|
33
|
+
.. image:: https://img.shields.io/badge/Coverage-100%25-success?style=for-the-badge
|
|
34
|
+
:target: https://github.com/adamchainz/tprof/actions?workflow=CI
|
|
35
|
+
|
|
36
|
+
.. image:: https://img.shields.io/pypi/v/tprof.svg?style=for-the-badge
|
|
37
|
+
:target: https://pypi.org/project/tprof/
|
|
38
|
+
|
|
39
|
+
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg?style=for-the-badge
|
|
40
|
+
:target: https://github.com/psf/black
|
|
41
|
+
|
|
42
|
+
.. image:: https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white&style=for-the-badge
|
|
43
|
+
:target: https://github.com/pre-commit/pre-commit
|
|
44
|
+
:alt: pre-commit
|
|
45
|
+
|
|
46
|
+
----
|
|
47
|
+
|
|
48
|
+
A targeting profiler.
|
|
49
|
+
|
|
50
|
+
.. figure:: https://raw.githubusercontent.com/adamchainz/tprof/main/screenshot.svg
|
|
51
|
+
:alt: tprof in action.
|
|
52
|
+
|
|
53
|
+
----
|
|
54
|
+
|
|
55
|
+
**Get better at command line Git** with my book `Boost Your Git DX <https://adamchainz.gumroad.com/l/bygdx>`__.
|
|
56
|
+
|
|
57
|
+
----
|
|
58
|
+
|
|
59
|
+
Requirements
|
|
60
|
+
------------
|
|
61
|
+
|
|
62
|
+
Python 3.12 to 3.14 supported.
|
|
63
|
+
|
|
64
|
+
Installation
|
|
65
|
+
------------
|
|
66
|
+
|
|
67
|
+
1. Install with **pip**:
|
|
68
|
+
|
|
69
|
+
.. code-block:: sh
|
|
70
|
+
|
|
71
|
+
python -m pip install tprof
|
|
72
|
+
|
|
73
|
+
Usage
|
|
74
|
+
-----
|
|
75
|
+
|
|
76
|
+
tprof measures the time spent in specified target functions when running a script or module.
|
|
77
|
+
Unlike a full program profiler, it only tracks the specified functions using |sys.monitoring|__ (new in Python 3.12), reducing overhead and helping you focus on the bits you’re changing.
|
|
78
|
+
Timing is done in C to further reduce overhead.
|
|
79
|
+
|
|
80
|
+
.. |sys.monitoring| replace:: ``sys.monitoring``
|
|
81
|
+
__ https://docs.python.org/3/library/sys.html#sys.monitoring
|
|
82
|
+
|
|
83
|
+
tprof supports usage as a CLI and with a Python API.
|
|
84
|
+
|
|
85
|
+
CLI
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
Specify one or more target functions with ``-t``, then what to run: a script file by filename, or a module with ``-m`` then its name.
|
|
89
|
+
Any unrecognized arguments are passed to the script or module.
|
|
90
|
+
|
|
91
|
+
Use the format ``<module>:<function>`` to specify target functions.
|
|
92
|
+
When using ``-m`` with a module, you can skip the ``<module>`` part and it will be inferred from the module name.
|
|
93
|
+
|
|
94
|
+
.. code-block:: console
|
|
95
|
+
|
|
96
|
+
$ tprof -t lib:maths ./example.py
|
|
97
|
+
...
|
|
98
|
+
🎯 tprof results:
|
|
99
|
+
function calls total mean ± σ min … max
|
|
100
|
+
lib:maths() 2 610ms 305ms ± 2ms 304ms … 307ms
|
|
101
|
+
|
|
102
|
+
Full help:
|
|
103
|
+
|
|
104
|
+
.. [[[cog
|
|
105
|
+
.. import cog
|
|
106
|
+
.. import subprocess
|
|
107
|
+
.. import sys
|
|
108
|
+
.. result = subprocess.run(
|
|
109
|
+
.. [sys.executable, "-m", "tprof", "--help"],
|
|
110
|
+
.. capture_output=True,
|
|
111
|
+
.. text=True,
|
|
112
|
+
.. )
|
|
113
|
+
.. cog.outl("")
|
|
114
|
+
.. cog.outl(".. code-block:: console")
|
|
115
|
+
.. cog.outl("")
|
|
116
|
+
.. for line in result.stdout.splitlines():
|
|
117
|
+
.. if line.strip() == "":
|
|
118
|
+
.. cog.outl("")
|
|
119
|
+
.. else:
|
|
120
|
+
.. cog.outl(" " + line.rstrip())
|
|
121
|
+
.. cog.outl("")
|
|
122
|
+
.. ]]]
|
|
123
|
+
|
|
124
|
+
.. code-block:: console
|
|
125
|
+
|
|
126
|
+
usage: tprof [-h] -t target [-x] (-m module | script) ...
|
|
127
|
+
|
|
128
|
+
positional arguments:
|
|
129
|
+
script Python script to run
|
|
130
|
+
args Arguments to pass to the script or module
|
|
131
|
+
|
|
132
|
+
options:
|
|
133
|
+
-h, --help show this help message and exit
|
|
134
|
+
-t target Target callable to profile (format: module:function).
|
|
135
|
+
-x, --compare Compare performance of targets, with the first as baseline.
|
|
136
|
+
-m module Run library module as a script (like python -m)
|
|
137
|
+
|
|
138
|
+
.. [[[end]]]
|
|
139
|
+
|
|
140
|
+
Comparison mode
|
|
141
|
+
^^^^^^^^^^^^^^^
|
|
142
|
+
|
|
143
|
+
Pass ``-x`` (``--compare``) to compare the performance of multiple target functions, with the first as the baseline, in an extra “delta” column.
|
|
144
|
+
For example, given this code:
|
|
145
|
+
|
|
146
|
+
.. code-block:: python
|
|
147
|
+
|
|
148
|
+
def before():
|
|
149
|
+
total = 0
|
|
150
|
+
for i in range(100_000):
|
|
151
|
+
total += i
|
|
152
|
+
return total
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def after():
|
|
156
|
+
return sum(range(100_000))
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
for _ in range(100):
|
|
160
|
+
before()
|
|
161
|
+
after()
|
|
162
|
+
|
|
163
|
+
…you can run tprof like this to compare the two functions:
|
|
164
|
+
|
|
165
|
+
.. code-block:: console
|
|
166
|
+
|
|
167
|
+
$ tprof -x -t before -t after -m example
|
|
168
|
+
🎯 tprof results:
|
|
169
|
+
function calls total mean ± σ min … max delta
|
|
170
|
+
example:before() 100 227ms 2ms ± 34μs 2ms … 2ms -
|
|
171
|
+
example:after() 100 86ms 856μs ± 15μs 835μs … 910μs -62.27%
|
|
172
|
+
|
|
173
|
+
API
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
``tprof(*targets, label: str | None = None, compare: bool = False)``
|
|
177
|
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
178
|
+
|
|
179
|
+
Use this context manager / decorator within your code to perform profiling in a specific block.
|
|
180
|
+
The report is printed when the block ends, each time it ends.
|
|
181
|
+
|
|
182
|
+
Each item in ``targets`` may be a callable to profile, or a string reference to one that will be resolved with |pkgutil.resolve_name()|__.
|
|
183
|
+
|
|
184
|
+
.. |pkgutil.resolve_name()| replace:: ``pkgutil.resolve_name()``
|
|
185
|
+
__ https://docs.python.org/3.14/library/pkgutil.html#pkgutil.resolve_name
|
|
186
|
+
|
|
187
|
+
``label`` is an optional string to add to the report heading to distinguish multiple reports.
|
|
188
|
+
|
|
189
|
+
Set ``compare`` to ``True`` to enable comparison mode, as documented above in the CLI section.
|
|
190
|
+
|
|
191
|
+
For example, given this code:
|
|
192
|
+
|
|
193
|
+
.. code-block:: python
|
|
194
|
+
|
|
195
|
+
from lib import maths
|
|
196
|
+
|
|
197
|
+
from tprof import tprof
|
|
198
|
+
|
|
199
|
+
print("Doing the maths…")
|
|
200
|
+
with tprof(maths):
|
|
201
|
+
maths()
|
|
202
|
+
print("The maths has been done!")
|
|
203
|
+
|
|
204
|
+
…running it would produce output like:
|
|
205
|
+
|
|
206
|
+
.. code-block:: console
|
|
207
|
+
|
|
208
|
+
$ python example.py
|
|
209
|
+
Doing the maths…
|
|
210
|
+
🎯 tprof results:
|
|
211
|
+
function calls total mean ± σ min … max
|
|
212
|
+
lib:maths() 1 305ms 305ms 305ms … 305ms
|
|
213
|
+
The maths has been done!
|
|
214
|
+
|
|
215
|
+
Another example using comparison mode:
|
|
216
|
+
|
|
217
|
+
.. code-block:: python
|
|
218
|
+
|
|
219
|
+
from tprof import tprof
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def before():
|
|
223
|
+
total = 0
|
|
224
|
+
for i in range(100_000):
|
|
225
|
+
total += i
|
|
226
|
+
return total
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def after():
|
|
230
|
+
return sum(range(100_000))
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
with tprof(before, after, compare=True):
|
|
234
|
+
for _ in range(100):
|
|
235
|
+
before()
|
|
236
|
+
after()
|
|
237
|
+
|
|
238
|
+
…which produces output like:
|
|
239
|
+
|
|
240
|
+
.. code-block:: console
|
|
241
|
+
|
|
242
|
+
$ python example.py
|
|
243
|
+
🎯 tprof results:
|
|
244
|
+
function calls total mean ± σ min … max delta
|
|
245
|
+
__main__:before() 100 227ms 2ms ± 83μs 2ms … 3ms -
|
|
246
|
+
__main__:after() 100 85ms 853μs ± 22μs 835μs … 1ms -62.35%
|
|
247
|
+
|
|
248
|
+
History
|
|
249
|
+
-------
|
|
250
|
+
|
|
251
|
+
When optimizing Python code, I found I was using this workflow:
|
|
252
|
+
|
|
253
|
+
1. Profile the whole program with a tool like `cProfile <https://docs.python.org/3.14/library/profile.html>`__ or `py-spy <https://github.com/benfred/py-spy>`__ to find slow functions.
|
|
254
|
+
2. Pick a function to optimize.
|
|
255
|
+
3. Make a change.
|
|
256
|
+
4. Re-profile the whole program to see if the changes helped.
|
|
257
|
+
|
|
258
|
+
This works fined but profiling the whole program again adds overhead, and picking out the one function’s stats from the report is extra work.
|
|
259
|
+
When I saw that Python 3.12’s |sys.monitoring2|__ allows tracking specific functions with low overhead, I created tprof to streamline this workflow, allowing the final step to re-profile just the target function.
|
|
260
|
+
|
|
261
|
+
.. |sys.monitoring2| replace:: ``sys.monitoring``
|
|
262
|
+
__ https://docs.python.org/3/library/sys.html#sys.monitoring
|
|
263
|
+
|
|
264
|
+
It also seemed a natural extension that tprof could compare multiple functions, supporting a nice microbenchmarking workflow.
|
|
265
|
+
|
|
266
|
+
Output inspired by `poop <https://github.com/andrewrk/poop>`__.
|
tprof-1.0.0/README.rst
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
=========
|
|
2
|
+
🎯 tprof
|
|
3
|
+
=========
|
|
4
|
+
|
|
5
|
+
.. image:: https://img.shields.io/github/actions/workflow/status/adamchainz/tprof/main.yml.svg?branch=main&style=for-the-badge
|
|
6
|
+
:target: https://github.com/adamchainz/tprof/actions?workflow=CI
|
|
7
|
+
|
|
8
|
+
.. image:: https://img.shields.io/badge/Coverage-100%25-success?style=for-the-badge
|
|
9
|
+
:target: https://github.com/adamchainz/tprof/actions?workflow=CI
|
|
10
|
+
|
|
11
|
+
.. image:: https://img.shields.io/pypi/v/tprof.svg?style=for-the-badge
|
|
12
|
+
:target: https://pypi.org/project/tprof/
|
|
13
|
+
|
|
14
|
+
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg?style=for-the-badge
|
|
15
|
+
:target: https://github.com/psf/black
|
|
16
|
+
|
|
17
|
+
.. image:: https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white&style=for-the-badge
|
|
18
|
+
:target: https://github.com/pre-commit/pre-commit
|
|
19
|
+
:alt: pre-commit
|
|
20
|
+
|
|
21
|
+
----
|
|
22
|
+
|
|
23
|
+
A targeting profiler.
|
|
24
|
+
|
|
25
|
+
.. figure:: https://raw.githubusercontent.com/adamchainz/tprof/main/screenshot.svg
|
|
26
|
+
:alt: tprof in action.
|
|
27
|
+
|
|
28
|
+
----
|
|
29
|
+
|
|
30
|
+
**Get better at command line Git** with my book `Boost Your Git DX <https://adamchainz.gumroad.com/l/bygdx>`__.
|
|
31
|
+
|
|
32
|
+
----
|
|
33
|
+
|
|
34
|
+
Requirements
|
|
35
|
+
------------
|
|
36
|
+
|
|
37
|
+
Python 3.12 to 3.14 supported.
|
|
38
|
+
|
|
39
|
+
Installation
|
|
40
|
+
------------
|
|
41
|
+
|
|
42
|
+
1. Install with **pip**:
|
|
43
|
+
|
|
44
|
+
.. code-block:: sh
|
|
45
|
+
|
|
46
|
+
python -m pip install tprof
|
|
47
|
+
|
|
48
|
+
Usage
|
|
49
|
+
-----
|
|
50
|
+
|
|
51
|
+
tprof measures the time spent in specified target functions when running a script or module.
|
|
52
|
+
Unlike a full program profiler, it only tracks the specified functions using |sys.monitoring|__ (new in Python 3.12), reducing overhead and helping you focus on the bits you’re changing.
|
|
53
|
+
Timing is done in C to further reduce overhead.
|
|
54
|
+
|
|
55
|
+
.. |sys.monitoring| replace:: ``sys.monitoring``
|
|
56
|
+
__ https://docs.python.org/3/library/sys.html#sys.monitoring
|
|
57
|
+
|
|
58
|
+
tprof supports usage as a CLI and with a Python API.
|
|
59
|
+
|
|
60
|
+
CLI
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
Specify one or more target functions with ``-t``, then what to run: a script file by filename, or a module with ``-m`` then its name.
|
|
64
|
+
Any unrecognized arguments are passed to the script or module.
|
|
65
|
+
|
|
66
|
+
Use the format ``<module>:<function>`` to specify target functions.
|
|
67
|
+
When using ``-m`` with a module, you can skip the ``<module>`` part and it will be inferred from the module name.
|
|
68
|
+
|
|
69
|
+
.. code-block:: console
|
|
70
|
+
|
|
71
|
+
$ tprof -t lib:maths ./example.py
|
|
72
|
+
...
|
|
73
|
+
🎯 tprof results:
|
|
74
|
+
function calls total mean ± σ min … max
|
|
75
|
+
lib:maths() 2 610ms 305ms ± 2ms 304ms … 307ms
|
|
76
|
+
|
|
77
|
+
Full help:
|
|
78
|
+
|
|
79
|
+
.. [[[cog
|
|
80
|
+
.. import cog
|
|
81
|
+
.. import subprocess
|
|
82
|
+
.. import sys
|
|
83
|
+
.. result = subprocess.run(
|
|
84
|
+
.. [sys.executable, "-m", "tprof", "--help"],
|
|
85
|
+
.. capture_output=True,
|
|
86
|
+
.. text=True,
|
|
87
|
+
.. )
|
|
88
|
+
.. cog.outl("")
|
|
89
|
+
.. cog.outl(".. code-block:: console")
|
|
90
|
+
.. cog.outl("")
|
|
91
|
+
.. for line in result.stdout.splitlines():
|
|
92
|
+
.. if line.strip() == "":
|
|
93
|
+
.. cog.outl("")
|
|
94
|
+
.. else:
|
|
95
|
+
.. cog.outl(" " + line.rstrip())
|
|
96
|
+
.. cog.outl("")
|
|
97
|
+
.. ]]]
|
|
98
|
+
|
|
99
|
+
.. code-block:: console
|
|
100
|
+
|
|
101
|
+
usage: tprof [-h] -t target [-x] (-m module | script) ...
|
|
102
|
+
|
|
103
|
+
positional arguments:
|
|
104
|
+
script Python script to run
|
|
105
|
+
args Arguments to pass to the script or module
|
|
106
|
+
|
|
107
|
+
options:
|
|
108
|
+
-h, --help show this help message and exit
|
|
109
|
+
-t target Target callable to profile (format: module:function).
|
|
110
|
+
-x, --compare Compare performance of targets, with the first as baseline.
|
|
111
|
+
-m module Run library module as a script (like python -m)
|
|
112
|
+
|
|
113
|
+
.. [[[end]]]
|
|
114
|
+
|
|
115
|
+
Comparison mode
|
|
116
|
+
^^^^^^^^^^^^^^^
|
|
117
|
+
|
|
118
|
+
Pass ``-x`` (``--compare``) to compare the performance of multiple target functions, with the first as the baseline, in an extra “delta” column.
|
|
119
|
+
For example, given this code:
|
|
120
|
+
|
|
121
|
+
.. code-block:: python
|
|
122
|
+
|
|
123
|
+
def before():
|
|
124
|
+
total = 0
|
|
125
|
+
for i in range(100_000):
|
|
126
|
+
total += i
|
|
127
|
+
return total
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def after():
|
|
131
|
+
return sum(range(100_000))
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
for _ in range(100):
|
|
135
|
+
before()
|
|
136
|
+
after()
|
|
137
|
+
|
|
138
|
+
…you can run tprof like this to compare the two functions:
|
|
139
|
+
|
|
140
|
+
.. code-block:: console
|
|
141
|
+
|
|
142
|
+
$ tprof -x -t before -t after -m example
|
|
143
|
+
🎯 tprof results:
|
|
144
|
+
function calls total mean ± σ min … max delta
|
|
145
|
+
example:before() 100 227ms 2ms ± 34μs 2ms … 2ms -
|
|
146
|
+
example:after() 100 86ms 856μs ± 15μs 835μs … 910μs -62.27%
|
|
147
|
+
|
|
148
|
+
API
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
``tprof(*targets, label: str | None = None, compare: bool = False)``
|
|
152
|
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
153
|
+
|
|
154
|
+
Use this context manager / decorator within your code to perform profiling in a specific block.
|
|
155
|
+
The report is printed when the block ends, each time it ends.
|
|
156
|
+
|
|
157
|
+
Each item in ``targets`` may be a callable to profile, or a string reference to one that will be resolved with |pkgutil.resolve_name()|__.
|
|
158
|
+
|
|
159
|
+
.. |pkgutil.resolve_name()| replace:: ``pkgutil.resolve_name()``
|
|
160
|
+
__ https://docs.python.org/3.14/library/pkgutil.html#pkgutil.resolve_name
|
|
161
|
+
|
|
162
|
+
``label`` is an optional string to add to the report heading to distinguish multiple reports.
|
|
163
|
+
|
|
164
|
+
Set ``compare`` to ``True`` to enable comparison mode, as documented above in the CLI section.
|
|
165
|
+
|
|
166
|
+
For example, given this code:
|
|
167
|
+
|
|
168
|
+
.. code-block:: python
|
|
169
|
+
|
|
170
|
+
from lib import maths
|
|
171
|
+
|
|
172
|
+
from tprof import tprof
|
|
173
|
+
|
|
174
|
+
print("Doing the maths…")
|
|
175
|
+
with tprof(maths):
|
|
176
|
+
maths()
|
|
177
|
+
print("The maths has been done!")
|
|
178
|
+
|
|
179
|
+
…running it would produce output like:
|
|
180
|
+
|
|
181
|
+
.. code-block:: console
|
|
182
|
+
|
|
183
|
+
$ python example.py
|
|
184
|
+
Doing the maths…
|
|
185
|
+
🎯 tprof results:
|
|
186
|
+
function calls total mean ± σ min … max
|
|
187
|
+
lib:maths() 1 305ms 305ms 305ms … 305ms
|
|
188
|
+
The maths has been done!
|
|
189
|
+
|
|
190
|
+
Another example using comparison mode:
|
|
191
|
+
|
|
192
|
+
.. code-block:: python
|
|
193
|
+
|
|
194
|
+
from tprof import tprof
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def before():
|
|
198
|
+
total = 0
|
|
199
|
+
for i in range(100_000):
|
|
200
|
+
total += i
|
|
201
|
+
return total
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def after():
|
|
205
|
+
return sum(range(100_000))
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
with tprof(before, after, compare=True):
|
|
209
|
+
for _ in range(100):
|
|
210
|
+
before()
|
|
211
|
+
after()
|
|
212
|
+
|
|
213
|
+
…which produces output like:
|
|
214
|
+
|
|
215
|
+
.. code-block:: console
|
|
216
|
+
|
|
217
|
+
$ python example.py
|
|
218
|
+
🎯 tprof results:
|
|
219
|
+
function calls total mean ± σ min … max delta
|
|
220
|
+
__main__:before() 100 227ms 2ms ± 83μs 2ms … 3ms -
|
|
221
|
+
__main__:after() 100 85ms 853μs ± 22μs 835μs … 1ms -62.35%
|
|
222
|
+
|
|
223
|
+
History
|
|
224
|
+
-------
|
|
225
|
+
|
|
226
|
+
When optimizing Python code, I found I was using this workflow:
|
|
227
|
+
|
|
228
|
+
1. Profile the whole program with a tool like `cProfile <https://docs.python.org/3.14/library/profile.html>`__ or `py-spy <https://github.com/benfred/py-spy>`__ to find slow functions.
|
|
229
|
+
2. Pick a function to optimize.
|
|
230
|
+
3. Make a change.
|
|
231
|
+
4. Re-profile the whole program to see if the changes helped.
|
|
232
|
+
|
|
233
|
+
This works fined but profiling the whole program again adds overhead, and picking out the one function’s stats from the report is extra work.
|
|
234
|
+
When I saw that Python 3.12’s |sys.monitoring2|__ allows tracking specific functions with low overhead, I created tprof to streamline this workflow, allowing the final step to re-profile just the target function.
|
|
235
|
+
|
|
236
|
+
.. |sys.monitoring2| replace:: ``sys.monitoring``
|
|
237
|
+
__ https://docs.python.org/3/library/sys.html#sys.monitoring
|
|
238
|
+
|
|
239
|
+
It also seemed a natural extension that tprof could compare multiple functions, supporting a nice microbenchmarking workflow.
|
|
240
|
+
|
|
241
|
+
Output inspired by `poop <https://github.com/andrewrk/poop>`__.
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
build-backend = "setuptools.build_meta"
|
|
3
|
+
requires = [
|
|
4
|
+
"setuptools>=77",
|
|
5
|
+
]
|
|
6
|
+
|
|
7
|
+
[project]
|
|
8
|
+
name = "tprof"
|
|
9
|
+
version = "1.0.0"
|
|
10
|
+
description = "A targeting profiler."
|
|
11
|
+
readme = "README.rst"
|
|
12
|
+
keywords = [
|
|
13
|
+
"performance",
|
|
14
|
+
"profiler",
|
|
15
|
+
"profiling",
|
|
16
|
+
"target",
|
|
17
|
+
]
|
|
18
|
+
license = "MIT"
|
|
19
|
+
license-files = [ "LICENSE" ]
|
|
20
|
+
authors = [
|
|
21
|
+
{ name = "Adam Johnson", email = "me@adamj.eu" },
|
|
22
|
+
]
|
|
23
|
+
requires-python = ">=3.12"
|
|
24
|
+
classifiers = [
|
|
25
|
+
"Development Status :: 5 - Production/Stable",
|
|
26
|
+
"Intended Audience :: Developers",
|
|
27
|
+
"Natural Language :: English",
|
|
28
|
+
"Operating System :: OS Independent",
|
|
29
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
30
|
+
"Programming Language :: Python :: 3.12",
|
|
31
|
+
"Programming Language :: Python :: 3.13",
|
|
32
|
+
"Programming Language :: Python :: 3.14",
|
|
33
|
+
"Typing :: Typed",
|
|
34
|
+
]
|
|
35
|
+
dependencies = [
|
|
36
|
+
"rich>=14.2",
|
|
37
|
+
]
|
|
38
|
+
urls.Changelog = "https://github.com/adamchainz/tprof/blob/main/CHANGELOG.rst"
|
|
39
|
+
urls.Funding = "https://adamj.eu/books/"
|
|
40
|
+
urls.Repository = "https://github.com/adamchainz/tprof"
|
|
41
|
+
scripts.tprof = "tprof.main:main"
|
|
42
|
+
|
|
43
|
+
[dependency-groups]
|
|
44
|
+
test = [
|
|
45
|
+
"cogapp>=3.6",
|
|
46
|
+
"coverage[toml]",
|
|
47
|
+
"pytest",
|
|
48
|
+
"pytest-randomly",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
[tool.cibuildwheel]
|
|
52
|
+
build = [
|
|
53
|
+
"cp314-*",
|
|
54
|
+
"cp314t-*",
|
|
55
|
+
"cp313-*",
|
|
56
|
+
"cp313t-*",
|
|
57
|
+
"cp312-*",
|
|
58
|
+
"cp311-*",
|
|
59
|
+
"cp310-*",
|
|
60
|
+
]
|
|
61
|
+
enable = [
|
|
62
|
+
# Enable free-threaded wheels on Python 3.13 (where it was experimental)
|
|
63
|
+
"cpython-freethreading",
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
[tool.ruff]
|
|
67
|
+
lint.select = [
|
|
68
|
+
# flake8-bugbear
|
|
69
|
+
"B",
|
|
70
|
+
# flake8-comprehensions
|
|
71
|
+
"C4",
|
|
72
|
+
# pycodestyle
|
|
73
|
+
"E",
|
|
74
|
+
# Pyflakes errors
|
|
75
|
+
"F",
|
|
76
|
+
# isort
|
|
77
|
+
"I",
|
|
78
|
+
# flake8-simplify
|
|
79
|
+
"SIM",
|
|
80
|
+
# flake8-tidy-imports
|
|
81
|
+
"TID",
|
|
82
|
+
# pyupgrade
|
|
83
|
+
"UP",
|
|
84
|
+
# Pyflakes warnings
|
|
85
|
+
"W",
|
|
86
|
+
]
|
|
87
|
+
lint.ignore = [
|
|
88
|
+
# flake8-bugbear opinionated rules
|
|
89
|
+
"B9",
|
|
90
|
+
# line-too-long
|
|
91
|
+
"E501",
|
|
92
|
+
# suppressible-exception
|
|
93
|
+
"SIM105",
|
|
94
|
+
# if-else-block-instead-of-if-exp
|
|
95
|
+
"SIM108",
|
|
96
|
+
]
|
|
97
|
+
lint.extend-safe-fixes = [
|
|
98
|
+
# non-pep585-annotation
|
|
99
|
+
"UP006",
|
|
100
|
+
]
|
|
101
|
+
lint.isort.required-imports = [ "from __future__ import annotations" ]
|
|
102
|
+
|
|
103
|
+
[tool.pyproject-fmt]
|
|
104
|
+
max_supported_python = "3.14"
|
|
105
|
+
|
|
106
|
+
[tool.pytest.ini_options]
|
|
107
|
+
addopts = """\
|
|
108
|
+
--strict-config
|
|
109
|
+
--strict-markers
|
|
110
|
+
"""
|
|
111
|
+
xfail_strict = true
|
|
112
|
+
|
|
113
|
+
[tool.coverage.run]
|
|
114
|
+
branch = true
|
|
115
|
+
parallel = true
|
|
116
|
+
source = [
|
|
117
|
+
"tprof",
|
|
118
|
+
"tests",
|
|
119
|
+
]
|
|
120
|
+
|
|
121
|
+
[tool.coverage.paths]
|
|
122
|
+
source = [
|
|
123
|
+
"src",
|
|
124
|
+
".tox/**/site-packages",
|
|
125
|
+
]
|
|
126
|
+
|
|
127
|
+
[tool.coverage.report]
|
|
128
|
+
show_missing = true
|
|
129
|
+
exclude_also = [
|
|
130
|
+
"if TYPE_CHECKING:",
|
|
131
|
+
]
|
|
132
|
+
|
|
133
|
+
[tool.mypy]
|
|
134
|
+
enable_error_code = [
|
|
135
|
+
"ignore-without-code",
|
|
136
|
+
"redundant-expr",
|
|
137
|
+
"truthy-bool",
|
|
138
|
+
]
|
|
139
|
+
mypy_path = "src/"
|
|
140
|
+
namespace_packages = false
|
|
141
|
+
strict = true
|
|
142
|
+
warn_unreachable = true
|
|
143
|
+
|
|
144
|
+
[[tool.mypy.overrides]]
|
|
145
|
+
module = "tests.*"
|
|
146
|
+
allow_untyped_defs = true
|
|
147
|
+
|
|
148
|
+
[tool.rstcheck]
|
|
149
|
+
report_level = "ERROR"
|
tprof-1.0.0/setup.cfg
ADDED