Skip to content

Commit 10e09e0

Browse files
authored
refactor(profiling): remove legacy Python exporter and enable libdatadog exporter by default (#13599)
Removes python exporter and enable libdatadog exporter by default. Since stack v2 requires libdatadog exporter and it's the default mode, most customers would not be affected by this. We are dropping 32-bit Linux support for profiling, but this can be added back once profiling depends on libdatadog via Rust bindings using PyO3. ## 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 - [x] 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 f5bed1c commit 10e09e0

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+424
-3445
lines changed

ddtrace/internal/datadog/profiling/ddup/__init__.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
# This module supports an optional feature. It may not even load on all platforms or configurations.
2-
# In ddtrace/settings/profiling.py, this module is imported and the is_available attribute is checked to determine
3-
# whether the feature is available. If not, then the feature is disabled and all downstream consumption is
4-
# suppressed.
1+
# In case when _ddup is not available, we set is_available attribute to False,
2+
# and failure_msg to the error message. This module is initially imported in
3+
# ddtrace/settings/profiling.py to determine if profiling can be run. If it
4+
# fails, we turn off profiling in ddtrace/settings/profiling.py
55
is_available = False
66
failure_msg = ""
77

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
# -*- encoding: utf-8 -*-
22
"""Bootstrapping code that is run when using `ddtrace.profiling.auto`."""
3+
4+
import platform
5+
import sys
6+
7+
from ddtrace.internal.logger import get_logger
38
from ddtrace.profiling import bootstrap
49
from ddtrace.profiling import profiler
510

611

12+
LOG = get_logger(__name__)
13+
14+
715
def start_profiler():
816
if hasattr(bootstrap, "profiler"):
917
bootstrap.profiler.stop()
@@ -12,4 +20,12 @@ def start_profiler():
1220
bootstrap.profiler.start()
1321

1422

15-
start_profiler()
23+
if platform.system() == "Linux" and not (sys.maxsize > (1 << 32)):
24+
LOG.error(
25+
"The Datadog Profiler is not supported on 32-bit Linux systems. "
26+
"To use the profiler, please upgrade to a 64-bit Linux system. "
27+
"If you believe this is an error or need assistance, please report it at "
28+
"https://github.com/DataDog/dd-trace-py/issues"
29+
)
30+
else:
31+
start_profiler()

ddtrace/profiling/collector/__init__.py

Lines changed: 7 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,8 @@
11
# -*- encoding: utf-8 -*-
2-
import typing # noqa:F401
3-
42
from ddtrace.internal import periodic
53
from ddtrace.internal import service
64
from ddtrace.settings.profiling import config
75

8-
from .. import event # noqa:F401
9-
from ..recorder import Recorder
10-
116

127
class CollectorError(Exception):
138
pass
@@ -20,16 +15,12 @@ class CollectorUnavailable(CollectorError):
2015
class Collector(service.Service):
2116
"""A profile collector."""
2217

23-
def __init__(self, recorder: typing.Optional[Recorder] = None, *args, **kwargs):
18+
def __init__(self, *args, **kwargs):
2419
super().__init__(*args, **kwargs)
25-
self.recorder = recorder
2620

2721
@staticmethod
2822
def snapshot():
29-
"""Take a snapshot of collected data.
30-
31-
:return: A list of sample list to push in the recorder.
32-
"""
23+
"""Take a snapshot of collected data, to be exported."""
3324

3425

3526
class PeriodicCollector(Collector, periodic.PeriodicService):
@@ -38,18 +29,11 @@ class PeriodicCollector(Collector, periodic.PeriodicService):
3829
__slots__ = ()
3930

4031
def periodic(self):
41-
# type: (...) -> None
42-
"""Collect events and push them into the recorder."""
43-
for events in self.collect():
44-
if self.recorder:
45-
self.recorder.push_events(events)
32+
# This is to simply override periodic.PeriodicService.periodic()
33+
self.collect()
4634

4735
def collect(self):
48-
# type: (...) -> typing.Iterable[typing.Iterable[event.Event]]
49-
"""Collect the actual data.
50-
51-
:return: A list of event list to push in the recorder.
52-
"""
36+
"""Collect the actual data."""
5337
raise NotImplementedError
5438

5539

@@ -77,7 +61,7 @@ def capture(self):
7761

7862

7963
class CaptureSamplerCollector(Collector):
80-
def __init__(self, recorder: typing.Optional[Recorder] = None, capture_pct=config.capture_pct, *args, **kwargs):
81-
super().__init__(recorder, *args, **kwargs)
64+
def __init__(self, capture_pct=config.capture_pct, *args, **kwargs):
65+
super().__init__(*args, **kwargs)
8266
self.capture_pct = capture_pct
8367
self._capture_sampler = CaptureSampler(self.capture_pct)

ddtrace/profiling/collector/_lock.py

Lines changed: 29 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -13,50 +13,12 @@
1313
from ddtrace.internal.datadog.profiling import ddup
1414
from ddtrace.profiling import _threading
1515
from ddtrace.profiling import collector
16-
from ddtrace.profiling import event
1716
from ddtrace.profiling.collector import _task
1817
from ddtrace.profiling.collector import _traceback
19-
from ddtrace.profiling.recorder import Recorder
2018
from ddtrace.settings.profiling import config
2119
from ddtrace.trace import Tracer
2220

2321

24-
class LockEventBase(event.StackBasedEvent):
25-
"""Base Lock event."""
26-
27-
__slots__ = ("lock_name", "sampling_pct")
28-
29-
def __init__(self, lock_name="<unknown lock name>", sampling_pct=0, *args, **kwargs):
30-
super().__init__(*args, **kwargs)
31-
self.lock_name = lock_name
32-
self.sampling_pct = sampling_pct
33-
34-
35-
class LockAcquireEvent(LockEventBase):
36-
"""A lock has been acquired."""
37-
38-
__slots__ = ("wait_time_ns",)
39-
40-
def __init__(self, wait_time_ns=0, *args, **kwargs):
41-
super().__init__(*args, **kwargs)
42-
self.wait_time_ns = wait_time_ns
43-
44-
45-
class LockReleaseEvent(LockEventBase):
46-
"""A lock has been released."""
47-
48-
__slots__ = ("locked_for_ns",)
49-
50-
def __init__(
51-
self,
52-
locked_for_ns=0, # type: int
53-
*args, # type: typing.Any
54-
**kwargs, # type: typing.Any
55-
):
56-
super().__init__(*args, **kwargs)
57-
self.locked_for_ns: int = locked_for_ns
58-
59-
6022
def _current_thread():
6123
# type: (...) -> typing.Tuple[int, str]
6224
thread_id = _thread.get_ident()
@@ -78,26 +40,19 @@ def _current_thread():
7840

7941

8042
class _ProfiledLock(wrapt.ObjectProxy):
81-
ACQUIRE_EVENT_CLASS = LockAcquireEvent
82-
RELEASE_EVENT_CLASS = LockReleaseEvent
83-
8443
def __init__(
8544
self,
8645
wrapped: typing.Any,
87-
recorder: Recorder,
8846
tracer: typing.Optional[Tracer],
8947
max_nframes: int,
9048
capture_sampler: collector.CaptureSampler,
9149
endpoint_collection_enabled: bool,
92-
export_libdd_enabled: bool,
9350
) -> None:
9451
wrapt.ObjectProxy.__init__(self, wrapped)
95-
self._self_recorder = recorder
9652
self._self_tracer = tracer
9753
self._self_max_nframes = max_nframes
9854
self._self_capture_sampler = capture_sampler
9955
self._self_endpoint_collection_enabled = endpoint_collection_enabled
100-
self._self_export_libdd_enabled = export_libdd_enabled
10156
frame = sys._getframe(2 if WRAPT_C_EXT else 3)
10257
code = frame.f_code
10358
self._self_init_loc = "%s:%d" % (os.path.basename(code.co_filename), frame.f_lineno)
@@ -131,41 +86,23 @@ def _acquire(self, inner_func, *args, **kwargs):
13186
else:
13287
frame = task_frame
13388

134-
frames, nframes = _traceback.pyframe_to_frames(frame, self._self_max_nframes)
89+
frames, _ = _traceback.pyframe_to_frames(frame, self._self_max_nframes)
13590

136-
if self._self_export_libdd_enabled:
137-
thread_native_id = _threading.get_thread_native_id(thread_id)
91+
thread_native_id = _threading.get_thread_native_id(thread_id)
13892

139-
handle = ddup.SampleHandle()
140-
handle.push_monotonic_ns(end)
141-
handle.push_lock_name(lock_name)
142-
handle.push_acquire(end - start, 1) # AFAICT, capture_pct does not adjust anything here
143-
handle.push_threadinfo(thread_id, thread_native_id, thread_name)
144-
handle.push_task_id(task_id)
145-
handle.push_task_name(task_name)
93+
handle = ddup.SampleHandle()
94+
handle.push_monotonic_ns(end)
95+
handle.push_lock_name(lock_name)
96+
handle.push_acquire(end - start, 1) # AFAICT, capture_pct does not adjust anything here
97+
handle.push_threadinfo(thread_id, thread_native_id, thread_name)
98+
handle.push_task_id(task_id)
99+
handle.push_task_name(task_name)
146100

147-
if self._self_tracer is not None:
148-
handle.push_span(self._self_tracer.current_span())
149-
for frame in frames:
150-
handle.push_frame(frame.function_name, frame.file_name, 0, frame.lineno)
151-
handle.flush_sample()
152-
else:
153-
event = self.ACQUIRE_EVENT_CLASS(
154-
lock_name=lock_name,
155-
frames=frames,
156-
nframes=nframes,
157-
thread_id=thread_id,
158-
thread_name=thread_name,
159-
task_id=task_id,
160-
task_name=task_name,
161-
wait_time_ns=end - start,
162-
sampling_pct=self._self_capture_sampler.capture_pct,
163-
)
164-
165-
if self._self_tracer is not None:
166-
event.set_trace_info(self._self_tracer.current_span(), self._self_endpoint_collection_enabled)
167-
168-
self._self_recorder.push_event(event)
101+
if self._self_tracer is not None:
102+
handle.push_span(self._self_tracer.current_span())
103+
for frame in frames:
104+
handle.push_frame(frame.function_name, frame.file_name, 0, frame.lineno)
105+
handle.flush_sample()
169106
except Exception:
170107
pass # nosec
171108

@@ -207,41 +144,23 @@ def _release(self, inner_func, *args, **kwargs):
207144
else:
208145
frame = task_frame
209146

210-
frames, nframes = _traceback.pyframe_to_frames(frame, self._self_max_nframes)
147+
frames, _ = _traceback.pyframe_to_frames(frame, self._self_max_nframes)
211148

212-
if self._self_export_libdd_enabled:
213-
thread_native_id = _threading.get_thread_native_id(thread_id)
149+
thread_native_id = _threading.get_thread_native_id(thread_id)
214150

215-
handle = ddup.SampleHandle()
216-
handle.push_monotonic_ns(end)
217-
handle.push_lock_name(lock_name)
218-
handle.push_release(end - start, 1) # AFAICT, capture_pct does not adjust anything here
219-
handle.push_threadinfo(thread_id, thread_native_id, thread_name)
220-
handle.push_task_id(task_id)
221-
handle.push_task_name(task_name)
151+
handle = ddup.SampleHandle()
152+
handle.push_monotonic_ns(end)
153+
handle.push_lock_name(lock_name)
154+
handle.push_release(end - start, 1) # AFAICT, capture_pct does not adjust anything here
155+
handle.push_threadinfo(thread_id, thread_native_id, thread_name)
156+
handle.push_task_id(task_id)
157+
handle.push_task_name(task_name)
222158

223-
if self._self_tracer is not None:
224-
handle.push_span(self._self_tracer.current_span())
225-
for frame in frames:
226-
handle.push_frame(frame.function_name, frame.file_name, 0, frame.lineno)
227-
handle.flush_sample()
228-
else:
229-
event = self.RELEASE_EVENT_CLASS(
230-
lock_name=lock_name,
231-
frames=frames,
232-
nframes=nframes,
233-
thread_id=thread_id,
234-
thread_name=thread_name,
235-
task_id=task_id,
236-
task_name=task_name,
237-
locked_for_ns=end - start,
238-
sampling_pct=self._self_capture_sampler.capture_pct,
239-
)
240-
241-
if self._self_tracer is not None:
242-
event.set_trace_info(self._self_tracer.current_span(), self._self_endpoint_collection_enabled)
243-
244-
self._self_recorder.push_event(event)
159+
if self._self_tracer is not None:
160+
handle.push_span(self._self_tracer.current_span())
161+
for frame in frames:
162+
handle.push_frame(frame.function_name, frame.file_name, 0, frame.lineno)
163+
handle.flush_sample()
245164

246165
def release(self, *args, **kwargs):
247166
return self._release(self.__wrapped__.release, *args, **kwargs)
@@ -313,23 +232,17 @@ class LockCollector(collector.CaptureSamplerCollector):
313232

314233
def __init__(
315234
self,
316-
recorder: typing.Optional[Recorder] = None,
317235
nframes=config.max_frames,
318236
endpoint_collection_enabled=config.endpoint_collection,
319-
export_libdd_enabled=config.export.libdd_enabled,
320237
tracer=None,
321238
*args,
322239
**kwargs,
323240
):
324-
super().__init__(recorder, *args, **kwargs)
241+
super().__init__(*args, **kwargs)
325242
self.nframes = nframes
326243
self.endpoint_collection_enabled = endpoint_collection_enabled
327-
self.export_libdd_enabled = export_libdd_enabled
328244
self.tracer = tracer
329245
self._original = None
330-
# Check if libdd is available, if not, disable the feature
331-
if self.export_libdd_enabled and not ddup.is_available:
332-
self.export_libdd_enabled = False
333246

334247
@abc.abstractmethod
335248
def _get_patch_target(self):
@@ -367,12 +280,10 @@ def _allocate_lock(wrapped, instance, args, kwargs):
367280
lock = wrapped(*args, **kwargs)
368281
return self.PROFILED_LOCK_CLASS(
369282
lock,
370-
self.recorder,
371283
self.tracer,
372284
self.nframes,
373285
self._capture_sampler,
374286
self.endpoint_collection_enabled,
375-
self.export_libdd_enabled,
376287
)
377288

378289
self._set_patch_target(FunctionWrapper(self._original, _allocate_lock))

ddtrace/profiling/collector/_task.pyx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import sys
21
from types import ModuleType
32
import weakref
43

@@ -23,7 +22,7 @@ def install_greenlet_tracer(gevent):
2322
from greenlet import settrace
2423
except ImportError:
2524
# We don't seem to have the required dependencies.
26-
return
25+
return
2726

2827
class DDGreenletTracer(object):
2928
def __init__(self, gevent):

ddtrace/profiling/collector/asyncio.py

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,8 @@
44
from . import _lock
55

66

7-
class AsyncioLockAcquireEvent(_lock.LockAcquireEvent):
8-
"""An asyncio.Lock has been acquired."""
9-
10-
__slots__ = ()
11-
12-
13-
class AsyncioLockReleaseEvent(_lock.LockReleaseEvent):
14-
"""An asyncio.Lock has been released."""
15-
16-
__slots__ = ()
17-
18-
197
class _ProfiledAsyncioLock(_lock._ProfiledLock):
20-
ACQUIRE_EVENT_CLASS = AsyncioLockAcquireEvent
21-
RELEASE_EVENT_CLASS = AsyncioLockReleaseEvent
8+
pass
229

2310

2411
class AsyncioLockCollector(_lock.LockCollector):

0 commit comments

Comments
 (0)