Skip to content

Commit 39a2b48

Browse files
Refactor InMemory exporters to use shared storage manager (#1559)
Introduced `InMemoryStorageManager` to centralize storage management for spans, metrics, and logs. Updated InMemory exporters and their factory classes to utilize this shared storage. * Add integration test for InMemoryExporter - add integration test that shows that storage contains metrics - because of src/SDK/Metrics/MetricReader/ExportingReader.php:148 I had to add `PushMetricExporterInterface` to InMemoryExporter class * Add integration test for InMemoryExporter
1 parent e253da3 commit 39a2b48

File tree

9 files changed

+214
-22
lines changed

9 files changed

+214
-22
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\SDK\Common\Export;
6+
7+
use ArrayObject;
8+
9+
class InMemoryStorageManager
10+
{
11+
private static ArrayObject $spans;
12+
private static ArrayObject $metrics;
13+
private static ArrayObject $logs;
14+
15+
public static function metrics(): ArrayObject
16+
{
17+
/** @psalm-suppress RedundantPropertyInitializationCheck */
18+
return self::$metrics ??= new ArrayObject();
19+
}
20+
21+
public static function logs(): ArrayObject
22+
{
23+
/** @psalm-suppress RedundantPropertyInitializationCheck */
24+
return self::$logs ??= new ArrayObject();
25+
}
26+
27+
public static function spans(): ArrayObject
28+
{
29+
/** @psalm-suppress RedundantPropertyInitializationCheck */
30+
return self::$spans ??= new ArrayObject();
31+
}
32+
}

src/SDK/Logs/Exporter/InMemoryExporterFactory.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44

55
namespace OpenTelemetry\SDK\Logs\Exporter;
66

7+
use OpenTelemetry\SDK\Common\Export\InMemoryStorageManager;
78
use OpenTelemetry\SDK\Logs\LogRecordExporterFactoryInterface;
89
use OpenTelemetry\SDK\Logs\LogRecordExporterInterface;
910

1011
class InMemoryExporterFactory implements LogRecordExporterFactoryInterface
1112
{
1213
public function create(): LogRecordExporterInterface
1314
{
14-
return new InMemoryExporter();
15+
return new InMemoryExporter(InMemoryStorageManager::logs());
1516
}
1617
}

src/SDK/Metrics/MetricExporter/InMemoryExporter.php

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,30 @@
44

55
namespace OpenTelemetry\SDK\Metrics\MetricExporter;
66

7-
use function array_push;
7+
use ArrayObject;
88
use OpenTelemetry\SDK\Metrics\AggregationTemporalitySelectorInterface;
99
use OpenTelemetry\SDK\Metrics\Data\Metric;
1010
use OpenTelemetry\SDK\Metrics\Data\Temporality;
1111
use OpenTelemetry\SDK\Metrics\MetricExporterInterface;
1212
use OpenTelemetry\SDK\Metrics\MetricMetadataInterface;
13+
use OpenTelemetry\SDK\Metrics\PushMetricExporterInterface;
1314

1415
/**
1516
* @see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk_exporters/in-memory.md
1617
*/
17-
final class InMemoryExporter implements MetricExporterInterface, AggregationTemporalitySelectorInterface
18+
final class InMemoryExporter implements MetricExporterInterface, AggregationTemporalitySelectorInterface, PushMetricExporterInterface
1819
{
19-
/**
20-
* @var list<Metric>
21-
*/
22-
private array $metrics = [];
23-
2420
private bool $closed = false;
2521

26-
public function __construct(private readonly string|Temporality|null $temporality = null)
27-
{
22+
/**
23+
* @template-implements ArrayObject<Metric> $storage
24+
* @param ArrayObject $storage
25+
* @param string|Temporality|null $temporality
26+
*/
27+
public function __construct(
28+
private ArrayObject $storage = new ArrayObject(),
29+
private readonly string|Temporality|null $temporality = null,
30+
) {
2831
}
2932

3033
public function temporality(MetricMetadataInterface $metric): string|Temporality|null
@@ -33,13 +36,13 @@ public function temporality(MetricMetadataInterface $metric): string|Temporality
3336
}
3437

3538
/**
36-
* @return list<Metric>
39+
* @return Metric[]
3740
*/
3841
public function collect(bool $reset = false): array
3942
{
40-
$metrics = $this->metrics;
43+
$metrics = $this->storage->getArrayCopy();
4144
if ($reset) {
42-
$this->metrics = [];
45+
$this->storage = new ArrayObject();
4346
}
4447

4548
return $metrics;
@@ -51,8 +54,9 @@ public function export(iterable $batch): bool
5154
return false;
5255
}
5356

54-
/** @psalm-suppress InvalidPropertyAssignmentValue */
55-
array_push($this->metrics, ...$batch);
57+
foreach ($batch as $metric) {
58+
$this->storage->append($metric);
59+
}
5660

5761
return true;
5862
}
@@ -67,4 +71,9 @@ public function shutdown(): bool
6771

6872
return true;
6973
}
74+
75+
public function forceFlush(): bool
76+
{
77+
return true;
78+
}
7079
}

