Skip to content

Commit 60a975f

Browse files
chore(profiling): remove stack v1 impl (#15237)
## Description Remove Stack v1 impl, `StackCollector.collect_stack()`. There still are `tests/profiling` and `tests/profiling_v2` directories, and cleaning up those two will be done in a follow up PR. One notable improvement from this PR is that `StackCollector` no longer inherits from `periodic.PeriodicCollector`, and `PeriodicCollector` is also deleted. `PeriodicCollector` provided a mechanism to invoke a Python function periodically using a background native thread. For `StackCollector` it was needed to sample stack. `stack_v2` creates its own background native thread and handles adaptive sampling its own. So we no longer need to have fields such as `interval`, `max_time_usage_pct` etc in `StackCollector`. <!-- Provide an overview of the change and motivation for the change --> ## Testing <!-- Describe your testing strategy or note what tests are included --> ## Risks <!-- Note any risks associated with this change, or "None" if no risks --> ## Additional Notes <!-- Any other information that would be helpful for reviewers --> [PROF-12836](https://datadoghq.atlassian.net/browse/PROF-12836) [PROF-12836]: https://datadoghq.atlassian.net/browse/PROF-12836?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ --------- Co-authored-by: T. Kowalski <thomas.kowalski@datadoghq.com>
1 parent 75a19c2 commit 60a975f

File tree

18 files changed

+112
-1378
lines changed

18 files changed

+112
-1378
lines changed

ddtrace/internal/datadog/profiling/stack_v2/__init__.pyi

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,39 @@ import asyncio
22
from types import FrameType
33
from typing import Optional, Sequence, Union
44

5-
def register_thread(id: int, native_id: int, name: str) -> None: ... # noqa: A002
5+
from ddtrace._trace import context
6+
from ddtrace._trace import span as ddspan
7+
8+
# Core stack v2 functions
9+
def start(min_interval: float = ...) -> bool: ...
10+
def stop() -> None: ...
11+
12+
# Sampling configuration
13+
def set_adaptive_sampling(do_adaptive_sampling: bool = False) -> None: ...
14+
def set_interval(new_interval: float) -> None: ...
15+
16+
# span <-> profile association
17+
def link_span(span: Optional[Union[context.Context, ddspan.Span]]) -> None: ...
18+
19+
# Thread management
20+
def register_thread(python_thread_id: int, native_id: int, name: str) -> None: ...
621
def unregister_thread(name: str) -> None: ...
22+
23+
# Asyncio support
724
def track_asyncio_loop(thread_id: int, loop: Optional[asyncio.AbstractEventLoop]) -> None: ...
825
def link_tasks(parent: asyncio.AbstractEventLoop, child: asyncio.Task) -> None: ...
926
def init_asyncio(
1027
current_tasks: Sequence[asyncio.Task],
1128
scheduled_tasks: Sequence[asyncio.Task],
1229
eager_tasks: Optional[Sequence[asyncio.Task]],
1330
) -> None: ...
31+
32+
# Greenlet support
1433
def track_greenlet(greenlet_id: int, name: str, frame: Union[FrameType, bool, None]) -> None: ...
1534
def untrack_greenlet(greenlet_id: int) -> None: ...
1635
def link_greenlets(greenlet_id: int, parent_id: int) -> None: ...
1736
def update_greenlet_frame(greenlet_id: int, frame: Union[FrameType, bool, None]) -> None: ...
1837

38+
# Module attributes
1939
is_available: bool
2040
failure_msg: str

ddtrace/profiling/collector/__init__.py

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# -*- encoding: utf-8 -*-
22
import typing
33

4-
from ddtrace.internal import periodic
54
from ddtrace.internal import service
65
from ddtrace.internal.settings.profiling import config
76

@@ -25,20 +24,6 @@ def snapshot() -> None:
2524
"""Take a snapshot of collected data, to be exported."""
2625

2726

28-
class PeriodicCollector(Collector, periodic.PeriodicService):
29-
"""A collector that needs to run periodically."""
30-
31-
__slots__ = ()
32-
33-
def periodic(self) -> None:
34-
# This is to simply override periodic.PeriodicService.periodic()
35-
self.collect()
36-
37-
def collect(self) -> None:
38-
"""Collect the actual data."""
39-
raise NotImplementedError
40-
41-
4227
class CaptureSampler(object):
4328
"""Determine the events that should be captured based on a sampling percentage."""
4429

ddtrace/profiling/collector/_task.pyi

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,3 @@ import typing
44
def get_task(
55
thread_id: int,
66
) -> typing.Tuple[typing.Optional[int], typing.Optional[str], typing.Optional[types.FrameType]]: ...
7-
def list_tasks() -> typing.List[typing.Tuple[int, str, types.FrameType]]: ...

ddtrace/profiling/collector/_task.pyx

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -108,43 +108,3 @@ cpdef get_task(thread_id):
108108
frame = _gevent_tracer.active_greenlet.gr_frame
109109

110110
return task_id, task_name, frame
111-
112-
113-
cpdef list_tasks(thread_id):
114-
# type: (...) -> typing.List[typing.Tuple[int, str, types.FrameType]]
115-
"""Return the list of running tasks.
116-
117-
This is computed for gevent by taking the list of existing threading.Thread object and removing if any real OS
118-
thread that might be running.
119-
120-
:return: [(task_id, task_name, task_frame), ...]"""
121-
122-
tasks = []
123-
124-
if not is_stack_v2 and _gevent_tracer is not None:
125-
if type(_threading.get_thread_by_id(thread_id)).__name__.endswith("_MainThread"):
126-
# Under normal circumstances, the Hub is running in the main thread.
127-
# Python will only ever have a single instance of a _MainThread
128-
# class, so if we find it we attribute all the greenlets to it.
129-
tasks.extend(
130-
[
131-
(
132-
greenlet_id,
133-
_threading.get_thread_name(greenlet_id),
134-
greenlet.gr_frame
135-
)
136-
for greenlet_id, greenlet in dict(_gevent_tracer.greenlets).items()
137-
if not greenlet.dead
138-
]
139-
)
140-
141-
loop = _asyncio.get_event_loop_for_thread(thread_id)
142-
if loop is not None:
143-
tasks.extend([
144-
(id(task),
145-
_asyncio._task_get_name(task),
146-
_asyncio_task_get_frame(task))
147-
for task in _asyncio.all_tasks(loop)
148-
])
149-
150-
return tasks
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
"""Simple wrapper around stack_v2 native extension module."""
2+
3+
import logging
4+
import typing
5+
6+
from ddtrace.internal import core
7+
from ddtrace.internal.datadog.profiling import stack_v2
8+
from ddtrace.internal.settings.profiling import config
9+
from ddtrace.profiling import collector
10+
from ddtrace.profiling.collector import threading
11+
from ddtrace.trace import Tracer
12+
13+
14+
LOG = logging.getLogger(__name__)
15+
16+
17+
class StackCollector(collector.Collector):
18+
"""Execution stacks collector."""
19+
20+
__slots__ = (
21+
"nframes",
22+
"tracer",
23+
)
24+
25+
def __init__(self, nframes: typing.Optional[int] = None, tracer: typing.Optional[Tracer] = None):
26+
super().__init__()
27+
28+
self.nframes = nframes if nframes is not None else config.max_frames
29+
self.tracer = tracer
30+
31+
def __repr__(self) -> str:
32+
class_name = self.__class__.__name__
33+
attrs = {k: v for k, v in self.__dict__.items() if not k.startswith("_")}
34+
attrs_str = ", ".join(f"{k}={v!r}" for k, v in attrs.items())
35+
36+
slot_attrs = {slot: getattr(self, slot) for slot in self.__slots__ if not slot.startswith("_")}
37+
slot_attrs_str = ", ".join(f"{k}={v!r}" for k, v in slot_attrs.items())
38+
39+
return f"{class_name}({attrs_str}, {slot_attrs_str})"
40+
41+
def _init(self) -> None:
42+
if self.tracer is not None:
43+
core.on("ddtrace.context_provider.activate", stack_v2.link_span)
44+
45+
# stack v2 requires us to patch the Threading module. It's possible to do this from the stack v2 code
46+
# itself, but it's a little bit fiddly and it's easier to make it correct here.
47+
# TODO take the `threading` import out of here and just handle it in v2 startup
48+
threading.init_stack_v2()
49+
stack_v2.set_adaptive_sampling(config.stack.v2_adaptive_sampling)
50+
stack_v2.start()
51+
52+
def _start_service(self) -> None:
53+
# This is split in its own function to ease testing
54+
LOG.debug("Profiling StackCollector starting")
55+
self._init()
56+
LOG.debug("Profiling StackCollector started")
57+
58+
def _stop_service(self) -> None:
59+
LOG.debug("Profiling StackCollector stopping")
60+
if self.tracer is not None:
61+
core.reset_listeners("ddtrace.context_provider.activate", stack_v2.link_span)
62+
LOG.debug("Profiling StackCollector stopped")
63+
64+
# Tell the native thread running the v2 sampler to stop
65+
stack_v2.stop()

ddtrace/profiling/collector/stack.pyi

Lines changed: 0 additions & 11 deletions
This file was deleted.

0 commit comments

Comments
 (0)