Skip to content

Commit 56af9ec

Browse files
committed
Init sdk bundle profiler
1 parent dd99a20 commit 56af9ec

File tree

12 files changed

+476
-3
lines changed

12 files changed

+476
-3
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\Symfony\OtelSdkBundle\DataCollector;
6+
7+
use OpenTelemetry\API\Trace\TracerInterface;
8+
use OpenTelemetry\SDK\Trace\SpanDataInterface;
9+
use OpenTelemetry\SDK\Trace\SpanExporterInterface;
10+
use OpenTelemetry\SDK\Trace\TracerProviderInterface;
11+
use Symfony\Component\HttpFoundation\Request;
12+
use Symfony\Component\HttpFoundation\Response;
13+
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
14+
use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface;
15+
16+
class OtelDataCollector extends DataCollector implements LateDataCollectorInterface
17+
{
18+
private ?TracerInterface $tracer = null;
19+
private ?TracerProviderInterface $tracerProvider = null;
20+
public array $collectedSpans = [];
21+
22+
public function reset(): void
23+
{
24+
$this->data = [];
25+
}
26+
27+
/**
28+
* {@inheritDoc}
29+
*/
30+
public function collect(Request $request, Response $response, \Throwable $exception = null): void
31+
{
32+
// Everything is collected during the request, and formatted on kernel terminate.
33+
}
34+
35+
/**
36+
* {@inheritDoc}
37+
*/
38+
public function getName(): string
39+
{
40+
return 'otel';
41+
}
42+
43+
/**
44+
* @return array|\Symfony\Component\VarDumper\Cloner\Data
45+
*/
46+
public function getData()
47+
{
48+
return $this->data;
49+
}
50+
51+
/**
52+
* {@inheritDoc}
53+
*/
54+
public function lateCollect(): void
55+
{
56+
$this->loadDataFromTracerSharedState();
57+
$this->data['spans'] = $this->orderedSpans();
58+
}
59+
60+
/**
61+
* @param class-string $class
62+
*/
63+
public function getClassLocation(string $class): array
64+
{
65+
$reflection = new \ReflectionClass($class);
66+
67+
return [
68+
'class' => $reflection->getShortName(),
69+
'file' => $reflection->getFileName(),
70+
];
71+
}
72+
73+
public function setTracer(TracerInterface $tracer): void
74+
{
75+
$this->tracer = $tracer;
76+
}
77+
78+
public function setTracerProvider(TracerProviderInterface $tracerProvider): void
79+
{
80+
$this->tracerProvider = $tracerProvider;
81+
}
82+
83+
public function setExporterData(SpanExporterInterface $exporter): void
84+
{
85+
$this->data['exporter'] = $this->getClassLocation(get_class($exporter));
86+
//Add directory to exporter class because all exporter are named "Exporter"
87+
$this->data['exporter']['class'] = str_replace('.php', '', implode('/', array_slice(explode('/', $this->data['exporter']['file']), -2, 2, true)));
88+
}
89+
90+
private function loadDataFromTracerSharedState(): void
91+
{
92+
$objectWithSharedState = $this->tracer ?? $this->tracerProvider ?? null;
93+
if ($objectWithSharedState === null) {
94+
return;
95+
}
96+
97+
$reflectedTracer = new \ReflectionClass($objectWithSharedState);
98+
$tss = $reflectedTracer->getProperty('tracerSharedState');
99+
$tss->setAccessible(true);
100+
$this->data['id_generator'] = $this->getClassLocation(get_class($tss->getValue($objectWithSharedState)->getIdGenerator()));
101+
$this->data['sampler'] = $this->getClassLocation(get_class($tss->getValue($objectWithSharedState)->getSampler()));
102+
$this->data['span_processor'] = $this->getClassLocation(get_class($tss->getValue($objectWithSharedState)->getSpanProcessor()));
103+
$this->data['resource_info_attributes'] = $this->cloneVar($tss->getValue($objectWithSharedState)->getResource()->getAttributes());
104+
$this->data['span_limits'] = $this->cloneVar($tss->getValue($objectWithSharedState)->getSpanLimits());
105+
}
106+
107+
private function orderedSpans(): array
108+
{
109+
$spanData = [];
110+
$rootSpanId = null;
111+
/** @var \OpenTelemetry\SDK\Trace\Span $span */
112+
foreach ($this->collectedSpans as $span) {
113+
//probably find better way to identify root span
114+
if (false === $span->getParentContext()->isValid()) {
115+
$spanData['root']['data'] = $this->spanDataToArray($span->toSpanData());
116+
$spanData['root']['children'] = [];
117+
$rootSpanId = $span->getContext()->getSpanId();
118+
}
119+
120+
if ($rootSpanId === $span->getParentContext()->getSpanId()) {
121+
$spanData['root']['children'][] = $this->spanDataToArray($span->toSpanData());
122+
}
123+
}
124+
125+
return $spanData;
126+
}
127+
128+
private function spanDataToArray(SpanDataInterface $spanData): array
129+
{
130+
return [
131+
'spanId' => $spanData->getSpanId(),
132+
'traceId' => $spanData->getTraceId(),
133+
'name' => $spanData->getName(),
134+
'kind' => $spanData->getKind(),
135+
'attributes' => $spanData->getAttributes()->toArray(),
136+
'status' => $spanData->getStatus()->getCode(),
137+
'parentSpanId' => $spanData->getParentSpanId(),
138+
'links' => $spanData->getLinks(),
139+
'events' => $spanData->getEvents(),
140+
];
141+
}
142+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\Symfony\OtelSdkBundle\Debug;
6+
7+
use OpenTelemetry\Context\Context;
8+
use OpenTelemetry\SDK\Trace\ReadableSpanInterface;
9+
use OpenTelemetry\SDK\Trace\ReadWriteSpanInterface;
10+
use OpenTelemetry\SDK\Trace\SpanProcessorInterface;
11+
use OpenTelemetry\Symfony\OtelSdkBundle\DataCollector\OtelDataCollector;
12+
13+
class TraceableSpanProcessor implements SpanProcessorInterface
14+
{
15+
private SpanProcessorInterface $spanProcessor;
16+
private OtelDataCollector $dataCollector;
17+
18+
public function __construct(SpanProcessorInterface $spanProcessor, OtelDataCollector $dataCollector)
19+
{
20+
$this->spanProcessor = $spanProcessor;
21+
$this->dataCollector = $dataCollector;
22+
23+
$reflectedTracer = new \ReflectionClass($this->spanProcessor);
24+
$exporter = $reflectedTracer->getProperty('exporter');
25+
$exporter->setAccessible(true);
26+
$this->dataCollector->setExporterData($exporter->getValue($this->spanProcessor));
27+
}
28+
29+
public function onStart(ReadWriteSpanInterface $span, ?Context $parentContext = null): void
30+
{
31+
$this->dataCollector->collectedSpans[$span->getContext()->getSpanId()] = $span;
32+
$this->spanProcessor->onStart($span, $parentContext);
33+
}
34+
35+
public function onEnd(ReadableSpanInterface $span): void
36+
{
37+
$this->spanProcessor->onEnd($span);
38+
$this->dataCollector->collectedSpans[$span->getContext()->getSpanId()] = $span;
39+
}
40+
41+
public function forceFlush(): bool
42+
{
43+
return $this->spanProcessor->forceFlush();
44+
}
45+
46+
public function shutdown(): bool
47+
{
48+
return $this->spanProcessor->shutdown();
49+
}
50+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\Symfony\OtelSdkBundle\Debug;
6+
7+
use OpenTelemetry\API\Trace\SpanBuilderInterface;
8+
use OpenTelemetry\API\Trace\TracerInterface;
9+
use OpenTelemetry\SDK\Trace\Tracer;
10+
use OpenTelemetry\Symfony\OtelSdkBundle\DataCollector\OtelDataCollector;
11+
12+
class TraceableTracer implements TracerInterface
13+
{
14+
private TracerInterface $tracer;
15+
private OtelDataCollector $dataCollector;
16+
17+
public function __construct(TracerInterface $tracer, OtelDataCollector $dataCollector)
18+
{
19+
$this->tracer = $tracer;
20+
$this->dataCollector = $dataCollector;
21+
$this->dataCollector->setTracer($tracer);
22+
}
23+
24+
/** @inheritDoc */
25+
public function spanBuilder(string $spanName): SpanBuilderInterface
26+
{
27+
if (ctype_space($spanName)) {
28+
$spanName = Tracer::FALLBACK_SPAN_NAME;
29+
}
30+
31+
return $this->tracer->spanBuilder($spanName);
32+
}
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\Symfony\OtelSdkBundle\Debug;
6+
7+
use OpenTelemetry\API\Trace\TracerInterface;
8+
use OpenTelemetry\SDK\Trace\TracerProviderInterface;
9+
use OpenTelemetry\Symfony\OtelSdkBundle\DataCollector\OtelDataCollector;
10+
11+
class TraceableTracerProvider implements TracerProviderInterface
12+
{
13+
private TracerProviderInterface $tracerProvider;
14+
private OtelDataCollector $dataCollector;
15+
16+
public function __construct(TracerProviderInterface $tracerProvider, OtelDataCollector $dataCollector)
17+
{
18+
$this->tracerProvider = $tracerProvider;
19+
$this->dataCollector = $dataCollector;
20+
$this->dataCollector->setTracerProvider($tracerProvider);
21+
}
22+
23+
public function forceFlush(): bool
24+
{
25+
return $this->tracerProvider->forceFlush();
26+
}
27+
28+
public function shutdown(): bool
29+
{
30+
return $this->tracerProvider->shutdown();
31+
}
32+
33+
public function getTracer(string $name, ?string $version = null, ?string $schemaUrl = null, iterable $attributes = []): TracerInterface
34+
{
35+
return $this->tracerProvider->getTracer($name, $version, $schemaUrl, $attributes);
36+
}
37+
}

src/Symfony/OtelSdkBundle/DependencyInjection/OtelSdkExtension.php

+31-2
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,12 @@
2727
class OtelSdkExtension extends Extension implements LoggerAwareInterface
2828
{
2929
public const SDK_CONFIG_FILE = __DIR__ . '/../Resources/config/sdk.php';
30+
public const DEBUG_CONFIG_FILE = __DIR__ . '/../Resources/config/tracer_debug.php';
3031
private const FACTORY_SUFFIX = 'factory';
3132
private const PROVIDER_ARG_PROCESSOR = 0;
3233
private const PROCESSOR_ARG_EXPORTER = 0;
3334
private const DEFAULT_SERVICE_NAME = 'SymfonyApplication';
35+
private const DEV_ENVIRONNEMENT = 'dev';
3436

3537
private string $serviceName = self::DEFAULT_SERVICE_NAME;
3638
private ContainerBuilder $container;
@@ -87,6 +89,7 @@ protected function loadInternal(array $mergedConfig, ContainerBuilder $container
8789
$this->configureTraceSamplers();
8890
$this->configureSpanLimits();
8991
$this->configureSpanProcessors();
92+
$this->loadDebugConfig();
9093
$this->configureSpanExporters();
9194
}
9295

@@ -226,7 +229,6 @@ private function configureSpanExporters(): void
226229
$processor = $this->getSpanProcessorDefinition($conf[Conf::PROCESSOR_NODE]);
227230
$processorId = $this->registerSpanProcessor($processor, $conf[Conf::PROCESSOR_NODE], (string) $exporterKey);
228231
$processorRefs[] = $this->createValidatedReference($processorId);
229-
230232
$exporterReference = $this->resolveExporterReference((string) $exporterKey, $conf);
231233
$processor->setArgument(self::PROCESSOR_ARG_EXPORTER, $exporterReference);
232234
}
@@ -432,9 +434,21 @@ private function registerSpanProcessor(Definition $definition, string $processor
432434
$processorType,
433435
$exporterKey
434436
);
435-
436437
$this->getContainer()->setDefinition($id, $definition);
437438

439+
$env = $this->container->getParameter('kernel.environment');
440+
if (self::DEV_ENVIRONNEMENT === $env) {
441+
$debugId = 'debug.open_telemetry.sdk.trace.span_processor.traceable';
442+
$this->getContainer()->getDefinition($debugId)
443+
->setArgument(
444+
0,
445+
$this->createValidatedReference($id)
446+
)
447+
;
448+
449+
return $debugId;
450+
}
451+
438452
return $id;
439453
}
440454

@@ -569,6 +583,21 @@ private function loadDefaultConfig(): void
569583
}
570584
}
571585

