Skip to content

Commit 8354fdf

Browse files
authored
refactor(debugger): use single dispatch on probes (#11596)
We refactor the debugger code to use a single dispatch function solution instead of a battery of instance check branches. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)
1 parent e04c8ac commit 8354fdf

File tree

12 files changed

+166
-133
lines changed

12 files changed

+166
-133
lines changed

ddtrace/debugging/_debugger.py

+11-63
Original file line numberDiff line numberDiff line change
@@ -31,26 +31,15 @@
3131
from ddtrace.debugging._probe.model import FunctionProbe
3232
from ddtrace.debugging._probe.model import LineLocationMixin
3333
from ddtrace.debugging._probe.model import LineProbe
34-
from ddtrace.debugging._probe.model import LogFunctionProbe
35-
from ddtrace.debugging._probe.model import LogLineProbe
36-
from ddtrace.debugging._probe.model import MetricFunctionProbe
37-
from ddtrace.debugging._probe.model import MetricLineProbe
3834
from ddtrace.debugging._probe.model import Probe
39-
from ddtrace.debugging._probe.model import SpanDecorationFunctionProbe
40-
from ddtrace.debugging._probe.model import SpanDecorationLineProbe
41-
from ddtrace.debugging._probe.model import SpanFunctionProbe
4235
from ddtrace.debugging._probe.registry import ProbeRegistry
4336
from ddtrace.debugging._probe.remoteconfig import ProbePollerEvent
4437
from ddtrace.debugging._probe.remoteconfig import ProbePollerEventType
4538
from ddtrace.debugging._probe.remoteconfig import ProbeRCAdapter
4639
from ddtrace.debugging._probe.status import ProbeStatusLogger
4740
from ddtrace.debugging._signal.collector import SignalCollector
48-
from ddtrace.debugging._signal.metric_sample import MetricSample
4941
from ddtrace.debugging._signal.model import Signal
5042
from ddtrace.debugging._signal.model import SignalState
51-
from ddtrace.debugging._signal.snapshot import Snapshot
52-
from ddtrace.debugging._signal.tracing import DynamicSpan
53-
from ddtrace.debugging._signal.tracing import SpanDecoration
5443
from ddtrace.debugging._uploader import LogsIntakeUploaderV1
5544
from ddtrace.debugging._uploader import UploaderProduct
5645
from ddtrace.internal import compat
@@ -62,7 +51,6 @@
6251
from ddtrace.internal.module import register_post_run_module_hook
6352
from ddtrace.internal.module import unregister_post_run_module_hook
6453
from ddtrace.internal.rate_limiter import BudgetRateLimiterWithJitter as RateLimiter
65-
from ddtrace.internal.rate_limiter import RateLimitExceeded
6654
from ddtrace.internal.remoteconfig.worker import remoteconfig_poller
6755
from ddtrace.internal.service import Service
6856
from ddtrace.internal.wrapping.context import WrappingContext
@@ -190,35 +178,15 @@ def _open_signals(self) -> None:
190178
# for each probe.
191179
trace_context = self._tracer.current_trace_context()
192180

