Skip to content

Commit 9ccc7e0

Browse files
committed
ref: More perf
1 parent e6b2819 commit 9ccc7e0

File tree

2 files changed

+131
-89
lines changed

2 files changed

+131
-89
lines changed

sentry_sdk/integrations/modules.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,5 +52,5 @@ def processor(event, hint):
5252
if Hub.current.get_integration(ModulesIntegration) is None:
5353
return event
5454

55-
event["modules"] = dict(_get_installed_modules())
55+
event["modules"] = _get_installed_modules()
5656
return event

sentry_sdk/serializer.py

+130-88
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import sys
2-
import itertools
32

43
from datetime import datetime
54

@@ -23,7 +22,6 @@
2322
from typing import Any
2423
from typing import Dict
2524
from typing import List
26-
from typing import Tuple
2725
from typing import Optional
2826
from typing import Callable
2927
from typing import Union
@@ -59,11 +57,11 @@ def add_global_repr_processor(processor):
5957

6058

6159
class Memo(object):
62-
__slots__ = ("_inner", "_objs")
60+
__slots__ = ("_ids", "_objs")
6361

6462
def __init__(self):
6563
# type: () -> None
66-
self._inner = {} # type: Dict[int, Any]
64+
self._ids = {} # type: Dict[int, Any]
6765
self._objs = [] # type: List[Any]
6866

6967
def memoize(self, obj):
@@ -74,10 +72,10 @@ def memoize(self, obj):
7472
def __enter__(self):
7573
# type: () -> bool
7674
obj = self._objs[-1]
77-
if id(obj) in self._inner:
75+
if id(obj) in self._ids:
7876
return True
7977
else:
80-
self._inner[id(obj)] = obj
78+
self._ids[id(obj)] = obj
8179
return False
8280

