austin-python 2.1.1__py3-none-any.whl → 2.2.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/base.py +0 -1
- austin/events.py +1 -3
- austin/format/collapsed_stack.py +3 -4
- austin/format/compress.py +7 -5
- austin/format/mojo.py +111 -25
- austin/format/mojo_compress.py +122 -0
- austin/format/pprof/__init__.py +0 -1
- austin/format/speedscope.py +0 -2
- austin/stats.py +27 -22
- austin/tools/diff.py +6 -4
- austin/tools/mojodbg.py +0 -1
- austin/tools/resolve.py +8 -5
- {austin_python-2.1.1.dist-info → austin_python-2.2.0.dist-info}/METADATA +4 -4
- austin_python-2.2.0.dist-info/RECORD +30 -0
- {austin_python-2.1.1.dist-info → austin_python-2.2.0.dist-info}/WHEEL +1 -1
- {austin_python-2.1.1.dist-info → austin_python-2.2.0.dist-info}/entry_points.txt +1 -0
- austin_python-2.1.1.dist-info/RECORD +0 -29
- {austin_python-2.1.1.dist-info → austin_python-2.2.0.dist-info}/licenses/AUTHORS +0 -0
- {austin_python-2.1.1.dist-info → austin_python-2.2.0.dist-info}/licenses/COPYING +0 -0
- {austin_python-2.1.1.dist-info → austin_python-2.2.0.dist-info}/licenses/LICENSE.md +0 -0
austin/base.py
CHANGED
austin/events.py
CHANGED
austin/format/collapsed_stack.py
CHANGED
|
@@ -10,7 +10,6 @@ from austin.events import AustinMetrics
|
|
|
10
10
|
from austin.events import AustinSample
|
|
11
11
|
from austin.format.mojo import MojoStreamReader
|
|
12
12
|
|
|
13
|
-
|
|
14
13
|
__version__ = "0.1.0"
|
|
15
14
|
|
|
16
15
|
|
|
@@ -66,17 +65,17 @@ def parse_collapsed_stack(sample: str) -> AustinSample:
|
|
|
66
65
|
raise InvalidSample(sample)
|
|
67
66
|
|
|
68
67
|
if sample[0] != "P":
|
|
69
|
-
raise InvalidSample(f"No process ID in sample
|
|
68
|
+
raise InvalidSample(f"No process ID in sample {sample!r}")
|
|
70
69
|
|
|
71
70
|
head, _, metrics = sample.rpartition(" ")
|
|
72
71
|
process, _, rest = head.partition(";")
|
|
73
72
|
try:
|
|
74
73
|
pid = int(process[1:])
|
|
75
74
|
except ValueError:
|
|
76
|
-
raise InvalidSample(f"Invalid process ID in sample
|
|
75
|
+
raise InvalidSample(f"Invalid process ID in sample {sample!r}") from None
|
|
77
76
|
|
|
78
77
|
if rest[0] != "T":
|
|
79
|
-
raise InvalidSample(f"No thread ID in sample
|
|
78
|
+
raise InvalidSample(f"No thread ID in sample {sample!r}")
|
|
80
79
|
|
|
81
80
|
thread, _, frames_data = rest.partition(";")
|
|
82
81
|
thread = thread[1:]
|
austin/format/compress.py
CHANGED
|
@@ -62,9 +62,11 @@ def compress(source: AustinFileReader, dest: TextIO, counts: bool = False) -> No
|
|
|
62
62
|
formatter.format(
|
|
63
63
|
AustinSample.from_key_and_metrics(
|
|
64
64
|
key,
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
(
|
|
66
|
+
AustinMetrics(memory=metrics)
|
|
67
|
+
if is_memory
|
|
68
|
+
else AustinMetrics(time=metrics)
|
|
69
|
+
),
|
|
68
70
|
)
|
|
69
71
|
)
|
|
70
72
|
+ "\n"
|
|
@@ -108,10 +110,10 @@ def main() -> None:
|
|
|
108
110
|
|
|
109
111
|
try:
|
|
110
112
|
with (
|
|
111
|
-
|
|
113
|
+
open(args.input) as _fin,
|
|
112
114
|
open(args.output or args.input, "w") as fout,
|
|
113
115
|
):
|
|
114
|
-
compress(
|
|
116
|
+
compress(AustinFileReader(_fin), fout, args.counts)
|
|
115
117
|
except FileNotFoundError:
|
|
116
118
|
print(f"No such input file: {args.input}")
|
|
117
119
|
exit(1)
|
austin/format/mojo.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import abc
|
|
2
2
|
import asyncio
|
|
3
|
-
import io
|
|
4
3
|
import typing as t
|
|
5
4
|
from dataclasses import dataclass
|
|
6
5
|
from dataclasses import field
|
|
@@ -67,6 +66,7 @@ class MojoEvents:
|
|
|
67
66
|
METRIC_MEMORY = 10
|
|
68
67
|
STRING = 11
|
|
69
68
|
STRING_REF = 12
|
|
69
|
+
STACK_REPEAT = 13
|
|
70
70
|
|
|
71
71
|
|
|
72
72
|
class MojoEventHandler:
|
|
@@ -84,6 +84,7 @@ class MojoEvent:
|
|
|
84
84
|
"""MOJO event."""
|
|
85
85
|
|
|
86
86
|
EVENT_ID: t.ClassVar = None
|
|
87
|
+
raw: t.ClassVar[bytes] = b""
|
|
87
88
|
|
|
88
89
|
def ref(self) -> int:
|
|
89
90
|
return getattr(self, fields(self)[0].name)
|
|
@@ -93,9 +94,7 @@ class MojoEvent:
|
|
|
93
94
|
for f in fields(self):
|
|
94
95
|
value = getattr(self, f.name)
|
|
95
96
|
field_type = (
|
|
96
|
-
f.type
|
|
97
|
-
if isinstance(f.type, t._UnionGenericAlias)
|
|
98
|
-
else f.type
|
|
97
|
+
t.get_args(f.type)[0] if t.get_origin(f.type) is t.Union else f.type
|
|
99
98
|
)
|
|
100
99
|
if field_type is str:
|
|
101
100
|
buffer += value.encode()
|
|
@@ -127,9 +126,11 @@ class MojoMetric(MojoEvent):
|
|
|
127
126
|
def to_bytes(self) -> bytes:
|
|
128
127
|
buffer = bytearray(
|
|
129
128
|
[
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
129
|
+
(
|
|
130
|
+
MojoEvents.METRIC_TIME
|
|
131
|
+
if self.metric_type is MojoMetricType.TIME
|
|
132
|
+
else MojoEvents.METRIC_MEMORY
|
|
133
|
+
)
|
|
133
134
|
]
|
|
134
135
|
)
|
|
135
136
|
buffer += to_varint(self.value)
|
|
@@ -184,6 +185,18 @@ class MojoStack(MojoEvent):
|
|
|
184
185
|
tid: str
|
|
185
186
|
|
|
186
187
|
|
|
188
|
+
@dataclass(frozen=True, eq=True)
|
|
189
|
+
class MojoStackRepeat(MojoEvent):
|
|
190
|
+
"""MOJO stack repeat event.
|
|
191
|
+
|
|
192
|
+
Signals that the previous sample for this thread provides the base
|
|
193
|
+
(outermost) frames for the current sample. The frames accumulated so
|
|
194
|
+
far in the current sample are the innermost (top) part of the call stack.
|
|
195
|
+
"""
|
|
196
|
+
|
|
197
|
+
EVENT_ID = MojoEvents.STACK_REPEAT
|
|
198
|
+
|
|
199
|
+
|
|
187
200
|
@dataclass(frozen=True, eq=True)
|
|
188
201
|
class MojoFrame(MojoEvent):
|
|
189
202
|
"""MOJO frame."""
|
|
@@ -277,7 +290,12 @@ def str_reader() -> t.Generator[t.Optional[str], bytes, str]:
|
|
|
277
290
|
(b,) = yield bytes(buffer[:i]).decode(errors="replace")
|
|
278
291
|
i = 0
|
|
279
292
|
else:
|
|
280
|
-
|
|
293
|
+
try:
|
|
294
|
+
buffer[i] = b
|
|
295
|
+
except IndexError:
|
|
296
|
+
# Expand the buffer if needed
|
|
297
|
+
buffer += bytearray(i - len(buffer) + 1)
|
|
298
|
+
buffer[i] = b
|
|
281
299
|
(b,) = yield None
|
|
282
300
|
i += 1
|
|
283
301
|
|
|
@@ -317,6 +335,11 @@ class BaseMojoStreamReader(AustinEventIterator):
|
|
|
317
335
|
self._str_reader = str_reader()
|
|
318
336
|
next(self._str_reader)
|
|
319
337
|
|
|
338
|
+
# Per-thread previous frame list for STACK_REPEAT expansion.
|
|
339
|
+
# Key: (pid, thread_name); value: list of MojoFrame accumulated by the
|
|
340
|
+
# last fully-finalised sample for that thread.
|
|
341
|
+
self._prev_frames: t.Dict[t.Tuple[int, str], t.List[MojoFrame]] = {}
|
|
342
|
+
|
|
320
343
|
# Austin events
|
|
321
344
|
self.metadata: t.Dict[str, str] = {}
|
|
322
345
|
self.samples: t.List[AustinSample] = []
|
|
@@ -353,6 +376,14 @@ class BaseMojoStreamReader(AustinEventIterator):
|
|
|
353
376
|
|
|
354
377
|
return metadata
|
|
355
378
|
|
|
379
|
+
@property
|
|
380
|
+
def mode(self) -> t.Optional[str]:
|
|
381
|
+
return self.metadata.get("mode")
|
|
382
|
+
|
|
383
|
+
@property
|
|
384
|
+
def gc(self) -> t.Optional[str]:
|
|
385
|
+
return self.metadata.get("gc")
|
|
386
|
+
|
|
356
387
|
def _finalize_sample(self) -> AustinSample:
|
|
357
388
|
"""Finalize the current sample."""
|
|
358
389
|
assert self._running_sample is not None, self._running_sample
|
|
@@ -368,34 +399,73 @@ class BaseMojoStreamReader(AustinEventIterator):
|
|
|
368
399
|
for metric_type, metric in self._running_sample.metrics.items()
|
|
369
400
|
}
|
|
370
401
|
),
|
|
371
|
-
frames=
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
402
|
+
frames=(
|
|
403
|
+
tuple(
|
|
404
|
+
AustinFrame(
|
|
405
|
+
filename=mf.filename.value,
|
|
406
|
+
function=mf.scope.value,
|
|
407
|
+
line=mf.line,
|
|
408
|
+
line_end=mf.line_end,
|
|
409
|
+
column=mf.column,
|
|
410
|
+
column_end=mf.column_end,
|
|
411
|
+
)
|
|
412
|
+
for mf in self._running_sample.frames
|
|
379
413
|
)
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
else None,
|
|
414
|
+
if self._running_sample.frames
|
|
415
|
+
else None
|
|
416
|
+
),
|
|
384
417
|
gc=self._running_sample.gc,
|
|
385
418
|
idle=self._running_sample.idle,
|
|
386
419
|
)
|
|
387
420
|
)
|
|
388
421
|
|
|
422
|
+
# Save fully-expanded frame list for future STACK_REPEAT events from
|
|
423
|
+
# this thread.
|
|
424
|
+
self._prev_frames[(self._running_sample.pid, self._running_sample.thread)] = (
|
|
425
|
+
list(self._running_sample.frames)
|
|
426
|
+
)
|
|
427
|
+
|
|
389
428
|
self._running_sample = None
|
|
390
429
|
|
|
391
430
|
return sample
|
|
392
431
|
|
|
432
|
+
@staticmethod
|
|
433
|
+
def _is_native_frame(frame: MojoFrame) -> bool:
|
|
434
|
+
name = frame.filename.value
|
|
435
|
+
return not name.endswith(".py") and not (
|
|
436
|
+
name.startswith("<") and name.endswith(">")
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
def get_stack_repeat(self) -> MojoStackRepeat:
|
|
440
|
+
"""Handle a STACK_REPEAT event.
|
|
441
|
+
|
|
442
|
+
The previous sample's frames (minus any trailing native frames that
|
|
443
|
+
belonged to the old top-of-stack above the eval frame) are prepended
|
|
444
|
+
to the frames accumulated so far.
|
|
445
|
+
"""
|
|
446
|
+
assert self._running_sample is not None
|
|
447
|
+
key = (self._running_sample.pid, self._running_sample.thread)
|
|
448
|
+
prev = list(self._prev_frames.get(key, []))
|
|
449
|
+
# Strip native frames from the innermost (top) end of the previous
|
|
450
|
+
# stack — those were above the eval frame and have now been replaced
|
|
451
|
+
# by the new frames in the current sample.
|
|
452
|
+
while prev and self._is_native_frame(prev[-1]):
|
|
453
|
+
prev.pop()
|
|
454
|
+
self._running_sample.frames = prev + self._running_sample.frames
|
|
455
|
+
return MojoStackRepeat()
|
|
456
|
+
|
|
393
457
|
def get_stack(self, pid: int, iid: t.Optional[int], thread: str) -> MojoStack:
|
|
394
458
|
"""Parse a stack."""
|
|
395
459
|
if self._running_sample is not None:
|
|
396
460
|
self._finalize_sample()
|
|
397
461
|
|
|
398
|
-
self._running_sample = _RunningSample(
|
|
462
|
+
self._running_sample = _RunningSample(
|
|
463
|
+
pid=pid,
|
|
464
|
+
iid=iid,
|
|
465
|
+
thread=thread,
|
|
466
|
+
idle=False if self.mode == "full" else None,
|
|
467
|
+
gc=False if self.gc is not None else None,
|
|
468
|
+
)
|
|
399
469
|
|
|
400
470
|
return MojoStack(pid, iid if iid is not None else -1, thread)
|
|
401
471
|
|
|
@@ -592,6 +662,11 @@ class MojoStreamReader(BaseMojoStreamReader):
|
|
|
592
662
|
"""Parse string reference."""
|
|
593
663
|
return self.get_string_ref(self.read_int())
|
|
594
664
|
|
|
665
|
+
@handles(MojoEvents.STACK_REPEAT)
|
|
666
|
+
def parse_stack_repeat(self) -> MojoStackRepeat:
|
|
667
|
+
"""Parse a stack repeat event."""
|
|
668
|
+
return self.get_stack_repeat()
|
|
669
|
+
|
|
595
670
|
def parse_event(self) -> t.Optional[MojoEvent]:
|
|
596
671
|
"""Parse a single event."""
|
|
597
672
|
try:
|
|
@@ -651,7 +726,9 @@ class MojoStreamReader(BaseMojoStreamReader):
|
|
|
651
726
|
if self._running_sample is not None:
|
|
652
727
|
yield self._finalize_sample()
|
|
653
728
|
|
|
654
|
-
def hexdump(
|
|
729
|
+
def hexdump(
|
|
730
|
+
self, start: int, end: int, highlight: t.Set[int] = set() # noqa: B006
|
|
731
|
+
) -> None:
|
|
655
732
|
"""Print a hexdump of the MOJO file."""
|
|
656
733
|
self.mojo.seek(start)
|
|
657
734
|
data = self.mojo.read(end - start)
|
|
@@ -775,6 +852,11 @@ class AsyncMojoStreamReader(BaseMojoStreamReader):
|
|
|
775
852
|
"""Parse string reference."""
|
|
776
853
|
return self.get_string_ref(await self.read_int())
|
|
777
854
|
|
|
855
|
+
@handles(MojoEvents.STACK_REPEAT)
|
|
856
|
+
async def parse_stack_repeat(self) -> MojoStackRepeat:
|
|
857
|
+
"""Parse a stack repeat event."""
|
|
858
|
+
return self.get_stack_repeat()
|
|
859
|
+
|
|
778
860
|
async def parse_event(self) -> t.Optional[MojoEvent]:
|
|
779
861
|
"""Parse a single event."""
|
|
780
862
|
try:
|
|
@@ -836,6 +918,8 @@ class AsyncMojoStreamReader(BaseMojoStreamReader):
|
|
|
836
918
|
|
|
837
919
|
|
|
838
920
|
class BaseMojoStreamWriter(abc.ABC):
|
|
921
|
+
"""Base class for MOJO stream writers."""
|
|
922
|
+
|
|
839
923
|
HEADER = b"MOJ\x03"
|
|
840
924
|
|
|
841
925
|
def __init__(self, mojo: t.Any) -> None:
|
|
@@ -885,11 +969,13 @@ class BaseMojoStreamWriter(abc.ABC):
|
|
|
885
969
|
return mojo_frame
|
|
886
970
|
|
|
887
971
|
@abc.abstractmethod
|
|
888
|
-
def write(self, event: AustinEvent) -> int: ...
|
|
972
|
+
def write(self, event: AustinEvent) -> int: ... # noqa: E704
|
|
889
973
|
|
|
890
974
|
|
|
891
975
|
class MojoStreamWriter(BaseMojoStreamWriter):
|
|
892
|
-
|
|
976
|
+
"""MOJO stream writer."""
|
|
977
|
+
|
|
978
|
+
def __init__(self, mojo: t.BinaryIO) -> None:
|
|
893
979
|
super().__init__(mojo)
|
|
894
980
|
|
|
895
981
|
mojo.write(self.HEADER)
|
|
@@ -920,7 +1006,7 @@ class MojoStreamWriter(BaseMojoStreamWriter):
|
|
|
920
1006
|
for frame in frames:
|
|
921
1007
|
size += self.mojo.write(MojoFrameReference(frame).to_bytes())
|
|
922
1008
|
|
|
923
|
-
if
|
|
1009
|
+
if event.gc:
|
|
924
1010
|
size += self.mojo.write(bytes([MojoEvents.GC]))
|
|
925
1011
|
|
|
926
1012
|
if self._mode == "full":
|
|
@@ -0,0 +1,122 @@
|
|
|
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 typing as t
|
|
25
|
+
from io import BytesIO
|
|
26
|
+
|
|
27
|
+
from austin.events import AustinMetadata
|
|
28
|
+
from austin.events import AustinMetrics
|
|
29
|
+
from austin.events import AustinSample
|
|
30
|
+
from austin.format.mojo import MojoStreamReader
|
|
31
|
+
from austin.format.mojo import MojoStreamWriter
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def compress(source: t.BinaryIO, dest: t.BinaryIO) -> None:
|
|
35
|
+
"""Compress a MOJO source stream.
|
|
36
|
+
|
|
37
|
+
Aggregates all metrics on equal stacks, where equality is determined by
|
|
38
|
+
pid, interpreter id, thread, frames, gc flag, and idle flag.
|
|
39
|
+
"""
|
|
40
|
+
time_stats: t.Dict[AustinSample.Key, int] = {}
|
|
41
|
+
memory_stats: t.Dict[AustinSample.Key, int] = {}
|
|
42
|
+
metadata: t.List[AustinMetadata] = []
|
|
43
|
+
samples: t.Dict[AustinSample.Key, AustinSample] = {}
|
|
44
|
+
|
|
45
|
+
reader = MojoStreamReader(source)
|
|
46
|
+
|
|
47
|
+
for event in reader:
|
|
48
|
+
if isinstance(event, AustinMetadata):
|
|
49
|
+
metadata.append(event)
|
|
50
|
+
continue
|
|
51
|
+
|
|
52
|
+
assert isinstance(event, AustinSample)
|
|
53
|
+
|
|
54
|
+
key = event.key()
|
|
55
|
+
if key not in samples:
|
|
56
|
+
samples[key] = event
|
|
57
|
+
if event.metrics.time is not None:
|
|
58
|
+
time_stats[key] = time_stats.get(key, 0) + event.metrics.time
|
|
59
|
+
if event.metrics.memory is not None:
|
|
60
|
+
memory_stats[key] = memory_stats.get(key, 0) + event.metrics.memory
|
|
61
|
+
|
|
62
|
+
writer = MojoStreamWriter(dest)
|
|
63
|
+
|
|
64
|
+
for m in metadata:
|
|
65
|
+
writer.write(m)
|
|
66
|
+
|
|
67
|
+
for key in samples:
|
|
68
|
+
writer.write(
|
|
69
|
+
AustinSample.from_key_and_metrics(
|
|
70
|
+
key,
|
|
71
|
+
AustinMetrics(
|
|
72
|
+
time=time_stats.get(key),
|
|
73
|
+
memory=memory_stats.get(key),
|
|
74
|
+
),
|
|
75
|
+
)
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def main() -> None:
|
|
80
|
+
"""mojo-compress entry point."""
|
|
81
|
+
from argparse import ArgumentParser
|
|
82
|
+
|
|
83
|
+
arg_parser = ArgumentParser(
|
|
84
|
+
prog="mojo-compress",
|
|
85
|
+
description=(
|
|
86
|
+
"Compress a MOJO sample file by aggregating the collected samples."
|
|
87
|
+
),
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
arg_parser.add_argument(
|
|
91
|
+
"input",
|
|
92
|
+
type=str,
|
|
93
|
+
help="The input MOJO file.",
|
|
94
|
+
)
|
|
95
|
+
arg_parser.add_argument(
|
|
96
|
+
"output",
|
|
97
|
+
type=str,
|
|
98
|
+
help="The output MOJO file; defaults to the input file for in-place compression.",
|
|
99
|
+
nargs="?",
|
|
100
|
+
default=None,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
arg_parser.add_argument("-V", "--version", action="version", version="0.1.0")
|
|
104
|
+
|
|
105
|
+
args = arg_parser.parse_args()
|
|
106
|
+
|
|
107
|
+
try:
|
|
108
|
+
with open(args.input, "rb") as fin:
|
|
109
|
+
buffer = BytesIO()
|
|
110
|
+
compress(fin, buffer)
|
|
111
|
+
|
|
112
|
+
output_path = args.output or args.input
|
|
113
|
+
with open(output_path, "wb") as fout:
|
|
114
|
+
fout.write(buffer.getvalue())
|
|
115
|
+
|
|
116
|
+
except FileNotFoundError:
|
|
117
|
+
print(f"No such input file: {args.input}")
|
|
118
|
+
exit(1)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
if __name__ == "__main__":
|
|
122
|
+
main()
|
austin/format/pprof/__init__.py
CHANGED
austin/format/speedscope.py
CHANGED
|
@@ -38,8 +38,6 @@ from austin.events import AustinMetadata
|
|
|
38
38
|
from austin.events import AustinSample
|
|
39
39
|
from austin.format import Mode
|
|
40
40
|
from austin.format.collapsed_stack import AustinFileReader
|
|
41
|
-
from austin.format.collapsed_stack import InvalidSample
|
|
42
|
-
|
|
43
41
|
|
|
44
42
|
__version__ = "0.3.1"
|
|
45
43
|
|
austin/stats.py
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
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
23
|
|
|
24
|
+
from collections import namedtuple
|
|
24
25
|
from copy import deepcopy
|
|
25
26
|
from dataclasses import dataclass
|
|
26
27
|
from dataclasses import field
|
|
@@ -39,9 +40,10 @@ from austin.events import AustinMetadata
|
|
|
39
40
|
from austin.events import AustinMetrics
|
|
40
41
|
from austin.events import AustinSample
|
|
41
42
|
from austin.events import ProcessId
|
|
42
|
-
from austin.events import ThreadName
|
|
43
43
|
from austin.format.collapsed_stack import AustinEventCollapsedStackFormatter
|
|
44
44
|
|
|
45
|
+
ThreadInfo = namedtuple("ThreadInfo", ["thread", "iid"])
|
|
46
|
+
|
|
45
47
|
|
|
46
48
|
COLLAPSED_STACK_FORMATTER = AustinEventCollapsedStackFormatter()
|
|
47
49
|
|
|
@@ -120,9 +122,11 @@ class FrameStats(HierarchicalStats):
|
|
|
120
122
|
if self.own:
|
|
121
123
|
yield (
|
|
122
124
|
[frame],
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
125
|
+
(
|
|
126
|
+
AustinMetrics(time=self.own)
|
|
127
|
+
if stats_type in {AustinStatsType.CPU, AustinStatsType.WALL}
|
|
128
|
+
else AustinMetrics(memory=self.own)
|
|
129
|
+
),
|
|
126
130
|
)
|
|
127
131
|
|
|
128
132
|
if self.children:
|
|
@@ -136,21 +140,22 @@ class FrameStats(HierarchicalStats):
|
|
|
136
140
|
class ThreadStats(HierarchicalStats):
|
|
137
141
|
"""Thread statistics."""
|
|
138
142
|
|
|
139
|
-
label:
|
|
143
|
+
label: ThreadInfo
|
|
140
144
|
children: Dict[AustinFrame, FrameStats] = field(default_factory=dict) # type: ignore[assignment]
|
|
141
145
|
|
|
142
146
|
def collapse(
|
|
143
147
|
self, stats_type: AustinStatsType
|
|
144
|
-
) -> Generator[Tuple[
|
|
148
|
+
) -> Generator[Tuple[ThreadInfo, List[AustinFrame], AustinMetrics], None, None]:
|
|
145
149
|
"""Collapse the hierarchical statistics."""
|
|
146
|
-
|
|
147
150
|
if self.own:
|
|
148
151
|
yield (
|
|
149
152
|
self.label,
|
|
150
153
|
[],
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
+
(
|
|
155
|
+
AustinMetrics(time=self.own)
|
|
156
|
+
if stats_type in {AustinStatsType.CPU, AustinStatsType.WALL}
|
|
157
|
+
else AustinMetrics(memory=self.own)
|
|
158
|
+
),
|
|
154
159
|
)
|
|
155
160
|
|
|
156
161
|
if self.children:
|
|
@@ -166,7 +171,7 @@ class ProcessStats:
|
|
|
166
171
|
"""Process statistics."""
|
|
167
172
|
|
|
168
173
|
pid: ProcessId
|
|
169
|
-
threads: Dict[
|
|
174
|
+
threads: Dict[ThreadInfo, ThreadStats] = field(default_factory=dict)
|
|
170
175
|
|
|
171
176
|
def collapse(
|
|
172
177
|
self, stats_type: AustinStatsType
|
|
@@ -175,22 +180,22 @@ class ProcessStats:
|
|
|
175
180
|
yield from (
|
|
176
181
|
AustinSample(
|
|
177
182
|
pid=self.pid,
|
|
178
|
-
iid=
|
|
179
|
-
thread=
|
|
183
|
+
iid=thread_info.iid,
|
|
184
|
+
thread=thread_info.thread,
|
|
180
185
|
frames=tuple(frames),
|
|
181
186
|
metrics=metric,
|
|
182
187
|
)
|
|
183
188
|
for thread in self.threads.values()
|
|
184
|
-
for
|
|
189
|
+
for thread_info, frames, metric in thread.collapse(stats_type)
|
|
185
190
|
)
|
|
186
191
|
|
|
187
|
-
def get_thread(self,
|
|
192
|
+
def get_thread(self, thread_info: ThreadInfo) -> Optional[ThreadStats]:
|
|
188
193
|
"""Get thread statistics from this process by name.
|
|
189
194
|
|
|
190
195
|
If the given thread name is not registered with this current process
|
|
191
196
|
statistics, then ``None`` is returned.
|
|
192
197
|
"""
|
|
193
|
-
return self.threads.get(
|
|
198
|
+
return self.threads.get(thread_info)
|
|
194
199
|
|
|
195
200
|
|
|
196
201
|
@dataclass
|
|
@@ -286,8 +291,8 @@ class AustinStats:
|
|
|
286
291
|
return
|
|
287
292
|
|
|
288
293
|
pid = sample.pid
|
|
289
|
-
|
|
290
|
-
thread_stats = ThreadStats(
|
|
294
|
+
thread_info = ThreadInfo(sample.thread, sample.iid or 0)
|
|
295
|
+
thread_stats = ThreadStats(thread_info, own=0, total=metric)
|
|
291
296
|
|
|
292
297
|
# Convert the list of frames into a nested FrameStats instance
|
|
293
298
|
stats: HierarchicalStats = thread_stats
|
|
@@ -300,15 +305,15 @@ class AustinStats:
|
|
|
300
305
|
|
|
301
306
|
with self._lock:
|
|
302
307
|
if pid not in self.processes:
|
|
303
|
-
self.processes[pid] = ProcessStats(pid, {
|
|
308
|
+
self.processes[pid] = ProcessStats(pid, {thread_info: thread_stats})
|
|
304
309
|
return
|
|
305
310
|
|
|
306
311
|
process = self.processes[pid]
|
|
307
|
-
if
|
|
308
|
-
process.threads[
|
|
312
|
+
if thread_info not in process.threads:
|
|
313
|
+
process.threads[thread_info] = thread_stats
|
|
309
314
|
return
|
|
310
315
|
|
|
311
|
-
process.threads[
|
|
316
|
+
process.threads[thread_info] << thread_stats
|
|
312
317
|
|
|
313
318
|
def flatten(self) -> Generator[AustinSample, None, None]:
|
|
314
319
|
for process in self.processes.values():
|
austin/tools/diff.py
CHANGED
|
@@ -30,9 +30,11 @@ from typing import TextIO
|
|
|
30
30
|
from typing import Tuple
|
|
31
31
|
|
|
32
32
|
from austin.events import AustinFrame
|
|
33
|
+
from austin.events import AustinMetrics
|
|
34
|
+
from austin.format.collapsed_stack import InvalidSample
|
|
35
|
+
from austin.format.collapsed_stack import parse_collapsed_stack
|
|
33
36
|
from austin.format.compress import compress
|
|
34
37
|
|
|
35
|
-
|
|
36
38
|
FoldedStack = List[AustinFrame]
|
|
37
39
|
|
|
38
40
|
|
|
@@ -41,7 +43,7 @@ def _similarities(
|
|
|
41
43
|
) -> List[Tuple[Tuple[int, int], float]]:
|
|
42
44
|
"""O(n * log(n)), n = len(x) * len(y)."""
|
|
43
45
|
|
|
44
|
-
def score(a: List[
|
|
46
|
+
def score(a: List[AustinFrame], b: List[AustinFrame]) -> float:
|
|
45
47
|
"""Score two folded stacks."""
|
|
46
48
|
if not len(a) and not len(b):
|
|
47
49
|
return 1
|
|
@@ -90,12 +92,12 @@ def diff(a: TextIO, b: TextIO) -> str:
|
|
|
90
92
|
compress(source, _)
|
|
91
93
|
return _.getvalue()
|
|
92
94
|
|
|
93
|
-
def get_frames(text: str) -> List[Tuple[FoldedStack,
|
|
95
|
+
def get_frames(text: str) -> List[Tuple[FoldedStack, AustinMetrics]]:
|
|
94
96
|
"""Get the folded stacks and metrics from a string of samples."""
|
|
95
97
|
x = []
|
|
96
98
|
for _ in text.splitlines(keepends=False):
|
|
97
99
|
try:
|
|
98
|
-
sample =
|
|
100
|
+
sample = parse_collapsed_stack(_)
|
|
99
101
|
x.append((sample.frames, sample.metrics))
|
|
100
102
|
except InvalidSample:
|
|
101
103
|
continue
|
austin/tools/mojodbg.py
CHANGED
austin/tools/resolve.py
CHANGED
|
@@ -30,7 +30,6 @@ from austin.format.mojo import MojoStreamReader
|
|
|
30
30
|
from austin.format.mojo import MojoString
|
|
31
31
|
from austin.format.mojo import to_varint
|
|
32
32
|
|
|
33
|
-
|
|
34
33
|
__version__ = "0.2.0"
|
|
35
34
|
|
|
36
35
|
|
|
@@ -176,8 +175,10 @@ def resolve_mojo(input: str, output: str) -> None:
|
|
|
176
175
|
filename, scope, line = resolved
|
|
177
176
|
new_value = filename or scope
|
|
178
177
|
|
|
179
|
-
|
|
180
|
-
event
|
|
178
|
+
object.__setattr__(
|
|
179
|
+
event,
|
|
180
|
+
"raw",
|
|
181
|
+
event.raw.replace(event.value.encode(), new_value.encode()),
|
|
181
182
|
)
|
|
182
183
|
|
|
183
184
|
if filename is not None:
|
|
@@ -185,9 +186,11 @@ def resolve_mojo(input: str, output: str) -> None:
|
|
|
185
186
|
|
|
186
187
|
elif isinstance(event, MojoFrame):
|
|
187
188
|
if event.filename.key in maps.lines:
|
|
188
|
-
|
|
189
|
+
object.__setattr__(
|
|
190
|
+
event,
|
|
191
|
+
"raw",
|
|
189
192
|
event.raw[: -len(to_varint(event.line))]
|
|
190
|
-
+ maps.lines[event.filename.key]
|
|
193
|
+
+ maps.lines[event.filename.key],
|
|
191
194
|
)
|
|
192
195
|
|
|
193
196
|
fout.write(event.raw)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: austin-python
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.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.9
|
|
19
18
|
Classifier: Programming Language :: Python :: 3.10
|
|
20
19
|
Classifier: Programming Language :: Python :: 3.11
|
|
21
20
|
Classifier: Programming Language :: Python :: 3.12
|
|
22
21
|
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
-
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
24
|
Requires-Dist: protobuf~=3.12
|
|
25
25
|
Requires-Dist: psutil>=5.7.0
|
|
26
26
|
Requires-Dist: toml~=0.10
|
|
@@ -187,7 +187,7 @@ raw samples by aggregation.
|
|
|
187
187
|
# Compatibility
|
|
188
188
|
|
|
189
189
|
The latest `austin-python` package is tested on Linux, macOS and Windows with
|
|
190
|
-
Python 3.9-3.
|
|
190
|
+
Python 3.9-3.14.
|
|
191
191
|
|
|
192
192
|
|
|
193
193
|
# Documentation
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
austin/__init__.py,sha256=1SCBrKP0MCtuALvQ3tZ8N8-L7pzIzqrMfA3emxtpHkM,974
|
|
2
|
+
austin/aio.py,sha256=Cpq_W1YxL_fUPKBbkupLBHSKi3Sys9OQCcsyq8wYHKY,6594
|
|
3
|
+
austin/base.py,sha256=Xq734VKzE2ggShs6UThsGznBGazg-gCw_nZEJDJKMlM,7553
|
|
4
|
+
austin/cli.py,sha256=--PKm2I4NBBSSSBV5l-YC0mcmLjrvb1FHzxgY4dkby4,7826
|
|
5
|
+
austin/config.py,sha256=PEwdG2Cy8ay1SrFr2N6YfQSZwseYBA1BA5dMfocKR3A,2091
|
|
6
|
+
austin/errors.py,sha256=lCDSTANW_95Az_eDXbn__JRBllB9yw7tGnFo1-tw09Q,1046
|
|
7
|
+
austin/events.py,sha256=u04uArj15b-CkQF7_WYnd1qZ41SFquAnxgpZN9KBskQ,2159
|
|
8
|
+
austin/simple.py,sha256=iTmJGAjSBeUv82SgdMTwdNlAQwuK33RyU8FkBCE8rRg,5579
|
|
9
|
+
austin/stats.py,sha256=xY4BZtiE1Z4Bfucah1da6jwkEREvd1jLfmW4WuCgbP0,11084
|
|
10
|
+
austin/threads.py,sha256=AvcoOkr3FeFAGBcqgXukNIt9xOZUipv7pXMqPIL_Qts,3649
|
|
11
|
+
austin/format/__init__.py,sha256=ZtBJCLpTiZaLFwCN1UbMXLQvYjOSpwmtyGwqgK8yruQ,405
|
|
12
|
+
austin/format/collapsed_stack.py,sha256=vD0CFkUgnRqHaA7dYsh4QYRZubE5IxyhVFnuPa37BSs,7245
|
|
13
|
+
austin/format/compress.py,sha256=by6i79o0pEmGf2Py8edC6RWHVdr5RGsG2FWT6xQjUvk,3797
|
|
14
|
+
austin/format/mojo.py,sha256=iqfPmJQ3tqoP-fclKDd0OiOSLiIQaWRjddA8L-7Huyw,31414
|
|
15
|
+
austin/format/mojo_compress.py,sha256=qhzXsenUThxwAz1lX1Z8xtGgBpPEIbD3cqZz2Iar3ws,3711
|
|
16
|
+
austin/format/speedscope.py,sha256=hIuB1A8wbGFFLQUpZqdM7Tlf4dqFwyy01zFHDybs2Iw,8324
|
|
17
|
+
austin/format/pprof/__init__.py,sha256=E2JJDaw_k_m18OZeITKtepUUIo3n09UHdkpvabSSABc,6804
|
|
18
|
+
austin/format/pprof/__main__.py,sha256=pzWoSmlFrjfmAAq5I4SIIDmxavdLStCnlw3NlBxgyDo,2652
|
|
19
|
+
austin/format/pprof/profile.proto,sha256=fPfBgzXIEmFdrrwgTQWs-9GwKjt-TShRmJq-eaxDx88,8840
|
|
20
|
+
austin/format/pprof/profile_pb2.py,sha256=i_uM8lDExR95yz7qnEVikhpkpNvetKaYzoiH7AbMAt8,28214
|
|
21
|
+
austin/tools/diff.py,sha256=5yrHlniofIeMSqzOUD55l2h6Ps0GqEpfisFRj65gpdQ,5008
|
|
22
|
+
austin/tools/mojodbg.py,sha256=l8Lsa4rTQLXfkau8RyGziyz8ZyPEgacFVXafbHT9FN4,2816
|
|
23
|
+
austin/tools/resolve.py,sha256=bTXkll68nOtXqa0XHW79mp_seAJW8_bhd6ej6wa2_Oc,7816
|
|
24
|
+
austin_python-2.2.0.dist-info/METADATA,sha256=EAzdTQnAVmxg6NqXea2G5DzYcqIFwPksjByks3Qvn2s,7537
|
|
25
|
+
austin_python-2.2.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
26
|
+
austin_python-2.2.0.dist-info/entry_points.txt,sha256=xmPVC_6UkSOYu7-YMYm3BwIcMFYEhRw0qlF05Cq5gf0,378
|
|
27
|
+
austin_python-2.2.0.dist-info/licenses/AUTHORS,sha256=pARRmpJQYH8iWy_0SonXsxOiV02AFitowle4AtURxUI,45
|
|
28
|
+
austin_python-2.2.0.dist-info/licenses/COPYING,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
|
|
29
|
+
austin_python-2.2.0.dist-info/licenses/LICENSE.md,sha256=edVDhmEiPf2W5lWzqLkrj6EYLSrCeYg9MJy28YsiMFU,34937
|
|
30
|
+
austin_python-2.2.0.dist-info/RECORD,,
|
|
@@ -4,5 +4,6 @@ austin-diff = austin.tools.diff:main
|
|
|
4
4
|
austin2pprof = austin.format.pprof.__main__:main
|
|
5
5
|
austin2speedscope = austin.format.speedscope:main
|
|
6
6
|
austinp-resolve = austin.tools.resolve:main
|
|
7
|
+
mojo-compress = austin.format.mojo_compress:main
|
|
7
8
|
mojo2austin = austin.format.collapsed_stack:main
|
|
8
9
|
mojodbg = austin.tools.mojodbg:main
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
austin/__init__.py,sha256=1SCBrKP0MCtuALvQ3tZ8N8-L7pzIzqrMfA3emxtpHkM,974
|
|
2
|
-
austin/aio.py,sha256=Cpq_W1YxL_fUPKBbkupLBHSKi3Sys9OQCcsyq8wYHKY,6594
|
|
3
|
-
austin/base.py,sha256=wAYVZQi3hcgJA9cVg3mfc3f3E0qwFXmeX7UX6qgn2Pk,7554
|
|
4
|
-
austin/cli.py,sha256=--PKm2I4NBBSSSBV5l-YC0mcmLjrvb1FHzxgY4dkby4,7826
|
|
5
|
-
austin/config.py,sha256=PEwdG2Cy8ay1SrFr2N6YfQSZwseYBA1BA5dMfocKR3A,2091
|
|
6
|
-
austin/errors.py,sha256=lCDSTANW_95Az_eDXbn__JRBllB9yw7tGnFo1-tw09Q,1046
|
|
7
|
-
austin/events.py,sha256=39BHP-pUHotZZxJ-MbECFu5_QOAnRJP-_hFEdYPYwKY,2235
|
|
8
|
-
austin/simple.py,sha256=iTmJGAjSBeUv82SgdMTwdNlAQwuK33RyU8FkBCE8rRg,5579
|
|
9
|
-
austin/stats.py,sha256=8cpuq3cyZUpGkm2VZS3j1Zpz-Yhr3i9Wg6bR-sGwWJM,10933
|
|
10
|
-
austin/threads.py,sha256=AvcoOkr3FeFAGBcqgXukNIt9xOZUipv7pXMqPIL_Qts,3649
|
|
11
|
-
austin/format/__init__.py,sha256=ZtBJCLpTiZaLFwCN1UbMXLQvYjOSpwmtyGwqgK8yruQ,405
|
|
12
|
-
austin/format/collapsed_stack.py,sha256=61iEu3oFl9e3vUNHvhpAQzgJndxtE7WcbRwvRLO03S4,7246
|
|
13
|
-
austin/format/compress.py,sha256=YEceebWzVB7eSCntnJ89_rIcbzBskvZGy86D5QgZli0,3741
|
|
14
|
-
austin/format/mojo.py,sha256=IlnFsH9gGofENWBMdbpAc0BrutNGvetk5JLLJeCHKuo,28294
|
|
15
|
-
austin/format/speedscope.py,sha256=RvlFugm0S3ATMf9lmzH8jrX4oBt8PJ0j9EsuXWayOQc,8381
|
|
16
|
-
austin/format/pprof/__init__.py,sha256=DvY1CDnPqBc-GUv9fidtMplshIIXj_HWZ3SlWOJoLxc,6805
|
|
17
|
-
austin/format/pprof/__main__.py,sha256=pzWoSmlFrjfmAAq5I4SIIDmxavdLStCnlw3NlBxgyDo,2652
|
|
18
|
-
austin/format/pprof/profile.proto,sha256=fPfBgzXIEmFdrrwgTQWs-9GwKjt-TShRmJq-eaxDx88,8840
|
|
19
|
-
austin/format/pprof/profile_pb2.py,sha256=i_uM8lDExR95yz7qnEVikhpkpNvetKaYzoiH7AbMAt8,28214
|
|
20
|
-
austin/tools/diff.py,sha256=KnY9kD5mmSKALCNxnSe-dSObC4LgkoxLGnoTiklKa5M,4821
|
|
21
|
-
austin/tools/mojodbg.py,sha256=BGdngj-r1c-GAQDrs_oJ_x4QZxuL4t_OQ194fvJJm7A,2817
|
|
22
|
-
austin/tools/resolve.py,sha256=pEOBiP8FNkaJqDTj3fOK8dx5-EocroqZDXLL3CzCWLY,7677
|
|
23
|
-
austin_python-2.1.1.dist-info/METADATA,sha256=t22jsfhB6Ssk8vR8LIwD0l9bcw0LnUb0eEXKx-uJEp0,7535
|
|
24
|
-
austin_python-2.1.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
25
|
-
austin_python-2.1.1.dist-info/entry_points.txt,sha256=YatHOUQsHQ3WABcrNKPf4RWatQ0CzbR-1ClXjMjZys8,329
|
|
26
|
-
austin_python-2.1.1.dist-info/licenses/AUTHORS,sha256=pARRmpJQYH8iWy_0SonXsxOiV02AFitowle4AtURxUI,45
|
|
27
|
-
austin_python-2.1.1.dist-info/licenses/COPYING,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
|
|
28
|
-
austin_python-2.1.1.dist-info/licenses/LICENSE.md,sha256=edVDhmEiPf2W5lWzqLkrj6EYLSrCeYg9MJy28YsiMFU,34937
|
|
29
|
-
austin_python-2.1.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|