Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Concurrent traces #674

Closed
wants to merge 14 commits into from
6 changes: 5 additions & 1 deletion examples/ConcurrentSpans.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@
use OpenTelemetry\SDK\Trace\SpanProcessor\SimpleSpanProcessor;
use OpenTelemetry\SDK\Trace\TracerProvider;

echo 'Starting ConsoleSpanExporter' . PHP_EOL;
/**
* This example demonstrates how to have multiple concurrent spans active in the same trace, for
* example if there were async/parallel http calls or database queries.
*/
echo 'Starting ConsoleSpanExporter with concurrent spans' . PHP_EOL;

$tracerProvider = new TracerProvider(
new SimpleSpanProcessor(
Expand Down
46 changes: 46 additions & 0 deletions examples/ConcurrentTraces.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

declare(strict_types=1);
require __DIR__ . '/../vendor/autoload.php';

use OpenTelemetry\Context\ContextStorage;
use OpenTelemetry\SDK\Trace\SpanExporter\ConsoleSpanExporter;
use OpenTelemetry\SDK\Trace\SpanProcessor\SimpleSpanProcessor;
use OpenTelemetry\SDK\Trace\TracerProvider;

/**
* This example demonstrates how to have multiple traces occurring within the one process, by creating custom storage
* for each trace to use. This might be used in a situation where one process can service multiple requests
* e.g react/swoole/roadrunner.
* Although this example uses the default storage for one of the traces, in practise you would either use only the default
* storage (eg in a shared-nothing webserver or single-task CLI process), or you would use only individual storages.
*/
echo 'Starting ConsoleSpanExporter with concurrent traces' . PHP_EOL;

$tracerProvider = new TracerProvider(
new SimpleSpanProcessor(
new ConsoleSpanExporter()
)
);
$storage = ContextStorage::create('custom');
$tracer = $tracerProvider->getTracer();

//start and activate a root span in $storage
$traceOneRootSpan = $tracer->spanBuilder('trace-one.root')->setStorage($storage)->startSpan();
$traceOneRootSpan->activate();

//start and activate a root span in default storage
$traceTwoRootSpan = $tracer->spanBuilder('trace-two.root')->startSpan();
$traceTwoRootSpan->activate();

//start a child span, which will have a parent of the active node from $storage
$childSpanOne = $tracer->spanBuilder('trace-one.child')->setStorage($storage)->startSpan();

//start another child span, which will have a parent of the active node from default storage
$childSpanTwo = $tracer->spanBuilder('trace-two.child')->startSpan();

$childSpanOne->end();
$childSpanTwo->end();

$traceOneRootSpan->end();
$traceTwoRootSpan->end();
27 changes: 16 additions & 11 deletions src/API/Trace/AbstractSpan.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
namespace OpenTelemetry\API\Trace;

use OpenTelemetry\Context\Context;
use OpenTelemetry\Context\ContextStorage;
use OpenTelemetry\Context\ContextStorageInterface;
use OpenTelemetry\Context\ScopeInterface;

abstract class AbstractSpan implements SpanInterface
{
private static ?self $invalidSpan = null;
protected ContextStorageInterface $storage;

/** @inheritDoc */
final public static function fromContext(Context $context): SpanInterface
Expand All @@ -18,23 +20,19 @@ final public static function fromContext(Context $context): SpanInterface
return $span;
}

return NonRecordingSpan::getInvalid();
return (NonRecordingSpan::getInvalid())->setStorage($context->getStorage());
}

/** @inheritDoc */
final public static function getCurrent(): SpanInterface
final public static function getCurrent(ContextStorageInterface $storage = null): SpanInterface
{
return self::fromContext(Context::getCurrent());
return self::fromContext($storage ? $storage->current() : ContextStorage::default()->current());
}

/** @inheritDoc */
final public static function getInvalid(): SpanInterface
final public static function getInvalid(?ContextStorageInterface $storage = null): SpanInterface
{
if (null === self::$invalidSpan) {
self::$invalidSpan = new NonRecordingSpan(SpanContext::getInvalid());
}

return self::$invalidSpan;
return (new NonRecordingSpan(SpanContext::getInvalid()))->setStorage($storage ?? ContextStorage::default());
}

/** @inheritDoc */
Expand All @@ -50,12 +48,19 @@ final public static function wrap(SpanContextInterface $spanContext): SpanInterf
/** @inheritDoc */
final public function activate(): ScopeInterface
{
return Context::getCurrent()->withContextValue($this)->activate();
return $this->storage->current()->withContextValue($this)->activate();
}

/** @inheritDoc */
final public function storeInContext(Context $context): Context
{
return $context->with(SpanContextKey::instance(), $this);
}

final public function setStorage(ContextStorageInterface $storage): SpanInterface
{
$this->storage = $storage;

return $this;
}
}
2 changes: 2 additions & 0 deletions src/API/Trace/NonRecordingSpan.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace OpenTelemetry\API\Trace;