8381
def __exit__(
@@ -87,7 +85,7 @@ def __exit__(
8785
tb, # type: Optional[TracebackType]
8886
):
8987
# type: (...) -> None
90-
self._inner.pop(id(self._objs.pop()), None)
88+
self._ids.pop(id(self._objs.pop()), None)
9189

9290

9391
def serialize(event, **kwargs):
@@ -109,27 +107,80 @@ def _annotate(**meta):
109107

110108
meta_stack[-1].setdefault("", {}).update(meta)
111109

112-
def _startswith_path(prefix):
113-
# type: (Tuple[Optional[Segment], ...]) -> bool
114-
if len(prefix) > len(path):
115-
return False
110+
def _should_repr_strings():
111+
# type: () -> Optional[bool]
112+
"""
113+
By default non-serializable objects are going through
114+
safe_repr(). For certain places in the event (local vars) we
115+
want to repr() even things that are JSON-serializable to
116+
make their type more apparent. For example, it's useful to
117+
see the difference between a unicode-string and a bytestring
118+
when viewing a stacktrace.
119+
120+
For container-types we still don't do anything different.
121+
Generally we just try to make the Sentry UI present exactly
122+
what a pretty-printed repr would look like.
123+
124+
:returns: `True` if we are somewhere in frame variables, and `False` if
125+
we are in a position where we will never encounter frame variables
126+
when recursing (for example, we're in `event.extra`). `None` if we
127+
are not (yet) in frame variables, but might encounter them when
128+
recursing (e.g. we're in `event.exception`)
129+
"""
130+
try:
131+
p0 = path[0]
132+
if p0 == "stacktrace" and path[1] == "frames" and path[3] == "vars":
133+
return True
134+
135+
if (
136+
p0 in ("threads", "exception")
137+
and path[1] == "values"
138+
and path[3] == "stacktrace"
139+
and path[4] == "frames"
140+
and path[6] == "vars"
141+
):
142+
return True
143+
except IndexError:
144+
return None
116145

117-
for i, segment in enumerate(prefix):
118-
if segment is None:
119-
continue
146+
return False
147+
148+
def _is_databag():
149+
# type: () -> Optional[bool]
150+
"""
151+
A databag is any value that we need to trim.
152+
153+
:returns: Works like `_should_repr_strings()`. `True` for "yes",
154+
`False` for :"no", `None` for "maybe soon".
155+
"""
156+
try:
157+
rv = _should_repr_strings()
158+
if rv in (True, None):
159+
return rv
120160

121-
if path[i] != segment:
122-
return False
161+
p0 = path[0]
162+
if p0 == "request" and path[1] == "data":
163+
return True
123164

124-
return True
165+
if p0 == "breadcrumbs":
166+
path[1]
167+
return True
168+
169+
if p0 == "extra":
170+
return True
171+
172+
except IndexError:
173+
return None
174+
175+
return False
125176

126177
def _serialize_node(
127178
obj, # type: Any
128-
max_depth=None, # type: Optional[int]
129-
max_breadth=None, # type: Optional[int]
130179
is_databag=None, # type: Optional[bool]
131180
should_repr_strings=None, # type: Optional[bool]
132181
segment=None, # type: Optional[Segment]
182+
remaining_breadth=None, # type: Optional[int]
183+
remaining_depth=None, # type: Optional[int]
133184
):
134185
# type: (...) -> Any
135186
if segment is not None:
@@ -142,10 +193,10 @@ def _serialize_node(
142193

143194
return _serialize_node_impl(
144195
obj,
145-
max_depth=max_depth,
146-
max_breadth=max_breadth,
147196
is_databag=is_databag,
148197
should_repr_strings=should_repr_strings,
198+
remaining_depth=remaining_depth,
199+
remaining_breadth=remaining_breadth,
149200
)
150201
except BaseException:
151202
capture_internal_exception(sys.exc_info())
@@ -167,47 +218,19 @@ def _flatten_annotated(obj):
167218
return obj
168219

169220
def _serialize_node_impl(
170-
obj, max_depth, max_breadth, is_databag, should_repr_strings
221+
obj, is_databag, should_repr_strings, remaining_depth, remaining_breadth
171222
):
172-
# type: (Any, Optional[int], Optional[int], Optional[bool], Optional[bool]) -> Any
173-
if not should_repr_strings:
174-
should_repr_strings = (
175-
_startswith_path(
176-
("exception", "values", None, "stacktrace", "frames", None, "vars")
177-
)
178-
or _startswith_path(
179-
("threads", "values", None, "stacktrace", "frames", None, "vars")
180-
)
181-
or _startswith_path(("stacktrace", "frames", None, "vars"))
182-
)
223+
# type: (Any, Optional[bool], Optional[bool], Optional[int], Optional[int]) -> Any
224+
if should_repr_strings is None:
225+
should_repr_strings = _should_repr_strings()
183226

184-
if obj is None or isinstance(obj, (bool, number_types)):
185-
return obj if not should_repr_strings else safe_repr(obj)
227+
if is_databag is None:
228+
is_databag = _is_databag()
186229

187-
if isinstance(obj, datetime):
188-
return (
189-
text_type(obj.strftime("%Y-%m-%dT%H:%M:%S.%fZ"))
190-
if not should_repr_strings
191-
else safe_repr(obj)
192-
)
193-
194-
if not is_databag:
195-
is_databag = (
196-
should_repr_strings
197-
or _startswith_path(("request", "data"))
198-
or _startswith_path(("breadcrumbs", None))
199-
or _startswith_path(("extra",))
200-
)
201-
202-
cur_depth = len(path)
203-
if max_depth is None and max_breadth is None and is_databag:
204-
max_depth = cur_depth + MAX_DATABAG_DEPTH
205-
max_breadth = cur_depth + MAX_DATABAG_BREADTH
206-
207-
if max_depth is None:
208-
remaining_depth = None
209-
else:
210-
remaining_depth = max_depth - cur_depth
230+
if is_databag and remaining_depth is None:
231+
remaining_depth = MAX_DATABAG_DEPTH
232+
if is_databag and remaining_breadth is None:
233+
remaining_breadth = MAX_DATABAG_BREADTH
211234

212235
obj = _flatten_annotated(obj)
213236

@@ -217,54 +240,72 @@ def _serialize_node_impl(
217240
return _flatten_annotated(strip_string(safe_repr(obj)))
218241
return None
219242

220-
if global_repr_processors and is_databag:
243+
if is_databag and global_repr_processors:
221244
hints = {"memo": memo, "remaining_depth": remaining_depth}
222245
for processor in global_repr_processors:
223246
result = processor(obj, hints)
224247
if result is not NotImplemented:
225248
return _flatten_annotated(result)
226249

227-
if isinstance(obj, Mapping):
250+
if obj is None or isinstance(obj, (bool, number_types)):
251+
return obj if not should_repr_strings else safe_repr(obj)
252+
253+
elif isinstance(obj, datetime):
254+
return (
255+
text_type(obj.strftime("%Y-%m-%dT%H:%M:%S.%fZ"))
256+
if not should_repr_strings
257+
else safe_repr(obj)
258+
)
259+
260+
elif isinstance(obj, Mapping):
228261
# Create temporary copy here to avoid calling too much code that
229262
# might mutate our dictionary while we're still iterating over it.
230-
if max_breadth is not None and len(obj) >= max_breadth:
231-
rv_dict = dict(itertools.islice(iteritems(obj), None, max_breadth))
232-
_annotate(len=len(obj))
233-
else:
234-
if type(obj) is dict:
235-
rv_dict = dict(obj)
236-
else:
237-
rv_dict = dict(iteritems(obj))
238-
239-
for k in list(rv_dict):
263+
obj = dict(iteritems(obj))
264+
265+
rv_dict = {}
266+
i = 0
267+
268+
for k, v in iteritems(obj):
269+
if remaining_breadth is not None and i >= remaining_breadth:
270+
_annotate(len=len(obj))
271+
break
272+
240273
str_k = text_type(k)
241274
v = _serialize_node(
242-
rv_dict.pop(k),
243-
max_depth=max_depth,
244-
max_breadth=max_breadth,
275+
v,
245276
segment=str_k,
246277
should_repr_strings=should_repr_strings,
247278
is_databag=is_databag,
279+
remaining_depth=remaining_depth - 1
280+
if remaining_depth is not None
281+
else None,
282+
remaining_breadth=remaining_breadth,
248283
)
249284
if v is not None:
250285
rv_dict[str_k] = v
286+
i += 1
251287

252288
return rv_dict
289+
253290
elif not isinstance(obj, string_types) and isinstance(obj, Sequence):
254-
if max_breadth is not None and len(obj) >= max_breadth:
255-
rv_list = list(obj)[:max_breadth]
256-
_annotate(len=len(obj))
257-
else:
258-
rv_list = list(obj)
259-
260-
for i in range(len(rv_list)):
261-
rv_list[i] = _serialize_node(
262-
rv_list[i],
263-
max_depth=max_depth,
264-
max_breadth=max_breadth,
265-
segment=i,
266-
should_repr_strings=should_repr_strings,
267-
is_databag=is_databag,
291+
rv_list = []
292+
293+
for i, v in enumerate(obj):
294+
if remaining_breadth is not None and i >= remaining_breadth:
295+
_annotate(len=len(obj))
296+
break
297+
298+
rv_list.append(
299+
_serialize_node(
300+
v,
301+
segment=i,
302+
should_repr_strings=should_repr_strings,
303+
is_databag=is_databag,
304+
remaining_depth=remaining_depth - 1
305+
if remaining_depth is not None
306+
else None,
307+
remaining_breadth=remaining_breadth,
308+
)
268309
)
269310

270311
return rv_list
@@ -285,6 +326,7 @@ def _serialize_node_impl(
285326
rv = _serialize_node(event, **kwargs)
286327
if meta_stack and isinstance(rv, dict):
287328
rv["_meta"] = meta_stack[0]
329+
288330
return rv
289331
finally:
290332
disable_capture_event.set(False)

0 commit comments

Comments
 (0)