Skip to content

Commit 05d2280

Browse files
fainohubCopilot
andauthored
Fixed: Eliminate tracer auto-flush latency; add periodic flush listener (#3)
Co-authored-by: Copilot <[email protected]>
1 parent 3c8c372 commit 05d2280

24 files changed

+488
-190
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@
6363
}
6464
},
6565
"scripts": {
66-
"test": "phpunit",
66+
"test": "co-phpunit --prepend tests/bootstrap.php -c phpunit.xml --colors=always",
6767
"lint:fix": [
6868
"composer lint:cs:fix",
6969
"composer lint:update"

publish/open-telemetry.php

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
'traces' => [
3333
'enabled' => env('OTEL_TRACES_ENABLED', true),
3434
'exporter' => env('OTEL_TRACES_EXPORTER', 'otlp_http'),
35+
'export_interval' => (int) env('OTEL_TRACES_EXPORT_INTERVAL', 5),
3536
'processor' => env('OTEL_TRACES_PROCESSOR', 'batch'),
3637
'sampler' => env('OTEL_TRACES_SAMPLER', 'always_on'),
3738
'exporters' => [
@@ -42,7 +43,7 @@
4243
'content_type' => 'application/x-protobuf',
4344
'compression' => TransportFactoryInterface::COMPRESSION_GZIP,
4445
'headers' => [],
45-
'timeout' => (float) env('OTEL_TRACES_TIMEOUT_MS', 3),
46+
'timeout' => (float) env('OTEL_TRACES_TIMEOUT_SECONDS', 3),
4647
'retry' => [
4748
'delay_ms' => (int) env('OTEL_TRACES_RETRY_DELAY_MS', 100),
4849
'max_retries' => (int) env('OTEL_TRACES_RETRY_MAX', 2),
@@ -57,11 +58,11 @@
5758
'batch' => [
5859
'driver' => BatchSpanProcessorFactory::class,
5960
'options' => [
60-
'max_queue_size' => BatchSpanProcessor::DEFAULT_MAX_EXPORT_BATCH_SIZE,
61+
'max_queue_size' => BatchSpanProcessor::DEFAULT_MAX_QUEUE_SIZE,
6162
'schedule_delay_ms' => BatchSpanProcessor::DEFAULT_SCHEDULE_DELAY,
6263
'export_timeout_ms' => BatchSpanProcessor::DEFAULT_EXPORT_TIMEOUT,
6364
'max_export_batch_size' => BatchSpanProcessor::DEFAULT_MAX_EXPORT_BATCH_SIZE,
64-
'auto_flush' => true,
65+
'auto_flush' => false,
6566
],
6667
],
6768
'simple' => [
@@ -88,7 +89,7 @@
8889
'content_type' => 'application/x-protobuf',
8990
'compression' => TransportFactoryInterface::COMPRESSION_GZIP,
9091
'headers' => [],
91-
'timeout' => (float) env('OTEL_METRICS_TIMEOUT_MS', 3),
92+
'timeout' => (float) env('OTEL_METRICS_TIMEOUT_SECONDS', 3),
9293
'retry' => [
9394
'delay_ms' => (int) env('OTEL_METRICS_RETRY_DELAY_MS', 100),
9495
'max_retries' => (int) env('OTEL_METRICS_RETRY_MAX', 2),
@@ -113,7 +114,7 @@
113114
'content_type' => 'application/x-protobuf',
114115
'compression' => TransportFactoryInterface::COMPRESSION_GZIP,
115116
'headers' => [],
116-
'timeout' => (float) env('OTEL_LOGS_TIMEOUT_MS', 3),
117+
'timeout' => (float) env('OTEL_LOGS_TIMEOUT_SECONDS', 3),
117118
'retry' => [
118119
'delay_ms' => (int) env('OTEL_LOGS_RETRY_DELAY_MS', 100),
119120
'max_retries' => (int) env('OTEL_LOGS_RETRY_MAX', 2),
@@ -128,11 +129,11 @@
128129
'batch' => [
129130
'driver' => BatchLogProcessorFactory::class,
130131
'options' => [
131-
'max_queue_size' => BatchLogRecordProcessor::DEFAULT_MAX_EXPORT_BATCH_SIZE,
132+
'max_queue_size' => BatchLogRecordProcessor::DEFAULT_MAX_QUEUE_SIZE,
132133
'schedule_delay_ms' => BatchLogRecordProcessor::DEFAULT_SCHEDULE_DELAY,
133134
'export_timeout_ms' => BatchLogRecordProcessor::DEFAULT_EXPORT_TIMEOUT,
134135
'max_export_batch_size' => BatchLogRecordProcessor::DEFAULT_MAX_EXPORT_BATCH_SIZE,
135-
'auto_flush' => true,
136+
'auto_flush' => false,
136137
],
137138
],
138139
'simple' => [

src/ConfigProvider.php

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

55
namespace Hyperf\OpenTelemetry;
66

7+
use Hyperf\OpenTelemetry\Factory\Log\LoggerProviderFactory;
8+
use Hyperf\OpenTelemetry\Factory\Metric\MeterProviderFactory;
9+
use Hyperf\OpenTelemetry\Factory\Trace\TracerProviderFactory;
710
use OpenTelemetry\API\Instrumentation\CachedInstrumentation;
11+
use OpenTelemetry\SDK\Logs\LoggerProviderInterface;
12+
use OpenTelemetry\SDK\Metrics\MeterProviderInterface;
813
use OpenTelemetry\SDK\Resource\ResourceInfo;
914
use Hyperf\OpenTelemetry\Factory\CachedInstrumentationFactory;
1015
use Hyperf\OpenTelemetry\Factory\OTelResourceFactory;
16+
use OpenTelemetry\SDK\Trace\TracerProviderInterface;
1117

1218
class ConfigProvider
1319
{
@@ -22,10 +28,15 @@ public function __invoke(): array
2228
'dependencies' => [
2329
CachedInstrumentation::class => CachedInstrumentationFactory::class,
2430
ResourceInfo::class => OTelResourceFactory::class,
31+
TracerProviderInterface::class => TracerProviderFactory::class,
32+
MeterProviderInterface::class => MeterProviderFactory::class,
33+
LoggerProviderInterface::class => LoggerProviderFactory::class,
2534
],
2635
'listeners' => [
27-
Listener\DbQueryExecutedListener::class,
2836
Listener\MetricFlushListener::class,
37+
Listener\TraceFlushListener::class,
38+
Listener\OtelShutdownListener::class,
39+
Listener\DbQueryExecutedListener::class,
2940
],
3041
'aspects' => [
3142
Aspect\RedisAspect::class,

src/Factory/Log/LoggerProviderFactory.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use OpenTelemetry\SDK\Logs\LoggerProviderInterface;
1111
use OpenTelemetry\SDK\Logs\LogRecordExporterInterface;
1212
use OpenTelemetry\SDK\Logs\LogRecordProcessorInterface;
13+
use OpenTelemetry\SDK\Logs\NoopLoggerProvider;
1314
use OpenTelemetry\SDK\Resource\ResourceInfo;
1415
use Hyperf\OpenTelemetry\Factory\Log\Exporter\LogExporterFactoryInterface;
1516
use Hyperf\OpenTelemetry\Factory\Log\Processor\LogProcessorFactoryInterface;
@@ -23,8 +24,14 @@ public function __construct(
2324
) {
2425
}
2526

26-
public function getLoggerProvider(): LoggerProviderInterface
27+
public function __invoke(ContainerInterface $container): LoggerProviderInterface
2728
{
29+
$logsEnabled = $this->config->get('open-telemetry.logs.enabled', false);
30+
31+
if (! $logsEnabled) {
32+
return new NoopLoggerProvider();
33+
}
34+
2835
$exporter = $this->getExporter();
2936
$processor = $this->getProcessor($exporter);
3037

src/Factory/Log/Processor/BatchLogProcessorFactory.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,11 @@ public function make(LogRecordExporterInterface $exporter): LogRecordProcessorIn
2424
return new BatchProcessor(
2525
exporter: $exporter,
2626
clock: Clock::getDefault(),
27-
maxQueueSize: $options['max_queue_size'] ?? BatchProcessor::DEFAULT_MAX_EXPORT_BATCH_SIZE,
27+
maxQueueSize: $options['max_queue_size'] ?? BatchProcessor::DEFAULT_MAX_QUEUE_SIZE,
2828
scheduledDelayMillis: $options['schedule_delay_ms'] ?? BatchProcessor::DEFAULT_SCHEDULE_DELAY,
2929
exportTimeoutMillis: $options['export_timeout_ms'] ?? BatchProcessor::DEFAULT_EXPORT_TIMEOUT,
3030
maxExportBatchSize: $options['max_export_batch_size'] ?? BatchProcessor::DEFAULT_MAX_EXPORT_BATCH_SIZE,
31-
autoFlush: $options['auto_flush'] ?? true,
31+
autoFlush: $options['auto_flush'] ?? false,
3232
);
3333
}
3434
}

src/Factory/Metric/MeterProviderFactory.php

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66

77
use Hyperf\Contract\ConfigInterface;
88
use Hyperf\Contract\ContainerInterface;
9+
use Hyperf\OpenTelemetry\Factory\Metric\Exporter\MetricExporterFactoryInterface;
910
use OpenTelemetry\SDK\Metrics\MeterProvider;
1011
use OpenTelemetry\SDK\Metrics\MeterProviderInterface;
1112
use OpenTelemetry\SDK\Metrics\MetricExporterInterface;
1213
use OpenTelemetry\SDK\Metrics\MetricReader\ExportingReader;
13-
use OpenTelemetry\SDK\Metrics\MetricReaderInterface;
14+
use OpenTelemetry\SDK\Metrics\NoopMeterProvider;
1415
use OpenTelemetry\SDK\Resource\ResourceInfo;
15-
use Hyperf\OpenTelemetry\Factory\Metric\Exporter\MetricExporterFactoryInterface;
1616

1717
class MeterProviderFactory
1818
{
@@ -23,17 +23,19 @@ public function __construct(
2323
) {
2424
}
2525

26-
public function getMeterProvider(): MeterProviderInterface
26+
public function __invoke(ContainerInterface $container): MeterProviderInterface
2727
{
28-
$exporter = $this->getExporter();
28+
$metricsEnabled = $this->config->get('open-telemetry.metrics.enabled', false);
2929

30-
$meterReader = new ExportingReader($exporter);
30+
if (! $metricsEnabled) {
31+
return new NoopMeterProvider();
32+
}
3133

32-
$this->container->set(MetricReaderInterface::class, $meterReader);
34+
$reader = new ExportingReader($this->getExporter());
3335

3436
return MeterProvider::builder()
3537
->setResource($this->resource)
36-
->addReader($meterReader)
38+
->addReader($reader)
3739
->build();
3840
}
3941

src/Factory/SDKBuilder.php

Lines changed: 9 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,45 +4,31 @@
44

55
namespace Hyperf\OpenTelemetry\Factory;
66

7-
use Hyperf\Contract\ConfigInterface;
87
use OpenTelemetry\API\Trace\Propagation\TraceContextPropagator;
8+
use OpenTelemetry\SDK\Logs\LoggerProviderInterface;
99
use OpenTelemetry\SDK\Logs\NoopLoggerProvider;
10+
use OpenTelemetry\SDK\Metrics\MeterProviderInterface;
1011
use OpenTelemetry\SDK\Metrics\NoopMeterProvider;
1112
use OpenTelemetry\SDK\Sdk;
1213
use OpenTelemetry\SDK\Trace\NoopTracerProvider;
13-
use Hyperf\OpenTelemetry\Factory\Log\LoggerProviderFactory;
14-
use Hyperf\OpenTelemetry\Factory\Metric\MeterProviderFactory;
15-
use Hyperf\OpenTelemetry\Factory\Trace\TracerProviderFactory;
14+
use OpenTelemetry\SDK\Trace\TracerProviderInterface;
1615

1716
class SDKBuilder
1817
{
1918
public function __construct(
20-
protected ConfigInterface $config,
21-
protected LoggerProviderFactory $logProviderFactory,
22-
protected TracerProviderFactory $tracerProviderFactory,
23-
protected MeterProviderFactory $meterProviderFactory,
19+
protected LoggerProviderInterface $logProvider,
20+
protected TracerProviderInterface $tracerProvider,
21+
protected MeterProviderInterface $meterProvider,
2422
) {
2523
}
2624

2725
public function build(): void
2826
{
29-
$traces = $this->config->get('open-telemetry.traces.enabled', false);
30-
$metrics = $this->config->get('open-telemetry.metrics.enabled', false);
31-
$logs = $this->config->get('open-telemetry.logs.enabled', false);
32-
3327
$enabled = ! Sdk::isDisabled();
3428

35-
$tracerProvider = ($traces && $enabled)
36-
? $this->tracerProviderFactory->getTracerProvider()
37-
: new NoopTracerProvider();
38-
39-
$meterProvider = ($metrics && $enabled)
40-
? $this->meterProviderFactory->getMeterProvider()
41-
: new NoopMeterProvider();
42-
43-
$loggerProvider = ($logs && $enabled)
44-
? $this->logProviderFactory->getLoggerProvider()
45-
: new NoopLoggerProvider();
29+
$tracerProvider = $enabled ? $this->tracerProvider : new NoopTracerProvider();
30+
$meterProvider = $enabled ? $this->meterProvider : new NoopMeterProvider();
31+
$loggerProvider = $enabled ? $this->logProvider : new NoopLoggerProvider();
4632

4733
Sdk::builder()
4834
->setTracerProvider($tracerProvider)

src/Factory/Trace/Processor/BatchSpanProcessorFactory.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use Hyperf\Contract\ConfigInterface;
88
use OpenTelemetry\API\Common\Time\Clock;
9+
use OpenTelemetry\SDK\Metrics\MeterProviderInterface;
910
use OpenTelemetry\SDK\Trace\SpanExporterInterface;
1011
use OpenTelemetry\SDK\Trace\SpanProcessor\BatchSpanProcessor;
1112
use OpenTelemetry\SDK\Trace\SpanProcessorInterface;
@@ -14,6 +15,7 @@ class BatchSpanProcessorFactory implements TraceProcessorFactoryInterface
1415
{
1516
public function __construct(
1617
protected readonly ConfigInterface $config,
18+
protected readonly MeterProviderInterface $meterProvider,
1719
) {
1820
}
1921

@@ -24,11 +26,12 @@ public function make(SpanExporterInterface $exporter): SpanProcessorInterface
2426
return new BatchSpanProcessor(
2527
exporter: $exporter,
2628
clock: Clock::getDefault(),
27-
maxQueueSize: $options['max_queue_size'] ?? BatchSpanProcessor::DEFAULT_MAX_EXPORT_BATCH_SIZE,
29+
maxQueueSize: $options['max_queue_size'] ?? BatchSpanProcessor::DEFAULT_MAX_QUEUE_SIZE,
2830
scheduledDelayMillis: $options['schedule_delay_ms'] ?? BatchSpanProcessor::DEFAULT_SCHEDULE_DELAY,
2931
exportTimeoutMillis: $options['export_timeout_ms'] ?? BatchSpanProcessor::DEFAULT_EXPORT_TIMEOUT,
3032
maxExportBatchSize: $options['max_export_batch_size'] ?? BatchSpanProcessor::DEFAULT_MAX_EXPORT_BATCH_SIZE,
31-
autoFlush: $options['auto_flush'] ?? true,
33+
autoFlush: $options['auto_flush'] ?? false,
34+
meterProvider: $this->meterProvider
3235
);
3336
}
3437
}

src/Factory/Trace/TracerProviderFactory.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Hyperf\Contract\ConfigInterface;
88
use Hyperf\Contract\ContainerInterface;
99
use OpenTelemetry\SDK\Resource\ResourceInfo;
10+
use OpenTelemetry\SDK\Trace\NoopTracerProvider;
1011
use OpenTelemetry\SDK\Trace\SamplerInterface;
1112
use OpenTelemetry\SDK\Trace\SpanExporterInterface;
1213
use OpenTelemetry\SDK\Trace\SpanProcessorInterface;
@@ -25,8 +26,14 @@ public function __construct(
2526
) {
2627
}
2728

28-
public function getTracerProvider(): TracerProviderInterface
29+
public function __invoke(ContainerInterface $container): TracerProviderInterface
2930
{
31+
$tracesEnabled = $this->config->get('open-telemetry.traces.enabled', false);
32+
33+
if (! $tracesEnabled) {
34+
return new NoopTracerProvider();
35+
}
36+
3037
$exporter = $this->getExporter();
3138
$processor = $this->getProcessor($exporter);
3239
$sampler = $this->getSampler();
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Hyperf\OpenTelemetry\Listener;
6+
7+
use Hyperf\Command\Event\AfterHandle;
8+
use Hyperf\Command\Event\BeforeHandle;
9+
use Hyperf\Contract\ConfigInterface;
10+
use Hyperf\Contract\ContainerInterface;
11+
use Hyperf\Contract\StdoutLoggerInterface;
12+
use Hyperf\Coordinator\Constants;
13+
use Hyperf\Coordinator\CoordinatorManager;
14+
use Hyperf\Coordinator\Timer;
15+
use Hyperf\Coroutine\Coroutine;
16+
use Hyperf\Event\Contract\ListenerInterface;
17+
use Hyperf\Framework\Event\BeforeWorkerStart;
18+
use Throwable;
19+
20+
abstract class AbstractFlushListener implements ListenerInterface
21+
{
22+
private Timer $timer;
23+
24+
private ?int $timerId = null;
25+
26+
private bool $running = false;
27+
28+
public function __construct(
29+
protected readonly ContainerInterface $container,
30+
protected readonly ConfigInterface $config,
31+
protected readonly StdoutLoggerInterface $logger,
32+
) {
33+
$this->timer = $this->container->make(Timer::class);
34+
}
35+
36+
public function listen(): array
37+
{
38+
return [
39+
BeforeWorkerStart::class,
40+
BeforeHandle::class,
41+
AfterHandle::class,
42+
];
43+
}
44+
45+
abstract function flush(): void;
46+
47+
abstract function exportInterval() :float;
48+
49+
public function process(object $event): void
50+
{
51+
if ($event instanceof BeforeWorkerStart || $event instanceof BeforeHandle) {
52+
$this->startTimer();
53+
return;
54+
}
55+
56+
if ($event instanceof AfterHandle) {
57+
$this->clearTimer();
58+
}
59+
}
60+
61+
private function startTimer(): void
62+
{
63+
if ($this->timerId !== null) {
64+
return;
65+
}
66+
67+
$this->timerId = $this->timer->tick($this->exportInterval(), function (): void {
68+
if ($this->running) {
69+
return;
70+
}
71+
72+
$this->running = true;
73+
74+
try {
75+
$this->flush();
76+
} catch (Throwable $e) {
77+
$this->logger->warning('[OTel] periodic flush failed', ['exception' => $e]);
78+
} finally {
79+
$this->running = false;
80+
}
81+
});
82+
83+
Coroutine::create(function (): void {
84+
CoordinatorManager::until(Constants::WORKER_EXIT)->yield();
85+
$this->clearTimer();
86+
});
87+
}
88+
89+
private function clearTimer(): void
90+
{
91+
if ($this->timerId !== null) {
92+
$this->timer->clear($this->timerId);
93+
$this->timerId = null;
94+
}
95+
}
96+
}

0 commit comments

Comments
 (0)