Skip to content

Commit

Permalink
fix(otel): Missing method and Fiber handling
Browse files Browse the repository at this point in the history
  • Loading branch information
PROFeNoM committed Sep 12, 2024
1 parent 459fe08 commit 0bc8071
Show file tree
Hide file tree
Showing 8 changed files with 175 additions and 107 deletions.
7 changes: 5 additions & 2 deletions src/DDTrace/OpenTelemetry/Context.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,11 @@ public static function setStorage(ContextStorageInterface $storage): void
*/
public static function storage(): ContextStorageInterface
{
/** @psalm-suppress RedundantPropertyInitializationCheck */
return self::$storage ??= new ContextStorage();
if (class_exists('\OpenTelemetry\Context\FiberBoundContextStorageExecutionAwareBC')) {
return self::$storage ??= new FiberBoundContextStorageExecutionAwareBC();
} else {
return self::$storage ??= new ContextStorage();
}
}

/**
Expand Down
84 changes: 56 additions & 28 deletions src/DDTrace/OpenTelemetry/Span.php
Original file line number Diff line number Diff line change
Expand Up @@ -112,28 +112,18 @@ private function __construct(
foreach ($links as $link) {
/** @var LinkInterface $link */
$linkContext = $link->getSpanContext();

$spanLink = new SpanLink();
$spanLink->traceId = $linkContext->getTraceId();
$spanLink->spanId = $linkContext->getSpanId();
$spanLink->traceState = (string)$linkContext->getTraceState(); // __toString()
$spanLink->attributes = $link->getAttributes()->toArray();
$spanLink->droppedAttributesCount = 0; // Attributes limit aren't supported/meaningful in DD

// Save the link
ObjectKVStore::put($spanLink, "link", $link);
$span->links[] = $spanLink;
$span->links[] = $this->createAndSaveSpanLink($linkContext, $link->getAttributes()->toArray(), $link);
}

foreach ($events as $event) {
/** @var EventInterface $event */

$spanEvent = new SpanEvent(
$event->getName(),
$event->getName(),
$event->getAttributes()->toArray(),
$event->getEpochNanos()
);

// Save the event
ObjectKVStore::put($spanEvent, "event", $event);
$span->events[] = $spanEvent;
Expand Down Expand Up @@ -235,17 +225,24 @@ public function toSpanData(): SpanDataInterface
$this->updateSpanLinks();
$this->updateSpanEvents();

return new ImmutableSpan(
$this,
$this->getName(),
$this->links,
$this->events,
Attributes::create(array_merge($this->span->meta, $this->span->metrics)),
0,
StatusData::create($this->status->getCode(), $this->status->getDescription()),
$hasEnded ? $this->span->getStartTime() + $this->span->getDuration() : 0,
$this->hasEnded()
);
$spanData = [
'span' => $this,
'name' => $this->getName(),
'links' => $this->links,
'events' => $this->events,
'attributes' => Attributes::create(array_merge($this->span->meta, $this->span->metrics)),
'totalRecordedEvents' => $this->totalRecordedEvents,
'status' => StatusData::create($this->status->getCode(), $this->status->getDescription()),
'endEpochNanos' => $hasEnded ? $this->span->getStartTime() + $this->span->getDuration() : 0,
'hasEnded' => $this->hasEnded(),
];

// Workaround for a breaking change introduced in open-telemetry/api 1.1.0
if (in_array('addLink', get_class_methods(SpanInterface::class))) {
$spanData['totalRecordedLinks'] = $this->totalRecordedLinks;
}

return new ImmutableSpan(...$spanData);
}

/**
Expand Down Expand Up @@ -358,14 +355,27 @@ public function setAttributes(iterable $attributes): SpanInterface
return $this;
}

/**
* @inheritDoc
*/
public function addLink(SpanContextInterface $context, iterable $attributes = []): SpanInterface
{
if ($this->hasEnded() || !$context->isValid()) {
return $this;
}

$this->span->links[] = $this->createAndSaveSpanLink($context, $attributes);
return $this;
}

