Skip to content

Commit 68e11c7

Browse files
authored
Check for agent version in client side stats (#3811)
1 parent 3df62fd commit 68e11c7

8 files changed

Lines changed: 62 additions & 6 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

components-rs/agent_info.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ pub unsafe extern "C" fn ddog_apply_agent_info(
5757
info.filter_tags_regex.as_ref().and_then(|f| f.require.as_deref()).unwrap_or(&[]).to_owned(),
5858
info.filter_tags_regex.as_ref().and_then(|f| f.reject.as_deref()).unwrap_or(&[]).to_owned(),
5959
info.ignore_resources.as_deref().unwrap_or(&[]).to_owned(),
60+
info.client_drop_p0s.unwrap_or(false),
61+
info.version.as_deref(),
6062
);
6163
}
6264
}
@@ -87,6 +89,8 @@ pub unsafe extern "C" fn ddog_apply_agent_info_concentrator_config(
8789
info.filter_tags_regex.as_ref().and_then(|f| f.require.as_deref()).unwrap_or(&[]).to_owned(),
8890
info.filter_tags_regex.as_ref().and_then(|f| f.reject.as_deref()).unwrap_or(&[]).to_owned(),
8991
info.ignore_resources.as_deref().unwrap_or(&[]).to_owned(),
92+
info.client_drop_p0s.unwrap_or(false),
93+
info.version.as_deref(),
9094
);
9195
}
9296
}