use OpenTelemetry\Context\ContextStorage;
use Throwable;

/**
Expand All @@ -20,6 +21,7 @@ public function __construct(
SpanContextInterface $context
) {
$this->context = $context;
$this->storage = ContextStorage::default();
}

/** @inheritDoc */
Expand Down
5 changes: 5 additions & 0 deletions src/API/Trace/NoopSpanBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ public function setSpanKind(int $spanKind): SpanBuilderInterface
return $this;
}

public function setStorage(ContextStorageInterface $storage): SpanBuilderInterface
{
return $this;
}

public function startSpan(): SpanInterface
{
$span = AbstractSpan::fromContext($this->parent ?? $this->contextStorage->current());
Expand Down
4 changes: 2 additions & 2 deletions src/API/Trace/NoopTracer.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace OpenTelemetry\API\Trace;

use OpenTelemetry\Context\Context;
use OpenTelemetry\Context\ContextStorage;

final class NoopTracer implements TracerInterface
{
Expand All @@ -21,6 +21,6 @@ public static function getInstance(): self

public function spanBuilder(string $spanName): SpanBuilderInterface
{
return new NoopSpanBuilder(Context::storage());
return new NoopSpanBuilder(ContextStorage::default());
}
}
6 changes: 6 additions & 0 deletions src/API/Trace/SpanBuilderInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace OpenTelemetry\API\Trace;

use OpenTelemetry\Context\Context;
use OpenTelemetry\Context\ContextStorageInterface;

/**
* Obtained from a {@see TracerInterface} and used to construct a {@see SpanInterface}.
Expand Down Expand Up @@ -45,6 +46,11 @@ public function setStartTimestamp(int $timestamp): SpanBuilderInterface;
*/
public function setSpanKind(int $spanKind): SpanBuilderInterface;

/**
* Set custom context storage
*/
public function setStorage(ContextStorageInterface $storage): SpanBuilderInterface;

/**
* Starts and returns a new {@see SpanInterface}.
*
Expand Down
5 changes: 4 additions & 1 deletion src/API/Trace/SpanInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace OpenTelemetry\API\Trace;

use OpenTelemetry\Context\Context;
use OpenTelemetry\Context\ContextStorageInterface;
use OpenTelemetry\Context\ImplicitContextKeyedInterface;
use Throwable;

Expand All @@ -28,7 +29,7 @@ public static function getCurrent(): SpanInterface;
/**
* Returns an invalid {@see SpanInterface} that is used when tracing is disabled, such s when there is no available SDK.
*/
public static function getInvalid(): SpanInterface;
public static function getInvalid(?ContextStorageInterface $storage = null): SpanInterface;

/**
* Returns a non-recording {@see SpanInterface} that hold the provided *$spanContext* but has no functionality.
Expand Down Expand Up @@ -89,4 +90,6 @@ public function setStatus(string $code, string $description = null): SpanInterfa
* @see https://github.com/open-telemetry/opentelemetry-specification/blob/v1.6.1/specification/trace/api.md#end
*/
public function end(int $endEpochNanos = null): void;

public function setStorage(ContextStorageInterface $storage): SpanInterface;
}
53 changes: 37 additions & 16 deletions src/Context/Context.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,20 @@
*/
class Context
{
private static ?ContextStorageInterface $storage = null;

private static ?\OpenTelemetry\Context\Context $root = null;
private static ?ContextStorageInterface $defaultStorage = null;
private static ?Context $root = null;

/**
* @internal
*/
public static function setStorage(ContextStorageInterface $storage): void
public static function defaultStorage(): ContextStorageInterface
{
self::$storage = $storage;
return self::$defaultStorage ??= ContextStorage::default();
}

public static function storage(): ContextStorageInterface
public function getStorage(): ContextStorageInterface
{
return self::$storage ??= new ContextStorage(self::getRoot());
return $this->storage ?? self::defaultStorage();
}

/**
Expand All @@ -32,7 +31,7 @@ public static function storage(): ContextStorageInterface
*/
public static function attach(Context $ctx): ScopeInterface
{
return self::storage()->attach($ctx);
return $ctx->getStorage()->attach($ctx);
}

/**
Expand All @@ -50,20 +49,28 @@ public static function createKey(string $key): ContextKey
*/
public static function detach(ScopeInterface $token): Context
{
//@todo what will this do with non-default storage? Do we need a non-static version?
$token->detach();

return self::getCurrent();
}

public static function getCurrent(): Context
/**
* Retrieve current context from storage
* @see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/context/context.md#get-current-context
* @see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/glossary.md#execution-unit
*
*/
public static function getCurrent(?ContextStorageInterface $storage = null): Context
{
return self::storage()->current();
return $storage ? $storage->current() : self::defaultStorage()->current();
}

public static function getRoot(): self
{
if (null === self::$root) {
self::$root = new self();
self::$root->setStorage(self::defaultStorage());
}

return self::$root;
Expand All @@ -85,7 +92,7 @@ public static function getRoot(): self
*
* @return mixed
*/
public static function getValue(ContextKey $key, $ctx=null)
public static function getValue(ContextKey $key, ?self $ctx=null)
{
$ctx = $ctx ?? static::getCurrent();

Expand Down Expand Up @@ -114,7 +121,7 @@ public static function withValue(ContextKey $key, $value, $parent=null)
{
if (null === $parent) {
// TODO This should not attach, leads to a context that cannot be detached
self::storage()->attach(new self($key, $value, self::getCurrent()));
self::defaultStorage()->attach(new self($key, $value, self::getCurrent()));

return self::getCurrent();
}
Expand All @@ -129,7 +136,8 @@ public static function withValue(ContextKey $key, $value, $parent=null)
*/
protected $value;

protected ?\OpenTelemetry\Context\Context $parent;
protected ?Context $parent;
protected ?ContextStorageInterface $storage;

/**
* This is a general purpose read-only key-value store. Read-only in the sense that adding a new value does not
Expand All @@ -148,11 +156,24 @@ public static function withValue(ContextKey $key, $value, $parent=null)
* @param mixed|null $value
* @param self|null $parent Reference to the parent object
*/
final public function __construct(?ContextKey $key=null, $value=null, $parent=null)
final public function __construct(?ContextKey $key=null, $value=null, ?self $parent=null, ?ContextStorageInterface $storage = null)
{
$this->key = $key;
$this->value = $value;
$this->parent = $parent;
$this->storage = $storage;
}

public function setStorage(ContextStorageInterface $storage): self
{
$this->storage = $storage;

return $this;
}

public function storage(): ContextStorageInterface
{
return $this->storage ?? self::defaultStorage();
}

/**
Expand All @@ -166,7 +187,7 @@ final public function __construct(?ContextKey $key=null, $value=null, $parent=nu
*/
public function with(ContextKey $key, $value)
{
return new self($key, $value, $this);
return (new self($key, $value, $this))->setStorage($this->storage());
}

/**
Expand All @@ -184,7 +205,7 @@ public function withContextValue(ImplicitContextKeyedInterface $value): Context
*/
public function activate(): ScopeInterface
{
return self::attach($this);
return $this->storage()->attach($this);
}

/**
Expand Down
Loading