austin-python 1.7.0__tar.gz → 2.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.
- {austin_python-1.7.0 → austin_python-2.0.0}/.readthedocs.yml +1 -1
- {austin_python-1.7.0 → austin_python-2.0.0}/PKG-INFO +23 -24
- {austin_python-1.7.0 → austin_python-2.0.0}/README.md +19 -20
- austin_python-2.0.0/austin/__init__.py +22 -0
- austin_python-2.0.0/austin/aio.py +184 -0
- austin_python-2.0.0/austin/base.py +238 -0
- {austin_python-1.7.0 → austin_python-2.0.0}/austin/cli.py +35 -32
- austin_python-1.7.0/test/stats/test_frame.py → austin_python-2.0.0/austin/errors.py +3 -28
- austin_python-2.0.0/austin/events.py +102 -0
- austin_python-2.0.0/austin/format/collapsed_stack.py +240 -0
- {austin_python-1.7.0 → austin_python-2.0.0}/austin/format/compress.py +42 -19
- austin_python-2.0.0/austin/format/mojo.py +780 -0
- {austin_python-1.7.0 → austin_python-2.0.0}/austin/format/pprof/__init__.py +37 -20
- {austin_python-1.7.0 → austin_python-2.0.0}/austin/format/pprof/__main__.py +16 -12
- {austin_python-1.7.0 → austin_python-2.0.0}/austin/format/speedscope.py +41 -26
- austin_python-2.0.0/austin/simple.py +157 -0
- austin_python-2.0.0/austin/stats.py +296 -0
- {austin_python-1.7.0 → austin_python-2.0.0}/austin/threads.py +42 -38
- {austin_python-1.7.0 → austin_python-2.0.0}/austin/tools/diff.py +2 -5
- austin_python-2.0.0/austin/tools/mojodbg.py +79 -0
- {austin_python-1.7.0 → austin_python-2.0.0}/austin/tools/resolve.py +10 -23
- {austin_python-1.7.0 → austin_python-2.0.0}/pyproject.toml +5 -4
- {austin_python-1.7.0 → austin_python-2.0.0}/test/data/austin.json +1 -1
- {austin_python-1.7.0 → austin_python-2.0.0}/test/data/column.py +1 -0
- {austin_python-1.7.0 → austin_python-2.0.0}/test/format/test_compress.py +19 -15
- {austin_python-1.7.0 → austin_python-2.0.0}/test/format/test_mojo.py +24 -56
- {austin_python-1.7.0 → austin_python-2.0.0}/test/format/test_pprof.py +20 -12
- {austin_python-1.7.0 → austin_python-2.0.0}/test/format/test_speedscope.py +19 -17
- austin_python-2.0.0/test/stats/test_austin_file_reader.py +18 -0
- {austin_python-1.7.0 → austin_python-2.0.0}/test/stats/test_austin_stats.py +100 -79
- austin_python-2.0.0/test/stats/test_hierarchical_stats.py +175 -0
- {austin_python-1.7.0 → austin_python-2.0.0}/test/test_aio.py +30 -20
- {austin_python-1.7.0 → austin_python-2.0.0}/test/test_cli.py +2 -6
- {austin_python-1.7.0 → austin_python-2.0.0}/test/test_semver.py +1 -1
- {austin_python-1.7.0 → austin_python-2.0.0}/test/test_simple.py +13 -12
- {austin_python-1.7.0 → austin_python-2.0.0}/test/test_threads.py +27 -20
- austin_python-1.7.0/austin/__init__.py +0 -329
- austin_python-1.7.0/austin/aio.py +0 -153
- austin_python-1.7.0/austin/format/mojo.py +0 -550
- austin_python-1.7.0/austin/simple.py +0 -129
- austin_python-1.7.0/austin/stats.py +0 -583
- austin_python-1.7.0/test/stats/test_austin_file_reader.py +0 -21
- austin_python-1.7.0/test/stats/test_hierarchical_stats.py +0 -159
- austin_python-1.7.0/test/stats/test_sample.py +0 -165
- {austin_python-1.7.0 → austin_python-2.0.0}/.flake8 +0 -0
- {austin_python-1.7.0 → austin_python-2.0.0}/.gitignore +0 -0
- {austin_python-1.7.0 → austin_python-2.0.0}/AUTHORS +0 -0
- {austin_python-1.7.0 → austin_python-2.0.0}/CODE-OF-CONDUCT.md +0 -0
- {austin_python-1.7.0 → austin_python-2.0.0}/CONTRIBUTING.md +0 -0
- {austin_python-1.7.0 → austin_python-2.0.0}/COPYING +0 -0
- {austin_python-1.7.0 → austin_python-2.0.0}/ISSUE_TEMPLATE.md +0 -0
- {austin_python-1.7.0 → austin_python-2.0.0}/LICENSE.md +0 -0
- {austin_python-1.7.0 → austin_python-2.0.0}/MANIFEST.in +0 -0
- {austin_python-1.7.0 → austin_python-2.0.0}/PULL_REQUEST_TEMPLATE.md +0 -0
- {austin_python-1.7.0 → austin_python-2.0.0}/austin/config.py +0 -0
- {austin_python-1.7.0 → austin_python-2.0.0}/austin/format/__init__.py +0 -0
- {austin_python-1.7.0 → austin_python-2.0.0}/austin/format/pprof/profile.proto +0 -0
- {austin_python-1.7.0 → austin_python-2.0.0}/austin/format/pprof/profile_pb2.py +0 -0
- {austin_python-1.7.0 → austin_python-2.0.0}/test/__init__.py +0 -0
- {austin_python-1.7.0 → austin_python-2.0.0}/test/conftest.py +0 -0
- {austin_python-1.7.0 → austin_python-2.0.0}/test/data/austin.out +0 -0
- {austin_python-1.7.0 → austin_python-2.0.0}/test/data/austin.pprof +0 -0
- {austin_python-1.7.0 → austin_python-2.0.0}/test/data/austinp.mojo +0 -0
- {austin_python-1.7.0 → austin_python-2.0.0}/test/data/austinp.resolved.mojo +0 -0
- {austin_python-1.7.0 → austin_python-2.0.0}/test/data/column.mojo +0 -0
- {austin_python-1.7.0 → austin_python-2.0.0}/test/data/diff_a-b.austin +0 -0
- {austin_python-1.7.0 → austin_python-2.0.0}/test/data/diff_a.austin +0 -0
- {austin_python-1.7.0 → austin_python-2.0.0}/test/data/diff_b.austin +0 -0
- {austin_python-1.7.0 → austin_python-2.0.0}/test/data/mp.austin +0 -0
- {austin_python-1.7.0 → austin_python-2.0.0}/test/data/mp.mojo +0 -0
- {austin_python-1.7.0 → austin_python-2.0.0}/test/data/test.austin +0 -0
- {austin_python-1.7.0 → austin_python-2.0.0}/test/data/test.mojo +0 -0
- {austin_python-1.7.0 → austin_python-2.0.0}/test/format/__init__.py +0 -0
- {austin_python-1.7.0 → austin_python-2.0.0}/test/test_config.py +0 -0
- {austin_python-1.7.0 → austin_python-2.0.0}/test/tools/test_diff.py +0 -0
- {austin_python-1.7.0 → austin_python-2.0.0}/test/tools/test_resolve.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: austin-python
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0
|
|
4
4
|
Summary: Python wrapper for Austin, the CPython frame stack sampler.
|
|
5
5
|
Project-URL: homepage, https://github.com/P403n1x87/austin-python
|
|
6
6
|
Project-URL: documentation, https://austin-python.readthedocs.io
|
|
@@ -15,12 +15,12 @@ Keywords: development,performance,profiling,testing
|
|
|
15
15
|
Classifier: Development Status :: 5 - Production/Stable
|
|
16
16
|
Classifier: Intended Audience :: Developers
|
|
17
17
|
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
19
18
|
Classifier: Programming Language :: Python :: 3.9
|
|
20
19
|
Classifier: Programming Language :: Python :: 3.10
|
|
21
20
|
Classifier: Programming Language :: Python :: 3.11
|
|
22
21
|
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
-
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Requires-Python: >=3.9
|
|
24
24
|
Requires-Dist: protobuf~=3.12
|
|
25
25
|
Requires-Dist: psutil>=5.7.0
|
|
26
26
|
Requires-Dist: toml~=0.10
|
|
@@ -93,35 +93,34 @@ you covered. This is, for instance, how you would turn Austin into a Python
|
|
|
93
93
|
application:
|
|
94
94
|
|
|
95
95
|
~~~ python
|
|
96
|
+
import asyncio
|
|
97
|
+
import sys
|
|
98
|
+
|
|
96
99
|
from austin.aio import AsyncAustin
|
|
100
|
+
from austin.format.collapsed_stack import AustinEventCollapsedStackFormatter
|
|
97
101
|
|
|
102
|
+
FORMATTER = AustinEventCollapsedStackFormatter()
|
|
98
103
|
|
|
99
|
-
# Make your sub-class of AsyncAustin
|
|
100
|
-
class EchoAsyncAustin(AsyncAustin):
|
|
101
|
-
def on_ready(self, process, child_process, command_line):
|
|
102
|
-
print(f"Austin PID: {process.pid}")
|
|
103
|
-
print(f"Python PID: {child_process.pid}")
|
|
104
|
-
print(f"Command Line: {command_line}")
|
|
105
104
|
|
|
106
|
-
|
|
107
|
-
|
|
105
|
+
class CollapsedStackAsyncAustin(AsyncAustin):
|
|
106
|
+
async def on_sample(self, sample):
|
|
107
|
+
print(FORMATTER.format(sample))
|
|
108
108
|
|
|
109
|
-
def
|
|
110
|
-
print(
|
|
109
|
+
async def on_metadata(self, metadata):
|
|
110
|
+
print(FORMATTER.format(metadata))
|
|
111
111
|
|
|
112
112
|
|
|
113
|
-
# Use the Proactor event loop on Windows
|
|
114
113
|
if sys.platform == "win32":
|
|
115
114
|
asyncio.set_event_loop(asyncio.ProactorEventLoop())
|
|
116
115
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
austin =
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
116
|
+
|
|
117
|
+
async def main():
|
|
118
|
+
austin = CollapsedStackAsyncAustin()
|
|
119
|
+
await austin.start(["-i", "10ms", "python", "myscript.py"])
|
|
120
|
+
await austin.wait()
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
asyncio.run(main())
|
|
125
124
|
~~~
|
|
126
125
|
|
|
127
126
|
The `austin-python` package is at the heart of the [Austin
|
|
@@ -188,7 +187,7 @@ raw samples by aggregation.
|
|
|
188
187
|
# Compatibility
|
|
189
188
|
|
|
190
189
|
The latest `austin-python` package is tested on Linux, macOS and Windows with
|
|
191
|
-
Python 3.
|
|
190
|
+
Python 3.9-3.13.
|
|
192
191
|
|
|
193
192
|
|
|
194
193
|
# Documentation
|
|
@@ -65,35 +65,34 @@ you covered. This is, for instance, how you would turn Austin into a Python
|
|
|
65
65
|
application:
|
|
66
66
|
|
|
67
67
|
~~~ python
|
|
68
|
+
import asyncio
|
|
69
|
+
import sys
|
|
70
|
+
|
|
68
71
|
from austin.aio import AsyncAustin
|
|
72
|
+
from austin.format.collapsed_stack import AustinEventCollapsedStackFormatter
|
|
69
73
|
|
|
74
|
+
FORMATTER = AustinEventCollapsedStackFormatter()
|
|
70
75
|
|
|
71
|
-
# Make your sub-class of AsyncAustin
|
|
72
|
-
class EchoAsyncAustin(AsyncAustin):
|
|
73
|
-
def on_ready(self, process, child_process, command_line):
|
|
74
|
-
print(f"Austin PID: {process.pid}")
|
|
75
|
-
print(f"Python PID: {child_process.pid}")
|
|
76
|
-
print(f"Command Line: {command_line}")
|
|
77
76
|
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
class CollapsedStackAsyncAustin(AsyncAustin):
|
|
78
|
+
async def on_sample(self, sample):
|
|
79
|
+
print(FORMATTER.format(sample))
|
|
80
80
|
|
|
81
|
-
def
|
|
82
|
-
print(
|
|
81
|
+
async def on_metadata(self, metadata):
|
|
82
|
+
print(FORMATTER.format(metadata))
|
|
83
83
|
|
|
84
84
|
|
|
85
|
-
# Use the Proactor event loop on Windows
|
|
86
85
|
if sys.platform == "win32":
|
|
87
86
|
asyncio.set_event_loop(asyncio.ProactorEventLoop())
|
|
88
87
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
austin =
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
88
|
+
|
|
89
|
+
async def main():
|
|
90
|
+
austin = CollapsedStackAsyncAustin()
|
|
91
|
+
await austin.start(["-i", "10ms", "python", "myscript.py"])
|
|
92
|
+
await austin.wait()
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
asyncio.run(main())
|
|
97
96
|
~~~
|
|
98
97
|
|
|
99
98
|
The `austin-python` package is at the heart of the [Austin
|
|
@@ -160,7 +159,7 @@ raw samples by aggregation.
|
|
|
160
159
|
# Compatibility
|
|
161
160
|
|
|
162
161
|
The latest `austin-python` package is tested on Linux, macOS and Windows with
|
|
163
|
-
Python 3.
|
|
162
|
+
Python 3.9-3.13.
|
|
164
163
|
|
|
165
164
|
|
|
166
165
|
# Documentation
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# This file is part of "austin-python" which is released under GPL.
|
|
2
|
+
#
|
|
3
|
+
# See file LICENCE or go to http://www.gnu.org/licenses/ for full license
|
|
4
|
+
# details.
|
|
5
|
+
#
|
|
6
|
+
# austin-python is a Python wrapper around Austin, the CPython frame stack
|
|
7
|
+
# sampler.
|
|
8
|
+
#
|
|
9
|
+
# Copyright (c) 2018-2020 Gabriele N. Tornetta <phoenix1987@gmail.com>.
|
|
10
|
+
# All rights reserved.
|
|
11
|
+
#
|
|
12
|
+
# This program is free software: you can redistribute it and/or modify
|
|
13
|
+
# it under the terms of the GNU General Public License as published by
|
|
14
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
15
|
+
# (at your option) any later version.
|
|
16
|
+
#
|
|
17
|
+
# This program is distributed in the hope that it will be useful,
|
|
18
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
19
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
20
|
+
# GNU General Public License for more details.
|
|
21
|
+
# You should have received a copy of the GNU General Public License
|
|
22
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# This file is part of "austin-python" which is released under GPL.
|
|
2
|
+
#
|
|
3
|
+
# See file LICENCE or go to http://www.gnu.org/licenses/ for full license
|
|
4
|
+
# details.
|
|
5
|
+
#
|
|
6
|
+
# austin-python is a Python wrapper around Austin, the CPython frame stack
|
|
7
|
+
# sampler.
|
|
8
|
+
#
|
|
9
|
+
# Copyright (c) 2018-2020 Gabriele N. Tornetta <phoenix1987@gmail.com>.
|
|
10
|
+
# All rights reserved.
|
|
11
|
+
#
|
|
12
|
+
# This program is free software: you can redistribute it and/or modify
|
|
13
|
+
# it under the terms of the GNU General Public License as published by
|
|
14
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
15
|
+
# (at your option) any later version.
|
|
16
|
+
#
|
|
17
|
+
# This program is distributed in the hope that it will be useful,
|
|
18
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
19
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
20
|
+
# GNU General Public License for more details.
|
|
21
|
+
# You should have received a copy of the GNU General Public License
|
|
22
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
23
|
+
|
|
24
|
+
import asyncio
|
|
25
|
+
import sys
|
|
26
|
+
import typing as t
|
|
27
|
+
|
|
28
|
+
from austin.base import AustinState
|
|
29
|
+
from austin.base import BaseAustin
|
|
30
|
+
from austin.cli import AustinArgumentParser
|
|
31
|
+
from austin.errors import AustinError
|
|
32
|
+
from austin.events import AustinMetadata
|
|
33
|
+
from austin.events import AustinSample
|
|
34
|
+
from austin.format.mojo import AsyncMojoStreamReader
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class AsyncAustin(BaseAustin):
|
|
38
|
+
"""Asynchronous implementation of Austin.
|
|
39
|
+
|
|
40
|
+
Implements an ``asyncio`` API for Austin so that it can be used alongside
|
|
41
|
+
other asynchronous tasks.
|
|
42
|
+
|
|
43
|
+
The following example shows how to make a simple asynchronous implementation
|
|
44
|
+
of Austin that returns stack in the collapsed format
|
|
45
|
+
|
|
46
|
+
Example::
|
|
47
|
+
|
|
48
|
+
import asyncio
|
|
49
|
+
import sys
|
|
50
|
+
|
|
51
|
+
from austin.aio import AsyncAustin
|
|
52
|
+
from austin.format.collapsed_stack import AustinEventCollapsedStackFormatter
|
|
53
|
+
|
|
54
|
+
FORMATTER = AustinEventCollapsedStackFormatter()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class CollapsedStackAsyncAustin(AsyncAustin):
|
|
58
|
+
async def on_sample(self, sample):
|
|
59
|
+
print(FORMATTER.format(sample))
|
|
60
|
+
|
|
61
|
+
async def on_metadata(self, metadata):
|
|
62
|
+
print(FORMATTER.format(metadata))
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
if sys.platform == "win32":
|
|
66
|
+
asyncio.set_event_loop(asyncio.ProactorEventLoop())
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
async def main():
|
|
70
|
+
austin = CollapsedStackAsyncAustin()
|
|
71
|
+
await austin.start(["-i", "10ms", "python", "myscript.py"])
|
|
72
|
+
await austin.wait()
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
asyncio.run(main())
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:
|
|
79
|
+
super().__init__(*args, **kwargs)
|
|
80
|
+
|
|
81
|
+
self._run_task: t.Optional[asyncio.Task] = None
|
|
82
|
+
self._proc: t.Optional[asyncio.subprocess.Process] = None
|
|
83
|
+
|
|
84
|
+
async def _run(self, mojo: AsyncMojoStreamReader) -> None:
|
|
85
|
+
# Start collecting samples
|
|
86
|
+
self._state = AustinState.RUNNING
|
|
87
|
+
|
|
88
|
+
async for e in mojo:
|
|
89
|
+
if isinstance(e, AustinSample):
|
|
90
|
+
try:
|
|
91
|
+
await t.cast(t.Awaitable[None], self._sample_callback(e))
|
|
92
|
+
except Exception as exc:
|
|
93
|
+
raise AustinError("Error in call to sample callback") from exc
|
|
94
|
+
elif isinstance(e, AustinMetadata):
|
|
95
|
+
self._meta[e.name] = e.value
|
|
96
|
+
if self._metadata_callback is not None:
|
|
97
|
+
try:
|
|
98
|
+
await t.cast(t.Awaitable[None], self._metadata_callback(e))
|
|
99
|
+
except Exception as exc:
|
|
100
|
+
raise AustinError("Error in call to metadata callback") from exc
|
|
101
|
+
|
|
102
|
+
self._state = AustinState.TERMINATING
|
|
103
|
+
|
|
104
|
+
# Call the terminate callback
|
|
105
|
+
try:
|
|
106
|
+
if self._terminate_callback is not None:
|
|
107
|
+
await t.cast(t.Awaitable[None], self._terminate_callback())
|
|
108
|
+
except Exception as exc:
|
|
109
|
+
raise AustinError("Error in call to terminate callback") from exc
|
|
110
|
+
|
|
111
|
+
self._state = AustinState.TERMINATED
|
|
112
|
+
|
|
113
|
+
async def start(self, args: t.Optional[t.Sequence[str]] = None) -> None:
|
|
114
|
+
"""Create the start coroutine.
|
|
115
|
+
|
|
116
|
+
Use with the ``asyncio`` event loop.
|
|
117
|
+
"""
|
|
118
|
+
self._args = AustinArgumentParser().parse_args(args)
|
|
119
|
+
|
|
120
|
+
self._state = AustinState.STARTING
|
|
121
|
+
|
|
122
|
+
try:
|
|
123
|
+
_args = list(args if args is not None else sys.argv[1:]) # Make a copy
|
|
124
|
+
_args.insert(0, "-P")
|
|
125
|
+
self._proc = await asyncio.create_subprocess_exec(
|
|
126
|
+
self.binary_path,
|
|
127
|
+
*_args,
|
|
128
|
+
stdin=asyncio.subprocess.PIPE,
|
|
129
|
+
stdout=asyncio.subprocess.PIPE,
|
|
130
|
+
stderr=asyncio.subprocess.PIPE,
|
|
131
|
+
)
|
|
132
|
+
except FileNotFoundError:
|
|
133
|
+
raise AustinError("Austin executable not found.") from None
|
|
134
|
+
|
|
135
|
+
if not self._proc.stdout:
|
|
136
|
+
raise AustinError("Standard output stream is unexpectedly missing")
|
|
137
|
+
if not self._proc.stderr:
|
|
138
|
+
raise AustinError("Standard error stream is unexpectedly missing")
|
|
139
|
+
|
|
140
|
+
mojo = AsyncMojoStreamReader(self._proc.stdout)
|
|
141
|
+
|
|
142
|
+
# Retrieve the Austin version, then call the ready callback
|
|
143
|
+
async for e in mojo:
|
|
144
|
+
if isinstance(e, AustinMetadata):
|
|
145
|
+
self._meta[e.name] = e.value
|
|
146
|
+
|
|
147
|
+
try:
|
|
148
|
+
if self._metadata_callback is not None:
|
|
149
|
+
await t.cast(t.Awaitable[None], self._metadata_callback(e))
|
|
150
|
+
except Exception as exc:
|
|
151
|
+
raise AustinError("Error in call to metadata callback") from exc
|
|
152
|
+
|
|
153
|
+
if e.name == "austin":
|
|
154
|
+
self._check_version()
|
|
155
|
+
break
|
|
156
|
+
else:
|
|
157
|
+
raise AustinError("Cannot determine Austin version from output")
|
|
158
|
+
|
|
159
|
+
# Start the run task where we collect the samples
|
|
160
|
+
self._run_task = asyncio.create_task(self._run(mojo))
|
|
161
|
+
|
|
162
|
+
def terminate(self) -> None:
|
|
163
|
+
"""Terminate Austin.
|
|
164
|
+
|
|
165
|
+
Stop the underlying Austin process by sending a termination signal.
|
|
166
|
+
"""
|
|
167
|
+
if self._proc is None:
|
|
168
|
+
raise AustinError("Austin is not running")
|
|
169
|
+
|
|
170
|
+
try:
|
|
171
|
+
self._proc.terminate()
|
|
172
|
+
except ProcessLookupError as exc:
|
|
173
|
+
raise AustinError("Austin process already terminated") from exc
|
|
174
|
+
|
|
175
|
+
async def wait(self) -> int:
|
|
176
|
+
if self._proc is None or self._run_task is None:
|
|
177
|
+
raise AustinError("Austin process is not running")
|
|
178
|
+
|
|
179
|
+
# Await the run task. This will terminate naturally when the Austin
|
|
180
|
+
# process has terminated.
|
|
181
|
+
await self._run_task
|
|
182
|
+
|
|
183
|
+
# Wait for the Austin process to terminate and return the exit code
|
|
184
|
+
return await self._proc.wait()
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# This file is part of "austin-python" which is released under GPL.
|
|
2
|
+
#
|
|
3
|
+
# See file LICENCE or go to http://www.gnu.org/licenses/ for full license
|
|
4
|
+
# details.
|
|
5
|
+
#
|
|
6
|
+
# austin-python is a Python wrapper around Austin, the CPython frame stack
|
|
7
|
+
# sampler.
|
|
8
|
+
#
|
|
9
|
+
# Copyright (c) 2018-2020 Gabriele N. Tornetta <phoenix1987@gmail.com>.
|
|
10
|
+
# All rights reserved.
|
|
11
|
+
#
|
|
12
|
+
# This program is free software: you can redistribute it and/or modify
|
|
13
|
+
# it under the terms of the GNU General Public License as published by
|
|
14
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
15
|
+
# (at your option) any later version.
|
|
16
|
+
#
|
|
17
|
+
# This program is distributed in the hope that it will be useful,
|
|
18
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
19
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
20
|
+
# GNU General Public License for more details.
|
|
21
|
+
# You should have received a copy of the GNU General Public License
|
|
22
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
import functools
|
|
26
|
+
import os
|
|
27
|
+
import sys
|
|
28
|
+
from abc import ABC
|
|
29
|
+
from abc import abstractmethod
|
|
30
|
+
from enum import Enum
|
|
31
|
+
from itertools import takewhile
|
|
32
|
+
from pathlib import Path
|
|
33
|
+
from typing import Any
|
|
34
|
+
from typing import Callable
|
|
35
|
+
from typing import Dict
|
|
36
|
+
from typing import List
|
|
37
|
+
from typing import Optional
|
|
38
|
+
from typing import Tuple
|
|
39
|
+
|
|
40
|
+
from austin.cli import AustinArguments
|
|
41
|
+
from austin.config import AustinConfiguration
|
|
42
|
+
from austin.errors import AustinError
|
|
43
|
+
from austin.events import AustinMetadata
|
|
44
|
+
from austin.events import AustinSample
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
SemVer = Tuple[int, int, int]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _to_semver(version: Optional[str]) -> SemVer:
|
|
51
|
+
if version is None:
|
|
52
|
+
return (0, 0, 0)
|
|
53
|
+
|
|
54
|
+
try:
|
|
55
|
+
return (
|
|
56
|
+
tuple(
|
|
57
|
+
int(_)
|
|
58
|
+
for _ in "".join(
|
|
59
|
+
list(
|
|
60
|
+
takewhile(
|
|
61
|
+
lambda _: _.isdigit() or _ == ".", version.replace("?", "0")
|
|
62
|
+
)
|
|
63
|
+
)
|
|
64
|
+
).split(".")
|
|
65
|
+
)
|
|
66
|
+
+ (0, 0, 0)
|
|
67
|
+
)[
|
|
68
|
+
# type: ignore[return-value]
|
|
69
|
+
:3
|
|
70
|
+
]
|
|
71
|
+
except ValueError:
|
|
72
|
+
raise ValueError("Invalid semantic version") from None
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class AustinState(int, Enum):
|
|
76
|
+
"""Austin state."""
|
|
77
|
+
|
|
78
|
+
NOT_STARTED = 0
|
|
79
|
+
STARTING = 1
|
|
80
|
+
RUNNING = 2
|
|
81
|
+
TERMINATING = 3
|
|
82
|
+
TERMINATED = 4
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
_EXE_EXT = ".exe" if sys.platform == "win32" else ""
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class BaseAustin(ABC):
|
|
89
|
+
"""Base Austin class.
|
|
90
|
+
|
|
91
|
+
Defines the general API that abstract Austin as an external process.
|
|
92
|
+
Subclasses should implement the :func:`start` and :func:`terminate` methods,
|
|
93
|
+
and either define the :func:`on_sample` method or pass it via the
|
|
94
|
+
constructor. Additionally, the :func:`on_metadata` :func:`on_terminate`
|
|
95
|
+
methods can be overridden or passed via the constructor to catch metadata
|
|
96
|
+
and termination events respectively. Austin is considered to be ready when
|
|
97
|
+
the first metadata event is received, and the `start` function returns, and
|
|
98
|
+
it is considered to have terminated when the Austin process has terminated
|
|
99
|
+
gracefully.
|
|
100
|
+
|
|
101
|
+
If an error was encountered, the :class:`AustinError` exception is thrown.
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
BINARY = Path("austin").with_suffix(_EXE_EXT)
|
|
105
|
+
MINIMUM_BINARY_VERSION: SemVer = (4, 0, 0)
|
|
106
|
+
|
|
107
|
+
def __init__(
|
|
108
|
+
self,
|
|
109
|
+
sample_callback: Optional[Callable[[AustinSample], None]] = None,
|
|
110
|
+
metadata_callback: Optional[Callable[[AustinMetadata], None]] = None,
|
|
111
|
+
terminate_callback: Optional[Callable[[], None]] = None,
|
|
112
|
+
) -> None:
|
|
113
|
+
"""The ``BaseAustin`` constructor.
|
|
114
|
+
|
|
115
|
+
The callbacks can be passed as arguments, if they are not
|
|
116
|
+
defined/overridden in the definition of a sub-class.
|
|
117
|
+
"""
|
|
118
|
+
self._args: Optional[AustinArguments] = None
|
|
119
|
+
self._meta: Dict[str, str] = {}
|
|
120
|
+
|
|
121
|
+
if sample_callback is None and not hasattr(self, "on_sample"):
|
|
122
|
+
msg = "No mandatory sample callback given or implemented."
|
|
123
|
+
raise AustinError(msg)
|
|
124
|
+
|
|
125
|
+
self._sample_callback = sample_callback or self.on_sample # type: ignore[attr-defined]
|
|
126
|
+
|
|
127
|
+
self._metadata_callback = metadata_callback or getattr(
|
|
128
|
+
self, "on_metadata", None
|
|
129
|
+
)
|
|
130
|
+
self._terminate_callback = terminate_callback or getattr(
|
|
131
|
+
self, "on_terminate", None
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
self._state: AustinState = AustinState.NOT_STARTED
|
|
135
|
+
|
|
136
|
+
@abstractmethod
|
|
137
|
+
def start(self, args: Optional[List[str]] = None) -> Any:
|
|
138
|
+
"""Start Austin.
|
|
139
|
+
|
|
140
|
+
Every subclass should implement this method and ensure that it spawns
|
|
141
|
+
a new Austin process.
|
|
142
|
+
"""
|
|
143
|
+
...
|
|
144
|
+
|
|
145
|
+
def get_arguments(self) -> Optional[AustinArguments]:
|
|
146
|
+
"""Get the inferred command line.
|
|
147
|
+
|
|
148
|
+
Return the command line of the (main) process that is being profiled
|
|
149
|
+
by Austin.
|
|
150
|
+
"""
|
|
151
|
+
return self._args
|
|
152
|
+
|
|
153
|
+
def is_running(self) -> bool:
|
|
154
|
+
"""Determine whether Austin is running."""
|
|
155
|
+
return self._state is AustinState.RUNNING
|
|
156
|
+
|
|
157
|
+
@abstractmethod
|
|
158
|
+
def terminate(self) -> Any:
|
|
159
|
+
"""Terminate Austin.
|
|
160
|
+
|
|
161
|
+
Stop the underlying Austin process by sending a termination signal.
|
|
162
|
+
"""
|
|
163
|
+
...
|
|
164
|
+
|
|
165
|
+
@abstractmethod
|
|
166
|
+
def wait(self) -> Any:
|
|
167
|
+
"""Wait for Austin to terminate.
|
|
168
|
+
|
|
169
|
+
This method blocks until the Austin process has terminated.
|
|
170
|
+
Returns the exit code of the Austin process.
|
|
171
|
+
"""
|
|
172
|
+
...
|
|
173
|
+
|
|
174
|
+
def _check_version(self) -> None:
|
|
175
|
+
"""Check for the minimum Austin binary version."""
|
|
176
|
+
austin_version = self.version
|
|
177
|
+
if austin_version is None:
|
|
178
|
+
raise AustinError("Cannot determine Austin version")
|
|
179
|
+
if austin_version < self.MINIMUM_BINARY_VERSION:
|
|
180
|
+
raise AustinError(
|
|
181
|
+
f"Incompatible Austin version (got {austin_version}, expected >= {self.MINIMUM_BINARY_VERSION})"
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
# ---- Properties ----
|
|
185
|
+
|
|
186
|
+
@functools.cached_property
|
|
187
|
+
def binary_path(self) -> Path:
|
|
188
|
+
"""Discover the path of the Austin binary.
|
|
189
|
+
|
|
190
|
+
Lookup order is:
|
|
191
|
+
- current working directory
|
|
192
|
+
- ``AUSTINPATH`` variable
|
|
193
|
+
- ``~/.austinrc`` file
|
|
194
|
+
- ``PATH`` variable.
|
|
195
|
+
"""
|
|
196
|
+
binary_name = self.BINARY
|
|
197
|
+
|
|
198
|
+
# Try CWD
|
|
199
|
+
binary_path = binary_name.resolve()
|
|
200
|
+
if binary_path.is_file():
|
|
201
|
+
return binary_path
|
|
202
|
+
|
|
203
|
+
# Try with AUSTINPATH variable
|
|
204
|
+
austin_path = os.environ.get("AUSTINPATH", None)
|
|
205
|
+
if austin_path:
|
|
206
|
+
binary_path = Path(austin_path).expanduser().resolve() / binary_name
|
|
207
|
+
if binary_path.is_file():
|
|
208
|
+
return binary_path
|
|
209
|
+
|
|
210
|
+
# Try with .austinrc file
|
|
211
|
+
if AustinConfiguration().binary is not None:
|
|
212
|
+
binary_path = Path(AustinConfiguration().binary).expanduser().resolve()
|
|
213
|
+
if binary_path.is_file():
|
|
214
|
+
return binary_path
|
|
215
|
+
|
|
216
|
+
# Try from PATH
|
|
217
|
+
return self.BINARY
|
|
218
|
+
|
|
219
|
+
@functools.cached_property
|
|
220
|
+
def version(self) -> SemVer:
|
|
221
|
+
"""Austin version."""
|
|
222
|
+
try:
|
|
223
|
+
return _to_semver(self._meta.get("austin"))
|
|
224
|
+
except KeyError as exc:
|
|
225
|
+
raise AustinError("Austin version not set") from exc
|
|
226
|
+
|
|
227
|
+
@functools.cached_property
|
|
228
|
+
def python_version(self) -> SemVer:
|
|
229
|
+
"""The version of the detected Python interpreter."""
|
|
230
|
+
try:
|
|
231
|
+
return _to_semver(self._meta.get("python"))
|
|
232
|
+
except KeyError as exc:
|
|
233
|
+
raise AustinError("Python version not set") from exc
|
|
234
|
+
|
|
235
|
+
@property
|
|
236
|
+
def state(self) -> AustinState:
|
|
237
|
+
"""The current state of Austin."""
|
|
238
|
+
return self._state
|