Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions config.m4
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,7 @@ if test "$PHP_DDTRACE" != "no" && test "$PHP_DDTRACE_PROFILING" = "no"; then
tracer/live_debugger.c \
tracer/limiter/limiter.c \
tracer/memory_limit.c \
tracer/otel_sampling.c \
tracer/tracer_otel_config.c \
tracer/priority_sampling/priority_sampling.c \
tracer/profiling.c \
Expand Down
1 change: 1 addition & 0 deletions config.w32
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ if (PHP_DDTRACE != 'no') {
DDTRACE_TRACER_SOURCES += " ip_extraction.c";
DDTRACE_TRACER_SOURCES += " live_debugger.c";
DDTRACE_TRACER_SOURCES += " memory_limit.c";
DDTRACE_TRACER_SOURCES += " otel_sampling.c";
DDTRACE_TRACER_SOURCES += " tracer_otel_config.c";
DDTRACE_TRACER_SOURCES += " profiling.c";
DDTRACE_TRACER_SOURCES += " random.c";
Expand Down
16 changes: 8 additions & 8 deletions tests/OpenTelemetry/Integration/API/TracerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -538,9 +538,9 @@ public function testGetSpanContextWithMultipleTraceStates()
'_dd.p.congo' => 't61rcWkgMzE',
'_dd.p.some_val' => 'tehehe'
]);
$this->assertRegularExpression('/^dd=p:[0-9a-f]{16};t.congo:t61rcWkgMzE;t.some_val:tehehe;t.dm:-0$/', (string)$span->getContext()->getTraceState());
$this->assertRegularExpression('/^dd=p:[0-9a-f]{16};t.congo:t61rcWkgMzE;t.some_val:tehehe;t.dm:-0,ot=rv:[0-9a-f]{14};th:0$/', (string)$span->getContext()->getTraceState());
$span->end();
$this->assertRegularExpression('/^dd=p:[0-9a-f]{16};t.congo:t61rcWkgMzE;t.some_val:tehehe;t.dm:-0$/', (string)$span->getContext()->getTraceState());
$this->assertRegularExpression('/^dd=p:[0-9a-f]{16};t.congo:t61rcWkgMzE;t.some_val:tehehe;t.dm:-0,ot=rv:[0-9a-f]{14};th:0$/', (string)$span->getContext()->getTraceState());
});

