austin-python 1.7.0__py3-none-any.whl → 2.0.0__py3-none-any.whl
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/__init__.py +0 -307
- austin/aio.py +107 -76
- austin/base.py +238 -0
- austin/cli.py +35 -32
- austin/errors.py +28 -0
- austin/events.py +102 -0
- austin/format/collapsed_stack.py +240 -0
- austin/format/compress.py +42 -19
- austin/format/mojo.py +511 -281
- austin/format/pprof/__init__.py +37 -20
- austin/format/pprof/__main__.py +16 -12
- austin/format/speedscope.py +41 -26
- austin/simple.py +92 -64
- austin/stats.py +75 -362
- austin/threads.py +42 -38
- austin/tools/diff.py +2 -5
- austin/tools/mojodbg.py +79 -0
- austin/tools/resolve.py +10 -23
- {austin_python-1.7.0.dist-info → austin_python-2.0.0.dist-info}/METADATA +23 -24
- austin_python-2.0.0.dist-info/RECORD +29 -0
- {austin_python-1.7.0.dist-info → austin_python-2.0.0.dist-info}/WHEEL +1 -1
- {austin_python-1.7.0.dist-info → austin_python-2.0.0.dist-info}/entry_points.txt +2 -1
- austin_python-1.7.0.dist-info/RECORD +0 -24
- {austin_python-1.7.0.dist-info → austin_python-2.0.0.dist-info}/licenses/AUTHORS +0 -0
- {austin_python-1.7.0.dist-info → austin_python-2.0.0.dist-info}/licenses/COPYING +0 -0
- {austin_python-1.7.0.dist-info → austin_python-2.0.0.dist-info}/licenses/LICENSE.md +0 -0
austin/__init__.py
CHANGED
|
@@ -20,310 +20,3 @@
|
|
|
20
20
|
# GNU General Public License for more details.
|
|
21
21
|
# You should have received a copy of the GNU General Public License
|
|
22
22
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
import argparse
|
|
26
|
-
import functools
|
|
27
|
-
import os
|
|
28
|
-
import os.path
|
|
29
|
-
from abc import ABC
|
|
30
|
-
from abc import abstractmethod
|
|
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
|
-
import psutil
|
|
41
|
-
|
|
42
|
-
from austin.config import AustinConfiguration
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
try:
|
|
46
|
-
_cached_property = functools.cached_property # type: ignore[attr-defined]
|
|
47
|
-
except AttributeError:
|
|
48
|
-
|
|
49
|
-
def _cached_property(f: Callable[..., Any]) -> property: # type: ignore[no-redef]
|
|
50
|
-
return property(functools.lru_cache(maxsize=1)(f))
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
SemVer = Tuple[int, int, int]
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def _to_semver(version: Optional[str]) -> SemVer:
|
|
57
|
-
if version is None:
|
|
58
|
-
return (0, 0, 0)
|
|
59
|
-
|
|
60
|
-
try:
|
|
61
|
-
return ( # type: ignore[return-value]
|
|
62
|
-
tuple(
|
|
63
|
-
int(_)
|
|
64
|
-
for _ in "".join(
|
|
65
|
-
list(
|
|
66
|
-
takewhile(
|
|
67
|
-
lambda _: _.isdigit() or _ == ".", version.replace("?", "0")
|
|
68
|
-
)
|
|
69
|
-
)
|
|
70
|
-
).split(".")
|
|
71
|
-
)
|
|
72
|
-
+ (0, 0, 0)
|
|
73
|
-
)[:3]
|
|
74
|
-
except ValueError:
|
|
75
|
-
raise ValueError("Invalid semantic version") from None
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
class AustinError(Exception):
|
|
79
|
-
"""Basic Austin Error."""
|
|
80
|
-
|
|
81
|
-
pass
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
class AustinTerminated(AustinError):
|
|
85
|
-
"""Austin termination exception.
|
|
86
|
-
|
|
87
|
-
Thrown when Austin is terminated with a call to ``terminate``.
|
|
88
|
-
"""
|
|
89
|
-
|
|
90
|
-
pass
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
class BaseAustin(ABC):
|
|
94
|
-
"""Base Austin class.
|
|
95
|
-
|
|
96
|
-
Defines the general API that abstract Austin as an external process.
|
|
97
|
-
Subclasses should implement the :func:`start` method and either define the
|
|
98
|
-
:func:`on_sample_received` method or pass it via the constructor.
|
|
99
|
-
Additionally, the :func:`on_ready` and the :func:`on_terminate` methods can
|
|
100
|
-
be overridden or passed via the constructor to catch the corresponding
|
|
101
|
-
events. Austin is considered to be ready when the first sample is received;
|
|
102
|
-
it is considered to have terminated when the process has terminated
|
|
103
|
-
gracefully.
|
|
104
|
-
|
|
105
|
-
If an error was encountered, the :class:`AustinError` exception is thrown.
|
|
106
|
-
"""
|
|
107
|
-
|
|
108
|
-
BINARY = "austin" + (os.name == "nt" and ".exe" or "")
|
|
109
|
-
BINARY_VERSION = (3, 0, 0)
|
|
110
|
-
|
|
111
|
-
def __init__(
|
|
112
|
-
self,
|
|
113
|
-
sample_callback: Optional[Callable[[str], None]] = None,
|
|
114
|
-
ready_callback: Optional[
|
|
115
|
-
Callable[[psutil.Process, psutil.Process, str], None]
|
|
116
|
-
] = None,
|
|
117
|
-
terminate_callback: Optional[Callable[[Dict[str, str]], None]] = None,
|
|
118
|
-
) -> None:
|
|
119
|
-
"""The ``BaseAustin`` constructor.
|
|
120
|
-
|
|
121
|
-
The callbacks can be passed as arguments, if they are not
|
|
122
|
-
defined/overridden in the definition of a sub-class.
|
|
123
|
-
"""
|
|
124
|
-
self._proc: psutil.Process = None
|
|
125
|
-
self._child_proc: psutil.Process = None
|
|
126
|
-
self._cmd_line: Optional[str] = None
|
|
127
|
-
self._running: bool = False
|
|
128
|
-
self._meta: Dict[str, str] = {}
|
|
129
|
-
|
|
130
|
-
try:
|
|
131
|
-
self._sample_callback = sample_callback or self.on_sample_received # type: ignore[attr-defined]
|
|
132
|
-
except AttributeError:
|
|
133
|
-
raise AustinError("No sample callback given or implemented.") from None
|
|
134
|
-
|
|
135
|
-
self._terminate_callback = terminate_callback or self.on_terminate
|
|
136
|
-
self._ready_callback = ready_callback or self.on_ready
|
|
137
|
-
|
|
138
|
-
self._version: Optional[str] = None
|
|
139
|
-
self._python_version: Optional[str] = None
|
|
140
|
-
|
|
141
|
-
def _get_process_info(
|
|
142
|
-
self, args: argparse.Namespace, austin_pid: int
|
|
143
|
-
) -> Tuple[psutil.Process, psutil.Process, str]:
|
|
144
|
-
try:
|
|
145
|
-
self._proc = psutil.Process(austin_pid)
|
|
146
|
-
except psutil.NoSuchProcess:
|
|
147
|
-
raise AustinError("Cannot find Austin process.") from None
|
|
148
|
-
|
|
149
|
-
if not args.pid: # Austin is forking
|
|
150
|
-
try:
|
|
151
|
-
self._child_proc = self._proc.children()[0]
|
|
152
|
-
if self._child_proc is None:
|
|
153
|
-
raise IndexError
|
|
154
|
-
except IndexError:
|
|
155
|
-
raise AustinError("Cannot find Austin child process.") from None
|
|
156
|
-
else: # Austin is attaching
|
|
157
|
-
try:
|
|
158
|
-
self._child_proc = psutil.Process(args.pid)
|
|
159
|
-
except psutil.NoSuchProcess:
|
|
160
|
-
raise AustinError(
|
|
161
|
-
f"Cannot attach to process with invalid PID {args.pid}."
|
|
162
|
-
) from None
|
|
163
|
-
|
|
164
|
-
self._cmd_line = " ".join(self._child_proc.cmdline())
|
|
165
|
-
|
|
166
|
-
self._running = True
|
|
167
|
-
|
|
168
|
-
return self._proc, self._child_proc, self._cmd_line
|
|
169
|
-
|
|
170
|
-
@abstractmethod
|
|
171
|
-
def start(self, args: Optional[List[str]] = None) -> Any:
|
|
172
|
-
"""Start Austin.
|
|
173
|
-
|
|
174
|
-
Every subclass should implement this method and ensure that it spawns
|
|
175
|
-
a new Austin process.
|
|
176
|
-
"""
|
|
177
|
-
...
|
|
178
|
-
|
|
179
|
-
def get_process(self) -> psutil.Process:
|
|
180
|
-
"""Get the underlying Austin process.
|
|
181
|
-
|
|
182
|
-
Return an instance of :class:`psuitl.Process` that can be used to
|
|
183
|
-
control the underlying Austin process at the OS level.
|
|
184
|
-
"""
|
|
185
|
-
return self._proc
|
|
186
|
-
|
|
187
|
-
def get_command_line(self) -> Optional[str]:
|
|
188
|
-
"""Get the inferred command line.
|
|
189
|
-
|
|
190
|
-
Return the command line of the (main) process that is being profiled
|
|
191
|
-
by Austin.
|
|
192
|
-
"""
|
|
193
|
-
return self._cmd_line
|
|
194
|
-
|
|
195
|
-
def is_running(self) -> bool:
|
|
196
|
-
"""Determine whether Austin is running."""
|
|
197
|
-
return self._running
|
|
198
|
-
|
|
199
|
-
def terminate(self, wait: bool = False) -> None:
|
|
200
|
-
"""Terminate Austin.
|
|
201
|
-
|
|
202
|
-
Stop the underlying Austin process by sending a termination signal.
|
|
203
|
-
"""
|
|
204
|
-
if not self._proc:
|
|
205
|
-
raise AustinError("Austin has not been started yet")
|
|
206
|
-
|
|
207
|
-
try:
|
|
208
|
-
self._proc.terminate()
|
|
209
|
-
if wait:
|
|
210
|
-
self._proc.wait()
|
|
211
|
-
except psutil.NoSuchProcess:
|
|
212
|
-
raise AustinError("Austin has already terminated") from None
|
|
213
|
-
|
|
214
|
-
self._running = False
|
|
215
|
-
self._proc = None
|
|
216
|
-
self._child_proc = None
|
|
217
|
-
|
|
218
|
-
def get_child_process(self) -> psutil.Process:
|
|
219
|
-
"""Get the child process.
|
|
220
|
-
|
|
221
|
-
Return an instalce of :class:`psutil.Process` representing the (main)
|
|
222
|
-
process being profiled by Austin at the OS level.
|
|
223
|
-
"""
|
|
224
|
-
return self._child_proc
|
|
225
|
-
|
|
226
|
-
def submit_sample(self, data: bytes) -> None:
|
|
227
|
-
"""Submit a sample to the sample callback.
|
|
228
|
-
|
|
229
|
-
This method takes care of converting the raw binary data retrieved from
|
|
230
|
-
Austin into a Python string.
|
|
231
|
-
"""
|
|
232
|
-
try:
|
|
233
|
-
sample = data.decode()
|
|
234
|
-
except UnicodeDecodeError:
|
|
235
|
-
try:
|
|
236
|
-
sample = data.decode("ascii")
|
|
237
|
-
except UnicodeDecodeError:
|
|
238
|
-
return
|
|
239
|
-
|
|
240
|
-
self._sample_callback(sample.rstrip())
|
|
241
|
-
|
|
242
|
-
def check_exit(self, rcode: int, stderr: Optional[str]) -> None:
|
|
243
|
-
"""Check Austin exit status."""
|
|
244
|
-
if rcode:
|
|
245
|
-
if rcode in {-15, 15, 241}:
|
|
246
|
-
raise AustinTerminated()
|
|
247
|
-
raise AustinError(f"({rcode}) {stderr}")
|
|
248
|
-
|
|
249
|
-
def check_version(self) -> None:
|
|
250
|
-
"""Check for the minimum Austin binary version."""
|
|
251
|
-
austin_version = self.version
|
|
252
|
-
if austin_version is None:
|
|
253
|
-
raise AustinError("Cannot determine Austin version")
|
|
254
|
-
if austin_version < self.BINARY_VERSION:
|
|
255
|
-
raise AustinError(
|
|
256
|
-
f"Incompatible Austin version (got {austin_version}, expected >= {self.BINARY_VERSION})"
|
|
257
|
-
)
|
|
258
|
-
|
|
259
|
-
# ---- Default callbacks ----
|
|
260
|
-
|
|
261
|
-
def on_terminate(self, stats: Dict[str, str]) -> Any:
|
|
262
|
-
"""Terminate event callback.
|
|
263
|
-
|
|
264
|
-
Implement to be notified when Austin has terminated gracefully. The
|
|
265
|
-
callback accepts an argument that will receive the global statistics.
|
|
266
|
-
"""
|
|
267
|
-
pass
|
|
268
|
-
|
|
269
|
-
def on_ready(
|
|
270
|
-
self,
|
|
271
|
-
process: psutil.Process,
|
|
272
|
-
child_process: psutil.Process,
|
|
273
|
-
command_line: str,
|
|
274
|
-
data: Any = None,
|
|
275
|
-
) -> Any:
|
|
276
|
-
"""Ready event callback.
|
|
277
|
-
|
|
278
|
-
Implement to get notified when Austin has successfully started or
|
|
279
|
-
attached the Python process to profile and the first sample has been
|
|
280
|
-
produced. This callback receives the Austin process and it's (main)
|
|
281
|
-
profiled process as instances of :class:`psutil.Process`, along with
|
|
282
|
-
the command line of the latter.
|
|
283
|
-
"""
|
|
284
|
-
pass
|
|
285
|
-
|
|
286
|
-
# ---- Properties ----
|
|
287
|
-
|
|
288
|
-
@_cached_property
|
|
289
|
-
def binary_path(self) -> str:
|
|
290
|
-
"""Discover the path of the Austin binary.
|
|
291
|
-
|
|
292
|
-
Lookup order is:
|
|
293
|
-
- current working directory
|
|
294
|
-
- ``AUSTINPATH`` variable
|
|
295
|
-
- ``~/.austinrc`` file
|
|
296
|
-
- ``PATH`` variable.
|
|
297
|
-
"""
|
|
298
|
-
binary_name = self.BINARY
|
|
299
|
-
|
|
300
|
-
# Try CWD
|
|
301
|
-
binary_path = Path.cwd() / binary_name
|
|
302
|
-
if binary_path.is_file():
|
|
303
|
-
return str(binary_path)
|
|
304
|
-
|
|
305
|
-
# Try with AUSTINPATH variable
|
|
306
|
-
austin_path = os.environ.get("AUSTINPATH", None)
|
|
307
|
-
if austin_path:
|
|
308
|
-
binary_path = Path(austin_path).expanduser() / binary_name
|
|
309
|
-
if binary_path.is_file():
|
|
310
|
-
return str(binary_path)
|
|
311
|
-
|
|
312
|
-
# Try with .austinrc file
|
|
313
|
-
if AustinConfiguration().binary is not None:
|
|
314
|
-
binary_path = Path(AustinConfiguration().binary).expanduser()
|
|
315
|
-
if binary_path.is_file():
|
|
316
|
-
return str(binary_path)
|
|
317
|
-
|
|
318
|
-
# Try from PATH
|
|
319
|
-
return self.BINARY
|
|
320
|
-
|
|
321
|
-
@property
|
|
322
|
-
def version(self) -> SemVer:
|
|
323
|
-
"""Austin version."""
|
|
324
|
-
return _to_semver(self._meta.get("austin"))
|
|
325
|
-
|
|
326
|
-
@property
|
|
327
|
-
def python_version(self) -> SemVer:
|
|
328
|
-
"""The version of the detected Python interpreter."""
|
|
329
|
-
return _to_semver(self._meta.get("python"))
|
austin/aio.py
CHANGED
|
@@ -23,13 +23,15 @@
|
|
|
23
23
|
|
|
24
24
|
import asyncio
|
|
25
25
|
import sys
|
|
26
|
-
|
|
27
|
-
from typing import List
|
|
28
|
-
from typing import Optional
|
|
26
|
+
import typing as t
|
|
29
27
|
|
|
30
|
-
from austin import
|
|
31
|
-
from austin import BaseAustin
|
|
28
|
+
from austin.base import AustinState
|
|
29
|
+
from austin.base import BaseAustin
|
|
32
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
|
|
33
35
|
|
|
34
36
|
|
|
35
37
|
class AsyncAustin(BaseAustin):
|
|
@@ -38,69 +40,89 @@ class AsyncAustin(BaseAustin):
|
|
|
38
40
|
Implements an ``asyncio`` API for Austin so that it can be used alongside
|
|
39
41
|
other asynchronous tasks.
|
|
40
42
|
|
|
41
|
-
The following example shows how to make a simple asynchronous
|
|
42
|
-
|
|
43
|
+
The following example shows how to make a simple asynchronous implementation
|
|
44
|
+
of Austin that returns stack in the collapsed format
|
|
43
45
|
|
|
44
46
|
Example::
|
|
45
47
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
print(f"Austin PID: {process.pid}")
|
|
49
|
-
print(f"Python PID: {child_process.pid}")
|
|
50
|
-
print(f"Command Line: {command_line}")
|
|
48
|
+
import asyncio
|
|
49
|
+
import sys
|
|
51
50
|
|
|
52
|
-
|
|
53
|
-
|
|
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))
|
|
54
63
|
|
|
55
|
-
def on_terminate(self, data):
|
|
56
|
-
print(data)
|
|
57
64
|
|
|
58
65
|
if sys.platform == "win32":
|
|
59
66
|
asyncio.set_event_loop(asyncio.ProactorEventLoop())
|
|
60
67
|
|
|
61
|
-
try:
|
|
62
|
-
austin = EchoAsyncAustin()
|
|
63
|
-
asyncio.get_event_loop().run_until_complete(
|
|
64
|
-
austin.start(["-i", "10000", "python3", "myscript.py"])
|
|
65
|
-
)
|
|
66
|
-
except (KeyboardInterrupt, asyncio.CancelledError):
|
|
67
|
-
pass
|
|
68
|
-
"""
|
|
69
68
|
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
async def main():
|
|
70
|
+
austin = CollapsedStackAsyncAustin()
|
|
71
|
+
await austin.start(["-i", "10ms", "python", "myscript.py"])
|
|
72
|
+
await austin.wait()
|
|
72
73
|
|
|
73
|
-
try:
|
|
74
|
-
return (
|
|
75
|
-
(await asyncio.wait_for(self.proc.stderr.read(), 0.1)).decode().rstrip()
|
|
76
|
-
)
|
|
77
|
-
except asyncio.TimeoutError:
|
|
78
|
-
return None
|
|
79
74
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
meta = {}
|
|
75
|
+
asyncio.run(main())
|
|
76
|
+
"""
|
|
84
77
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
|
91
110
|
|
|
92
|
-
self.
|
|
93
|
-
return meta
|
|
111
|
+
self._state = AustinState.TERMINATED
|
|
94
112
|
|
|
95
|
-
async def start(self, args: Optional[
|
|
113
|
+
async def start(self, args: t.Optional[t.Sequence[str]] = None) -> None:
|
|
96
114
|
"""Create the start coroutine.
|
|
97
115
|
|
|
98
116
|
Use with the ``asyncio`` event loop.
|
|
99
117
|
"""
|
|
118
|
+
self._args = AustinArgumentParser().parse_args(args)
|
|
119
|
+
|
|
120
|
+
self._state = AustinState.STARTING
|
|
121
|
+
|
|
100
122
|
try:
|
|
101
123
|
_args = list(args if args is not None else sys.argv[1:]) # Make a copy
|
|
102
124
|
_args.insert(0, "-P")
|
|
103
|
-
self.
|
|
125
|
+
self._proc = await asyncio.create_subprocess_exec(
|
|
104
126
|
self.binary_path,
|
|
105
127
|
*_args,
|
|
106
128
|
stdin=asyncio.subprocess.PIPE,
|
|
@@ -110,44 +132,53 @@ class AsyncAustin(BaseAustin):
|
|
|
110
132
|
except FileNotFoundError:
|
|
111
133
|
raise AustinError("Austin executable not found.") from None
|
|
112
134
|
|
|
113
|
-
if not self.
|
|
135
|
+
if not self._proc.stdout:
|
|
114
136
|
raise AustinError("Standard output stream is unexpectedly missing")
|
|
115
|
-
if not self.
|
|
137
|
+
if not self._proc.stderr:
|
|
116
138
|
raise AustinError("Standard error stream is unexpectedly missing")
|
|
117
139
|
|
|
118
|
-
|
|
140
|
+
mojo = AsyncMojoStreamReader(self._proc.stdout)
|
|
119
141
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
self.check_version()
|
|
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
|
|
125
146
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
|
131
152
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
data = (await self.proc.stdout.readline()).rstrip()
|
|
135
|
-
if not data:
|
|
153
|
+
if e.name == "austin":
|
|
154
|
+
self._check_version()
|
|
136
155
|
break
|
|
156
|
+
else:
|
|
157
|
+
raise AustinError("Cannot determine Austin version from output")
|
|
137
158
|
|
|
138
|
-
|
|
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
|
|
139
174
|
|
|
140
|
-
|
|
141
|
-
|
|
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")
|
|
142
178
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
await self.proc.wait()
|
|
147
|
-
except Exception:
|
|
148
|
-
# best effort
|
|
149
|
-
pass
|
|
150
|
-
raise
|
|
179
|
+
# Await the run task. This will terminate naturally when the Austin
|
|
180
|
+
# process has terminated.
|
|
181
|
+
await self._run_task
|
|
151
182
|
|
|
152
|
-
|
|
153
|
-
|
|
183
|
+
# Wait for the Austin process to terminate and return the exit code
|
|
184
|
+
return await self._proc.wait()
|