austin-python 1.7.1__tar.gz → 2.1.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.
Files changed (78) hide show
  1. {austin_python-1.7.1 → austin_python-2.1.0}/.readthedocs.yml +1 -1
  2. {austin_python-1.7.1 → austin_python-2.1.0}/PKG-INFO +23 -24
  3. {austin_python-1.7.1 → austin_python-2.1.0}/README.md +19 -20
  4. austin_python-2.1.0/austin/__init__.py +22 -0
  5. austin_python-2.1.0/austin/aio.py +184 -0
  6. austin_python-2.1.0/austin/base.py +238 -0
  7. {austin_python-1.7.1 → austin_python-2.1.0}/austin/cli.py +35 -32
  8. austin_python-1.7.1/test/stats/test_frame.py → austin_python-2.1.0/austin/errors.py +3 -28
  9. austin_python-2.1.0/austin/events.py +103 -0
  10. austin_python-2.1.0/austin/format/collapsed_stack.py +240 -0
  11. {austin_python-1.7.1 → austin_python-2.1.0}/austin/format/compress.py +42 -19
  12. austin_python-2.1.0/austin/format/mojo.py +952 -0
  13. {austin_python-1.7.1 → austin_python-2.1.0}/austin/format/pprof/__init__.py +37 -20
  14. {austin_python-1.7.1 → austin_python-2.1.0}/austin/format/pprof/__main__.py +16 -12
  15. {austin_python-1.7.1 → austin_python-2.1.0}/austin/format/speedscope.py +41 -26
  16. austin_python-2.1.0/austin/simple.py +157 -0
  17. austin_python-2.1.0/austin/stats.py +315 -0
  18. {austin_python-1.7.1 → austin_python-2.1.0}/austin/threads.py +42 -38
  19. {austin_python-1.7.1 → austin_python-2.1.0}/austin/tools/diff.py +2 -5
  20. austin_python-2.1.0/austin/tools/mojodbg.py +85 -0
  21. {austin_python-1.7.1 → austin_python-2.1.0}/austin/tools/resolve.py +10 -23
  22. {austin_python-1.7.1 → austin_python-2.1.0}/pyproject.toml +5 -4
  23. {austin_python-1.7.1 → austin_python-2.1.0}/test/data/austin.json +1 -1
  24. {austin_python-1.7.1 → austin_python-2.1.0}/test/data/column.py +1 -0
  25. {austin_python-1.7.1 → austin_python-2.1.0}/test/format/test_compress.py +19 -15
  26. austin_python-2.1.0/test/format/test_mojo.py +238 -0
  27. {austin_python-1.7.1 → austin_python-2.1.0}/test/format/test_pprof.py +20 -12
  28. {austin_python-1.7.1 → austin_python-2.1.0}/test/format/test_speedscope.py +19 -17
  29. austin_python-2.1.0/test/stats/test_austin_file_reader.py +18 -0
  30. austin_python-2.1.0/test/stats/test_austin_stats.py +351 -0
  31. austin_python-2.1.0/test/stats/test_hierarchical_stats.py +175 -0
  32. {austin_python-1.7.1 → austin_python-2.1.0}/test/test_aio.py +30 -20
  33. {austin_python-1.7.1 → austin_python-2.1.0}/test/test_cli.py +2 -6
  34. {austin_python-1.7.1 → austin_python-2.1.0}/test/test_semver.py +1 -1
  35. {austin_python-1.7.1 → austin_python-2.1.0}/test/test_simple.py +13 -12
  36. {austin_python-1.7.1 → austin_python-2.1.0}/test/test_threads.py +27 -20
  37. austin_python-1.7.1/austin/__init__.py +0 -329
  38. austin_python-1.7.1/austin/aio.py +0 -153
  39. austin_python-1.7.1/austin/format/mojo.py +0 -550
  40. austin_python-1.7.1/austin/simple.py +0 -129
  41. austin_python-1.7.1/austin/stats.py +0 -588
  42. austin_python-1.7.1/test/format/test_mojo.py +0 -190
  43. austin_python-1.7.1/test/stats/test_austin_file_reader.py +0 -21
  44. austin_python-1.7.1/test/stats/test_austin_stats.py +0 -273
  45. austin_python-1.7.1/test/stats/test_hierarchical_stats.py +0 -159
  46. austin_python-1.7.1/test/stats/test_sample.py +0 -165
  47. {austin_python-1.7.1 → austin_python-2.1.0}/.flake8 +0 -0
  48. {austin_python-1.7.1 → austin_python-2.1.0}/.gitignore +0 -0
  49. {austin_python-1.7.1 → austin_python-2.1.0}/AUTHORS +0 -0
  50. {austin_python-1.7.1 → austin_python-2.1.0}/CODE-OF-CONDUCT.md +0 -0
  51. {austin_python-1.7.1 → austin_python-2.1.0}/CONTRIBUTING.md +0 -0
  52. {austin_python-1.7.1 → austin_python-2.1.0}/COPYING +0 -0
  53. {austin_python-1.7.1 → austin_python-2.1.0}/ISSUE_TEMPLATE.md +0 -0
  54. {austin_python-1.7.1 → austin_python-2.1.0}/LICENSE.md +0 -0
  55. {austin_python-1.7.1 → austin_python-2.1.0}/MANIFEST.in +0 -0
  56. {austin_python-1.7.1 → austin_python-2.1.0}/PULL_REQUEST_TEMPLATE.md +0 -0
  57. {austin_python-1.7.1 → austin_python-2.1.0}/austin/config.py +0 -0
  58. {austin_python-1.7.1 → austin_python-2.1.0}/austin/format/__init__.py +0 -0
  59. {austin_python-1.7.1 → austin_python-2.1.0}/austin/format/pprof/profile.proto +0 -0
  60. {austin_python-1.7.1 → austin_python-2.1.0}/austin/format/pprof/profile_pb2.py +0 -0
  61. {austin_python-1.7.1 → austin_python-2.1.0}/test/__init__.py +0 -0
  62. {austin_python-1.7.1 → austin_python-2.1.0}/test/conftest.py +0 -0
  63. {austin_python-1.7.1 → austin_python-2.1.0}/test/data/austin.out +0 -0
  64. {austin_python-1.7.1 → austin_python-2.1.0}/test/data/austin.pprof +0 -0
  65. {austin_python-1.7.1 → austin_python-2.1.0}/test/data/austinp.mojo +0 -0
  66. {austin_python-1.7.1 → austin_python-2.1.0}/test/data/austinp.resolved.mojo +0 -0
  67. {austin_python-1.7.1 → austin_python-2.1.0}/test/data/column.mojo +0 -0
  68. {austin_python-1.7.1 → austin_python-2.1.0}/test/data/diff_a-b.austin +0 -0
  69. {austin_python-1.7.1 → austin_python-2.1.0}/test/data/diff_a.austin +0 -0
  70. {austin_python-1.7.1 → austin_python-2.1.0}/test/data/diff_b.austin +0 -0
  71. {austin_python-1.7.1 → austin_python-2.1.0}/test/data/mp.austin +0 -0
  72. {austin_python-1.7.1 → austin_python-2.1.0}/test/data/mp.mojo +0 -0
  73. {austin_python-1.7.1 → austin_python-2.1.0}/test/data/test.austin +0 -0
  74. {austin_python-1.7.1 → austin_python-2.1.0}/test/data/test.mojo +0 -0
  75. {austin_python-1.7.1 → austin_python-2.1.0}/test/format/__init__.py +0 -0
  76. {austin_python-1.7.1 → austin_python-2.1.0}/test/test_config.py +0 -0
  77. {austin_python-1.7.1 → austin_python-2.1.0}/test/tools/test_diff.py +0 -0
  78. {austin_python-1.7.1 → austin_python-2.1.0}/test/tools/test_resolve.py +0 -0
