austin-python 2.0.0__py3-none-any.whl → 2.1.1__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/events.py +2 -1
- austin/format/collapsed_stack.py +1 -1
- austin/format/mojo.py +175 -3
- austin/format/pprof/__main__.py +7 -4
- austin/format/speedscope.py +23 -15
- austin/stats.py +73 -54
- austin/tools/mojodbg.py +9 -3
- {austin_python-2.0.0.dist-info → austin_python-2.1.1.dist-info}/METADATA +1 -1
- {austin_python-2.0.0.dist-info → austin_python-2.1.1.dist-info}/RECORD +14 -14
- {austin_python-2.0.0.dist-info → austin_python-2.1.1.dist-info}/WHEEL +0 -0
- {austin_python-2.0.0.dist-info → austin_python-2.1.1.dist-info}/entry_points.txt +0 -0
- {austin_python-2.0.0.dist-info → austin_python-2.1.1.dist-info}/licenses/AUTHORS +0 -0
- {austin_python-2.0.0.dist-info → austin_python-2.1.1.dist-info}/licenses/COPYING +0 -0
- {austin_python-2.0.0.dist-info → austin_python-2.1.1.dist-info}/licenses/LICENSE.md +0 -0
austin/events.py
CHANGED
austin/format/collapsed_stack.py
CHANGED
|
@@ -48,7 +48,7 @@ def parse_frame(frame: str) -> AustinFrame:
|
|
|
48
48
|
filename, function, line = frame.rsplit(":", maxsplit=2)
|
|
49
49
|
except ValueError:
|
|
50
50
|
raise InvalidFrame(frame) from None
|
|
51
|
-
return AustinFrame(filename=filename, function=function, line=int(line))
|
|
51
|
+
return AustinFrame(filename=filename, function=function, line=int(line or 0))
|
|
52
52
|
|
|
53
53
|
|
|
54
54
|
def parse_collapsed_stack(sample: str) -> AustinSample:
|
austin/format/mojo.py
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import abc
|
|
1
2
|
import asyncio
|
|
3
|
+
import io
|
|
2
4
|
import typing as t
|
|
3
5
|
from dataclasses import dataclass
|
|
4
6
|
from dataclasses import field
|
|
7
|
+
from dataclasses import fields
|
|
5
8
|
from enum import Enum
|
|
6
9
|
|
|
7
10
|
from austin.events import AustinEvent
|
|
@@ -76,12 +79,35 @@ class MojoEventHandler:
|
|
|
76
79
|
...
|
|
77
80
|
|
|
78
81
|
|
|
82
|
+
@dataclass(frozen=True, eq=True)
|
|
79
83
|
class MojoEvent:
|
|
80
84
|
"""MOJO event."""
|
|
81
85
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
86
|
+
EVENT_ID: t.ClassVar = None
|
|
87
|
+
|
|
88
|
+
def ref(self) -> int:
|
|
89
|
+
return getattr(self, fields(self)[0].name)
|
|
90
|
+
|
|
91
|
+
def to_bytes(self) -> bytes:
|
|
92
|
+
buffer = bytearray([self.EVENT_ID])
|
|
93
|
+
for f in fields(self):
|
|
94
|
+
value = getattr(self, f.name)
|
|
95
|
+
field_type = (
|
|
96
|
+
f.type.__args__[0]
|
|
97
|
+
if isinstance(f.type, t._UnionGenericAlias)
|
|
98
|
+
else f.type
|
|
99
|
+
)
|
|
100
|
+
if field_type is str:
|
|
101
|
+
buffer += value.encode()
|
|
102
|
+
buffer += b"\x00"
|
|
103
|
+
elif field_type is int:
|
|
104
|
+
buffer += to_varint(value)
|
|
105
|
+
elif issubclass(field_type, MojoEvent):
|
|
106
|
+
buffer += to_varint(value.ref())
|
|
107
|
+
else:
|
|
108
|
+
msg = f"Invalid MOJO event field type {f.type}"
|
|
109
|
+
raise TypeError(msg)
|
|
110
|
+
return bytes(buffer)
|
|
85
111
|
|
|
86
112
|
|
|
87
113
|
class MojoMetricType(str, Enum):
|
|
@@ -98,11 +124,24 @@ class MojoMetric(MojoEvent):
|
|
|
98
124
|
metric_type: MojoMetricType
|
|
99
125
|
value: int
|
|
100
126
|
|
|
127
|
+
def to_bytes(self) -> bytes:
|
|
128
|
+
buffer = bytearray(
|
|
129
|
+
[
|
|
130
|
+
MojoEvents.METRIC_TIME
|
|
131
|
+
if self.metric_type is MojoMetricType.TIME
|
|
132
|
+
else MojoEvents.METRIC_MEMORY
|
|
133
|
+
]
|
|
134
|
+
)
|
|
135
|
+
buffer += to_varint(self.value)
|
|
136
|
+
return bytes(buffer)
|
|
137
|
+
|
|
101
138
|
|
|
102
139
|
@dataclass(frozen=True, eq=True)
|
|
103
140
|
class MojoString(MojoEvent):
|
|
104
141
|
"""MOJO string."""
|
|
105
142
|
|
|
143
|
+
EVENT_ID = MojoEvents.STRING
|
|
144
|
+
|
|
106
145
|
key: int
|
|
107
146
|
value: str
|
|
108
147
|
|
|
@@ -111,12 +150,16 @@ class MojoString(MojoEvent):
|
|
|
111
150
|
class MojoStringReference(MojoEvent):
|
|
112
151
|
"""MOJO string reference."""
|
|
113
152
|
|
|
153
|
+
EVENT_ID = MojoEvents.STRING_REF
|
|
154
|
+
|
|
114
155
|
string: MojoString
|
|
115
156
|
|
|
116
157
|
|
|
117
158
|
class MojoIdle(MojoEvent):
|
|
118
159
|
"""MOJO idle event."""
|
|
119
160
|
|
|
161
|
+
EVENT_ID = MojoEvents.IDLE
|
|
162
|
+
|
|
120
163
|
pass
|
|
121
164
|
|
|
122
165
|
|
|
@@ -124,6 +167,8 @@ class MojoIdle(MojoEvent):
|
|
|
124
167
|
class MojoMetadata(MojoEvent):
|
|
125
168
|
"""MOJO metadata."""
|
|
126
169
|
|
|
170
|
+
EVENT_ID = MojoEvents.METADATA
|
|
171
|
+
|
|
127
172
|
key: str
|
|
128
173
|
value: str
|
|
129
174
|
|
|
@@ -132,6 +177,8 @@ class MojoMetadata(MojoEvent):
|
|
|
132
177
|
class MojoStack(MojoEvent):
|
|
133
178
|
"""MOJO stack."""
|
|
134
179
|
|
|
180
|
+
EVENT_ID = MojoEvents.STACK
|
|
181
|
+
|
|
135
182
|
pid: int
|
|
136
183
|
iid: int
|
|
137
184
|
tid: str
|
|
@@ -141,6 +188,8 @@ class MojoStack(MojoEvent):
|
|
|
141
188
|
class MojoFrame(MojoEvent):
|
|
142
189
|
"""MOJO frame."""
|
|
143
190
|
|
|
191
|
+
EVENT_ID = MojoEvents.FRAME
|
|
192
|
+
|
|
144
193
|
key: int
|
|
145
194
|
filename: MojoString
|
|
146
195
|
scope: MojoString
|
|
@@ -154,6 +203,8 @@ class MojoFrame(MojoEvent):
|
|
|
154
203
|
class MojoKernelFrame(MojoEvent):
|
|
155
204
|
"""MOJO kernel frame."""
|
|
156
205
|
|
|
206
|
+
EVENT_ID = MojoEvents.FRAME_KERNEL
|
|
207
|
+
|
|
157
208
|
scope: str
|
|
158
209
|
|
|
159
210
|
|
|
@@ -161,6 +212,8 @@ class MojoKernelFrame(MojoEvent):
|
|
|
161
212
|
class MojoSpecialFrame(MojoEvent):
|
|
162
213
|
"""MOJO special frame."""
|
|
163
214
|
|
|
215
|
+
EVENT_ID = MojoEvents.FRAME_INVALID
|
|
216
|
+
|
|
164
217
|
label: str
|
|
165
218
|
|
|
166
219
|
|
|
@@ -168,6 +221,8 @@ class MojoSpecialFrame(MojoEvent):
|
|
|
168
221
|
class MojoFrameReference(MojoEvent):
|
|
169
222
|
"""MOJO frame reference."""
|
|
170
223
|
|
|
224
|
+
EVENT_ID = MojoEvents.FRAME_REF
|
|
225
|
+
|
|
171
226
|
frame: MojoFrame
|
|
172
227
|
|
|
173
228
|
|
|
@@ -778,3 +833,120 @@ class AsyncMojoStreamReader(BaseMojoStreamReader):
|
|
|
778
833
|
yield self.samples[-1]
|
|
779
834
|
if self._running_sample is not None:
|
|
780
835
|
yield self._finalize_sample()
|
|
836
|
+
|
|
837
|
+
|
|
838
|
+
class BaseMojoStreamWriter(abc.ABC):
|
|
839
|
+
HEADER = b"MOJ\x03"
|
|
840
|
+
|
|
841
|
+
def __init__(self, mojo: t.Any) -> None:
|
|
842
|
+
self.mojo = mojo
|
|
843
|
+
self._frames: t.Dict[AustinFrame, MojoFrame] = {}
|
|
844
|
+
self._strings: t.Dict[str, MojoString] = {
|
|
845
|
+
EMPTY.value: EMPTY,
|
|
846
|
+
UNKNOWN.value: UNKNOWN,
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
self._meta: t.Dict[str, str] = {}
|
|
850
|
+
|
|
851
|
+
self._mode: t.Optional[str] = None
|
|
852
|
+
self._gc = False
|
|
853
|
+
|
|
854
|
+
self._new_entries: t.List[MojoEvent] = []
|
|
855
|
+
|
|
856
|
+
def set_metadata(self, metadata: AustinMetadata) -> None:
|
|
857
|
+
self._meta[metadata.name] = metadata.value
|
|
858
|
+
if metadata.name == "gc" and metadata.value == "on":
|
|
859
|
+
self._gc = True
|
|
860
|
+
elif metadata.name == "mode":
|
|
861
|
+
self._mode = metadata.value
|
|
862
|
+
|
|
863
|
+
def resolve_string(self, value: str) -> MojoString:
|
|
864
|
+
try:
|
|
865
|
+
return self._strings[value]
|
|
866
|
+
except KeyError:
|
|
867
|
+
self._strings[value] = mojo_string = MojoString(len(self._strings), value)
|
|
868
|
+
self._new_entries.append(mojo_string)
|
|
869
|
+
return mojo_string
|
|
870
|
+
|
|
871
|
+
def resolve_frame(self, frame: AustinFrame) -> MojoFrame:
|
|
872
|
+
try:
|
|
873
|
+
return self._frames[frame]
|
|
874
|
+
except KeyError:
|
|
875
|
+
self._frames[frame] = mojo_frame = MojoFrame(
|
|
876
|
+
len(self._frames),
|
|
877
|
+
self.resolve_string(frame.filename),
|
|
878
|
+
self.resolve_string(frame.function),
|
|
879
|
+
frame.line,
|
|
880
|
+
frame.line_end or 0,
|
|
881
|
+
frame.column or 0,
|
|
882
|
+
frame.column_end or 0,
|
|
883
|
+
)
|
|
884
|
+
self._new_entries.append(mojo_frame)
|
|
885
|
+
return mojo_frame
|
|
886
|
+
|
|
887
|
+
@abc.abstractmethod
|
|
888
|
+
def write(self, event: AustinEvent) -> int: ...
|
|
889
|
+
|
|
890
|
+
|
|
891
|
+
class MojoStreamWriter(BaseMojoStreamWriter):
|
|
892
|
+
def __init__(self, mojo: io.BytesIO):
|
|
893
|
+
super().__init__(mojo)
|
|
894
|
+
|
|
895
|
+
mojo.write(self.HEADER)
|
|
896
|
+
|
|
897
|
+
def write(self, event: AustinEvent) -> int:
|
|
898
|
+
size = 0
|
|
899
|
+
|
|
900
|
+
if isinstance(event, AustinMetadata):
|
|
901
|
+
self.set_metadata(event)
|
|
902
|
+
size += self.mojo.write(
|
|
903
|
+
MojoMetadata(key=event.name, value=event.value).to_bytes()
|
|
904
|
+
)
|
|
905
|
+
|
|
906
|
+
elif isinstance(event, AustinSample):
|
|
907
|
+
frames = (
|
|
908
|
+
[self.resolve_frame(f) for f in event.frames] if event.frames else []
|
|
909
|
+
)
|
|
910
|
+
|
|
911
|
+
size += self.mojo.write(
|
|
912
|
+
MojoStack(
|
|
913
|
+
pid=event.pid, iid=event.iid or 0, tid=event.thread
|
|
914
|
+
).to_bytes()
|
|
915
|
+
)
|
|
916
|
+
|
|
917
|
+
while self._new_entries:
|
|
918
|
+
size += self.mojo.write(self._new_entries.pop(0).to_bytes())
|
|
919
|
+
|
|
920
|
+
for frame in frames:
|
|
921
|
+
size += self.mojo.write(MojoFrameReference(frame).to_bytes())
|
|
922
|
+
|
|
923
|
+
if self._gc:
|
|
924
|
+
size += self.mojo.write(bytes([MojoEvents.GC]))
|
|
925
|
+
|
|
926
|
+
if self._mode == "full":
|
|
927
|
+
if event.idle:
|
|
928
|
+
size += self.mojo.write(bytes([MojoEvents.IDLE]))
|
|
929
|
+
size += self.mojo.write(
|
|
930
|
+
MojoMetric(MojoMetricType.TIME, event.metrics.time or 0).to_bytes()
|
|
931
|
+
)
|
|
932
|
+
size += self.mojo.write(
|
|
933
|
+
MojoMetric(
|
|
934
|
+
MojoMetricType.MEMORY, event.metrics.memory or 0
|
|
935
|
+
).to_bytes()
|
|
936
|
+
)
|
|
937
|
+
elif self._mode == "memory":
|
|
938
|
+
size += self.mojo.write(
|
|
939
|
+
MojoMetric(
|
|
940
|
+
MojoMetricType.MEMORY, event.metrics.memory or 0
|
|
941
|
+
).to_bytes()
|
|
942
|
+
)
|
|
943
|
+
else:
|
|
944
|
+
size += self.mojo.write(
|
|
945
|
+
MojoMetric(MojoMetricType.TIME, event.metrics.time or 0).to_bytes()
|
|
946
|
+
)
|
|
947
|
+
|
|
948
|
+
else:
|
|
949
|
+
msg = f"Unhandled event type {type(event)}"
|
|
950
|
+
raise TypeError(msg)
|
|
951
|
+
|
|
952
|
+
return size
|
austin/format/pprof/__main__.py
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
23
23
|
|
|
24
24
|
from argparse import ArgumentParser
|
|
25
|
+
from pathlib import Path
|
|
25
26
|
|
|
26
27
|
from austin.events import AustinMetadata
|
|
27
28
|
from austin.events import AustinSample
|
|
@@ -41,19 +42,21 @@ def main() -> None:
|
|
|
41
42
|
|
|
42
43
|
arg_parser.add_argument(
|
|
43
44
|
"input",
|
|
44
|
-
type=
|
|
45
|
+
type=Path,
|
|
45
46
|
help="The input file containing Austin samples.",
|
|
46
47
|
)
|
|
47
48
|
arg_parser.add_argument(
|
|
48
|
-
"output",
|
|
49
|
+
"output",
|
|
50
|
+
type=Path,
|
|
51
|
+
help="The name of the output pprof file.",
|
|
49
52
|
)
|
|
50
53
|
|
|
51
|
-
arg_parser.add_argument("-V", "--version", action="version", version="0.2.
|
|
54
|
+
arg_parser.add_argument("-V", "--version", action="version", version="0.2.1")
|
|
52
55
|
|
|
53
56
|
args = arg_parser.parse_args()
|
|
54
57
|
|
|
55
58
|
try:
|
|
56
|
-
with
|
|
59
|
+
with args.input.open() as austin, AustinFileReader(austin) as fin:
|
|
57
60
|
pprof = None
|
|
58
61
|
for e in fin:
|
|
59
62
|
if isinstance(e, AustinMetadata) and e.name == "mode":
|
austin/format/speedscope.py
CHANGED
|
@@ -26,6 +26,7 @@ from dataclasses import asdict
|
|
|
26
26
|
from dataclasses import dataclass
|
|
27
27
|
from dataclasses import field
|
|
28
28
|
from enum import Enum
|
|
29
|
+
from pathlib import Path
|
|
29
30
|
from typing import Dict
|
|
30
31
|
from typing import List
|
|
31
32
|
from typing import Optional
|
|
@@ -33,13 +34,14 @@ from typing import TextIO
|
|
|
33
34
|
from typing import Union
|
|
34
35
|
|
|
35
36
|
from austin.events import AustinFrame
|
|
37
|
+
from austin.events import AustinMetadata
|
|
36
38
|
from austin.events import AustinSample
|
|
37
39
|
from austin.format import Mode
|
|
38
40
|
from austin.format.collapsed_stack import AustinFileReader
|
|
39
41
|
from austin.format.collapsed_stack import InvalidSample
|
|
40
42
|
|
|
41
43
|
|
|
42
|
-
__version__ = "0.3.
|
|
44
|
+
__version__ = "0.3.1"
|
|
43
45
|
|
|
44
46
|
SpeedscopeJson = Dict
|
|
45
47
|
SpeedscopeWeight = int
|
|
@@ -208,11 +210,11 @@ def main() -> None:
|
|
|
208
210
|
|
|
209
211
|
arg_parser.add_argument(
|
|
210
212
|
"input",
|
|
211
|
-
type=
|
|
213
|
+
type=Path,
|
|
212
214
|
help="The input file containing Austin samples in normal format.",
|
|
213
215
|
)
|
|
214
216
|
arg_parser.add_argument(
|
|
215
|
-
"output", type=
|
|
217
|
+
"output", type=Path, help="The name of the output Speedscope JSON file."
|
|
216
218
|
)
|
|
217
219
|
arg_parser.add_argument(
|
|
218
220
|
"--indent", type=int, help="Give a non-null value to prettify the JSON output."
|
|
@@ -223,24 +225,30 @@ def main() -> None:
|
|
|
223
225
|
args = arg_parser.parse_args()
|
|
224
226
|
|
|
225
227
|
try:
|
|
226
|
-
with
|
|
227
|
-
|
|
228
|
-
speedscope = Speedscope(os.path.basename(args.input), mode, args.indent)
|
|
228
|
+
with args.input.open() as austin, AustinFileReader(austin) as fin:
|
|
229
|
+
speedscope = None
|
|
229
230
|
for event in fin:
|
|
230
|
-
if
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
231
|
+
if isinstance(event, AustinMetadata):
|
|
232
|
+
if event.name == "mode":
|
|
233
|
+
speedscope = Speedscope(
|
|
234
|
+
os.path.basename(args.input), event.value, args.indent
|
|
235
|
+
)
|
|
236
|
+
elif isinstance(event, AustinSample):
|
|
237
|
+
if speedscope is not None:
|
|
238
|
+
speedscope.add_sample(event)
|
|
239
|
+
else:
|
|
240
|
+
raise ValueError("Invalid sample found in input file")
|
|
241
|
+
|
|
242
|
+
if speedscope is None:
|
|
243
|
+
raise RuntimeError("Invalid input file: no mode metadata found")
|
|
244
|
+
|
|
245
|
+
with args.output.open("w") as fout:
|
|
246
|
+
speedscope.dump(fout)
|
|
236
247
|
|
|
237
248
|
except FileNotFoundError:
|
|
238
249
|
print(f"No such input file: {args.input}")
|
|
239
250
|
exit(1)
|
|
240
251
|
|
|
241
|
-
with open(args.output, "w") as fout:
|
|
242
|
-
speedscope.dump(fout)
|
|
243
|
-
|
|
244
252
|
|
|
245
253
|
if __name__ == "__main__":
|
|
246
254
|
main()
|
austin/stats.py
CHANGED
|
@@ -28,13 +28,15 @@ from enum import Enum
|
|
|
28
28
|
from threading import Lock
|
|
29
29
|
from typing import Any
|
|
30
30
|
from typing import Dict
|
|
31
|
+
from typing import Generator
|
|
31
32
|
from typing import List
|
|
32
33
|
from typing import Optional
|
|
33
|
-
from typing import
|
|
34
|
+
from typing import Tuple
|
|
34
35
|
|
|
35
36
|
from austin.events import AustinEventIterator
|
|
36
37
|
from austin.events import AustinFrame
|
|
37
38
|
from austin.events import AustinMetadata
|
|
39
|
+
from austin.events import AustinMetrics
|
|
38
40
|
from austin.events import AustinSample
|
|
39
41
|
from austin.events import ProcessId
|
|
40
42
|
from austin.events import ThreadName
|
|
@@ -44,6 +46,15 @@ from austin.format.collapsed_stack import AustinEventCollapsedStackFormatter
|
|
|
44
46
|
COLLAPSED_STACK_FORMATTER = AustinEventCollapsedStackFormatter()
|
|
45
47
|
|
|
46
48
|
|
|
49
|
+
class AustinStatsType(Enum):
|
|
50
|
+
"""Austin stats type."""
|
|
51
|
+
|
|
52
|
+
WALL = "wall"
|
|
53
|
+
CPU = "cpu"
|
|
54
|
+
MEMORY_ALLOC = "memory_alloc"
|
|
55
|
+
MEMORY_DEALLOC = "memory_dealloc"
|
|
56
|
+
|
|
57
|
+
|
|
47
58
|
@dataclass
|
|
48
59
|
class HierarchicalStats:
|
|
49
60
|
"""Base dataclass for representing hierarchical statistics.
|
|
@@ -87,7 +98,7 @@ class HierarchicalStats:
|
|
|
87
98
|
"""Get a child from the children collection."""
|
|
88
99
|
return self.children[label]
|
|
89
100
|
|
|
90
|
-
def collapse(self,
|
|
101
|
+
def collapse(self, stats_type: AustinStatsType) -> Generator[Any, None, None]:
|
|
91
102
|
"""Collapse the hierarchical statistics."""
|
|
92
103
|
raise NotImplementedError()
|
|
93
104
|
|
|
@@ -100,19 +111,26 @@ class FrameStats(HierarchicalStats):
|
|
|
100
111
|
height: int = 0
|
|
101
112
|
children: Dict[AustinFrame, "FrameStats"] = field(default_factory=dict) # type: ignore[assignment]
|
|
102
113
|
|
|
103
|
-
def collapse(
|
|
114
|
+
def collapse(
|
|
115
|
+
self, stats_type: AustinStatsType
|
|
116
|
+
) -> Generator[Tuple[List[AustinFrame], AustinMetrics], None, None]:
|
|
104
117
|
"""Collapse the hierarchical statistics."""
|
|
105
|
-
frame =
|
|
118
|
+
frame = self.label
|
|
106
119
|
|
|
107
|
-
if
|
|
108
|
-
|
|
120
|
+
if self.own:
|
|
121
|
+
yield (
|
|
122
|
+
[frame],
|
|
123
|
+
AustinMetrics(time=self.own)
|
|
124
|
+
if stats_type in {AustinStatsType.CPU, AustinStatsType.WALL}
|
|
125
|
+
else AustinMetrics(memory=self.own),
|
|
126
|
+
)
|
|
109
127
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
128
|
+
if self.children:
|
|
129
|
+
yield from (
|
|
130
|
+
([frame, *rest], metric)
|
|
131
|
+
for child in self.children.values()
|
|
132
|
+
for rest, metric in child.collapse(stats_type)
|
|
133
|
+
)
|
|
116
134
|
|
|
117
135
|
|
|
118
136
|
class ThreadStats(HierarchicalStats):
|
|
@@ -121,16 +139,26 @@ class ThreadStats(HierarchicalStats):
|
|
|
121
139
|
label: ThreadName
|
|
122
140
|
children: Dict[AustinFrame, FrameStats] = field(default_factory=dict) # type: ignore[assignment]
|
|
123
141
|
|
|
124
|
-
def collapse(
|
|
142
|
+
def collapse(
|
|
143
|
+
self, stats_type: AustinStatsType
|
|
144
|
+
) -> Generator[Tuple[ThreadName, List[AustinFrame], AustinMetrics], None, None]:
|
|
125
145
|
"""Collapse the hierarchical statistics."""
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
146
|
+
|
|
147
|
+
if self.own:
|
|
148
|
+
yield (
|
|
149
|
+
self.label,
|
|
150
|
+
[],
|
|
151
|
+
AustinMetrics(time=self.own)
|
|
152
|
+
if stats_type in {AustinStatsType.CPU, AustinStatsType.WALL}
|
|
153
|
+
else AustinMetrics(memory=self.own),
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
if self.children:
|
|
157
|
+
yield from (
|
|
158
|
+
(self.label, frames, metric)
|
|
159
|
+
for stats in self.children.values()
|
|
160
|
+
for frames, metric in stats.collapse(stats_type)
|
|
161
|
+
)
|
|
134
162
|
|
|
135
163
|
|
|
136
164
|
@dataclass
|
|
@@ -140,13 +168,21 @@ class ProcessStats:
|
|
|
140
168
|
pid: ProcessId
|
|
141
169
|
threads: Dict[ThreadName, ThreadStats] = field(default_factory=dict)
|
|
142
170
|
|
|
143
|
-
def collapse(
|
|
171
|
+
def collapse(
|
|
172
|
+
self, stats_type: AustinStatsType
|
|
173
|
+
) -> Generator[AustinSample, None, None]:
|
|
144
174
|
"""Collapse the hierarchical statistics."""
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
175
|
+
yield from (
|
|
176
|
+
AustinSample(
|
|
177
|
+
pid=self.pid,
|
|
178
|
+
iid=thread_name.iid,
|
|
179
|
+
thread=thread_name.thread,
|
|
180
|
+
frames=tuple(frames),
|
|
181
|
+
metrics=metric,
|
|
182
|
+
)
|
|
183
|
+
for thread in self.threads.values()
|
|
184
|
+
for thread_name, frames, metric in thread.collapse(stats_type)
|
|
185
|
+
)
|
|
150
186
|
|
|
151
187
|
def get_thread(self, thread_name: ThreadName) -> Optional[ThreadStats]:
|
|
152
188
|
"""Get thread statistics from this process by name.
|
|
@@ -157,15 +193,6 @@ class ProcessStats:
|
|
|
157
193
|
return self.threads.get(thread_name)
|
|
158
194
|
|
|
159
195
|
|
|
160
|
-
class AustinStatsType(Enum):
|
|
161
|
-
"""Austin stats type."""
|
|
162
|
-
|
|
163
|
-
WALL = "wall"
|
|
164
|
-
CPU = "cpu"
|
|
165
|
-
MEMORY_ALLOC = "memory_alloc"
|
|
166
|
-
MEMORY_DEALLOC = "memory_dealloc"
|
|
167
|
-
|
|
168
|
-
|
|
169
196
|
@dataclass
|
|
170
197
|
class AustinStats:
|
|
171
198
|
"""Austin statistics.
|
|
@@ -190,19 +217,6 @@ class AustinStats:
|
|
|
190
217
|
|
|
191
218
|
return copy
|
|
192
219
|
|
|
193
|
-
def dump(self, stream: TextIO) -> None:
|
|
194
|
-
"""Dump the statistics to the given text stream."""
|
|
195
|
-
with self._lock:
|
|
196
|
-
stream.write(f"# mode: {self.stats_type.value.partition('_')[0]}\n\n")
|
|
197
|
-
for _, process in self.processes.items():
|
|
198
|
-
samples = process.collapse()
|
|
199
|
-
|
|
200
|
-
if all(sample.endswith(" 0 0") for sample in samples):
|
|
201
|
-
samples = [sample[:-4] for sample in samples]
|
|
202
|
-
|
|
203
|
-
for sample in samples:
|
|
204
|
-
stream.write(sample + "\n")
|
|
205
|
-
|
|
206
220
|
def get_process(self, pid: ProcessId) -> ProcessStats:
|
|
207
221
|
"""Get process statistics for the given PID."""
|
|
208
222
|
return self.processes[pid]
|
|
@@ -272,7 +286,8 @@ class AustinStats:
|
|
|
272
286
|
return
|
|
273
287
|
|
|
274
288
|
pid = sample.pid
|
|
275
|
-
|
|
289
|
+
thread_name = ThreadName(sample.thread, sample.iid or 0)
|
|
290
|
+
thread_stats = ThreadStats(thread_name, own=0, total=metric)
|
|
276
291
|
|
|
277
292
|
# Convert the list of frames into a nested FrameStats instance
|
|
278
293
|
stats: HierarchicalStats = thread_stats
|
|
@@ -285,12 +300,16 @@ class AustinStats:
|
|
|
285
300
|
|
|
286
301
|
with self._lock:
|
|
287
302
|
if pid not in self.processes:
|
|
288
|
-
self.processes[pid] = ProcessStats(pid, {
|
|
303
|
+
self.processes[pid] = ProcessStats(pid, {thread_name: thread_stats})
|
|
289
304
|
return
|
|
290
305
|
|
|
291
306
|
process = self.processes[pid]
|
|
292
|
-
if
|
|
293
|
-
process.threads[
|
|
307
|
+
if thread_name not in process.threads:
|
|
308
|
+
process.threads[thread_name] = thread_stats
|
|
294
309
|
return
|
|
295
310
|
|
|
296
|
-
process.threads[
|
|
311
|
+
process.threads[thread_name] << thread_stats
|
|
312
|
+
|
|
313
|
+
def flatten(self) -> Generator[AustinSample, None, None]:
|
|
314
|
+
for process in self.processes.values():
|
|
315
|
+
yield from process.collapse(self.stats_type)
|
austin/tools/mojodbg.py
CHANGED
|
@@ -33,12 +33,13 @@ from austin.format.mojo import MojoStreamReader
|
|
|
33
33
|
__version__ = "0.1.0"
|
|
34
34
|
|
|
35
35
|
|
|
36
|
-
def mojodbg(
|
|
37
|
-
mojo = MojoStreamReader(args.input.expanduser().open("rb"))
|
|
36
|
+
def mojodbg(mojo: MojoStreamReader, echo: bool = False) -> None:
|
|
38
37
|
offset = 4
|
|
39
38
|
last_event_data: t.Tuple[t.Optional[MojoEvent], int] = (None, 0)
|
|
40
39
|
try:
|
|
41
40
|
for e in mojo.parse():
|
|
41
|
+
if echo:
|
|
42
|
+
print(e)
|
|
42
43
|
last_event_data = (e, offset)
|
|
43
44
|
offset = mojo._offset + 1
|
|
44
45
|
except Exception as exc:
|
|
@@ -63,10 +64,15 @@ def main() -> None:
|
|
|
63
64
|
argp = ArgumentParser(prog="mojodbg", description="Debug MOJO files.")
|
|
64
65
|
|
|
65
66
|
argp.add_argument("input", type=Path, help="The MOJO file to debug.")
|
|
67
|
+
argp.add_argument(
|
|
68
|
+
"-e", "echo", action="store_true", help="Echo all the MOJO events"
|
|
69
|
+
)
|
|
66
70
|
argp.add_argument("-V", "--version", action="version", version=__version__)
|
|
67
71
|
|
|
72
|
+
args = argp.parse_args()
|
|
73
|
+
|
|
68
74
|
try:
|
|
69
|
-
mojodbg(args
|
|
75
|
+
mojodbg(MojoStreamReader(args.input.expanduser().open("rb")), args.echo)
|
|
70
76
|
except FileNotFoundError:
|
|
71
77
|
print(f"No such input file: {args.input}")
|
|
72
78
|
exit(2)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: austin-python
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.1.1
|
|
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
|
|
@@ -4,26 +4,26 @@ austin/base.py,sha256=wAYVZQi3hcgJA9cVg3mfc3f3E0qwFXmeX7UX6qgn2Pk,7554
|
|
|
4
4
|
austin/cli.py,sha256=--PKm2I4NBBSSSBV5l-YC0mcmLjrvb1FHzxgY4dkby4,7826
|
|
5
5
|
austin/config.py,sha256=PEwdG2Cy8ay1SrFr2N6YfQSZwseYBA1BA5dMfocKR3A,2091
|
|
6
6
|
austin/errors.py,sha256=lCDSTANW_95Az_eDXbn__JRBllB9yw7tGnFo1-tw09Q,1046
|
|
7
|
-
austin/events.py,sha256=
|
|
7
|
+
austin/events.py,sha256=39BHP-pUHotZZxJ-MbECFu5_QOAnRJP-_hFEdYPYwKY,2235
|
|
8
8
|
austin/simple.py,sha256=iTmJGAjSBeUv82SgdMTwdNlAQwuK33RyU8FkBCE8rRg,5579
|
|
9
|
-
austin/stats.py,sha256=
|
|
9
|
+
austin/stats.py,sha256=8cpuq3cyZUpGkm2VZS3j1Zpz-Yhr3i9Wg6bR-sGwWJM,10933
|
|
10
10
|
austin/threads.py,sha256=AvcoOkr3FeFAGBcqgXukNIt9xOZUipv7pXMqPIL_Qts,3649
|
|
11
11
|
austin/format/__init__.py,sha256=ZtBJCLpTiZaLFwCN1UbMXLQvYjOSpwmtyGwqgK8yruQ,405
|
|
12
|
-
austin/format/collapsed_stack.py,sha256=
|
|
12
|
+
austin/format/collapsed_stack.py,sha256=61iEu3oFl9e3vUNHvhpAQzgJndxtE7WcbRwvRLO03S4,7246
|
|
13
13
|
austin/format/compress.py,sha256=YEceebWzVB7eSCntnJ89_rIcbzBskvZGy86D5QgZli0,3741
|
|
14
|
-
austin/format/mojo.py,sha256=
|
|
15
|
-
austin/format/speedscope.py,sha256=
|
|
14
|
+
austin/format/mojo.py,sha256=IlnFsH9gGofENWBMdbpAc0BrutNGvetk5JLLJeCHKuo,28294
|
|
15
|
+
austin/format/speedscope.py,sha256=RvlFugm0S3ATMf9lmzH8jrX4oBt8PJ0j9EsuXWayOQc,8381
|
|
16
16
|
austin/format/pprof/__init__.py,sha256=DvY1CDnPqBc-GUv9fidtMplshIIXj_HWZ3SlWOJoLxc,6805
|
|
17
|
-
austin/format/pprof/__main__.py,sha256=
|
|
17
|
+
austin/format/pprof/__main__.py,sha256=pzWoSmlFrjfmAAq5I4SIIDmxavdLStCnlw3NlBxgyDo,2652
|
|
18
18
|
austin/format/pprof/profile.proto,sha256=fPfBgzXIEmFdrrwgTQWs-9GwKjt-TShRmJq-eaxDx88,8840
|
|
19
19
|
austin/format/pprof/profile_pb2.py,sha256=i_uM8lDExR95yz7qnEVikhpkpNvetKaYzoiH7AbMAt8,28214
|
|
20
20
|
austin/tools/diff.py,sha256=KnY9kD5mmSKALCNxnSe-dSObC4LgkoxLGnoTiklKa5M,4821
|
|
21
|
-
austin/tools/mojodbg.py,sha256=
|
|
21
|
+
austin/tools/mojodbg.py,sha256=BGdngj-r1c-GAQDrs_oJ_x4QZxuL4t_OQ194fvJJm7A,2817
|
|
22
22
|
austin/tools/resolve.py,sha256=pEOBiP8FNkaJqDTj3fOK8dx5-EocroqZDXLL3CzCWLY,7677
|
|
23
|
-
austin_python-2.
|
|
24
|
-
austin_python-2.
|
|
25
|
-
austin_python-2.
|
|
26
|
-
austin_python-2.
|
|
27
|
-
austin_python-2.
|
|
28
|
-
austin_python-2.
|
|
29
|
-
austin_python-2.
|
|
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
|
|
File without changes
|
|
File without changes
|