586+
private function loadDebugConfig(): void
587+
{
588+
$env = $this->container->getParameter('kernel.environment');
589+
if (self::DEV_ENVIRONNEMENT !== $env) {
590+
return;
591+
}
592+
593+
try {
594+
self::createPhpFileLoader($this->container)
595+
->load(self::DEBUG_CONFIG_FILE);
596+
} catch (Throwable $e) {
597+
throw new RuntimeException('Could not load config file: ' . self::DEBUG_CONFIG_FILE);
598+
}
599+
}
600+
572601
/**
573602
* @param array $config
574603
* @param bool $classAlias

src/Symfony/OtelSdkBundle/DependencyInjection/SpanProcessors.php

+2
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@
55
namespace OpenTelemetry\Symfony\OtelSdkBundle\DependencyInjection;
66

77
use OpenTelemetry\SDK\Trace\SpanProcessor;
8+
use OpenTelemetry\Symfony\OtelSdkBundle\Debug\TraceableSpanProcessor;
89

910
interface SpanProcessors
1011
{
1112
public const SIMPLE = SpanProcessor\SimpleSpanProcessor::class;
1213
public const BATCH = SpanProcessor\BatchSpanProcessor::class;
1314
public const NOOP = SpanProcessor\NoopSpanProcessor::class;
1415
public const MULTI = SpanProcessor\MultiSpanProcessor::class;
16+
public const TRACEABLE = TraceableSpanProcessor::class;
1517
public const SPAN_PROCESSORS = [
1618
self::SIMPLE,
1719
self::BATCH,

src/Symfony/OtelSdkBundle/Resources/config/sdk.php

+1
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@
103103
);
104104

105105
// TRACER
106+
$helper->setAlias(Trace\TracerProviderInterface::class, 'open_telemetry.sdk.trace.tracer_provider', );
106107

107108
$helper->setService(Trace\TracerProvider::class)
108109
->args([

0 commit comments

Comments
 (0)