/**
* @inheritDoc
*/
public function addEvent(string $name, iterable $attributes = [], int $timestamp = null): SpanInterface
{
if (!$this->hasEnded()) {
$this->span->events[] = new SpanEvent(
$name,
$name,
$attributes,
$timestamp ?? (int)(microtime(true) * 1e9)
);
Expand Down Expand Up @@ -522,7 +532,7 @@ private function updateSpanEvents()
{
$datadogSpanEvents = $this->span->events;
$this->span->meta["events"] = count($this->events);

$otel = [];
foreach ($datadogSpanEvents as $datadogSpanEvent) {
$exceptionAttributes = [];
Expand All @@ -539,17 +549,35 @@ private function updateSpanEvents()
$event = new Event(
$datadogSpanEvent->name,
(int)$datadogSpanEvent->timestamp,
Attributes::create(array_merge($exceptionAttributes, \is_array($datadogSpanEvent->attributes) ? $datadogSpanEvent->attributes : iterator_to_array($datadogSpanEvent->attributes)))
Attributes::create(array_merge($exceptionAttributes, \is_array($datadogSpanEvent->attributes) ? $datadogSpanEvent->attributes : iterator_to_array($datadogSpanEvent->attributes)))
);

// Save the event
ObjectKVStore::put($datadogSpanEvent, "event", $event);
}
$otel[] = $event;
}

// Update the events
$this->events = $otel;
$this->totalRecordedEvents = count($otel);
}

private function createAndSaveSpanLink(SpanContextInterface $context, iterable $attributes = [], LinkInterface $link = null)
{
$spanLink = new SpanLink();
$spanLink->traceId = $context->getTraceId();
$spanLink->spanId = $context->getSpanId();
$spanLink->traceState = (string)$context->getTraceState(); // __toString()
$spanLink->attributes = $attributes;
$spanLink->droppedAttributesCount = 0; // Attributes limit aren't supported/meaningful in DD

// Save the link
if (is_null($link)) {
$link = new Link($context, Attributes::create($attributes));
}
ObjectKVStore::put($spanLink, "link", $link);

return $spanLink;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
use OpenTelemetry\API\Trace\Span;
use OpenTelemetry\API\Trace\SpanKind;
use OpenTelemetry\Context\Context;
use OpenTelemetry\Context\ContextStorage;
use OpenTelemetry\Context\ExecutionContextAwareInterface;
use OpenTelemetry\SDK\Trace\TracerProvider;
use function DDTrace\close_span;
use function DDTrace\start_span;
Expand All @@ -31,78 +31,6 @@ public function ddSetUp(): void
public function ddTearDown()
{
parent::ddTearDown();
Context::setStorage(new ContextStorage()); // Reset OpenTelemetry context
}

/**
* @throws \Throwable
*/
public function test_context_switching_ffi_observer()
{
$key = Context::createKey('-');
$scope = Context::getCurrent()
->with($key, 'main')
->activate();

$fiber = new Fiber(function () use ($key) {
$scope = Context::getCurrent()
->with($key, 'fiber')
->activate();

$this->assertSame('fiber:fiber', 'fiber:' . Context::getCurrent()->get($key));

Fiber::suspend();

$this->assertSame('fiber:fiber', 'fiber:' . Context::getCurrent()->get($key));

$scope->detach();
});

$this->assertSame('main:main', 'main:' . Context::getCurrent()->get($key));

$fiber->start();

$this->assertSame('main:main', 'main:' . Context::getCurrent()->get($key));

$fiber->resume();

$this->assertSame('main:main', 'main:' . Context::getCurrent()->get($key));

$scope->detach();
}

public function test_context_switching_ffi_observer_registered_on_startup()
{
$key = Context::createKey('-');

$fiber = new Fiber(function () use ($key) {
$scope = Context::getCurrent()
->with($key, 'fiber')
->activate();

$this->assertSame('fiber:fiber', 'fiber:' . Context::getCurrent()->get($key));

Fiber::suspend();

$this->assertSame('fiber:fiber', 'fiber:' . Context::getCurrent()->get($key));

$scope->detach();
});


$fiber->start();

$this->assertSame('main:', 'main:' . Context::getCurrent()->get($key));

$scope = Context::getCurrent()
->with($key, 'main')
->activate();

$fiber->resume();

$this->assertSame('main:main', 'main:' . Context::getCurrent()->get($key));

$scope->detach();
}

public function testFiberInteroperabilityStackSwitch()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
--TEST--
Context switches on execution context switch.
--SKIPIF--
<?php if (PHP_VERSION_ID < 80100 || !extension_loaded('ffi')) die('skip requires PHP8.1 and FFI'); ?>
--ENV--
OTEL_PHP_FIBERS_ENABLED=1
--FILE--
<?php
use OpenTelemetry\Context\Context;

require_once './tests/OpenTelemetry/vendor/autoload.php';

$key = Context::createKey('-');
$scope = Context::getCurrent()
->with($key, 'main')
->activate();

$fiber = new Fiber(function() use ($key) {
$scope = Context::getCurrent()
->with($key, 'fiber')
->activate();

echo 'fiber:' . Context::getCurrent()->get($key), PHP_EOL;

Fiber::suspend();
echo 'fiber:' . Context::getCurrent()->get($key), PHP_EOL;

$scope->detach();
});

echo 'main:' . Context::getCurrent()->get($key), PHP_EOL;

$fiber->start();
echo 'main:' . Context::getCurrent()->get($key), PHP_EOL;

$fiber->resume();
echo 'main:' . Context::getCurrent()->get($key), PHP_EOL;

$scope->detach();
?>
--EXPECT--
main:main
fiber:fiber
main:main
fiber:fiber
main:main
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
--TEST--
Fiber handler has to be loaded before fibers are used.
--SKIPIF--
<?php if (PHP_VERSION_ID < 80100 || !extension_loaded('ffi')) die('skip requires PHP8.1 and FFI'); ?>
--ENV--
OTEL_PHP_FIBERS_ENABLED=1
--FILE--
<?php
use OpenTelemetry\Context\Context;

require_once './tests/OpenTelemetry/vendor/autoload.php';

$key = Context::createKey('-');

$fiber = new Fiber(function() use ($key) {
$scope = Context::getCurrent()
->with($key, 'fiber')
->activate();

echo 'fiber:' . Context::getCurrent()->get($key), PHP_EOL;

Fiber::suspend();
echo 'fiber:' . Context::getCurrent()->get($key), PHP_EOL;

$scope->detach();
});

$fiber->start();
echo 'main:' . Context::getCurrent()->get($key), PHP_EOL;

$scope = Context::getCurrent()
->with($key, 'main')
->activate();

$fiber->resume();
echo 'main:' . Context::getCurrent()->get($key), PHP_EOL;

$scope->detach();

?>
--EXPECT--
fiber:fiber
main:
fiber:fiber
main:main
16 changes: 16 additions & 0 deletions tests/OpenTelemetry/Integration/SDK/SpanBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,22 @@ public function test_add_link(): void
$this->assertCount(2, $span->toSpanData()->getLinks());
}

/**
* @group trace-compliance
*/
public function test_add_link_after_span_creation(): void
{
/** @var Span $span */
$span = $this
->tracer
->spanBuilder(self::SPAN_NAME)
->addLink($this->sampledSpanContext)
->startSpan()
->addLink($this->sampledSpanContext);

$this->assertCount(2, $span->toSpanData()->getLinks());
}

public function test_add_link_invalid(): void
{
/** @var Span $span */
Expand Down
9 changes: 5 additions & 4 deletions tests/OpenTelemetry/composer.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
{
"name": "datadog/dd-trace-tests",
"require": {
"open-telemetry/sdk": "@stable",
"open-telemetry/extension-propagator-b3": "@stable",
"open-telemetry/opentelemetry-logger-monolog": "@stable"
}
"open-telemetry/sdk": "@beta",
"open-telemetry/extension-propagator-b3": "@beta",
"open-telemetry/opentelemetry-logger-monolog": "@beta"
},
"minimum-stability": "beta"
}
1 change: 1 addition & 0 deletions tests/phpunit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
<directory>./Integrations/Laravel/Octane</directory>
</testsuite>
<testsuite name="opentelemetry1">
<directory suffix=".phpt">./OpenTelemetry/Integration/Context/Fiber</directory>
<directory>./OpenTelemetry/Unit/API</directory>
<directory>./OpenTelemetry/Unit/Context</directory>
<directory>./OpenTelemetry/Unit/Propagation</directory>
Expand Down

0 comments on commit 0bc8071

Please sign in to comment.