components-rs/ddtrace.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,13 @@ bool ddog_exception_hash_limiter_inc(struct ddog_SidecarTransport *connection,
181181
*/
182182
bool ddog_is_agent_info_ready(void);
183183

184+
/**
185+
* Returns true when the agent supports client-side stats computation:
186+
* agent info has been received, `client_drop_p0s` is true, and the reported
187+
* agent version is >= 7.65.0.
188+
*/
189+
bool ddog_agent_has_stats_computation(void);
190+
184191
/**
185192
* Look up (or lazily create) the concentrator for `(env, version, service)` and invoke
186193
* `callback` with a shared reference to it while holding the global read lock.

components-rs/stats.rs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ pub struct PhpSpanStats<'a> {
7979
}
8080

8181
#[inline]
82-
fn char_slice_str(s: CharSlice) -> &str {
82+
fn char_slice_str(s: CharSlice<'_>) -> &str {
8383
s.try_to_utf8().unwrap_or("")
8484
}
8585

@@ -232,6 +232,19 @@ static SPAN_CONCENTRATORS: LazyLock<RwLock<HashMap<String, SpanConcentrator>>> =
232232
/// i.e. the sidecar has received and applied the agent's /info response.
233233
static AGENT_INFO_RECEIVED: AtomicBool = AtomicBool::new(false);
234234

235+
/// Set to true once agent info confirms `client_drop_p0s` is true and the
236+
/// reported agent version is >= 7.65.0. Cached so the per-span hot-path is a
237+
/// single atomic load.
238+
static STATS_COMPUTATION_READY: AtomicBool = AtomicBool::new(false);
239+
240+
fn agent_version_ge(version: Option<&str>, req_major: u32, req_minor: u32, req_patch: u32) -> bool {
241+
let Some(v) = version else { return false; };
242+
let v = v.split('-').next().unwrap_or(v); // strip agent version suffixes
243+
let mut parts = v.split('.').filter_map(|p| p.parse::<u32>().ok());
244+
let (Some(major), Some(minor), Some(patch)) = (parts.next(), parts.next(), parts.next()) else { return false; };
245+
(major, minor, patch) >= (req_major, req_minor, req_patch)
246+
}
247+
235248
/// Returns true once the agent /info has been received and applied.
236249
/// Used by the PHP extension to skip stats computation until the concentrator
237250
/// has been properly initialised with peer-tag keys and span kinds.
@@ -240,6 +253,14 @@ pub extern "C" fn ddog_is_agent_info_ready() -> bool {
240253
AGENT_INFO_RECEIVED.load(Ordering::Acquire)
241254
}
242255

256+
/// Returns true when the agent supports client-side stats computation:
257+
/// agent info has been received, `client_drop_p0s` is true, and the reported
258+
/// agent version is >= 7.65.0.
259+
#[no_mangle]
260+
pub extern "C" fn ddog_agent_has_stats_computation() -> bool {
261+
STATS_COMPUTATION_READY.load(Ordering::Acquire)
262+
}
263+
243264
/// Desired concentrator configuration sourced from the agent's /info endpoint.
244265
/// Populated via `ddog_apply_agent_info`; applied to every concentrator
245266
/// at creation time and when the config changes.
@@ -253,6 +274,7 @@ static DESIRED_CONFIG: LazyLock<RwLock<DesiredConfig>> = LazyLock::new(|| RwLock
253274

254275
/// Apply updated concentrator config (peer-tag keys, span kinds, trace filters) to
255276
/// `DESIRED_CONFIG` and propagate peer-tag / span-kind changes to all open concentrators.
277+
/// Also updates the `STATS_COMPUTATION_READY` flag based on `client_drop_p0s` and `version`.
256278
pub(crate) fn apply_concentrator_config(
257279
peer_tag_keys: Vec<String>,
258280
span_kinds: Vec<String>,
@@ -261,12 +283,16 @@ pub(crate) fn apply_concentrator_config(
261283
regex_require: Vec<String>,
262284
regex_reject: Vec<String>,
263285
ignore_resources: Vec<String>,
286+
client_drop_p0s: bool,
287+
version: Option<&str>,
264288
) {
265289
let compiled = trace_filter::compile_trace_filter(
266290
&tags_require, &tags_reject, &regex_require, &regex_reject, &ignore_resources,
267291
);
268292
trace_filter::set_trace_filter(compiled);
269293
AGENT_INFO_RECEIVED.store(true, Ordering::Release);
294+
let ready = client_drop_p0s && agent_version_ge(version, 7, 65, 0);
295+
STATS_COMPUTATION_READY.store(ready, Ordering::Release);
270296
{
271297
let mut dc = DESIRED_CONFIG.write().unwrap();
272298
dc.peer_tag_keys = peer_tag_keys.clone();

ext/serializer.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1534,7 +1534,7 @@ ddog_SpanBytes *ddtrace_serialize_span_to_rust_span(ddtrace_span_data *span, ddo
15341534
ZEND_HASH_FOREACH_END();
15351535

15361536

1537-
if (!span_sampling_applied && DDTRACE_G(sidecar) && get_DD_TRACE_STATS_COMPUTATION_ENABLED() && ddog_is_agent_info_ready()) {
1537+
if (!span_sampling_applied && DDTRACE_G(sidecar) && get_DD_TRACE_STATS_COMPUTATION_ENABLED() && ddog_agent_has_stats_computation()) {
15381538
if (inferred_span) {
15391539
// Inferred span won't be serialized, so feed it to the concentrator here.
15401540
ddtrace_span_precomputed inferred_pre;
@@ -1779,7 +1779,7 @@ ddog_SpanBytes *ddtrace_serialize_span_to_rust_span(ddtrace_span_data *span, ddo
17791779
zend_string_release(Z_STR(prop_root_service_as_string));
17801780
zend_string_release(Z_STR(prop_service_as_string));
17811781

1782-
if (DDTRACE_G(sidecar) && get_DD_TRACE_STATS_COMPUTATION_ENABLED() && ddog_is_agent_info_ready()) {
1782+
if (DDTRACE_G(sidecar) && get_DD_TRACE_STATS_COMPUTATION_ENABLED() && ddog_agent_has_stats_computation()) {
17831783
ddtrace_feed_span_to_concentrator(span, &pre);
17841784
}
17851785

tests/ext/request-replayer/client_side_stats.phpt

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,23 @@
22
Client-side SHM span stats are computed and flushed to the agent on trace flush
33
--SKIPIF--
44
<?php include __DIR__ . '/../includes/skipif_no_dev_env.inc'; ?>
5+
<?php
6+
if (PHP_VERSION_ID < 70400) die("skip: Before PHP 7.4, the skip-task would cause the sidecar to fetch the info already.");
7+
if (PHP_VERSION_ID >= 80100) {
8+
echo "nocache\n";
9+
}
10+
$ctx = stream_context_create([
11+
'http' => [
12+
'method' => 'PUT',
13+
'header' => [
14+
'Content-Type: application/json',
15+
'X-Datadog-Test-Session-Token: client_side_stats',
16+
],
17+
'content' => json_encode(['version' => '7.65.0', 'client_drop_p0s' => true]),
18+
]
19+
]);
20+
file_get_contents('http://request-replayer/set-agent-info', false, $ctx);
21+
?>
522
--ENV--
623
DD_AGENT_HOST=request-replayer
724
DD_TRACE_AGENT_PORT=80
@@ -21,8 +38,7 @@ include __DIR__ . '/../includes/request_replayer.inc';
2138

2239
$rr = new RequestReplayer();
2340

24-
// Block until the sidecar has received the agent's /info response so that
25-
// ddog_is_agent_info_ready() returns true before stats are computed.
41+
// Block until the sidecar has received the agent's /info response before stats are computed
2642
dd_trace_internal_fn('await_agent_info');
2743

2844
$root = \DDTrace\start_trace_span();

tests/ext/request-replayer/client_side_stats_peer_tags.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ $ctx = stream_context_create([
1414
'Content-Type: application/json',
1515
'X-Datadog-Test-Session-Token: client_side_stats_peer_tags',
1616
],
17-
'content' => '{"peer_tags":["db.hostname"]}',
17+
'content' => json_encode(['version' => '7.65.0', 'client_drop_p0s' => true, 'peer_tags' => ['db.hostname']]),
1818
]
1919
]);
2020
file_get_contents('http://request-replayer/set-agent-info', false, $ctx);

tests/ext/request-replayer/client_side_stats_trace_filters.phpt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ $ctx = stream_context_create([
2424
'X-Datadog-Test-Session-Token: client_side_stats_trace_filters',
2525
],
2626
'content' => json_encode([
27+
'version' => '7.65.0',
28+
'client_drop_p0s' => true,
2729
'filter_tags' => [
2830
'require' => ['filter_required:yes'],
2931
'reject' => ['filter_reject:yes'],

0 commit comments

Comments
 (0)