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 CHANGED
@@ -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
 
austin/events.py CHANGED
@@ -1,10 +1,8 @@
1
1
  import typing as t
2
- from collections import namedtuple
3
2
  from dataclasses import dataclass
4
3
 
5
-
6
- ThreadName = namedtuple("ThreadName", ["thread", "iid"])
7
4
  ProcessId = int
5
+ ThreadName = str
8
6
  InterpreterId = int
9
7
  MicroSeconds = int
10
8
  Bytes = int
@@ -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:]
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
- 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)
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.__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."""
@@ -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
- buffer[i] = b
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=tuple(
372
- AustinFrame(
373
- filename=mf.filename.value,
374
- function=mf.scope.value,
375
- line=mf.line,
376
- line_end=mf.line_end,
377
- column=mf.column,
378
- 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
379
413
  )
380
- for mf in self._running_sample.frames
381
- )
382
- if self._running_sample.frames
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(pid=pid, iid=iid, thread=thread)
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(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:
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
- def __init__(self, mojo: io.BytesIO):
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 self._gc:
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()
@@ -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
 
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
- AustinMetrics(time=self.own)
124
- if stats_type in {AustinStatsType.CPU, AustinStatsType.WALL}
125
- 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
+ ),
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: ThreadName
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[ThreadName, List[AustinFrame], AustinMetrics], None, None]:
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
- AustinMetrics(time=self.own)
152
- if stats_type in {AustinStatsType.CPU, AustinStatsType.WALL}
153
- 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
+ ),
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[ThreadName, ThreadStats] = field(default_factory=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=thread_name.iid,
179
- thread=thread_name.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 thread_name, frames, metric in thread.collapse(stats_type)
189
+ for thread_info, frames, metric in thread.collapse(stats_type)
185
190
  )
186
191
 
187
- def get_thread(self, thread_name: ThreadName) -> Optional[ThreadStats]:
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(thread_name)
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
- thread_name = ThreadName(sample.thread, sample.iid or 0)
290
- thread_stats = ThreadStats(thread_name, own=0, total=metric)
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, {thread_name: thread_stats})
308
+ self.processes[pid] = ProcessStats(pid, {thread_info: thread_stats})
304
309
  return
305
310
 
306
311
  process = self.processes[pid]
307
- if thread_name not in process.threads:
308
- process.threads[thread_name] = thread_stats
312
+ if thread_info not in process.threads:
313
+ process.threads[thread_info] = thread_stats
309
314
  return
310
315
 
311
- process.threads[thread_name] << thread_stats
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[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
austin/tools/mojodbg.py CHANGED
@@ -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
 
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
- 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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: austin-python
3
- Version: 2.1.1
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
@@ -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,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.27.0
2
+ Generator: hatchling 1.29.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -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,,