pykdebugparser 1.5.0__py2.py3-none-any.whl → 1.6.0__py2.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.
- pykdebugparser/highlight.py +48 -116
- pykdebugparser/pykdebugparser.py +64 -3
- {pykdebugparser-1.5.0.dist-info → pykdebugparser-1.6.0.dist-info}/METADATA +1 -2
- {pykdebugparser-1.5.0.dist-info → pykdebugparser-1.6.0.dist-info}/RECORD +8 -8
- {pykdebugparser-1.5.0.dist-info → pykdebugparser-1.6.0.dist-info}/WHEEL +0 -0
- {pykdebugparser-1.5.0.dist-info → pykdebugparser-1.6.0.dist-info}/entry_points.txt +0 -0
- {pykdebugparser-1.5.0.dist-info → pykdebugparser-1.6.0.dist-info}/licenses/LICENSE +0 -0
- {pykdebugparser-1.5.0.dist-info → pykdebugparser-1.6.0.dist-info}/top_level.txt +0 -0
pykdebugparser/highlight.py
CHANGED
|
@@ -1,134 +1,66 @@
|
|
|
1
|
-
"""Syntax highlighting of trace lines, as
|
|
1
|
+
"""Syntax highlighting of trace lines, such as `open("/etc/hosts", O_RDONLY), fd: 3`.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
Lines are colored with the terminal's 16 basic colors, so they follow its theme:
|
|
4
|
+
|
|
5
|
+
- the call or event name at the start, bold;
|
|
6
|
+
- strings (paths, symbols, ...), green;
|
|
7
|
+
- numbers, cyan;
|
|
8
|
+
- constants (flags, enums, signals: `O_RDONLY`, `KERN_SUCCESS`, `SIGKILL`), yellow;
|
|
9
|
+
- an error (`errno: ENOENT(2)`, a `KERN_*` failure), red;
|
|
10
|
+
- the labels of results and fields (`fd:`, `count:`), dim.
|
|
11
|
+
|
|
12
|
+
A trace is mostly the same calls with the same arguments, so the most recent lines are cached.
|
|
8
13
|
"""
|
|
9
14
|
import re
|
|
10
15
|
from functools import lru_cache
|
|
11
16
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
_RESET = '\x1b[0m'
|
|
18
|
+
_BOLD = '\x1b[1m'
|
|
19
|
+
_DIM = '\x1b[2m'
|
|
20
|
+
_RED = '\x1b[31m'
|
|
21
|
+
_GREEN = '\x1b[32m'
|
|
22
|
+
_YELLOW = '\x1b[33m'
|
|
23
|
+
_CYAN = '\x1b[36m'
|
|
19
24
|
|
|
20
|
-
# pygments.lexers.c_cpp.CFamilyLexer's 'keywords', 'types' and builtins, the names it does not lex as `Name`.
|
|
21
|
-
_KEYWORDS = {
|
|
22
|
-
'asm', 'auto', 'break', 'const', 'constexpr', 'continue', 'countof', 'default', 'defer', 'do', 'else', 'enum',
|
|
23
|
-
'extern', 'for', 'goto', 'if', 'register', 'restricted', 'return', 'sizeof', 'static', 'switch', 'typedef',
|
|
24
|
-
'typeof', 'typeof_unqual', 'volatile', 'while', 'thread_local', 'alignas', 'alignof', 'static_assert', '_Pragma',
|
|
25
|
-
'fortran',
|
|
26
|
-
}
|
|
27
|
-
_RESERVED = {'inline', '_inline', '__inline', 'naked', 'restrict', 'thread', '__int8', '__int16', '__int32',
|
|
28
|
-
'__int64', '__wchar_t', '__m128i', '__m128d', '__m128', '__m64'} | {
|
|
29
|
-
'__' + name for name in ('asm', 'based', 'except', 'stdcall', 'cdecl', 'fastcall', 'declspec', 'finally', 'try',
|
|
30
|
-
'leave', 'w64', 'unaligned', 'raise', 'noop', 'identifier', 'forceinline', 'assume',
|
|
31
|
-
'null')}
|
|
32
|
-
_TYPES = {'bool', 'int', 'long', 'float', 'short', 'double', 'char', 'unsigned', 'signed', 'void', '_BitInt',
|
|
33
|
-
'__int128'}
|
|
34
|
-
_BUILTINS = {'true', 'false', 'NULL', 'nullptr'}
|
|
35
|
-
# Names that start another lexer state, or a string with an affix (`L"..."`): left to pygments.
|
|
36
|
-
_STATEFUL = {'struct', 'union', 'case', 'L', 'u', 'U', 'u8'}
|
|
37
|
-
|
|
38
|
-
_INT_SUFFIX = r'(?:[uU][lL]{0,2}|[lL]{1,2}[uU]?)?'
|
|
39
|
-
_DEC = r'\d(?:\'?\d)*'
|
|
40
|
-
_HEX = r'[0-9a-fA-F](?:\'?[0-9a-fA-F])*'
|
|
41
|
-
# The alternatives are tried in the lexer's order at each token start. `unsupported` marks what pygments lexes
|
|
42
|
-
# differently from this tokenizer (comments and floating-point numbers); characters no alternative matches (`'`, `\\`,
|
|
43
|
-
# `#`, `$`, a string with an escape, ...) also send the line to pygments.
|
|
44
25
|
_TOKEN = re.compile(
|
|
45
|
-
r'(?P<
|
|
46
|
-
r'|0[xX]
|
|
47
|
-
r'
|
|
48
|
-
r'
|
|
49
|
-
r')'
|
|
50
|
-
r'|(?P<string>")(?P<content>[^\\"\n]*)(?P<close>")'
|
|
51
|
-
r'|(?P<hex>-?0[xX]' + _HEX + _INT_SUFFIX + r')'
|
|
52
|
-
r'|(?P<bin>-?0[bB][01](?:\'?[01])*' + _INT_SUFFIX + r')'
|
|
53
|
-
r'|(?P<oct>-?0(?:\'?[0-7])+' + _INT_SUFFIX + r')'
|
|
54
|
-
r'|(?P<int>-?' + _DEC + _INT_SUFFIX + r')'
|
|
55
|
-
r'|(?P<op>[~!%^&*+=|?:<>/-])'
|
|
56
|
-
r'|(?P<punct>[()\[\],.])'
|
|
57
|
-
r'|(?P<name>[a-zA-Z_]\w*)'
|
|
58
|
-
r'|(?P<ws>[^\S\n]+)'
|
|
26
|
+
r'(?P<string>"[^"]*")'
|
|
27
|
+
r'|(?P<number>-?0[xX][0-9a-fA-F]+|-?\d+)'
|
|
28
|
+
r'|(?P<label>[A-Za-z_][\w ]*?:)(?= |$)'
|
|
29
|
+
r'|(?P<name>[A-Za-z_]\w*)'
|
|
59
30
|
)
|
|
60
|
-
#
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def _style(ttype):
|
|
65
|
-
# The formatter uses the style of the token type, or of its closest parent that has one.
|
|
66
|
-
while str(ttype) not in color_formatter.style_string and ttype.parent is not None:
|
|
67
|
-
ttype = ttype.parent
|
|
68
|
-
return color_formatter.style_string.get(str(ttype), ('', ''))
|
|
31
|
+
# A constant is all capitals, digits and underscores, with at least one capital.
|
|
32
|
+
_CONSTANT = re.compile(r'[A-Z][A-Z0-9_]*')
|
|
33
|
+
# `KERN_SUCCESS` is a result, not an error.
|
|
34
|
+
_SUCCESS = {'KERN_SUCCESS'}
|
|
69
35
|
|
|
70
36
|
|
|
71
|
-
_GROUPS = {'hex': _style(Number.Hex), 'bin': _style(Number.Bin), 'oct': _style(Number.Oct),
|
|
72
|
-
'int': _style(Number.Integer), 'op': _style(Operator), 'punct': _style(Punctuation)}
|
|
73
|
-
_NAME_STYLES = {}
|
|
74
|
-
for _names, _ttype in ((_KEYWORDS, Keyword), (_RESERVED, Keyword.Reserved), (_TYPES, Keyword.Type),
|
|
75
|
-
(_BUILTINS, Name.Builtin)):
|
|
76
|
-
for _name in _names:
|
|
77
|
-
_NAME_STYLES[_name] = _style(_ttype)
|
|
78
|
-
_NAME = _style(Name)
|
|
79
|
-
_STRING = _style(String)
|
|
80
|
-
# Whitespace only gets a foreground color, which a space does not show: it can stay in the style before it.
|
|
81
|
-
_FG_RESET = '\x1b[39m'
|
|
82
|
-
assert _style(Whitespace)[1] == _FG_RESET
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
def _pygments(text: str) -> str:
|
|
86
|
-
return pygments_highlight(text, c_lexer, color_formatter).strip()
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
# Traces repeat (the same calls with the same arguments): in a busy `--bsc` trace, 77% of the lines were among the
|
|
90
|
-
# 16k most recent ones.
|
|
91
37
|
@lru_cache(maxsize=1 << 14)
|
|
92
38
|
def highlight(text: str) -> str:
|
|
93
|
-
"""`text`
|
|
94
|
-
|
|
95
|
-
pygments starts and ends the escape sequence of every token, even between tokens of the same color, and colors
|
|
96
|
-
spaces. Here a sequence starts only where the color changes, which renders the same in about half the bytes (the
|
|
97
|
-
terminal is the bottleneck of a busy trace).
|
|
98
|
-
"""
|
|
99
|
-
if _UNSUPPORTED.search(text):
|
|
100
|
-
return _pygments(text)
|
|
39
|
+
"""`text` with terminal colors (SGR escape sequences); the text itself is unchanged."""
|
|
101
40
|
out = []
|
|
102
|
-
style = None # the (on, off) in effect
|
|
103
41
|
pos = 0
|
|
42
|
+
error = False # inside the value of an `errno:` result
|
|
43
|
+
first = True
|
|
104
44
|
for match in _TOKEN.finditer(text):
|
|
105
|
-
if match.start()
|
|
106
|
-
|
|
45
|
+
if match.start() > pos:
|
|
46
|
+
out.append(text[pos:match.start()])
|
|
107
47
|
pos = match.end()
|
|
108
48
|
kind = match.lastgroup
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
49
|
+
value = match.group()
|
|
50
|
+
if kind == 'string':
|
|
51
|
+
out.append(_GREEN + value + _RESET)
|
|
52
|
+
elif kind == 'number':
|
|
53
|
+
out.append((_RED if error else _CYAN) + value + _RESET)
|
|
54
|
+
elif kind == 'label':
|
|
55
|
+
error = value == 'errno:'
|
|
56
|
+
out.append((_RED if error else _DIM) + value + _RESET)
|
|
57
|
+
elif first:
|
|
58
|
+
out.append(_BOLD + value + _RESET)
|
|
59
|
+
elif _CONSTANT.fullmatch(value):
|
|
60
|
+
failed = error or (value.startswith('KERN_') and value not in _SUCCESS)
|
|
61
|
+
out.append((_RED if failed else _YELLOW) + value + _RESET)
|
|
121
62
|
else:
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
out.append(style[1])
|
|
127
|
-
out.append(token_style[0])
|
|
128
|
-
style = token_style
|
|
129
|
-
out.append(match.group())
|
|
130
|
-
if pos != len(text):
|
|
131
|
-
return _pygments(text)
|
|
132
|
-
if style is not None:
|
|
133
|
-
out.append(style[1])
|
|
134
|
-
return ''.join(out).strip()
|
|
63
|
+
out.append(value)
|
|
64
|
+
first = False
|
|
65
|
+
out.append(text[pos:])
|
|
66
|
+
return ''.join(out)
|
pykdebugparser/pykdebugparser.py
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import dataclasses
|
|
1
2
|
from datetime import datetime
|
|
3
|
+
from enum import Enum
|
|
2
4
|
import io
|
|
3
5
|
import math
|
|
4
6
|
|
|
@@ -9,6 +11,7 @@ from pykdebugparser.highlight import highlight
|
|
|
9
11
|
from pykdebugparser.kd_buf_parser import KdBufParser
|
|
10
12
|
from pykdebugparser.kevent import DgbFuncQual
|
|
11
13
|
from pykdebugparser.trace_codes import default_trace_codes
|
|
14
|
+
from pykdebugparser.trace_handlers import UnknownEnumValue
|
|
12
15
|
from pykdebugparser.trace_handlers.trace import TRACE_LOST_EVENTS
|
|
13
16
|
from pykdebugparser.traces_parser import TracesParser
|
|
14
17
|
from pykdebugparser.os_log_event import OsLogEvent
|
|
@@ -19,6 +22,32 @@ DBG_FSYSTEM = 3
|
|
|
19
22
|
DBG_BSD = 4
|
|
20
23
|
|
|
21
24
|
|
|
25
|
+
def _fields(trace) -> dict:
|
|
26
|
+
"""A trace's parsed values (its dataclass fields but the raw kevents), as JSON-compatible values."""
|
|
27
|
+
return {field.name: _jsonable(getattr(trace, field.name)) for field in dataclasses.fields(trace)
|
|
28
|
+
if field.name != 'ktraces'}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _jsonable(value):
|
|
32
|
+
if isinstance(value, (bool, str)) or value is None:
|
|
33
|
+
return value
|
|
34
|
+
if isinstance(value, UnknownEnumValue):
|
|
35
|
+
return int(value)
|
|
36
|
+
if isinstance(value, Enum):
|
|
37
|
+
return value.name
|
|
38
|
+
if isinstance(value, (int, float)):
|
|
39
|
+
return value
|
|
40
|
+
if isinstance(value, (bytes, bytearray)):
|
|
41
|
+
return value.hex()
|
|
42
|
+
if isinstance(value, (list, tuple)):
|
|
43
|
+
return [_jsonable(item) for item in value]
|
|
44
|
+
if isinstance(value, dict):
|
|
45
|
+
return {str(key): _jsonable(item) for key, item in value.items()}
|
|
46
|
+
if dataclasses.is_dataclass(value) and not isinstance(value, type):
|
|
47
|
+
return _fields(value)
|
|
48
|
+
return str(value)
|
|
49
|
+
|
|
50
|
+
|
|
22
51
|
class PyKdebugParser:
|
|
23
52
|
def __init__(self):
|
|
24
53
|
self.filter_tid = None
|
|
@@ -99,6 +128,32 @@ class PyKdebugParser:
|
|
|
99
128
|
trace_generator = filter(lambda t: t.ktraces[0].eventid >> 24 != DBG_FSYSTEM, trace_generator)
|
|
100
129
|
return trace_generator
|
|
101
130
|
|
|
131
|
+
def dict_traces(self, kdebug: io.IOBase, trace_codes=None):
|
|
132
|
+
"""The traces as JSON-compatible dicts (see `trace_dict`)."""
|
|
133
|
+
trace_codes_map = default_trace_codes() if trace_codes is None else trace_codes
|
|
134
|
+
return map(lambda t: self.trace_dict(t, trace_codes_map), self.traces(kdebug, trace_codes_map))
|
|
135
|
+
|
|
136
|
+
def trace_dict(self, trace, trace_codes=None) -> dict:
|
|
137
|
+
"""
|
|
138
|
+
A trace as JSON-compatible data: when (`timestamp` in mach ticks, and `time` in ISO 8601 when the device's time
|
|
139
|
+
information is set), where (`tid`, `pid`, `process`), what (`event`, the trace code name of its first kevent,
|
|
140
|
+
and `fields`, the parsed values) and `text`, as `formatted_traces` prints it.
|
|
141
|
+
"""
|
|
142
|
+
trace_codes_map = default_trace_codes() if trace_codes is None else trace_codes
|
|
143
|
+
first = trace.ktraces[0]
|
|
144
|
+
pid = self.threads_pids.get(first.tid)
|
|
145
|
+
posix_time = self._posix_time(first.timestamp)
|
|
146
|
+
return {
|
|
147
|
+
'timestamp': first.timestamp,
|
|
148
|
+
'time': None if posix_time is None else datetime.fromtimestamp(posix_time, tz=self.timezone).isoformat(),
|
|
149
|
+
'tid': first.tid,
|
|
150
|
+
'pid': pid,
|
|
151
|
+
'process': self.pids_names.get(pid, '') if pid is not None else '',
|
|
152
|
+
'event': trace_codes_map.get(first.eventid, hex(first.eventid)),
|
|
153
|
+
'fields': _fields(trace),
|
|
154
|
+
'text': str(trace),
|
|
155
|
+
}
|
|
156
|
+
|
|
102
157
|
def formatted_traces(self, kdebug: io.IOBase, trace_codes=None):
|
|
103
158
|
return map(lambda t: self._format_trace(t), self.traces(kdebug, trace_codes))
|
|
104
159
|
|
|
@@ -128,13 +183,19 @@ class PyKdebugParser:
|
|
|
128
183
|
process_name = self.pids_names.get(pid, '')
|
|
129
184
|
return self.filter_process == str(pid) or self.filter_process == process_name
|
|
130
185
|
|
|
131
|
-
def
|
|
186
|
+
def _posix_time(self, timestamp):
|
|
187
|
+
"""A kevent timestamp (mach ticks) as POSIX time, or None without the device's time information."""
|
|
132
188
|
if None in (self.mach_absolute_time, self.numer, self.denom, self.usecs_since_epoch, self.timezone):
|
|
133
|
-
return
|
|
189
|
+
return None
|
|
134
190
|
offset_usec = (
|
|
135
191
|
((timestamp - self.mach_absolute_time) * self.numer) / (self.denom * 1000)
|
|
136
192
|
)
|
|
137
|
-
|
|
193
|
+
return (self.usecs_since_epoch + offset_usec) / 1000000
|
|
194
|
+
|
|
195
|
+
def _format_timestamp(self, timestamp):
|
|
196
|
+
posix_time = self._posix_time(timestamp)
|
|
197
|
+
if posix_time is None:
|
|
198
|
+
return str(timestamp) + ' '
|
|
138
199
|
# Split to seconds and microseconds exactly like `datetime.fromtimestamp` does (round half to even), so
|
|
139
200
|
# the date and time part can be formatted only once per second.
|
|
140
201
|
fraction, seconds = math.modf(posix_time)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pykdebugparser
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.6.0
|
|
4
4
|
Summary: Python parser for kdebug events
|
|
5
5
|
Author: Matan Perelman
|
|
6
6
|
Author-email: matan1008@gmail.com
|
|
@@ -39,7 +39,6 @@ Requires-Python: >=3.9
|
|
|
39
39
|
Description-Content-Type: text/markdown
|
|
40
40
|
License-File: LICENSE
|
|
41
41
|
Requires-Dist: bytestruct>=0.1.0
|
|
42
|
-
Requires-Dist: pygments
|
|
43
42
|
Requires-Dist: click
|
|
44
43
|
Requires-Dist: termcolor
|
|
45
44
|
Provides-Extra: testing
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
pykdebugparser/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
2
|
pykdebugparser/__main__.py,sha256=WfgLv5SGr0hQlUeM68ai1g0AqtYDgDFqYlbvvm5rua4,3711
|
|
3
3
|
pykdebugparser/callstacks_parser.py,sha256=i2nfhx0olZZ65UV_ZV3Tw9E_6SWtre8CAbfRBSig4-E,1654
|
|
4
|
-
pykdebugparser/highlight.py,sha256=
|
|
4
|
+
pykdebugparser/highlight.py,sha256=LqIf0kn8va0aE6tVGJyzu0MRnsR8o4OEJsYaUPXjDjM,2270
|
|
5
5
|
pykdebugparser/kd_buf_parser.py,sha256=CpifFeBnQ_MZSokjO44lzhXO-KNnZvO-teaPyQqSfio,10991
|
|
6
6
|
pykdebugparser/kevent.py,sha256=DudovIuES4QGxosYeEFJm3AW_FWGbJJBH1kcLnL7UFM,1779
|
|
7
7
|
pykdebugparser/os_log_event.py,sha256=OQUlHpS44oUNwma_bxvnK0SKh_RFeEUdH1RmyrM4nLc,12356
|
|
8
|
-
pykdebugparser/pykdebugparser.py,sha256=
|
|
8
|
+
pykdebugparser/pykdebugparser.py,sha256=sx77jkvXEyEpmoW3XjANjTUZbm3BaOqRJjbQhIZxVxU,13052
|
|
9
9
|
pykdebugparser/trace.codes,sha256=S6ZN5l9FSGrlSon8nKoVaqRfLdCPl3HL93XSjuoCUnM,103995
|
|
10
10
|
pykdebugparser/trace_codes.py,sha256=4ImGc_jNpY-12cGXdwy6NMCEYcI24oYn1NBOaKNdWNo,1984
|
|
11
11
|
pykdebugparser/traces_parser.py,sha256=LyHVTi4P2OeOLNG2Tahu1x7roQXkC-Jx8LS-E4mBcAQ,5799
|
|
@@ -17,9 +17,9 @@ pykdebugparser/trace_handlers/mach.py,sha256=_2YEgUfKYxaB7NfA2jCVxUbdKDrU21KJKhx
|
|
|
17
17
|
pykdebugparser/trace_handlers/perf.py,sha256=CBffheZFlHa9IOVUZ-UcjNtKmjamv-un44bRJYYo9dw,5070
|
|
18
18
|
pykdebugparser/trace_handlers/trace.py,sha256=s3Kr0wDvWZL2JZVKhxcvA1uvqsdePlEWiXfqSI76znw,5797
|
|
19
19
|
pykdebugparser/trace_handlers/turnstile.py,sha256=8zZfba2Obq7a6qdhLIkItmZxRzzOe1PTTeHnrLuJUx0,8233
|
|
20
|
-
pykdebugparser-1.
|
|
21
|
-
pykdebugparser-1.
|
|
22
|
-
pykdebugparser-1.
|
|
23
|
-
pykdebugparser-1.
|
|
24
|
-
pykdebugparser-1.
|
|
25
|
-
pykdebugparser-1.
|
|
20
|
+
pykdebugparser-1.6.0.dist-info/licenses/LICENSE,sha256=KjZXYP_CGYzDTtVfcxPT6wlGRoQKHPSJu0vh55_2QSo,1071
|
|
21
|
+
pykdebugparser-1.6.0.dist-info/METADATA,sha256=U2COUKhE9WYKZlpGQulRUvrspyB7k0qMgKMmKg5S3l0,18971
|
|
22
|
+
pykdebugparser-1.6.0.dist-info/WHEEL,sha256=4YBfCYNH4wlLpv3pzq1hbEuIlXA4WJabKLFurZ7eTL0,109
|
|
23
|
+
pykdebugparser-1.6.0.dist-info/entry_points.txt,sha256=NrVIOgPR6v2MbpTZ3aKedP1jQ93nUQus6sgP4wAD0_E,63
|
|
24
|
+
pykdebugparser-1.6.0.dist-info/top_level.txt,sha256=FcHHXR9VKmqufFw0a6UP4c8NyS-u4ORY-yXQ6ZzP_28,15
|
|
25
|
+
pykdebugparser-1.6.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|