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.
Files changed (74) hide show
  1. {austin_python-2.1.2 → austin_python-2.2.0}/.flake8 +1 -2
  2. {austin_python-2.1.2 → austin_python-2.2.0}/PKG-INFO +4 -4
  3. {austin_python-2.1.2 → austin_python-2.2.0}/README.md +1 -1
  4. {austin_python-2.1.2 → austin_python-2.2.0}/austin/base.py +0 -1
  5. {austin_python-2.1.2 → austin_python-2.2.0}/austin/events.py +0 -1
  6. {austin_python-2.1.2 → austin_python-2.2.0}/austin/format/collapsed_stack.py +4 -5
  7. {austin_python-2.1.2 → austin_python-2.2.0}/austin/format/compress.py +7 -5
  8. {austin_python-2.1.2 → austin_python-2.2.0}/austin/format/mojo.py +90 -23
  9. austin_python-2.2.0/austin/format/mojo_compress.py +122 -0
  10. {austin_python-2.1.2 → austin_python-2.2.0}/austin/format/pprof/__init__.py +0 -1
  11. {austin_python-2.1.2 → austin_python-2.2.0}/austin/format/speedscope.py +0 -2
  12. {austin_python-2.1.2 → austin_python-2.2.0}/austin/stats.py +10 -8
  13. {austin_python-2.1.2 → austin_python-2.2.0}/austin/tools/diff.py +6 -4
  14. {austin_python-2.1.2 → austin_python-2.2.0}/austin/tools/mojodbg.py +0 -1
  15. {austin_python-2.1.2 → austin_python-2.2.0}/austin/tools/resolve.py +8 -5
  16. {austin_python-2.1.2 → austin_python-2.2.0}/pyproject.toml +31 -12
  17. {austin_python-2.1.2 → austin_python-2.2.0}/test/conftest.py +0 -1
  18. austin_python-2.2.0/test/data/repeat.austin +1717 -0
  19. austin_python-2.2.0/test/data/repeat.mojo +0 -0
  20. {austin_python-2.1.2 → austin_python-2.2.0}/test/format/test_compress.py +33 -0
  21. {austin_python-2.1.2 → austin_python-2.2.0}/test/format/test_mojo.py +1 -4
  22. austin_python-2.2.0/test/format/test_mojo_compress.py +303 -0
  23. {austin_python-2.1.2 → austin_python-2.2.0}/test/format/test_speedscope.py +0 -1
  24. {austin_python-2.1.2 → austin_python-2.2.0}/test/stats/test_austin_stats.py +1 -2
  25. {austin_python-2.1.2 → austin_python-2.2.0}/test/test_aio.py +23 -1
  26. austin_python-2.2.0/test/test_base.py +39 -0
  27. {austin_python-2.1.2 → austin_python-2.2.0}/test/test_semver.py +4 -0
  28. {austin_python-2.1.2 → austin_python-2.2.0}/test/test_simple.py +12 -0
  29. austin_python-2.2.0/test/tools/test_diff.py +13 -0
  30. {austin_python-2.1.2 → austin_python-2.2.0}/test/tools/test_resolve.py +0 -1
  31. austin_python-2.1.2/test/tools/test_diff.py +0 -11
  32. {austin_python-2.1.2 → austin_python-2.2.0}/.gitignore +0 -0
  33. {austin_python-2.1.2 → austin_python-2.2.0}/.readthedocs.yml +0 -0
  34. {austin_python-2.1.2 → austin_python-2.2.0}/AUTHORS +0 -0
  35. {austin_python-2.1.2 → austin_python-2.2.0}/CODE-OF-CONDUCT.md +0 -0
  36. {austin_python-2.1.2 → austin_python-2.2.0}/CONTRIBUTING.md +0 -0
  37. {austin_python-2.1.2 → austin_python-2.2.0}/COPYING +0 -0
  38. {austin_python-2.1.2 → austin_python-2.2.0}/ISSUE_TEMPLATE.md +0 -0
  39. {austin_python-2.1.2 → austin_python-2.2.0}/LICENSE.md +0 -0
  40. {austin_python-2.1.2 → austin_python-2.2.0}/MANIFEST.in +0 -0
  41. {austin_python-2.1.2 → austin_python-2.2.0}/PULL_REQUEST_TEMPLATE.md +0 -0
  42. {austin_python-2.1.2 → austin_python-2.2.0}/austin/__init__.py +0 -0
  43. {austin_python-2.1.2 → austin_python-2.2.0}/austin/aio.py +0 -0
  44. {austin_python-2.1.2 → austin_python-2.2.0}/austin/cli.py +0 -0
  45. {austin_python-2.1.2 → austin_python-2.2.0}/austin/config.py +0 -0
  46. {austin_python-2.1.2 → austin_python-2.2.0}/austin/errors.py +0 -0
  47. {austin_python-2.1.2 → austin_python-2.2.0}/austin/format/__init__.py +0 -0
  48. {austin_python-2.1.2 → austin_python-2.2.0}/austin/format/pprof/__main__.py +0 -0
  49. {austin_python-2.1.2 → austin_python-2.2.0}/austin/format/pprof/profile.proto +0 -0
  50. {austin_python-2.1.2 → austin_python-2.2.0}/austin/format/pprof/profile_pb2.py +0 -0
  51. {austin_python-2.1.2 → austin_python-2.2.0}/austin/simple.py +0 -0
  52. {austin_python-2.1.2 → austin_python-2.2.0}/austin/threads.py +0 -0
  53. {austin_python-2.1.2 → austin_python-2.2.0}/test/__init__.py +0 -0
  54. {austin_python-2.1.2 → austin_python-2.2.0}/test/data/austin.json +0 -0
  55. {austin_python-2.1.2 → austin_python-2.2.0}/test/data/austin.out +0 -0
  56. {austin_python-2.1.2 → austin_python-2.2.0}/test/data/austin.pprof +0 -0
  57. {austin_python-2.1.2 → austin_python-2.2.0}/test/data/austinp.mojo +0 -0
  58. {austin_python-2.1.2 → austin_python-2.2.0}/test/data/austinp.resolved.mojo +0 -0
  59. {austin_python-2.1.2 → austin_python-2.2.0}/test/data/column.mojo +0 -0
  60. {austin_python-2.1.2 → austin_python-2.2.0}/test/data/column.py +0 -0
  61. {austin_python-2.1.2 → austin_python-2.2.0}/test/data/diff_a-b.austin +0 -0
  62. {austin_python-2.1.2 → austin_python-2.2.0}/test/data/diff_a.austin +0 -0
  63. {austin_python-2.1.2 → austin_python-2.2.0}/test/data/diff_b.austin +0 -0
  64. {austin_python-2.1.2 → austin_python-2.2.0}/test/data/mp.austin +0 -0
  65. {austin_python-2.1.2 → austin_python-2.2.0}/test/data/mp.mojo +0 -0
  66. {austin_python-2.1.2 → austin_python-2.2.0}/test/data/test.austin +0 -0
  67. {austin_python-2.1.2 → austin_python-2.2.0}/test/data/test.mojo +0 -0
  68. {austin_python-2.1.2 → austin_python-2.2.0}/test/format/__init__.py +0 -0
  69. {austin_python-2.1.2 → austin_python-2.2.0}/test/format/test_pprof.py +0 -0
  70. {austin_python-2.1.2 → austin_python-2.2.0}/test/stats/test_austin_file_reader.py +0 -0
  71. {austin_python-2.1.2 → austin_python-2.2.0}/test/stats/test_hierarchical_stats.py +0 -0
  72. {austin_python-2.1.2 → austin_python-2.2.0}/test/test_cli.py +0 -0
  73. {austin_python-2.1.2 → austin_python-2.2.0}/test/test_config.py +0 -0
  74. {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.1.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
- Requires-Python: >=3.9
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.13.
190
+ Python 3.9-3.14.
191
191
 
192
192
 
193
193
  # Documentation
@@ -159,7 +159,7 @@ raw samples by aggregation.
159
159
  # Compatibility
160
160
 
161
161
  The latest `austin-python` package is tested on Linux, macOS and Windows with
162
- Python 3.9-3.13.
162
+ Python 3.9-3.14.
163
163
 
164
164
 
165
165
  # Documentation
@@ -43,7 +43,6 @@ from austin.errors import AustinError
43
43
  from austin.events import AustinMetadata
44
44
  from austin.events import AustinSample
45
45
 
46
-
47
46
  SemVer = Tuple[int, int, int]
48
47
 
49
48
 
@@ -1,7 +1,6 @@
1
1
  import typing as t
2
2
  from dataclasses import dataclass
3
3
 
4
-
5
4
  ProcessId = int
6
5
  ThreadName = str
7
6
  InterpreterId = int
@@ -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 '{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 '{sample}'") from None
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 '{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
- AustinMetrics(memory=metrics)
66
- if is_memory
67
- else AustinMetrics(time=metrics),
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
- AustinFileReader(args.input) as fin,
113
+ open(args.input) as _fin,
112
114
  open(args.output or args.input, "w") as fout,
113
115
  ):
