austin-python 2.1.2__tar.gz → 2.2.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-2.1.2 → austin_python-2.2.0}/.flake8 +1 -2
- {austin_python-2.1.2 → austin_python-2.2.0}/PKG-INFO +4 -4
- {austin_python-2.1.2 → austin_python-2.2.0}/README.md +1 -1
- {austin_python-2.1.2 → austin_python-2.2.0}/austin/base.py +0 -1
- {austin_python-2.1.2 → austin_python-2.2.0}/austin/events.py +0 -1
- {austin_python-2.1.2 → austin_python-2.2.0}/austin/format/collapsed_stack.py +4 -5
- {austin_python-2.1.2 → austin_python-2.2.0}/austin/format/compress.py +7 -5
- {austin_python-2.1.2 → austin_python-2.2.0}/austin/format/mojo.py +90 -23
- austin_python-2.2.0/austin/format/mojo_compress.py +122 -0
- {austin_python-2.1.2 → austin_python-2.2.0}/austin/format/pprof/__init__.py +0 -1
- {austin_python-2.1.2 → austin_python-2.2.0}/austin/format/speedscope.py +0 -2
- {austin_python-2.1.2 → austin_python-2.2.0}/austin/stats.py +10 -8
- {austin_python-2.1.2 → austin_python-2.2.0}/austin/tools/diff.py +6 -4
- {austin_python-2.1.2 → austin_python-2.2.0}/austin/tools/mojodbg.py +0 -1
- {austin_python-2.1.2 → austin_python-2.2.0}/austin/tools/resolve.py +8 -5
- {austin_python-2.1.2 → austin_python-2.2.0}/pyproject.toml +31 -12
- {austin_python-2.1.2 → austin_python-2.2.0}/test/conftest.py +0 -1
- austin_python-2.2.0/test/data/repeat.austin +1717 -0
- austin_python-2.2.0/test/data/repeat.mojo +0 -0
- {austin_python-2.1.2 → austin_python-2.2.0}/test/format/test_compress.py +33 -0
- {austin_python-2.1.2 → austin_python-2.2.0}/test/format/test_mojo.py +1 -4
- austin_python-2.2.0/test/format/test_mojo_compress.py +303 -0
- {austin_python-2.1.2 → austin_python-2.2.0}/test/format/test_speedscope.py +0 -1
- {austin_python-2.1.2 → austin_python-2.2.0}/test/stats/test_austin_stats.py +1 -2
- {austin_python-2.1.2 → austin_python-2.2.0}/test/test_aio.py +23 -1
- austin_python-2.2.0/test/test_base.py +39 -0
- {austin_python-2.1.2 → austin_python-2.2.0}/test/test_semver.py +4 -0
- {austin_python-2.1.2 → austin_python-2.2.0}/test/test_simple.py +12 -0
- austin_python-2.2.0/test/tools/test_diff.py +13 -0
- {austin_python-2.1.2 → austin_python-2.2.0}/test/tools/test_resolve.py +0 -1
- austin_python-2.1.2/test/tools/test_diff.py +0 -11
- {austin_python-2.1.2 → austin_python-2.2.0}/.gitignore +0 -0
- {austin_python-2.1.2 → austin_python-2.2.0}/.readthedocs.yml +0 -0
- {austin_python-2.1.2 → austin_python-2.2.0}/AUTHORS +0 -0
- {austin_python-2.1.2 → austin_python-2.2.0}/CODE-OF-CONDUCT.md +0 -0
- {austin_python-2.1.2 → austin_python-2.2.0}/CONTRIBUTING.md +0 -0
- {austin_python-2.1.2 → austin_python-2.2.0}/COPYING +0 -0
- {austin_python-2.1.2 → austin_python-2.2.0}/ISSUE_TEMPLATE.md +0 -0
- {austin_python-2.1.2 → austin_python-2.2.0}/LICENSE.md +0 -0
- {austin_python-2.1.2 → austin_python-2.2.0}/MANIFEST.in +0 -0
- {austin_python-2.1.2 → austin_python-2.2.0}/PULL_REQUEST_TEMPLATE.md +0 -0
- {austin_python-2.1.2 → austin_python-2.2.0}/austin/__init__.py +0 -0
- {austin_python-2.1.2 → austin_python-2.2.0}/austin/aio.py +0 -0
- {austin_python-2.1.2 → austin_python-2.2.0}/austin/cli.py +0 -0
- {austin_python-2.1.2 → austin_python-2.2.0}/austin/config.py +0 -0
- {austin_python-2.1.2 → austin_python-2.2.0}/austin/errors.py +0 -0
- {austin_python-2.1.2 → austin_python-2.2.0}/austin/format/__init__.py +0 -0
- {austin_python-2.1.2 → austin_python-2.2.0}/austin/format/pprof/__main__.py +0 -0
- {austin_python-2.1.2 → austin_python-2.2.0}/austin/format/pprof/profile.proto +0 -0
- {austin_python-2.1.2 → austin_python-2.2.0}/austin/format/pprof/profile_pb2.py +0 -0
- {austin_python-2.1.2 → austin_python-2.2.0}/austin/simple.py +0 -0
- {austin_python-2.1.2 → austin_python-2.2.0}/austin/threads.py +0 -0
- {austin_python-2.1.2 → austin_python-2.2.0}/test/__init__.py +0 -0
- {austin_python-2.1.2 → austin_python-2.2.0}/test/data/austin.json +0 -0
- {austin_python-2.1.2 → austin_python-2.2.0}/test/data/austin.out +0 -0
- {austin_python-2.1.2 → austin_python-2.2.0}/test/data/austin.pprof +0 -0
- {austin_python-2.1.2 → austin_python-2.2.0}/test/data/austinp.mojo +0 -0
- {austin_python-2.1.2 → austin_python-2.2.0}/test/data/austinp.resolved.mojo +0 -0
- {austin_python-2.1.2 → austin_python-2.2.0}/test/data/column.mojo +0 -0
- {austin_python-2.1.2 → austin_python-2.2.0}/test/data/column.py +0 -0
- {austin_python-2.1.2 → austin_python-2.2.0}/test/data/diff_a-b.austin +0 -0
- {austin_python-2.1.2 → austin_python-2.2.0}/test/data/diff_a.austin +0 -0
- {austin_python-2.1.2 → austin_python-2.2.0}/test/data/diff_b.austin +0 -0
- {austin_python-2.1.2 → austin_python-2.2.0}/test/data/mp.austin +0 -0
- {austin_python-2.1.2 → austin_python-2.2.0}/test/data/mp.mojo +0 -0
- {austin_python-2.1.2 → austin_python-2.2.0}/test/data/test.austin +0 -0
- {austin_python-2.1.2 → austin_python-2.2.0}/test/data/test.mojo +0 -0
- {austin_python-2.1.2 → austin_python-2.2.0}/test/format/__init__.py +0 -0
- {austin_python-2.1.2 → austin_python-2.2.0}/test/format/test_pprof.py +0 -0
- {austin_python-2.1.2 → austin_python-2.2.0}/test/stats/test_austin_file_reader.py +0 -0
- {austin_python-2.1.2 → austin_python-2.2.0}/test/stats/test_hierarchical_stats.py +0 -0
- {austin_python-2.1.2 → austin_python-2.2.0}/test/test_cli.py +0 -0
- {austin_python-2.1.2 → austin_python-2.2.0}/test/test_config.py +0 -0
- {austin_python-2.1.2 → austin_python-2.2.0}/test/test_threads.py +0 -0
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
[flake8]
|
|
2
2
|
select = ANN,B,B9,C,D,E,F,W,I
|
|
3
|
-
ignore = ANN101,ANN102,ANN401,B950,D100,D102,D103,D104,D107,E203,E501,I001,I005,W503,W606
|
|
3
|
+
ignore = ANN101,ANN102,ANN401,B905,B950,D100,D102,D103,D104,D107,E203,E501,I001,I005,W503,W606
|
|
4
4
|
exclude =
|
|
5
5
|
austin/format/pprof/profile_pb2.py
|
|
6
6
|
test/data/column.py
|
|
7
7
|
max-line-length = 88
|
|
8
8
|
docstring-convention = google
|
|
9
|
-
import-order-style = pep8
|
|
10
9
|
application-import-names = austin
|
|
11
10
|
per-file-ignores =
|
|
12
11
|
test/*:ANN,D
|
|
@@ -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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import typing as t
|
|
1
2
|
from functools import singledispatchmethod
|
|
2
3
|
from pathlib import Path
|
|
3
|
-
import typing as t
|
|
4
4
|
|
|
5
5
|
from austin.events import AustinEvent
|
|
6
6
|
from austin.events import AustinEventIterator
|
|
@@ -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:]
|
|
@@ -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)
|
|
@@ -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."""
|
|
@@ -322,6 +335,11 @@ class BaseMojoStreamReader(AustinEventIterator):
|
|
|
322
335
|
self._str_reader = str_reader()
|
|
323
336
|
next(self._str_reader)
|
|
324
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
|
+
|
|
325
343
|
# Austin events
|
|
326
344
|
self.metadata: t.Dict[str, str] = {}
|
|
327
345
|
self.samples: t.List[AustinSample] = []
|
|
@@ -381,28 +399,61 @@ class BaseMojoStreamReader(AustinEventIterator):
|
|
|
381
399
|
for metric_type, metric in self._running_sample.metrics.items()
|
|
382
400
|
}
|
|
383
401
|
),
|
|
384
|
-
frames=
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
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
|
|
392
413
|
)
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
else None,
|
|
414
|
+
if self._running_sample.frames
|
|
415
|
+
else None
|
|
416
|
+
),
|
|
397
417
|
gc=self._running_sample.gc,
|
|
398
418
|
idle=self._running_sample.idle,
|
|
399
419
|
)
|
|
400
420
|
)
|
|
401
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
|
+
|
|
402
428
|
self._running_sample = None
|
|
403
429
|
|
|
404
430
|
return sample
|
|
405
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
|
+
|
|
406
457
|
def get_stack(self, pid: int, iid: t.Optional[int], thread: str) -> MojoStack:
|
|
407
458
|
"""Parse a stack."""
|
|
408
459
|
if self._running_sample is not None:
|
|
@@ -611,6 +662,11 @@ class MojoStreamReader(BaseMojoStreamReader):
|
|
|
611
662
|
"""Parse string reference."""
|
|
612
663
|
return self.get_string_ref(self.read_int())
|
|
613
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
|
+
|
|
614
670
|
def parse_event(self) -> t.Optional[MojoEvent]:
|
|
615
671
|
"""Parse a single event."""
|
|
616
672
|
try:
|
|
@@ -670,7 +726,9 @@ class MojoStreamReader(BaseMojoStreamReader):
|
|
|
670
726
|
if self._running_sample is not None:
|
|
671
727
|
yield self._finalize_sample()
|
|
672
728
|
|
|
673
|
-
def hexdump(
|
|
729
|
+
def hexdump(
|
|
730
|
+
self, start: int, end: int, highlight: t.Set[int] = set() # noqa: B006
|
|
731
|
+
) -> None:
|
|
674
732
|
"""Print a hexdump of the MOJO file."""
|
|
675
733
|
self.mojo.seek(start)
|
|
676
734
|
data = self.mojo.read(end - start)
|
|
@@ -794,6 +852,11 @@ class AsyncMojoStreamReader(BaseMojoStreamReader):
|
|
|
794
852
|
"""Parse string reference."""
|
|
795
853
|
return self.get_string_ref(await self.read_int())
|
|
796
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
|
+
|
|
797
860
|
async def parse_event(self) -> t.Optional[MojoEvent]:
|
|
798
861
|
"""Parse a single event."""
|
|
799
862
|
try:
|
|
@@ -855,6 +918,8 @@ class AsyncMojoStreamReader(BaseMojoStreamReader):
|
|
|
855
918
|
|
|
856
919
|
|
|
857
920
|
class BaseMojoStreamWriter(abc.ABC):
|
|
921
|
+
"""Base class for MOJO stream writers."""
|
|
922
|
+
|
|
858
923
|
HEADER = b"MOJ\x03"
|
|
859
924
|
|
|
860
925
|
def __init__(self, mojo: t.Any) -> None:
|
|
@@ -904,11 +969,13 @@ class BaseMojoStreamWriter(abc.ABC):
|
|
|
904
969
|
return mojo_frame
|
|
905
970
|
|
|
906
971
|
@abc.abstractmethod
|
|
907
|
-
def write(self, event: AustinEvent) -> int: ...
|
|
972
|
+
def write(self, event: AustinEvent) -> int: ... # noqa: E704
|
|
908
973
|
|
|
909
974
|
|
|
910
975
|
class MojoStreamWriter(BaseMojoStreamWriter):
|
|
911
|
-
|
|
976
|
+
"""MOJO stream writer."""
|
|
977
|
+
|
|
978
|
+
def __init__(self, mojo: t.BinaryIO) -> None:
|
|
912
979
|
super().__init__(mojo)
|
|
913
980
|
|
|
914
981
|
mojo.write(self.HEADER)
|
|
@@ -939,7 +1006,7 @@ class MojoStreamWriter(BaseMojoStreamWriter):
|
|
|
939
1006
|
for frame in frames:
|
|
940
1007
|
size += self.mojo.write(MojoFrameReference(frame).to_bytes())
|
|
941
1008
|
|
|
942
|
-
if
|
|
1009
|
+
if event.gc:
|
|
943
1010
|
size += self.mojo.write(bytes([MojoEvents.GC]))
|
|
944
1011
|
|
|
945
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()
|
|
@@ -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
|
|
|
@@ -42,7 +42,6 @@ from austin.events import AustinSample
|
|
|
42
42
|
from austin.events import ProcessId
|
|
43
43
|
from austin.format.collapsed_stack import AustinEventCollapsedStackFormatter
|
|
44
44
|
|
|
45
|
-
|
|
46
45
|
ThreadInfo = namedtuple("ThreadInfo", ["thread", "iid"])
|
|
47
46
|
|
|
48
47
|
|
|
@@ -123,9 +122,11 @@ class FrameStats(HierarchicalStats):
|
|
|
123
122
|
if self.own:
|
|
124
123
|
yield (
|
|
125
124
|
[frame],
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
125
|
+
(
|
|
126
|
+
AustinMetrics(time=self.own)
|
|
127
|
+
if stats_type in {AustinStatsType.CPU, AustinStatsType.WALL}
|
|
128
|
+
else AustinMetrics(memory=self.own)
|
|
129
|
+
),
|
|
129
130
|
)
|
|
130
131
|
|
|
131
132
|
if self.children:
|
|
@@ -146,14 +147,15 @@ class ThreadStats(HierarchicalStats):
|
|
|
146
147
|
self, stats_type: AustinStatsType
|
|
147
148
|
) -> Generator[Tuple[ThreadInfo, List[AustinFrame], AustinMetrics], None, None]:
|
|
148
149
|
"""Collapse the hierarchical statistics."""
|
|
149
|
-
|
|
150
150
|
if self.own:
|
|
151
151
|
yield (
|
|
152
152
|
self.label,
|
|
153
153
|
[],
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
154
|
+
(
|
|
155
|
+
AustinMetrics(time=self.own)
|
|
156
|
+
if stats_type in {AustinStatsType.CPU, AustinStatsType.WALL}
|
|
157
|
+
else AustinMetrics(memory=self.own)
|
|
158
|
+
),
|
|
157
159
|
)
|
|
158
160
|
|
|
159
161
|
if self.children:
|
|
@@ -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
|
|
@@ -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)
|