193-
if isinstance(probe, MetricFunctionProbe):
194-
signal = MetricSample(
195-
probe=probe,
181+
try:
182+
signal = Signal.from_probe(
183+
probe,
196184
frame=frame,
197185
thread=thread,
198186
trace_context=trace_context,
199187
meter=self._probe_meter,
200188
)
201-
elif isinstance(probe, LogFunctionProbe):
202-
signal = Snapshot(
203-
probe=probe,
204-
frame=frame,
205-
thread=thread,
206-
trace_context=trace_context,
207-
)
208-
elif isinstance(probe, SpanFunctionProbe):
209-
signal = DynamicSpan(
210-
probe=probe,
211-
frame=frame,
212-
thread=thread,
213-
trace_context=trace_context,
214-
)
215-
elif isinstance(probe, SpanDecorationFunctionProbe):
216-
signal = SpanDecoration(
217-
probe=probe,
218-
frame=frame,
219-
thread=thread,
220-
)
221-
else:
189+
except TypeError:
222190
log.error("Unsupported probe type: %s", type(probe))
223191
continue
224192

@@ -385,39 +353,19 @@ def _dd_debugger_hook(self, probe: Probe) -> None:
385353
instrumented code is running.
386354
"""
387355
try:
388-
actual_frame = sys._getframe(1)
389-
signal: Optional[Signal] = None
390-
if isinstance(probe, MetricLineProbe):
391-
signal = MetricSample(
392-
probe=probe,
393-
frame=actual_frame,
356+
try:
357+
signal = Signal.from_probe(
358+
probe,
359+
frame=sys._getframe(1),
394360
thread=threading.current_thread(),
395361
trace_context=self._tracer.current_trace_context(),
396362
meter=self._probe_meter,
397363
)
398-
elif isinstance(probe, LogLineProbe):
399-
if probe.take_snapshot:
400-
# TODO: Global limit evaluated before probe conditions
401-
if self._global_rate_limiter.limit() is RateLimitExceeded:
402-
return
403-
404-
signal = Snapshot(
405-
probe=probe,
406-
frame=actual_frame,
407-
thread=threading.current_thread(),
408-
trace_context=self._tracer.current_trace_context(),
409-
)
410-
elif isinstance(probe, SpanDecorationLineProbe):
411-
signal = SpanDecoration(
412-
probe=probe,
413-
frame=actual_frame,
414-
thread=threading.current_thread(),
415-
)
416-
else:
417-
log.error("Unsupported probe type: %r", type(probe))
364+
except TypeError:
365+
log.error("Unsupported probe type: %r", type(probe), exc_info=True)
418366
return
419367

420-
signal.do_line()
368+
signal.do_line(self._global_rate_limiter if probe.is_global_rate_limited() else None)
421369

422370
if signal.state is SignalState.DONE:
423371
self._probe_registry.set_emitting(probe)

ddtrace/debugging/_encoding.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from typing import Union
1616

1717
from ddtrace.debugging._config import di_config
18-
from ddtrace.debugging._signal.model import LogSignal
18+
from ddtrace.debugging._signal.log import LogSignal
1919
from ddtrace.debugging._signal.snapshot import Snapshot
2020
from ddtrace.internal import forksafe
2121
from ddtrace.internal._encoding import BufferFull

ddtrace/debugging/_expressions.py

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
arg_operation => {"<arg_op_type>": [<argument_list>]}
2424
arg_op_type => filter | substring | getmember | index
2525
""" # noqa
26+
2627
from dataclasses import dataclass
2728
from itertools import chain
2829
import re

ddtrace/debugging/_probe/model.py

+7-2
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ def update(self, other: "Probe") -> None:
8585
for attrib in (f.name for f in fields(self) if f.compare):
8686
setattr(self, attrib, getattr(other, attrib))
8787

88+
def is_global_rate_limited(self) -> bool:
89+
return False
90+
8891
def __hash__(self):
8992
return hash(self.probe_id)
9093

@@ -245,12 +248,14 @@ class LogProbeMixin(AbstractProbeMixIn):
245248

246249
@dataclass
247250
class LogLineProbe(Probe, LineLocationMixin, LogProbeMixin, ProbeConditionMixin, RateLimitMixin):
248-
pass
251+
def is_global_rate_limited(self) -> bool:
252+
return self.take_snapshot
249253

250254

251255
@dataclass
252256
class LogFunctionProbe(Probe, FunctionLocationMixin, TimingMixin, LogProbeMixin, ProbeConditionMixin, RateLimitMixin):
253-
pass
257+
def is_global_rate_limited(self) -> bool:
258+
return self.take_snapshot
254259

255260

256261
@dataclass

ddtrace/debugging/_signal/__init__.py

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# DEV: Import these modules to allow registering the single dispatch functions
2+
from ddtrace.debugging._signal.metric_sample import MetricSample # noqa
3+
from ddtrace.debugging._signal.snapshot import Snapshot # noqa
4+
from ddtrace.debugging._signal.tracing import DynamicSpan # noqa
5+
from ddtrace.debugging._signal.tracing import SpanDecoration # noqa

ddtrace/debugging/_signal/collector.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from ddtrace.debugging._encoding import BufferedEncoder
88
from ddtrace.debugging._metrics import metrics
9-
from ddtrace.debugging._signal.model import LogSignal
9+
from ddtrace.debugging._signal.log import LogSignal
1010
from ddtrace.debugging._signal.model import Signal
1111
from ddtrace.debugging._signal.model import SignalState
1212
from ddtrace.internal._encoding import BufferFull

ddtrace/debugging/_signal/log.py

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import abc
2+
from dataclasses import dataclass
3+
import typing as t
4+
5+
from ddtrace.debugging._probe.model import FunctionLocationMixin
6+
from ddtrace.debugging._probe.model import LineLocationMixin
7+
from ddtrace.debugging._signal.model import Signal
8+
9+
10+
@dataclass
11+
class LogSignal(Signal):
12+
"""A signal that also emits a log message.
13+
14+
Some signals might require sending a log message along with the base signal
15+
data. For example, all the collected errors from expression evaluations
16+
(e.g. conditions) might need to be reported.
17+
"""
18+
19+
@property
20+
@abc.abstractmethod
21+
def message(self) -> t.Optional[str]:
22+
"""The log message to emit."""
23+
pass
24+
25+
@abc.abstractmethod
26+
def has_message(self) -> bool:
27+
"""Whether the signal has a log message to emit."""
28+
pass
29+
30+
@property
31+
def data(self) -> t.Dict[str, t.Any]:
32+
"""Extra data to include in the snapshot portion of the log message."""
33+
return {}
34+
35+
def _probe_details(self) -> t.Dict[str, t.Any]:
36+
probe = self.probe
37+
if isinstance(probe, LineLocationMixin):
38+
location = {
39+
"file": str(probe.resolved_source_file),
40+
"lines": [str(probe.line)],
41+
}
42+
elif isinstance(probe, FunctionLocationMixin):
43+
location = {
44+
"type": probe.module,
45+
"method": probe.func_qname,
46+
}
47+
else:
48+
return {}
49+
50+
return {
51+
"id": probe.probe_id,
52+
"version": probe.version,
53+
"location": location,
54+
}
55+
56+
@property
57+
def snapshot(self) -> t.Dict[str, t.Any]:
58+
full_data = {
59+
"id": self.uuid,
60+
"timestamp": int(self.timestamp * 1e3), # milliseconds
61+
"evaluationErrors": [{"expr": e.expr, "message": e.message} for e in self.errors],
62+
"probe": self._probe_details(),
63+
"language": "python",
64+
}
65+
full_data.update(self.data)
66+
67+
return full_data

ddtrace/debugging/_signal/metric_sample.py

+14-1
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@
44
from typing import cast
55

66
from ddtrace.debugging._metrics import probe_metrics
7+
from ddtrace.debugging._probe.model import MetricFunctionProbe
8+
from ddtrace.debugging._probe.model import MetricLineProbe
79
from ddtrace.debugging._probe.model import MetricProbeKind
810
from ddtrace.debugging._probe.model import MetricProbeMixin
9-
from ddtrace.debugging._signal.model import LogSignal
11+
from ddtrace.debugging._signal.log import LogSignal
12+
from ddtrace.debugging._signal.model import probe_to_signal
1013
from ddtrace.internal.metrics import Metrics
1114

1215

@@ -50,3 +53,13 @@ def message(self) -> Optional[str]:
5053

5154
def has_message(self) -> bool:
5255
return bool(self.errors)
56+
57+
58+
@probe_to_signal.register
59+
def _(probe: MetricFunctionProbe, frame, thread, trace_context, meter):
60+
return MetricSample(probe=probe, frame=frame, thread=thread, trace_context=trace_context, meter=meter)
61+
62+
63+
@probe_to_signal.register
64+
def _(probe: MetricLineProbe, frame, thread, trace_context, meter):
65+
return MetricSample(probe=probe, frame=frame, thread=thread, trace_context=trace_context, meter=meter)

0 commit comments

Comments
 (0)