Skip to content

Commit 469e63e

Browse files
ZNeumannlavarou
andauthored
feat(agent): Add sampling control based on upstream sampling decisions (#1104)
This implements the sampling spec to allow control over the agent sampling decisions based on the value in the sampled flag of an inbound traceparent header. --------- Co-authored-by: Michal Nowacki <[email protected]>
1 parent abdf991 commit 469e63e

17 files changed

+875
-0
lines changed

agent/php_newrelic.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,16 @@ nrinibool_t span_events_enabled; /* newrelic.span_events_enabled */
494494
nriniuint_t
495495
span_events_max_samples_stored; /* newrelic.span_events.max_samples_stored
496496
*/
497+
498+
nrinistr_t dt_remote_parent_sampled; /* newrelic.distributed_tracing.sampler.remote_parent_sampled */
499+
nrinistr_t
500+
dt_remote_parent_not_sampled; /* newrelic.distributed_tracing.sampler.remote_parent_not_sampled */
501+
/* decoding of newrelic.distributed_tracing.sampler.remote_parent_sampled and
502+
* newrelic.distributed_tracing.sampler.remote_parent_not_sampled.
503+
*/
504+
nr_upstream_parent_sampling_control_t dt_sampler_parent_sampled;
505+
nr_upstream_parent_sampling_control_t dt_sampler_parent_not_sampled;
506+
497507
nrinistr_t
498508
trace_observer_host; /* newrelic.infinite_tracing.trace_observer.host */
499509
nriniuint_t

agent/php_nrini.c

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1970,6 +1970,55 @@ static PHP_INI_MH(nr_wordpress_hooks_options_mh) {
19701970
return SUCCESS;
19711971
}
19721972

1973+
static PHP_INI_MH(nr_dt_sampler_remote_parent_mh) {
1974+
nrinistr_t* p;
1975+
1976+
char* base = (char*)mh_arg2;
1977+
p = (nrinistr_t*)(base + (size_t)mh_arg1);
1978+
bool parent_sampled = false;
1979+
1980+
(void)mh_arg3;
1981+
NR_UNUSED_TSRMLS;
1982+
1983+
p->where = 0;
1984+
1985+
if (0 == nr_strcmp(ZEND_STRING_VALUE(entry->name),
1986+
"newrelic.distributed_tracing.sampler.remote_parent_sampled")) {
1987+
parent_sampled = true;
1988+
}
1989+
1990+
if (0 == nr_strcmp(NEW_VALUE, "default")) {
1991+
if (parent_sampled) {
1992+
NRPRG(dt_sampler_parent_sampled) = DEFAULT;
1993+
} else {
1994+
NRPRG(dt_sampler_parent_not_sampled) = DEFAULT;
1995+
}
1996+
} else if (0 == nr_strcmp(NEW_VALUE, "always_on")) {
1997+
if (parent_sampled) {
1998+
NRPRG(dt_sampler_parent_sampled) = ALWAYS_KEEP;
1999+
} else {
2000+
NRPRG(dt_sampler_parent_not_sampled) = ALWAYS_KEEP;
2001+
}
2002+
} else if (0 == nr_strcmp(NEW_VALUE, "always_off")) {
2003+
if (parent_sampled) {
2004+
NRPRG(dt_sampler_parent_sampled) = ALWAYS_DROP;
2005+
} else {
2006+
NRPRG(dt_sampler_parent_not_sampled) = ALWAYS_DROP;
2007+
}
2008+
} else {
2009+
nrl_warning(NRL_INIT, "Invalid %s value \"%s\"; using \"%s\" instead.",
2010+
ZEND_STRING_VALUE(entry->name), NEW_VALUE,
2011+
DEFAULT_WORDPRESS_HOOKS_OPTIONS);
2012+
/* This will cause PHP to call the handler again with default value */
2013+
return FAILURE;
2014+
}
2015+
2016+
p->where = stage;
2017+
p->value = NEW_VALUE;
2018+
2019+
return SUCCESS;
2020+
}
2021+
19732022
/*
19742023
* Now for the actual INI entry table. Please note there are two types of INI
19752024
* entry specification used.
@@ -2938,6 +2987,24 @@ STD_PHP_INI_ENTRY_EX("newrelic.distributed_tracing_exclude_newrelic_header",
29382987
newrelic_globals,
29392988
0)
29402989

2990+
2991+
STD_PHP_INI_ENTRY_EX("newrelic.distributed_tracing.sampler.remote_parent_sampled",
2992+
"default",
2993+
NR_PHP_REQUEST,
2994+
nr_dt_sampler_remote_parent_mh,
2995+
dt_remote_parent_sampled,
2996+
zend_newrelic_globals,
2997+
newrelic_globals,
2998+
0)
2999+
STD_PHP_INI_ENTRY_EX("newrelic.distributed_tracing.sampler.remote_parent_not_sampled",
3000+
"default",
3001+
NR_PHP_REQUEST,
3002+
nr_dt_sampler_remote_parent_mh,
3003+
dt_remote_parent_not_sampled,
3004+
zend_newrelic_globals,
3005+
newrelic_globals,
3006+
0)
3007+
29413008
/*
29423009
* This setting is not documented and affects the length of the interally used
29433010
* trace id. This INI setting should not be modified unless requested by

agent/php_txn.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -942,6 +942,10 @@ nr_status_t nr_php_txn_begin(const char* appnames,
942942
= is_cli ? NRINI(tt_max_segments_cli) : NRINI(tt_max_segments_web);
943943
opts.span_queue_batch_size = NRINI(agent_span_queue_size);
944944
opts.span_queue_batch_timeout = NRINI(agent_span_queue_timeout);
945+
opts.dt_sampler_parent_sampled
946+
= NRPRG(dt_sampler_parent_sampled);
947+
opts.dt_sampler_parent_not_sampled
948+
= NRPRG(dt_sampler_parent_not_sampled);
945949
opts.logging_enabled = NRINI(logging_enabled);
946950
opts.log_decorating_enabled = NRINI(log_decorating_enabled);
947951
opts.log_forwarding_enabled = NRINI(log_forwarding_enabled);

agent/scripts/newrelic.ini.template

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -927,6 +927,36 @@ newrelic.daemon.logfile = "/var/log/newrelic/newrelic-daemon.log"
927927
;
928928
;newrelic.distributed_tracing_exclude_newrelic_header = false
929929

930+
; Setting: newrelic.distributed_tracing.sampler.remote_parent_sampled
931+
; Type : string
932+
; Scope : per-directory
933+
; Default: "default"
934+
; Info : This option defines how the agent should handle sampling spans when
935+
; their parent span from an upstream entity was sampled. For example,
936+
; setting remote_parent_sampled: always_on means the agent will sample
937+
; anything if the upstream entity sampled the parent.
938+
; The possible values are:
939+
; - "default": Use New Relic's standard sampling rules.
940+
; - "always_on": Always sample spans whose upstream parent was not sampled.
941+
; - "always_off": Always skip sampling spans whose upstream parent was not sampled.
942+
;
943+
;newrelic.distributed_tracing.sampler.remote_parent_sampled = "default"
944+
945+
; Setting: newrelic.distributed_tracing.sampler.remote_parent_not_sampled
946+
; Type : string
947+
; Scope : per-directory
948+
; Default: "default"
949+
; Info : This option defines how the agent should handle sampling spans when
950+
; their parent span from an upstream entity was not sampled. For example,
951+
; setting remote_parent_not_sampled: always_off means the agent will not
952+
; try to sample anything if the upstream entity did not sample the parent.
953+
; The possible values are:
954+
; - "default": Use New Relic's standard sampling rules.
955+
; - "always_on": Always sample spans whose upstream parent was not sampled.
956+
; - "always_off": Always skip sampling spans whose upstream parent was not sampled.
957+
;
958+
;newrelic.distributed_tracing.sampler.remote_parent_sampled = "default"
959+
930960
; Setting: newrelic.span_events_enabled
931961
; Type : boolean
932962
; Scope : per-directory

axiom/nr_distributed_trace.c

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1298,3 +1298,43 @@ char* nr_distributed_trace_create_w3c_traceparent_header(const char* trace_id,
12981298

12991299
return trace_parent_header;
13001300
}
1301+
1302+
void nr_distributed_trace_handle_inbound_w3c_sampled_flag(
1303+
nr_distributed_trace_t* dt,
1304+
const nrobj_t* trace_headers,
1305+
nr_upstream_parent_sampling_control_t remote_parent_sampled,
1306+
nr_upstream_parent_sampling_control_t remote_parent_not_sampled) {
1307+
const nrobj_t* traceparent = NULL;
1308+
int sampled = 0;
1309+
nr_status_t parse_err = NR_FAILURE;
1310+
if (DEFAULT != remote_parent_sampled || DEFAULT != remote_parent_not_sampled) {
1311+
traceparent = nro_get_hash_value(trace_headers, "traceparent", &parse_err);
1312+
if (nrunlikely(NULL == traceparent || NR_SUCCESS != parse_err)) {
1313+
return;
1314+
}
1315+
sampled = nro_get_hash_int(traceparent, "trace_flags", &parse_err);
1316+
if (nrunlikely(NR_SUCCESS != parse_err)) {
1317+
return;
1318+
}
1319+
/* The final bit of the trace_flags indicates the sampling decision */
1320+
if (sampled & 0x01) {
1321+
if (DEFAULT != remote_parent_sampled) {
1322+
if (ALWAYS_KEEP == remote_parent_sampled) {
1323+
dt->sampled = true;
1324+
dt->priority = 2;
1325+
} else {
1326+
dt->sampled = false;
1327+
}
1328+
}
1329+
} else {
1330+
if (DEFAULT != remote_parent_not_sampled) {
1331+
if (ALWAYS_DROP == remote_parent_not_sampled) {
1332+
dt->sampled = false;
1333+
} else {
1334+
dt->sampled = true;
1335+
dt->priority = 2;
1336+
}
1337+
}
1338+
}
1339+
}
1340+
}