src/SDK/Metrics/MetricExporter/InMemoryExporterFactory.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44

55
namespace OpenTelemetry\SDK\Metrics\MetricExporter;
66

7+
use OpenTelemetry\SDK\Common\Export\InMemoryStorageManager;
78
use OpenTelemetry\SDK\Metrics\MetricExporterFactoryInterface;
89
use OpenTelemetry\SDK\Metrics\MetricExporterInterface;
910

1011
class InMemoryExporterFactory implements MetricExporterFactoryInterface
1112
{
1213
public function create(): MetricExporterInterface
1314
{
14-
return new InMemoryExporter();
15+
return new InMemoryExporter(InMemoryStorageManager::metrics());
1516
}
1617
}

src/SDK/Trace/SpanExporter/InMemorySpanExporterFactory.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@
44

55
namespace OpenTelemetry\SDK\Trace\SpanExporter;
66

7+
use OpenTelemetry\SDK\Common\Export\InMemoryStorageManager;
78
use OpenTelemetry\SDK\Trace\SpanExporterInterface;
89

910
class InMemorySpanExporterFactory implements SpanExporterFactoryInterface
1011
{
1112
public function create(): SpanExporterInterface
1213
{
13-
return new InMemoryExporter();
14+
return new InMemoryExporter(InMemoryStorageManager::spans());
1415
}
1516
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Integration\SDK\Common;
6+
7+
use OpenTelemetry\API\Globals;
8+
use OpenTelemetry\API\LoggerHolder;
9+
use OpenTelemetry\API\Logs\LogRecord;
10+
use OpenTelemetry\SDK\Common\Configuration\Variables;
11+
use OpenTelemetry\SDK\Common\Export\InMemoryStorageManager;
12+
use OpenTelemetry\SDK\Logs\LoggerProvider;
13+
use OpenTelemetry\SDK\Metrics\MeterProvider;
14+
use OpenTelemetry\SDK\SdkAutoloader;
15+
use OpenTelemetry\SDK\Trace\TracerProvider;
16+
use OpenTelemetry\Tests\TestState;
17+
use PHPUnit\Framework\MockObject\MockObject;
18+
use PHPUnit\Framework\TestCase;
19+
use Psr\Log\LoggerInterface;
20+
21+
class InMemoryStorageManagerWithSKDAutoload extends TestCase
22+
{
23+
use TestState;
24+
25+
protected LoggerInterface&MockObject $logger;
26+
27+
public function setUp(): void
28+
{
29+
$this->logger = $this->createMock(LoggerInterface::class);
30+
LoggerHolder::set($this->logger);
31+
}
32+
33+
public function test_in_memory_storage_manager_for_metrics_with_sdk_autoload_enabled(): void
34+
{
35+
$this->setEnvironmentVariable('OTEL_METRICS_EXPORTER', 'memory');
36+
$this->setEnvironmentVariable('OTEL_METRICS_EXEMPLAR_FILTER', 'all');
37+
$this->setEnvironmentVariable('OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE', 'cumulative');
38+
$this->setEnvironmentVariable(Variables::OTEL_PHP_AUTOLOAD_ENABLED, 'true');
39+
SdkAutoloader::autoload();
40+
41+
$meterProvider = Globals::meterProvider();
42+
43+
$m = $meterProvider->getMeter('some_meter');
44+
$counter = $m->createCounter('test_value');
45+
$counter->add(1);
46+
$counter->add(5);
47+
48+
if ($meterProvider instanceof MeterProvider) {
49+
$meterProvider->forceFlush();
50+
}
51+
52+
$storage = InMemoryStorageManager::metrics();
53+
$this->assertEquals(1, $storage->count());
54+
$this->assertEquals('test_value', $storage[0]->name);
55+
$this->assertEquals(6, $storage[0]->data->dataPoints[0]->value);
56+
}
57+
58+
public function test_in_memory_storage_manager_for_logs_with_sdk_autoload_enabled(): void
59+
{
60+
$this->setEnvironmentVariable('OTEL_LOGS_EXPORTER', 'memory');
61+
$this->setEnvironmentVariable(Variables::OTEL_PHP_AUTOLOAD_ENABLED, 'true');
62+
SdkAutoloader::autoload();
63+
64+
$loggerProvider = Globals::loggerProvider();
65+
$log = new LogRecord('some body');
66+
$loggerProvider->getLogger('test_logger')->emit($log);
67+
if ($loggerProvider instanceof LoggerProvider) {
68+
$loggerProvider->forceFlush();
69+
}
70+
71+
$storage = InMemoryStorageManager::logs();
72+
$this->assertEquals(1, $storage->count());
73+
$this->assertEquals('some body', $storage[0]->getBody());
74+
}
75+
76+
public function test_in_memory_storage_manager_for_traces_with_sdk_autoload_enabled(): void
77+
{
78+
$this->setEnvironmentVariable('OTEL_TRACES_EXPORTER', 'memory');
79+
$this->setEnvironmentVariable(Variables::OTEL_PHP_AUTOLOAD_ENABLED, 'true');
80+
SdkAutoloader::autoload();
81+
82+
$tracerProvider = Globals::tracerProvider();
83+
$tracer = $tracerProvider->getTracer('test_tracer');
84+
$tracer->spanBuilder('test_span_1')->startSpan()->end();
85+
$tracer->spanBuilder('test_span_2')->startSpan()->end();
86+
if ($tracerProvider instanceof TracerProvider) {
87+
$tracerProvider->forceFlush();
88+
}
89+
90+
$storage = InMemoryStorageManager::spans();
91+
$this->assertEquals(2, $storage->count());
92+
$this->assertEquals('test_span_1', $storage[0]->getName());
93+
$this->assertEquals('test_span_2', $storage[1]->getName());
94+
}
95+
}

tests/Integration/SDK/Metrics/MeterConfigTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ public function test_streams_recreated_on_enable(): void
105105
$clock = new TestClock(self::T0);
106106
$disabledConfigurator = Configurator::meter()
107107
->with(static fn (MeterConfig $config) => $config->setDisabled(true), name: '*');
108-
$exporter = new InMemoryExporter(Temporality::CUMULATIVE);
108+
$exporter = new InMemoryExporter(temporality: Temporality::CUMULATIVE);
109109
$reader = new ExportingReader($exporter);
110110
$meterProvider = MeterProvider::builder()
111111
->addReader($reader)
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\Tests\Unit\SDK\Common\Export;
6+
7+
use ArrayObject;
8+
use OpenTelemetry\SDK\Common\Export\InMemoryStorageManager;
9+
use PHPUnit\Framework\Attributes\CoversClass;
10+
use PHPUnit\Framework\Attributes\DataProvider;
11+
use PHPUnit\Framework\TestCase;
12+
13+
/**
14+
* Test class for InMemoryStorageManager.
15+
*
16+
* This class focuses on testing the `getStorageForMetrics` method.
17+
*/
18+
#[CoversClass(\OpenTelemetry\SDK\Common\Export\InMemoryStorageManager::class)]
19+
class InMemoryStorageManagerTest extends TestCase
20+
{
21+
public static function getStorageName(): array
22+
{
23+
return [
24+
['metrics'],
25+
['logs'],
26+
['spans'],
27+
];
28+
}
29+
30+
public function test_get_storage_for_metrics_returns_same_instance(): void
31+
{
32+
$storageFirstCall = InMemoryStorageManager::metrics();
33+
$storageSecondCall = InMemoryStorageManager::metrics();
34+
$this->assertSame($storageFirstCall, $storageSecondCall);
35+
}
36+
37+
#[DataProvider('getStorageName')]
38+
public function test_get_storage_for_metrics_operates_properly($method): void
39+
{
40+
/** @var ArrayObject $storage */
41+
$storage = call_user_func(InMemoryStorageManager::class . '::' . $method);
42+
$storage->append('test_metric');
43+
44+
$this->assertCount(1, $storage);
45+
$this->assertEquals('test_metric', $storage[0]);
46+
47+
// test the similar when getting storage again
48+
/** @var ArrayObject $storage */
49+
$storage = call_user_func(InMemoryStorageManager::class . '::' . $method);
50+
$this->assertCount(1, $storage);
51+
$this->assertEquals('test_metric', $storage[0]);
52+
}
53+
}

tests/Unit/SDK/Metrics/MetricReader/ExportingReaderTest.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ public function test_default_aggregation_returns_exporter_aggregation_if_default
7575

7676
public function test_add_creates_metric_source_with_exporter_temporality(): void
7777
{
78-
$exporter = new InMemoryExporter(Temporality::CUMULATIVE);
78+
$exporter = new InMemoryExporter(temporality: Temporality::CUMULATIVE);
7979
$reader = new ExportingReader($exporter);
8080

8181
$provider = $this->createMock(MetricSourceProviderInterface::class);
@@ -103,7 +103,7 @@ public function test_add_does_not_create_metric_source_if_exporter_temporality_n
103103

104104
public function test_add_does_not_create_metric_source_if_reader_closed(): void
105105
{
106-
$exporter = new InMemoryExporter(Temporality::CUMULATIVE);
106+
$exporter = new InMemoryExporter(temporality: Temporality::CUMULATIVE);
107107
$reader = new ExportingReader($exporter);
108108

109109
$provider = $this->createMock(MetricSourceProviderInterface::class);
@@ -118,7 +118,7 @@ public function test_add_does_not_create_metric_source_if_reader_closed(): void
118118

119119
public function test_staleness_handler_clears_source(): void
120120
{
121-
$exporter = new InMemoryExporter(Temporality::CUMULATIVE);
121+
$exporter = new InMemoryExporter(temporality: Temporality::CUMULATIVE);
122122
$reader = new ExportingReader($exporter);
123123

124124
$provider = $this->createMock(MetricSourceProviderInterface::class);
@@ -134,7 +134,7 @@ public function test_staleness_handler_clears_source(): void
134134

135135
public function test_collect_collects_sources_with_current_timestamp(): void
136136
{
137-
$exporter = new InMemoryExporter(Temporality::CUMULATIVE);
137+
$exporter = new InMemoryExporter(temporality: Temporality::CUMULATIVE);
138138
$reader = new ExportingReader($exporter);
139139

140140
$metric = new Metric(

0 commit comments

Comments
 (0)