@@ -3,7 +3,7 @@ sphinx:
3
3
  configuration: docs/source/conf.py
4
4
  formats: all
5
5
  python:
6
- version: 3.8
6
+ version: 3.13
7
7
  install:
8
8
  - requirements: docs/requirements.txt
9
9
  - path: .
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: austin-python
3
- Version: 1.7.1
3
+ Version: 2.1.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
- Requires-Python: >=3.8
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
- def on_sample_received(self, line):
107
- print(line)
105
+ class CollapsedStackAsyncAustin(AsyncAustin):
106
+ async def on_sample(self, sample):
107
+ print(FORMATTER.format(sample))
108
108
 
109
- def on_terminate(self, data):
110
- print(data)
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
- try:
118
- # Start the Austin application with some command line arguments
119
- austin = EchoAsyncAustin()
120
- asyncio.get_event_loop().run_until_complete(
121
- austin.start(["-i", "10000", "python3", "myscript.py"])
122
- )
123
- except (KeyboardInterrupt, asyncio.CancelledError):
124
- pass
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.8-3.12.
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
- def on_sample_received(self, line):
79
- print(line)
77
+ class CollapsedStackAsyncAustin(AsyncAustin):
78
+ async def on_sample(self, sample):
79
+ print(FORMATTER.format(sample))
80
80
 
81
- def on_terminate(self, data):
82
- print(data)
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
- try:
90
- # Start the Austin application with some command line arguments
91
- austin = EchoAsyncAustin()
92
- asyncio.get_event_loop().run_until_complete(
93
- austin.start(["-i", "10000", "python3", "myscript.py"])
94
- )
95
- except (KeyboardInterrupt, asyncio.CancelledError):
96
- pass
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.8-3.12.
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