axiom/nr_distributed_trace.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,16 @@ static const char NR_DISTRIBUTED_TRACE_W3C_TRACECONTEXT_ACCEPT_EXCEPTION[]
6060
typedef struct _nr_distributed_trace_t nr_distributed_trace_t;
6161
typedef struct _nr_distributed_trace_payload_t nr_distributed_trace_payload_t;
6262

63+
/*
64+
* Control options for how to respect other-vendor upstream sampling
65+
* decisions.
66+
*/
67+
typedef enum {
68+
DEFAULT,
69+
ALWAYS_KEEP,
70+
ALWAYS_DROP
71+
} nr_upstream_parent_sampling_control_t;
72+
6373
/*
6474
* Purpose : Creates/allocates a new distributed tracing metadata struct
6575
* instance. It's the responsibility of the caller to
@@ -409,4 +419,18 @@ bool nr_distributed_trace_accept_inbound_w3c_payload(
409419
const char* transport_type,
410420
const char** error);
411421

422+
/*
423+
* Purpose : Handle upstream w3c sampled flag according to settings
424+
*
425+
* Params : 1. The distributed trace object
426+
* 2. W3C trace headers objet
427+
* 3. Setting for if the upstream trace is sampled
428+
* 4. Setting for if the upstream trace is not sampled
429+
*/
430+
void nr_distributed_trace_handle_inbound_w3c_sampled_flag(
431+
nr_distributed_trace_t* dt,
432+
const nrobj_t* trace_headers,
433+
nr_upstream_parent_sampling_control_t remote_parent_sampled,
434+
nr_upstream_parent_sampling_control_t remote_parent_not_sampled);
435+
412436
#endif /* NR_DISTRIBUTED_TRACE_HDR */