$span = $traces[0][0];
Expand Down Expand Up @@ -659,11 +659,11 @@ public function getDescription(): string
)))->getTracer('OpenTelemetry.TracerTest');
$parent = $tracer->spanBuilder("parent")->startSpan(); // root sampler will be used
$scope = $parent->activate();
$this->assertRegularExpression('/^dd=p:[0-9a-f]{16};t.dm:-0,root=yes,rojo=00f067aa0ba902b7,congo=t61rcWkgMzE$/', (string)$parent->getContext()->getTraceState());
$this->assertRegularExpression('/^dd=p:[0-9a-f]{16};t.dm:-0,ot=rv:[0-9a-f]{14};th:0,root=yes,rojo=00f067aa0ba902b7,congo=t61rcWkgMzE$/', (string)$parent->getContext()->getTraceState());
$parent->setAttributes([
'_dd.p.some_val' => 'tehehe'
]);
$this->assertRegularExpression('/^dd=p:[0-9a-f]{16};t.dm:-0;t.some_val:tehehe,root=yes,rojo=00f067aa0ba902b7,congo=t61rcWkgMzE$/', (string)$parent->getContext()->getTraceState());
$this->assertRegularExpression('/^dd=p:[0-9a-f]{16};t.dm:-0;t.some_val:tehehe,ot=rv:[0-9a-f]{14};th:0,root=yes,rojo=00f067aa0ba902b7,congo=t61rcWkgMzE$/', (string)$parent->getContext()->getTraceState());
try {
$child = $tracer->spanBuilder("child")->startSpan(); // local parent sampler will be used

Expand All @@ -673,7 +673,7 @@ public function getDescription(): string
$this->assertFalse($childContext->isRemote()); // "When creating children from remote spans, their IsRemote flag MUST be set to false."
$this->assertEquals(1, $childContext->getTraceFlags()); // RECORD_AND_SAMPLED ==> 01 (AlwaysOn sampler)
$this->assertRegularExpression('/^dd=p:[0-9a-f]{16};t.dm:-0,localparent=yes,rojo=00f067aa0ba902b7,congo=t61rcWkgMzE$/', (string)$childContext->getTraceState());
$this->assertRegularExpression('/^dd=p:[0-9a-f]{16};t.dm:-0;t.some_val:tehehe,root=yes,rojo=00f067aa0ba902b7,congo=t61rcWkgMzE$/', (string)$parent->getContext()->getTraceState());
$this->assertRegularExpression('/^dd=p:[0-9a-f]{16};t.dm:-0;t.some_val:tehehe,ot=rv:[0-9a-f]{14};th:0,root=yes,rojo=00f067aa0ba902b7,congo=t61rcWkgMzE$/', (string)$parent->getContext()->getTraceState());

$grandChild = $tracer->spanBuilder("grandChild")
->setParent(Context::getCurrent()->withContextValue($child))
Expand All @@ -692,7 +692,7 @@ public function getDescription(): string

$child->end();
} finally {
$this->assertRegularExpression('/^dd=p:[0-9a-f]{16};t.dm:-0;t.some_val:tehehe,root=yes,rojo=00f067aa0ba902b7,congo=t61rcWkgMzE$/', (string)$parent->getContext()->getTraceState());
$this->assertRegularExpression('/^dd=p:[0-9a-f]{16};t.dm:-0;t.some_val:tehehe,ot=rv:[0-9a-f]{14};th:0,root=yes,rojo=00f067aa0ba902b7,congo=t61rcWkgMzE$/', (string)$parent->getContext()->getTraceState());
$scope->detach();
$parent->end();
}
Expand Down Expand Up @@ -851,7 +851,7 @@ public function testAddItemToTracestate()
'_dd.p.congo' => 't61rcWkgMzE',
]);

$this->assertRegularExpression('/^dd=p:[0-9a-f]{16};t.congo:t61rcWkgMzE;t.dm:-0$/', (string)$span->getContext()->getTraceState());
$this->assertRegularExpression('/^dd=p:[0-9a-f]{16};t.congo:t61rcWkgMzE;t.dm:-0,ot=rv:[0-9a-f]{14};th:0$/', (string)$span->getContext()->getTraceState());

