Skip to content

Commit 9f4f8b8

Browse files
authored
chore(runtime_metrics): add process_tags (#15465)
This PR implements this [RFC](https://docs.google.com/document/d/1AFdLUuVk70i0bJd5335-RxqsvwAV9ovAqcO2z5mEMbA/edit?pli=1&tab=t.0#heading=h.s9l1lctqlg11) for Runtime Metrics. Add process_tags to Runtime Metrics ## Testing - Check that process tags are empty when deactivated - Check the process tags are created with the right values when activated
1 parent 7bff945 commit 9f4f8b8

File tree

3 files changed

+59
-3
lines changed

3 files changed

+59
-3
lines changed

ddtrace/internal/runtime/runtime_metrics.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from .metric_collectors import PSUtilRuntimeMetricCollector
1919
from .tag_collectors import PlatformTagCollector
2020
from .tag_collectors import PlatformTagCollectorV2
21+
from .tag_collectors import ProcessTagCollector
2122
from .tag_collectors import TracerTagCollector
2223

2324

@@ -59,6 +60,12 @@ class TracerTags(RuntimeCollectorsIterable):
5960
COLLECTORS = [TracerTagCollector]
6061

6162

63+
class ProcessTags(RuntimeCollectorsIterable):
64+
# DEV: `None` means to allow all tags generated by ProcessTagsCollector
65+
ENABLED = None
66+
COLLECTORS = [ProcessTagCollector]
67+
68+
6269
class RuntimeMetrics(RuntimeCollectorsIterable):
6370
ENABLED = DEFAULT_RUNTIME_METRICS
6471
COLLECTORS = [
@@ -94,6 +101,8 @@ def __init__(self, interval=DEFAULT_RUNTIME_METRICS_INTERVAL, tracer=None, dogst
94101
else:
95102
self._platform_tags = self._format_tags(PlatformTags())
96103

104+
self._process_tags: List[str] = ProcessTags() # type: ignore[assignment]
105+
97106
@classmethod
98107
def disable(cls) -> None:
99108
with cls._lock:
@@ -140,9 +149,9 @@ def enable(
140149

141150
def flush(self) -> None:
142151
# Ensure runtime metrics have up-to-date tags (ex: service, env, version)
143-
rumtime_tags = self._format_tags(TracerTags()) + self._platform_tags
144-
log.debug("Sending runtime metrics with the following tags: %s", rumtime_tags)
145-
self._dogstatsd_client.constant_tags = rumtime_tags
152+
runtime_tags = self._format_tags(TracerTags()) + self._platform_tags + self._process_tags
153+
log.debug("Sending runtime metrics with the following tags: %s", runtime_tags)
154+
self._dogstatsd_client.constant_tags = runtime_tags
146155

147156
with self._dogstatsd_client:
148157
for key, value in self._runtime_metrics:

ddtrace/internal/runtime/tag_collectors.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from ...constants import ENV_KEY
77
from ...constants import VERSION_KEY
8+
from .. import process_tags
89
from ..constants import DEFAULT_SERVICE_NAME
910
from .collector import ValueCollector
1011
from .constants import LANG
@@ -97,3 +98,13 @@ def collect_fn(self, keys):
9798
tags = super(PlatformTagCollectorV2, self).collect_fn(keys)
9899
tags.append(("runtime-id", get_runtime_id()))
99100
return tags
101+
102+
103+
class ProcessTagCollector(RuntimeTagCollector):
104+
"""Tag collector for process tags."""
105+
106+
def collect_fn(self, keys):
107+
# DEV: we do not access direct process_tags_list so we can
108+
# reload it in the tests
109+
process_tags_list = process_tags.process_tags_list
110+
return process_tags_list or []

tests/tracer/runtime/test_tag_collectors.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,39 @@ def process_trace(self, _):
104104
assert values == [
105105
("service", "my-service"),
106106
], values
107+
108+
109+
def test_process_tags_disabled_by_default():
110+
ptc = tag_collectors.ProcessTagCollector()
111+
tags = list(ptc.collect())
112+
assert len(tags) == 0, "Process tags should be empty when not enabled"
113+
114+
115+
@pytest.mark.subprocess(env={"DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED": "true"})
116+
def test_process_tags_enabled():
117+
from unittest.mock import patch
118+
119+
from ddtrace.internal.process_tags import ENTRYPOINT_BASEDIR_TAG
120+
from ddtrace.internal.process_tags import ENTRYPOINT_NAME_TAG
121+
from ddtrace.internal.process_tags import ENTRYPOINT_TYPE_TAG
122+
from ddtrace.internal.process_tags import ENTRYPOINT_WORKDIR_TAG
123+
from ddtrace.internal.runtime import tag_collectors
124+
from tests.utils import process_tag_reload
125+
126+
with patch("sys.argv", ["/path/to/test_script.py"]), patch("os.getcwd", return_value="/path/to/workdir"):
127+
process_tag_reload()
128+
129+
ptc = tag_collectors.ProcessTagCollector()
130+
tags: list[str] = ptc.collect()
131+
assert len(tags) == 4, f"Expected 4 process tags, got {len(tags)}: {tags}"
132+
133+
tags_dict = {k: v for k, v in (s.split(":") for s in tags)}
134+
assert ENTRYPOINT_NAME_TAG in tags_dict
135+
assert ENTRYPOINT_WORKDIR_TAG in tags_dict
136+
assert ENTRYPOINT_BASEDIR_TAG in tags_dict
137+
assert ENTRYPOINT_TYPE_TAG in tags_dict
138+
139+
assert tags_dict[ENTRYPOINT_NAME_TAG] == "test_script"
140+
assert tags_dict[ENTRYPOINT_WORKDIR_TAG] == "workdir"
141+
assert tags_dict[ENTRYPOINT_BASEDIR_TAG] == "to"
142+
assert tags_dict[ENTRYPOINT_TYPE_TAG] == "script"

0 commit comments

Comments
 (0)