axiom/nr_txn.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2972,6 +2972,14 @@ static bool nr_txn_accept_w3c_trace_context_headers(
29722972
nr_distributed_trace_accept_inbound_w3c_payload(
29732973
txn->distributed_trace, trace_headers, transport_type, &error_metrics);
29742974

2975+
/* Depending on the user's INI settings, we may or may not want to
2976+
* consider the traceparent's sampled field */
2977+
nr_distributed_trace_handle_inbound_w3c_sampled_flag(
2978+
txn->distributed_trace,
2979+
trace_headers,
2980+
txn->options.dt_sampler_parent_sampled,
2981+
txn->options.dt_sampler_parent_not_sampled);
2982+
29752983
if (error_metrics) {
29762984
nr_txn_force_single_count(txn, error_metrics);
29772985
}

axiom/nr_txn.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,14 @@ typedef struct _nrtxnopt_t {
100100
headers in favor of only
101101
W3C trace context headers
102102
*/
103+
nr_upstream_parent_sampling_control_t
104+
dt_sampler_parent_sampled; /* how to sample spans when non-
105+
New Relic upstream did sample.
106+
*/
107+
nr_upstream_parent_sampling_control_t
108+
dt_sampler_parent_not_sampled; /* how to sample spans when non-
109+
New Relic upstream didn't sample.
110+
*/
103111
int span_events_enabled; /* Whether span events are enabled */
104112
size_t
105113
span_events_max_samples_stored; /* The maximum number of span events per

0 commit comments

Comments
 (0)