$traceState = $span->getContext()->getTraceState()->with('rojo', '00f067aa0ba902b7');
$context = SpanContext::create(
Expand All @@ -865,7 +865,7 @@ public function testAddItemToTracestate()
->setParent(Context::getCurrent()->withContextValue(Span::wrap($context)))
->startSpan();

$this->assertRegularExpression('/^dd=p:[0-9a-f]{16};t.congo:t61rcWkgMzE;t.dm:-0,rojo=00f067aa0ba902b7$/', (string)$child->getContext()->getTraceState());
$this->assertRegularExpression('/^dd=p:[0-9a-f]{16};t.congo:t61rcWkgMzE;t.dm:-0,rojo=00f067aa0ba902b7,ot=rv:[0-9a-f]{14};th:0$/', (string)$child->getContext()->getTraceState());

$child->end();
$span->end();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ DDTrace\SpanLink Object
(
[traceId] => 0000000000000000000000000000002a
[spanId] => %s
[traceState] => dd=o:datadog;t.custom_tag:inherited;t.second_tag:bar;t.dm:-0
[traceState] => dd=o:datadog;t.custom_tag:inherited;t.second_tag:bar;t.dm:-0,ot=rv:%x;th:0
[attributes] => Array
(
[_dd.p.custom_tag] => inherited
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ datadog.trace.propagation_style_extract=datadog
--FILE--
<?php

$origin = str_repeat("=", 1000);
$origin = str_repeat("=", 500);
$link = DDTrace\SpanLink::fromHeaders([
"x-datadog-trace-id" => "42",
"x-datadog-parent-id" => "1",
Expand Down
65 changes: 65 additions & 0 deletions tests/ext/distributed_tracing/otel_sampling_caps.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
--TEST--
OpenTelemetry tracestate sampling honors value, member, and byte caps
--ENV--
DD_TRACE_SAMPLE_RATE=0.5
DD_TRACE_RATE_LIMIT=10000000
--FILE--
<?php

function headersWithTracestate(string $tracestate): array
{
$span = DDTrace\start_span();
$root = DDTrace\root_span();
$root->traceId = str_pad('1', 32, '0', STR_PAD_LEFT);
$root->tracestate = $tracestate;
$headers = DDTrace\generate_distributed_tracing_headers(['tracecontext']);
DDTrace\close_span();
return $headers;
}

$vendors = [];
for ($i = 0; $i < 32; ++$i) {
$vendors[] = "vendor{$i}=value";
}
$tracestate = headersWithTracestate(implode(',', $vendors))['tracestate'];
$members = explode(',', $tracestate);
echo 'members=', count($members), ' leading=', implode(',', array_map(
function (string $member): string {
return strstr($member, '=', true);
},
array_slice($members, 0, 2)
)), PHP_EOL;

$largeVendors = [];
for ($i = 0; $i < 32; ++$i) {
$largeVendors[] = "vendor{$i}=" . str_repeat('x', 30);
}
$tracestate = headersWithTracestate(implode(',', $largeVendors))['tracestate'];
echo 'bytes=', strlen($tracestate) <= 512 ? 'within-cap' : 'over-cap',
' complete=', substr($tracestate, -1) === 'x' ? 'yes' : 'no', PHP_EOL;

$largeDatadog = 'dd=p:0000000000000001;t.large:' . str_repeat('x', 470);
$tracestate = headersWithTracestate($largeDatadog)['tracestate'];
$members = explode(',', $tracestate);
echo 'owned-bytes=', strlen($tracestate) <= 512 ? 'within-cap' : 'over-cap',
' leading=', implode(',', array_map(
function (string $member): string {
return strstr($member, '=', true);
},
array_slice($members, 0, 2)
)),
' large=', strpos($tracestate, 't.large:') === false ? 'dropped' : 'kept', PHP_EOL;

$oversizedUnknown = 'future:' . str_repeat('x', 230) . ';next:value';
$tracestate = headersWithTracestate('ot=' . $oversizedUnknown)['tracestate'];
preg_match('/(?:^|,)ot=([^,]+)/', $tracestate, $matches);
echo 'ot-bytes=', strlen($matches[1]),
' future=', strpos($matches[1], 'future:') === false ? 'dropped' : 'kept',
' next=', strpos($matches[1], 'next:value') === false ? 'dropped' : 'kept', PHP_EOL;

?>
--EXPECTF--
members=32 leading=dd,ot
bytes=within-cap complete=yes
owned-bytes=within-cap leading=dd,ot large=dropped
ot-bytes=33 future=dropped next=kept
82 changes: 82 additions & 0 deletions tests/ext/distributed_tracing/otel_sampling_inherited.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
--TEST--
Inherited OpenTelemetry tracestate sampling fields are validated and forwarded
--ENV--
DD_TRACE_SAMPLE_RATE=0.5
DD_TRACE_RATE_LIMIT=10000000
DD_TRACE_SAMPLING_RULES=[{"sample_rate":0.1,"service":"locally-decided","target_span":"any"}]
--FILE--
<?php

function propagate(string $tracestate, bool $sampled = true, bool $manualKeep = false): string
{
$span = DDTrace\start_span();
DDTrace\consume_distributed_tracing_headers([
'traceparent' => '00-0000000000000000fff972474538efff-0000000000000001-0' . ($sampled ? '1' : '0'),
'tracestate' => $tracestate,
]);
if ($manualKeep) {
DDTrace\set_priority_sampling(DD_TRACE_PRIORITY_SAMPLING_USER_KEEP);
}

$headers = DDTrace\generate_distributed_tracing_headers(['tracecontext']);
DDTrace\close_span();
return $headers['tracestate'];
}

function ot(string $tracestate): string
{
foreach (explode(',', $tracestate) as $member) {
$member = ltrim($member, " \t");
if (strncmp($member, 'ot=', 3) === 0) {
return substr($member, 3);
}
}
return '<absent>';
}

function locallyDecide(string $tracestate): string
{
$span = DDTrace\start_span();
DDTrace\consume_distributed_tracing_headers([
'traceparent' => '00-0000000000000000fff972474538efff-0000000000000001-00',
'tracestate' => $tracestate,
]);
$span->service = 'locally-decided';

$headers = DDTrace\generate_distributed_tracing_headers(['tracecontext']);
DDTrace\close_span();
return $headers['tracestate'];
}

echo ot(propagate('dd=s:2;t.dm:-3,ot=rv:ef284ace7a91e1;th:e6666666666668;foo:bar')), PHP_EOL;
echo ot(propagate('ot=th:e6666666666668')), PHP_EOL;
echo ot(propagate('dd=s:0,ot=rv:ef284ace7a91e1;th:e6666666666668', false)), PHP_EOL;
echo ot(propagate('dd=s:1,ot=rv:not-hex;th:not-hex,congo=xyz123')), PHP_EOL;
echo ot(propagate('ot=rv:1234567890abcd;th:not-hex')), PHP_EOL;
echo ot(propagate('dd=s:1')), PHP_EOL;
echo ot(propagate('ot=foo:bar')), PHP_EOL;
echo ot(propagate('ot=rv:65cd67504a538e;th:e6666666666668', false, true)), PHP_EOL;
echo ot(propagate('', false, true)), PHP_EOL;

$ordered = propagate('dd=s:1,foo=bar,ot=rv:6e6d1a75832a2f,something=else');
echo substr($ordered, strpos($ordered, ',') + 1), PHP_EOL;

echo ot(locallyDecide('ot=rv:00000000000000;th:f0000000000000;foo:bar')), PHP_EOL;
echo ot(locallyDecide('ot=rv:00000000000000;foo:bar')), PHP_EOL;
echo ot(locallyDecide('ot=th:f0000000000000;foo:bar')), PHP_EOL;

?>
--EXPECT--
rv:ef284ace7a91e1;th:e6666666666668;foo:bar
th:e6666666666668
rv:ef284ace7a91e1;th:e6666666666668
<absent>
rv:1234567890abcd
<absent>
foo:bar
rv:65cd67504a538e
<absent>
foo=bar,ot=rv:6e6d1a75832a2f,something=else
rv:ef284ace7a91e1;th:e6666666666668;foo:bar
rv:ef284ace7a91e1;th:e6666666666668;foo:bar
rv:ef284ace7a91e1;th:e6666666666668;foo:bar
30 changes: 30 additions & 0 deletions tests/ext/distributed_tracing/otel_sampling_limiter.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
--TEST--
OpenTelemetry threshold is not emitted when the trace rate limiter makes the drop decision
--SKIPIF--
<?php if (getenv('USE_ZEND_ALLOC') === '0') die('skip timing sensitive test, does not make sense with valgrind'); ?>
--ENV--
DD_TRACE_AUTO_FLUSH_ENABLED=0
DD_TRACE_GENERATE_ROOT_SPAN=0
DD_TRACE_RATE_LIMIT=10
DD_TRACE_SAMPLE_RATE=1
--FILE--
<?php

for ($i = 0; $i < 1000; ++$i) {
DDTrace\start_span();
$headers = DDTrace\generate_distributed_tracing_headers(['tracecontext']);
DDTrace\close_span();
dd_trace_serialize_closed_spans();

$traceFlags = hexdec(substr($headers['traceparent'], -2));
if (($traceFlags & 1) === 0) {
echo strpos($headers['tracestate'], 'ot=') === false ? "OK\n" : "unexpected ot member\n";
return;
}
}

echo "rate limiter did not reject a trace\n";

?>
--EXPECT--
OK
21 changes: 21 additions & 0 deletions tests/ext/distributed_tracing/otel_sampling_precision.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
--TEST--
OpenTelemetry random value is reconciled with the 64-bit Datadog sampling decision
--ENV--
DD_TRACE_SAMPLE_RATE=0.1
DD_TRACE_RATE_LIMIT=10000000
--FILE--
<?php

$span = DDTrace\start_span();
$root = DDTrace\root_span();
$root->traceId = str_pad('03a93ee8b1999f00', 32, '0', STR_PAD_LEFT);

$headers = DDTrace\generate_distributed_tracing_headers(['tracecontext']);
preg_match('/(?:^|,)ot=([^,]+)/', $headers['tracestate'], $matches);
echo $matches[1], ' sampled=', substr($headers['traceparent'], -1), PHP_EOL;

DDTrace\close_span();

?>
--EXPECT--
rv:e6666666666668;th:e6666666666668 sampled=1
21 changes: 21 additions & 0 deletions tests/ext/distributed_tracing/otel_sampling_precision_drop.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
--TEST--
OpenTelemetry random value is reconciled for a 64-bit Datadog drop decision
--ENV--
DD_TRACE_SAMPLE_RATE=0.05
DD_TRACE_RATE_LIMIT=10000000
--FILE--
<?php

$span = DDTrace\start_span();
$root = DDTrace\root_span();
$root->traceId = str_pad(dechex(5401449561355763072), 32, '0', STR_PAD_LEFT);

$headers = DDTrace\generate_distributed_tracing_headers(['tracecontext']);
preg_match('/(?:^|,)ot=([^,]+)/', $headers['tracestate'], $matches);
echo $matches[1], ' sampled=', substr($headers['traceparent'], -1), PHP_EOL;

DDTrace\close_span();

?>
--EXPECT--
rv:f333333333332f;th:f333333333333 sampled=0
28 changes: 28 additions & 0 deletions tests/ext/distributed_tracing/otel_sampling_probability.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
--TEST--
OpenTelemetry tracestate fields are emitted for probability sampling decisions
--ENV--
DD_TRACE_SAMPLE_RATE=0.5
DD_TRACE_RATE_LIMIT=10000000
--FILE--
<?php

function sample(string $traceId)
{
$span = DDTrace\start_span();
$root = DDTrace\root_span();
$root->traceId = str_pad($traceId, 32, '0', STR_PAD_LEFT);

$headers = DDTrace\generate_distributed_tracing_headers(['tracecontext']);
preg_match('/(?:^|,)ot=([^,]+)/', $headers['tracestate'], $matches);
echo $matches[1], ' sampled=', substr($headers['traceparent'], -1), PHP_EOL;

DDTrace\close_span();
}

sample('1');
sample('a');

?>
--EXPECT--
rv:f0948a54d43b8e;th:8 sampled=1
rv:65cd67504a538e;th:8 sampled=0
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
--TEST--
OpenTelemetry sampling state is regenerated when extraction ignores the context
--ENV--
DD_TRACE_GENERATE_ROOT_SPAN=0
DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT=ignore
DD_TRACE_PROPAGATION_STYLE_EXTRACT=tracecontext
DD_TRACE_SAMPLE_RATE=0.5
DD_TRACE_RATE_LIMIT=10000000
--FILE--
<?php

$inboundTraceId = '0000000000000000fff972474538efff';
$inboundRandomValue = '1234567890abcd';
DDTrace\consume_distributed_tracing_headers([
'traceparent' => "00-$inboundTraceId-0000000000000001-01",
'tracestate' => "ot=rv:$inboundRandomValue;th:e6666666666668",
]);

DDTrace\start_span();
$headers = DDTrace\generate_distributed_tracing_headers(['tracecontext']);
preg_match('/(?:^|,)ot=([^,]+)/', $headers['tracestate'], $matches);
$ot = $matches[1] ?? '';

echo 'new trace: ', DDTrace\root_span()->traceId === $inboundTraceId ? 'no' : 'yes', PHP_EOL;
echo 'new probability state: ', preg_match('/^rv:[0-9a-f]{14};th:8$/', $ot) ? 'yes' : 'no', PHP_EOL;
echo 'inbound rv removed: ', strpos($ot, $inboundRandomValue) === false ? 'yes' : 'no', PHP_EOL;

DDTrace\close_span();

?>
--EXPECT--
new trace: yes
new probability state: yes
inbound rv removed: yes
Loading
Loading