114
- compress(fin, fout, args.counts)
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.__args__[0]
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
- MojoEvents.METRIC_TIME
131
- if self.metric_type is MojoMetricType.TIME
132
- else MojoEvents.METRIC_MEMORY
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=tuple(
385
- AustinFrame(
386
- filename=mf.filename.value,
387
- function=mf.scope.value,
388
- line=mf.line,
389
- line_end=mf.line_end,
390
- column=mf.column,
391
- column_end=mf.column_end,
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
- for mf in self._running_sample.frames
394
- )
395
- if self._running_sample.frames
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(self, start: int, end: int, highlight: t.Set[int] = set()) -> None: # noqa: B006
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
- def __init__(self, mojo: io.BytesIO):
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 self._gc:
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()
@@ -33,7 +33,6 @@ from austin.format import Mode
33
33
  from austin.format.pprof.profile_pb2 import Profile
34
34
  from austin.format.pprof.profile_pb2 import Sample
35
35
 
36
-
37
36
  PROCESS_ID_LABEL = "Process ID"
38
37
  THREAD_ID_LABEL = "Thread ID"
39
38
  INTERPRETER_ID_LABEL = "Interpreter ID"
@@ -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
- AustinMetrics(time=self.own)
127
- if stats_type in {AustinStatsType.CPU, AustinStatsType.WALL}
128
- else AustinMetrics(memory=self.own),
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
- AustinMetrics(time=self.own)
155
- if stats_type in {AustinStatsType.CPU, AustinStatsType.WALL}
156
- else AustinMetrics(memory=self.own),
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[Frame], b: List[Frame]) -> float:
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, Metric]]:
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 = Sample.parse(_)
100
+ sample = parse_collapsed_stack(_)
99
101
  x.append((sample.frames, sample.metrics))
100
102
  except InvalidSample:
101
103
  continue
@@ -29,7 +29,6 @@ from traceback import print_exc
29
29
  from austin.format.mojo import MojoEvent
30
30
  from austin.format.mojo import MojoStreamReader
31
31
 
32
-
33
32
  __version__ = "0.1.0"
34
33
 
35
34
 
@@ -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
- event.raw = event.raw.replace(
180
- event.value.encode(), new_value.encode()
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
- event.raw = (
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)