diff --git a/common/Internal/InstrumentationScopeCache.php b/common/Internal/InstrumentationScopeCache.php new file mode 100644 index 0000000..58814b5 --- /dev/null +++ b/common/Internal/InstrumentationScopeCache.php @@ -0,0 +1,72 @@ +> */ + private array $instrumentationScopes = []; + /** + * @var WeakMap + * @noinspection PhpPropertyOnlyWrittenInspection + */ + private readonly WeakMap $destructors; + + public function __construct( + private readonly ?LoggerInterface $logger, + ) { + $this->destructors = new WeakMap(); + } + + public function intern(InstrumentationScope $instrumentationScope): InstrumentationScope { + $instrumentationScopeId = hash('xxh128', serialize([ + $instrumentationScope->name, + $instrumentationScope->version, + $instrumentationScope->schemaUrl, + ]), true); + + if (!$internScope = ($this->instrumentationScopes[$instrumentationScopeId] ?? null)?->get()) { + /** @noinspection PhpSecondWriteToReadonlyPropertyInspection */ + $this->destructors[$instrumentationScope] = $this->destructor($instrumentationScopeId); + $this->instrumentationScopes[$instrumentationScopeId] = WeakReference::create($instrumentationScope); + + return $instrumentationScope; + } + + if ($internScope->attributes->toArray() !== $instrumentationScope->attributes->toArray()) { + $this->logger->warning('Instrumentation scope with same identity and differing non-identifying fields, using first-seen instrumentation scope', [ + 'name' => $instrumentationScope->name, + 'version' => $instrumentationScope->version, + 'schemaUrl' => $instrumentationScope->schemaUrl, + ]); + } + + return $internScope; + } + + private function destructor(string $instrumentationScopeId): object { + return new class($this->prune(...), $instrumentationScopeId) { + public function __construct( + private readonly Closure $prune, + private readonly string $instrumentationScopeId, + ) {} + public function __destruct() { + ($this->prune)($this->instrumentationScopeId); + } + }; + } + + private function prune(string $instrumentationScopeId): void { + unset($this->instrumentationScopes[$instrumentationScopeId]); + } +} diff --git a/logs/Internal/EventLogger.php b/logs/Internal/EventLogger.php index c2f06fe..f276712 100644 --- a/logs/Internal/EventLogger.php +++ b/logs/Internal/EventLogger.php @@ -3,6 +3,7 @@ use Nevay\OTelSDK\Common\ContextResolver; use Nevay\OTelSDK\Common\InstrumentationScope; +use Nevay\OTelSDK\Logs\LoggerConfig; use OpenTelemetry\API\Logs\EventLoggerInterface; use OpenTelemetry\API\Logs\Severity; use OpenTelemetry\API\Trace\Span; @@ -16,6 +17,7 @@ final class EventLogger implements EventLoggerInterface { public function __construct( private readonly LoggerState $loggerState, private readonly InstrumentationScope $instrumentationScope, + private readonly LoggerConfig $loggerConfig, ) {} public function emit( @@ -26,6 +28,10 @@ public function emit( ?Severity $severityNumber = null, iterable $attributes = [], ): void { + if ($this->loggerConfig->disabled) { + return; + } + $context = ContextResolver::resolve($context, $this->loggerState->contextStorage); $observedTimestamp = $this->loggerState->clock->now(); diff --git a/logs/Internal/LoggerProvider.php b/logs/Internal/LoggerProvider.php index 4b8979b..ee52f1c 100644 --- a/logs/Internal/LoggerProvider.php +++ b/logs/Internal/LoggerProvider.php @@ -6,6 +6,7 @@ use Nevay\OTelSDK\Common\AttributesFactory; use Nevay\OTelSDK\Common\Clock; use Nevay\OTelSDK\Common\InstrumentationScope; +use Nevay\OTelSDK\Common\Internal\InstrumentationScopeCache; use Nevay\OTelSDK\Common\Provider; use Nevay\OTelSDK\Common\Resource; use Nevay\OTelSDK\Logs\LoggerConfig; @@ -14,9 +15,9 @@ use OpenTelemetry\API\Logs\EventLoggerProviderInterface; use OpenTelemetry\API\Logs\LoggerInterface; use OpenTelemetry\API\Logs\LoggerProviderInterface; -use OpenTelemetry\API\Logs\NoopEventLogger; use OpenTelemetry\Context\ContextStorageInterface; use Psr\Log\LoggerInterface as PsrLoggerInterface; +use WeakMap; /** * @internal @@ -25,6 +26,8 @@ final class LoggerProvider implements LoggerProviderInterface, EventLoggerProvid private readonly LoggerState $loggerState; private readonly AttributesFactory $instrumentationScopeAttributesFactory; + private readonly InstrumentationScopeCache $instrumentationScopeCache; + private readonly WeakMap $configCache; private readonly Closure $loggerConfigurator; /** @@ -49,6 +52,8 @@ public function __construct( $logger, ); $this->instrumentationScopeAttributesFactory = $instrumentationScopeAttributesFactory; + $this->instrumentationScopeCache = new InstrumentationScopeCache($logger); + $this->configCache = new WeakMap(); $this->loggerConfigurator = $loggerConfigurator; } @@ -64,7 +69,10 @@ public function getLogger( $instrumentationScope = new InstrumentationScope($name, $version, $schemaUrl, $this->instrumentationScopeAttributesFactory->builder()->addAll($attributes)->build()); - $loggerConfig = ($this->loggerConfigurator)($instrumentationScope); + $instrumentationScope = $this->instrumentationScopeCache->intern($instrumentationScope); + + /** @noinspection PhpSecondWriteToReadonlyPropertyInspection */ + $loggerConfig = $this->configCache[$instrumentationScope] ??= ($this->loggerConfigurator)($instrumentationScope); return new Logger($this->loggerState, $instrumentationScope, $loggerConfig); } @@ -81,13 +89,12 @@ public function getEventLogger( $instrumentationScope = new InstrumentationScope($name, $version, $schemaUrl, $this->instrumentationScopeAttributesFactory->builder()->addAll($attributes)->build()); + $instrumentationScope = $this->instrumentationScopeCache->intern($instrumentationScope); - $config = ($this->loggerConfigurator)($instrumentationScope); - if ($config->disabled) { - return new NoopEventLogger(); - } + /** @noinspection PhpSecondWriteToReadonlyPropertyInspection */ + $loggerConfig = $this->configCache[$instrumentationScope] ??= ($this->loggerConfigurator)($instrumentationScope); - return new EventLogger($this->loggerState, $instrumentationScope); + return new EventLogger($this->loggerState, $instrumentationScope, $loggerConfig); } public function shutdown(?Cancellation $cancellation = null): bool { diff --git a/logs/LoggerConfig.php b/logs/LoggerConfig.php index 63530a4..910dccc 100644 --- a/logs/LoggerConfig.php +++ b/logs/LoggerConfig.php @@ -2,6 +2,8 @@ namespace Nevay\OTelSDK\Logs; /** + * @property-read bool $disabled + * * @experimental */ final class LoggerConfig { @@ -9,4 +11,10 @@ final class LoggerConfig { public function __construct( public bool $disabled = false, ) {} + + public function setDisabled(bool $disabled): self { + $this->disabled = $disabled; + + return $this; + } } diff --git a/metrics/Internal/Instrument/AsynchronousInstrument.php b/metrics/Internal/Instrument/AsynchronousInstrument.php index f669003..cb89a43 100644 --- a/metrics/Internal/Instrument/AsynchronousInstrument.php +++ b/metrics/Internal/Instrument/AsynchronousInstrument.php @@ -4,7 +4,6 @@ use Nevay\OTelSDK\Metrics\Instrument; use Nevay\OTelSDK\Metrics\Internal\Registry\MetricWriter; use Nevay\OTelSDK\Metrics\Internal\StalenessHandler\ReferenceCounter; -use Nevay\OTelSDK\Metrics\MeterConfig; use OpenTelemetry\API\Metrics\ObservableCallbackInterface; use OpenTelemetry\API\Metrics\ObserverInterface; use WeakMap; @@ -23,7 +22,6 @@ public function __construct( private readonly Instrument $instrument, private readonly ReferenceCounter $referenceCounter, private readonly WeakMap $destructors, - private readonly MeterConfig $meterConfig, ) { assert($this instanceof InstrumentHandle); @@ -39,7 +37,7 @@ public function getHandle(): Instrument { } public function enabled(): bool { - return !$this->meterConfig->disabled && $this->writer->enabled($this->instrument); + return $this->writer->enabled($this->instrument); } /** diff --git a/metrics/Internal/Instrument/SynchronousInstrument.php b/metrics/Internal/Instrument/SynchronousInstrument.php index b62e890..209a03a 100644 --- a/metrics/Internal/Instrument/SynchronousInstrument.php +++ b/metrics/Internal/Instrument/SynchronousInstrument.php @@ -4,7 +4,6 @@ use Nevay\OTelSDK\Metrics\Instrument; use Nevay\OTelSDK\Metrics\Internal\Registry\MetricWriter; use Nevay\OTelSDK\Metrics\Internal\StalenessHandler\ReferenceCounter; -use Nevay\OTelSDK\Metrics\MeterConfig; use OpenTelemetry\Context\ContextInterface; use function assert; @@ -17,7 +16,6 @@ public function __construct( private readonly MetricWriter $writer, private readonly Instrument $instrument, private readonly ReferenceCounter $referenceCounter, - private readonly MeterConfig $meterConfig, ) { assert($this instanceof InstrumentHandle); @@ -33,7 +31,7 @@ public function getHandle(): Instrument { } public function enabled(): bool { - return !$this->meterConfig->disabled && $this->writer->enabled($this->instrument); + return $this->writer->enabled($this->instrument); } /** @@ -43,10 +41,6 @@ public function enabled(): bool { * @noinspection PhpMissingParamTypeInspection */ public function write($amount, iterable $attributes = [], $context = null): void { - if ($this->meterConfig->disabled) { - return; - } - $this->writer->record($this->instrument, $amount, $attributes, $context); } } diff --git a/metrics/Internal/Meter.php b/metrics/Internal/Meter.php index 47be4bb..bbaa076 100644 --- a/metrics/Internal/Meter.php +++ b/metrics/Internal/Meter.php @@ -14,7 +14,6 @@ use Nevay\OTelSDK\Metrics\Internal\Instrument\ObservableUpDownCounter; use Nevay\OTelSDK\Metrics\Internal\Instrument\UpDownCounter; use Nevay\OTelSDK\Metrics\Internal\StalenessHandler\MultiReferenceCounter; -use Nevay\OTelSDK\Metrics\MeterConfig; use OpenTelemetry\API\Metrics\AsynchronousInstrument; use OpenTelemetry\API\Metrics\CounterInterface; use OpenTelemetry\API\Metrics\GaugeInterface; @@ -36,7 +35,6 @@ final class Meter implements MeterInterface { public function __construct( private readonly MeterState $meterState, private readonly InstrumentationScope $instrumentationScope, - private readonly MeterConfig $meterConfig, ) {} private static function dummyInstrument(): Instrument { @@ -123,9 +121,9 @@ public function createObservableUpDownCounter(string $name, ?string $unit = null */ private function createSynchronousInstrument(string $class, InstrumentType $type, string $name, ?string $unit, ?string $description, array $advisory): InstrumentHandle { [$instrument, $referenceCounter] = $this->meterState->createSynchronousInstrument(new Instrument( - $type, $name, $unit, $description, $advisory), $this->instrumentationScope, $this->meterConfig); + $type, $name, $unit, $description, $advisory), $this->instrumentationScope); - return new $class($this->meterState->registry, $instrument, $referenceCounter, $this->meterConfig); + return new $class($this->meterState->registry, $instrument, $referenceCounter); } /** @@ -140,13 +138,13 @@ private function createAsynchronousInstrument(string $class, InstrumentType $typ $advisory = []; } [$instrument, $referenceCounter] = $this->meterState->createAsynchronousInstrument(new Instrument( - $type, $name, $unit, $description, $advisory), $this->instrumentationScope, $this->meterConfig); + $type, $name, $unit, $description, $advisory), $this->instrumentationScope); foreach ($callbacks as $callback) { $this->meterState->registry->registerCallback(closure($callback), $instrument); $referenceCounter->acquire(true); } - return new $class($this->meterState->registry, $instrument, $referenceCounter, $this->meterState->destructors, $this->meterConfig); + return new $class($this->meterState->registry, $instrument, $referenceCounter, $this->meterState->destructors); } } diff --git a/metrics/Internal/MeterMetricProducer.php b/metrics/Internal/MeterMetricProducer.php index fef2434..b914a6e 100644 --- a/metrics/Internal/MeterMetricProducer.php +++ b/metrics/Internal/MeterMetricProducer.php @@ -43,7 +43,9 @@ public function registerMetricSource(int $streamId, MetricStreamSource $streamSo } public function produce(?MetricFilter $metricFilter = null, ?Cancellation $cancellation = null): iterable { - $sources = $this->applyMetricFilter($this->sources, $metricFilter); + $sources = $metricFilter + ? $this->applyMetricFilter($this->sources, $metricFilter) + : $this->sources; $streamIds = count($sources) === count($this->sources) ? $this->streamIds ??= array_keys($this->sources) : array_keys($sources); @@ -71,10 +73,15 @@ public function produce(?MetricFilter $metricFilter = null, ?Cancellation $cance * @param array> $sources * @return array> */ - private function applyMetricFilter(array $sources, ?MetricFilter $filter): array { + private function applyMetricFilter(array $sources, MetricFilter $filter): array { foreach ($sources as $streamId => $streamSources) { foreach ($streamSources as $sourceId => $source) { - $result = self::testMetric($source, $filter); + $result = $filter->testMetric( + $source->descriptor->instrumentationScope, + $source->descriptor->name, + $source->descriptor->instrumentType, + $source->descriptor->unit, + ); if ($result === MetricFilterResult::Accept) { // no-op @@ -84,7 +91,6 @@ private function applyMetricFilter(array $sources, ?MetricFilter $filter): array $source->descriptor, new FilteredMetricStream($source->descriptor, $source->stream, $filter), $source->reader, - $source->meterConfig, ); } if ($result === MetricFilterResult::Drop) { @@ -99,17 +105,4 @@ private function applyMetricFilter(array $sources, ?MetricFilter $filter): array return $sources; } - - private static function testMetric(MetricStreamSource $source, ?MetricFilter $filter): MetricFilterResult { - if ($source->meterConfig->disabled) { - return MetricFilterResult::Drop; - } - - return $filter?->testMetric( - $source->descriptor->instrumentationScope, - $source->descriptor->name, - $source->descriptor->instrumentType, - $source->descriptor->unit, - ) ?? MetricFilterResult::Accept; - } } diff --git a/metrics/Internal/MeterProvider.php b/metrics/Internal/MeterProvider.php index 8344ba9..340fb3d 100644 --- a/metrics/Internal/MeterProvider.php +++ b/metrics/Internal/MeterProvider.php @@ -7,6 +7,7 @@ use Nevay\OTelSDK\Common\AttributesFactory; use Nevay\OTelSDK\Common\Clock; use Nevay\OTelSDK\Common\InstrumentationScope; +use Nevay\OTelSDK\Common\Internal\InstrumentationScopeCache; use Nevay\OTelSDK\Common\Provider; use Nevay\OTelSDK\Common\Resource; use Nevay\OTelSDK\Metrics\Aggregator; @@ -31,6 +32,8 @@ final class MeterProvider implements MeterProviderInterface, Provider { private readonly MeterState $meterState; private readonly AttributesFactory $instrumentationScopeAttributesFactory; + private readonly InstrumentationScopeCache $instrumentationScopeCache; + private readonly WeakMap $configCache; private readonly Closure $meterConfigurator; /** @@ -74,6 +77,8 @@ public function __construct( $logger, ); $this->instrumentationScopeAttributesFactory = $instrumentationScopeAttributesFactory; + $this->instrumentationScopeCache = new InstrumentationScopeCache($logger); + $this->configCache = new WeakMap(); $this->meterConfigurator = $meterConfigurator; } @@ -89,9 +94,16 @@ public function getMeter( $instrumentationScope = new InstrumentationScope($name, $version, $schemaUrl, $this->instrumentationScopeAttributesFactory->builder()->addAll($attributes)->build()); - $meterConfig = ($this->meterConfigurator)($instrumentationScope); + $instrumentationScope = $this->instrumentationScopeCache->intern($instrumentationScope); - return new Meter($this->meterState, $instrumentationScope, $meterConfig); + /** @noinspection PhpSecondWriteToReadonlyPropertyInspection */ + $this->configCache[$instrumentationScope] ??= ($this->meterConfigurator)($instrumentationScope) + ->onChange(fn(MeterConfig $meterConfig) => $meterConfig->disabled + ? $this->meterState->disableInstrumentationScope($instrumentationScope) + : $this->meterState->enableInstrumentationScope($instrumentationScope)) + ->triggerOnChange(); + + return new Meter($this->meterState, $instrumentationScope); } public function shutdown(?Cancellation $cancellation = null): bool { diff --git a/metrics/Internal/MeterState.php b/metrics/Internal/MeterState.php index 3a812a1..3bdf994 100644 --- a/metrics/Internal/MeterState.php +++ b/metrics/Internal/MeterState.php @@ -24,13 +24,11 @@ use Nevay\OTelSDK\Metrics\Internal\Stream\SynchronousMetricStream; use Nevay\OTelSDK\Metrics\Internal\View\ResolvedView; use Nevay\OTelSDK\Metrics\Internal\View\ViewRegistry; -use Nevay\OTelSDK\Metrics\MeterConfig; use Nevay\OTelSDK\Metrics\MetricReader; use Nevay\OTelSDK\Metrics\View; use Psr\Log\LoggerInterface; use Throwable; use WeakMap; -use function array_keys; use function assert; use function md5; use function preg_match; @@ -45,9 +43,12 @@ final class MeterState { private ?int $startTimestamp = null; - /** @var array> */ + /** @var array */ + private array $disabledInstrumentationScopes = []; + + /** @var array> */ private array $asynchronous = []; - /** @var array> */ + /** @var array> */ private array $synchronous = []; /** @var array> */ private array $instrumentIdentities = []; @@ -72,6 +73,45 @@ public function __construct( public readonly ?LoggerInterface $logger, ) {} + public function enableInstrumentationScope(InstrumentationScope $instrumentationScope): bool { + $instrumentationScopeId = self::instrumentationScopeId($instrumentationScope); + if (!isset($this->disabledInstrumentationScopes[$instrumentationScopeId])) { + return false; + } + + $this->logger?->debug('Enabling instruments of instrumentation scope', ['scope' => $instrumentationScope]); + unset($this->disabledInstrumentationScopes[$instrumentationScopeId]); + + $startTimestamp = $this->clock->now(); + foreach ($this->asynchronous[$instrumentationScopeId] ?? [] as [$instrument]) { + $this->createAsynchronousStreams($instrument, $instrumentationScope, $startTimestamp); + } + foreach ($this->synchronous[$instrumentationScopeId] ?? [] as [$instrument]) { + $this->createSynchronousStreams($instrument, $instrumentationScope, $startTimestamp); + } + + return true; + } + + public function disableInstrumentationScope(InstrumentationScope $instrumentationScope): bool { + $instrumentationScopeId = self::instrumentationScopeId($instrumentationScope); + if (isset($this->disabledInstrumentationScopes[$instrumentationScopeId])) { + return false; + } + + $this->logger?->debug('Disabling instruments of instrumentation scope', ['scope' => $instrumentationScope]); + $this->disabledInstrumentationScopes[$instrumentationScopeId] = true; + + foreach ($this->asynchronous[$instrumentationScopeId] ?? [] as [$instrument]) { + $this->releaseStreams($instrument); + } + foreach ($this->synchronous[$instrumentationScopeId] ?? [] as [$instrument]) { + $this->releaseStreams($instrument); + } + + return true; + } + /** * @return array{Instrument, ReferenceCounter}|null */ @@ -90,7 +130,7 @@ public function getAsynchronousInstrument(Instrument $instrument, Instrumentatio /** * @return array{Instrument, ReferenceCounter} */ - public function createSynchronousInstrument(Instrument $instrument, InstrumentationScope $instrumentationScope, MeterConfig $meterConfig): array { + public function createSynchronousInstrument(Instrument $instrument, InstrumentationScope $instrumentationScope): array { $instrumentationScopeId = self::instrumentationScopeId($instrumentationScope); $instrumentId = self::instrumentId($instrument); @@ -104,52 +144,33 @@ public function createSynchronousInstrument(Instrument $instrument, Instrumentat self::ensureInstrumentNameNotConflicting($instrument, $instrumentationScopeId, $instrumentId, $instrumentName); self::acquireInstrumentName($instrumentationScopeId, $instrumentId, $instrumentName); - $stalenessHandler = $this->stalenessHandlerFactory->create(); - $this->startTimestamp ??= $this->clock->now(); - $streams = []; - $dedup = []; - foreach ($this->views($instrument, $instrumentationScope, Temporality::Delta) as $view) { - $dedupId = self::streamDedupId($view); - if (($streamId = $dedup[$dedupId] ?? null) === null) { - $stream = new SynchronousMetricStream($view->aggregator, $this->startTimestamp, $view->cardinalityLimit); - assert($stream->temporality() === $view->descriptor->temporality); - - $streamId = $this->registry->registerSynchronousStream($instrument, $stream, new DefaultMetricAggregator( - $view->aggregator, - $view->attributeProcessor, - $view->exemplarFilter, - $view->exemplarReservoir, - $view->cardinalityLimit, - )); - - $streams[$streamId] = $stream; - $dedup[$dedupId] = $streamId; - } - $stream = $streams[$streamId]; - $source = new MetricStreamSource($view->descriptor, $stream, $stream->register($view->temporality), $meterConfig); - $view->metricProducer->registerMetricSource($streamId, $source); + if (!isset($this->disabledInstrumentationScopes[$instrumentationScopeId])) { + $this->startTimestamp ??= $this->clock->now(); + $this->createSynchronousStreams($instrument, $instrumentationScope, $this->startTimestamp); } - $streamIds = array_keys($streams); - $stalenessHandler->onStale(function() use ($instrumentationScopeId, $instrumentId, $instrumentName, $streamIds): void { + $stalenessHandler = $this->stalenessHandlerFactory->create(); + $stalenessHandler->onStale(fn() => $this->releaseStreams($instrument)); + $stalenessHandler->onStale(function() use ($instrumentationScopeId, $instrumentId, $instrumentName): void { unset($this->synchronous[$instrumentationScopeId][$instrumentId]); if (!$this->synchronous[$instrumentationScopeId]) { unset($this->synchronous[$instrumentationScopeId]); } $this->releaseInstrumentName($instrumentationScopeId, $instrumentId, $instrumentName); - $this->releaseStreams($streamIds); + $this->startTimestamp = null; }); return $this->synchronous[$instrumentationScopeId][$instrumentId] = [ $instrument, $stalenessHandler, + $instrumentationScope, // keep reference alive ]; } /** * @return array{Instrument, ReferenceCounter} */ - public function createAsynchronousInstrument(Instrument $instrument, InstrumentationScope $instrumentationScope, MeterConfig $meterConfig): array { + public function createAsynchronousInstrument(Instrument $instrument, InstrumentationScope $instrumentationScope): array { $instrumentationScopeId = self::instrumentationScopeId($instrumentationScope); $instrumentId = self::instrumentId($instrument); @@ -163,19 +184,43 @@ public function createAsynchronousInstrument(Instrument $instrument, Instrumenta self::ensureInstrumentNameNotConflicting($instrument, $instrumentationScopeId, $instrumentId, $instrumentName); self::acquireInstrumentName($instrumentationScopeId, $instrumentId, $instrumentName); + if (!isset($this->disabledInstrumentationScopes[$instrumentationScopeId])) { + $this->startTimestamp ??= $this->clock->now(); + $this->createAsynchronousStreams($instrument, $instrumentationScope, $this->startTimestamp); + } + $stalenessHandler = $this->stalenessHandlerFactory->create(); - $this->startTimestamp ??= $this->clock->now(); + $stalenessHandler->onStale(fn() => $this->releaseStreams($instrument)); + $stalenessHandler->onStale(function() use ($instrumentationScopeId, $instrumentId, $instrumentName): void { + unset($this->asynchronous[$instrumentationScopeId][$instrumentId]); + if (!$this->asynchronous[$instrumentationScopeId]) { + unset($this->asynchronous[$instrumentationScopeId]); + } + $this->releaseInstrumentName($instrumentationScopeId, $instrumentId, $instrumentName); + $this->startTimestamp = null; + }); + + return $this->asynchronous[$instrumentationScopeId][$instrumentId] = [ + $instrument, + $stalenessHandler, + $instrumentationScope, // keep reference alive + ]; + } + + private function createSynchronousStreams(Instrument $instrument, InstrumentationScope $instrumentationScope, int $startTimestamp): void { $streams = []; $dedup = []; - foreach ($this->views($instrument, $instrumentationScope, Temporality::Cumulative) as $view) { + foreach ($this->views($instrument, $instrumentationScope, Temporality::Delta) as $view) { $dedupId = self::streamDedupId($view); if (($streamId = $dedup[$dedupId] ?? null) === null) { - $stream = new AsynchronousMetricStream($view->aggregator, $this->startTimestamp); + $stream = new SynchronousMetricStream($view->aggregator, $startTimestamp, $view->cardinalityLimit); assert($stream->temporality() === $view->descriptor->temporality); - $streamId = $this->registry->registerAsynchronousStream($instrument, $stream, new DefaultMetricAggregatorFactory( + $streamId = $this->registry->registerSynchronousStream($instrument, $stream, new DefaultMetricAggregator( $view->aggregator, $view->attributeProcessor, + $view->exemplarFilter, + $view->exemplarReservoir, $view->cardinalityLimit, )); @@ -183,24 +228,33 @@ public function createAsynchronousInstrument(Instrument $instrument, Instrumenta $dedup[$dedupId] = $streamId; } $stream = $streams[$streamId]; - $source = new MetricStreamSource($view->descriptor, $stream, $stream->register($view->temporality), $meterConfig); + $source = new MetricStreamSource($view->descriptor, $stream, $stream->register($view->temporality)); $view->metricProducer->registerMetricSource($streamId, $source); } + } - $streamIds = array_keys($streams); - $stalenessHandler->onStale(function() use ($instrumentationScopeId, $instrumentId, $instrumentName, $streamIds): void { - unset($this->asynchronous[$instrumentationScopeId][$instrumentId]); - if (!$this->asynchronous[$instrumentationScopeId]) { - unset($this->asynchronous[$instrumentationScopeId]); - } - $this->releaseInstrumentName($instrumentationScopeId, $instrumentId, $instrumentName); - $this->releaseStreams($streamIds); - }); + private function createAsynchronousStreams(Instrument $instrument, InstrumentationScope $instrumentationScope, int $startTimestamp): void { + $streams = []; + $dedup = []; + foreach ($this->views($instrument, $instrumentationScope, Temporality::Cumulative) as $view) { + $dedupId = self::streamDedupId($view); + if (($streamId = $dedup[$dedupId] ?? null) === null) { + $stream = new AsynchronousMetricStream($view->aggregator, $startTimestamp); + assert($stream->temporality() === $view->descriptor->temporality); - return $this->asynchronous[$instrumentationScopeId][$instrumentId] = [ - $instrument, - $stalenessHandler, - ]; + $streamId = $this->registry->registerAsynchronousStream($instrument, $stream, new DefaultMetricAggregatorFactory( + $view->aggregator, + $view->attributeProcessor, + $view->cardinalityLimit, + )); + + $streams[$streamId] = $stream; + $dedup[$dedupId] = $streamId; + } + $stream = $streams[$streamId]; + $source = new MetricStreamSource($view->descriptor, $stream, $stream->register($view->temporality)); + $view->metricProducer->registerMetricSource($streamId, $source); + } } /** @@ -269,10 +323,12 @@ private function views(Instrument $instrument, InstrumentationScope $instrumenta } } - private function releaseStreams(array $streamIds): void { - $this->startTimestamp = null; + private function releaseStreams(Instrument $instrument): void { + if (!$streamIds = $this->registry->unregisterStreams($instrument)) { + return; + } + foreach ($streamIds as $streamId) { - $this->registry->unregisterStream($streamId); foreach ($this->metricProducers as $metricProducer) { $metricProducer->unregisterStream($streamId); } @@ -325,7 +381,7 @@ private function ensureIdentityInstrumentEquals(Instrument $instrument, string $ $this->logger?->warning('Instrument with same identity and differing non-identifying fields, using stream of first-seen instrument', [ 'scope_hash' => md5($instrumentationScopeId), 'instrument_hash' => md5($instrumentId), - 'first-seen' => $registered, + 'first_seen' => $registered, 'instrument' => $instrument, ]); } diff --git a/metrics/Internal/MetricStreamSource.php b/metrics/Internal/MetricStreamSource.php index 441d7d3..74542c6 100644 --- a/metrics/Internal/MetricStreamSource.php +++ b/metrics/Internal/MetricStreamSource.php @@ -14,6 +14,5 @@ public function __construct( public readonly Descriptor $descriptor, public readonly MetricStream $stream, public readonly int $reader, - public readonly MeterConfig $meterConfig, ) {} } diff --git a/metrics/Internal/Registry/MetricRegistry.php b/metrics/Internal/Registry/MetricRegistry.php index f0c3abe..2e5f129 100644 --- a/metrics/Internal/Registry/MetricRegistry.php +++ b/metrics/Internal/Registry/MetricRegistry.php @@ -85,18 +85,21 @@ public function registerAsynchronousStream(Instrument $instrument, MetricStream return $streamId; } - public function unregisterStream(int $streamId): void { - $instrumentId = $this->streamToInstrument[$streamId]; - unset( - $this->streams[$streamId], - $this->synchronousAggregators[$streamId], - $this->asynchronousAggregatorFactories[$streamId], - $this->instrumentToStreams[$instrumentId][$streamId], - $this->streamToInstrument[$streamId], - ); - if (!$this->instrumentToStreams[$instrumentId]) { - unset($this->instrumentToStreams[$instrumentId]); + public function unregisterStreams(Instrument $instrument): array { + $instrumentId = spl_object_id($instrument); + $streamIds = $this->instrumentToStreams[$instrumentId] ?? []; + + foreach ($streamIds as $streamId) { + unset( + $this->streams[$streamId], + $this->synchronousAggregators[$streamId], + $this->asynchronousAggregatorFactories[$streamId], + $this->streamToInstrument[$streamId], + ); } + unset($this->instrumentToStreams[$instrumentId]); + + return $streamIds; } public function enabled(Instrument $instrument): bool { @@ -104,11 +107,14 @@ public function enabled(Instrument $instrument): bool { } public function record(Instrument $instrument, float|int $value, iterable $attributes = [], ContextInterface|false|null $context = null): void { + if (!$streamIds = $this->instrumentToStreams[spl_object_id($instrument)] ?? []) { + return; + } + $context = ContextResolver::resolve($context, $this->contextStorage); $attributes = $this->attributesFactory->build($attributes); $timestamp = $this->clock->now(); - $instrumentId = spl_object_id($instrument); - foreach ($this->instrumentToStreams[$instrumentId] ?? [] as $streamId) { + foreach ($streamIds as $streamId) { if ($aggregator = $this->synchronousAggregators[$streamId] ?? null) { $aggregator->record($value, $attributes, $context, $timestamp); } diff --git a/metrics/MeterConfig.php b/metrics/MeterConfig.php index 806e175..ae37838 100644 --- a/metrics/MeterConfig.php +++ b/metrics/MeterConfig.php @@ -1,12 +1,49 @@ disabled) { + $this->disabled = $disabled; + $this->triggerOnChange(); + } + + return $this; + } + + /** + * @param Closure(MeterConfig): void $closure + * + * @internal + */ + public function onChange(Closure $closure): self { + $this->onChange[] = $closure; + + return $this; + } + + /** + * @internal + */ + public function triggerOnChange(): self { + foreach ($this->onChange as $onChange) { + $onChange($this); + } + + return $this; + } } diff --git a/trace/Internal/TracerProvider.php b/trace/Internal/TracerProvider.php index 3288eb2..df6c5d3 100644 --- a/trace/Internal/TracerProvider.php +++ b/trace/Internal/TracerProvider.php @@ -7,6 +7,7 @@ use Nevay\OTelSDK\Common\Clock; use Nevay\OTelSDK\Common\HighResolutionTime; use Nevay\OTelSDK\Common\InstrumentationScope; +use Nevay\OTelSDK\Common\Internal\InstrumentationScopeCache; use Nevay\OTelSDK\Common\Provider; use Nevay\OTelSDK\Common\Resource; use Nevay\OTelSDK\Trace\IdGenerator; @@ -17,6 +18,7 @@ use OpenTelemetry\API\Trace\TracerProviderInterface; use OpenTelemetry\Context\ContextStorageInterface; use Psr\Log\LoggerInterface; +use WeakMap; /** * @internal @@ -25,6 +27,8 @@ final class TracerProvider implements TracerProviderInterface, Provider { private readonly TracerState $tracerState; private readonly AttributesFactory $instrumentationScopeAttributesFactory; + private readonly InstrumentationScopeCache $instrumentationScopeCache; + private readonly WeakMap $configCache; private readonly Closure $tracerConfigurator; /** @@ -63,6 +67,8 @@ public function __construct( $logger, ); $this->instrumentationScopeAttributesFactory = $instrumentationScopeAttributesFactory; + $this->instrumentationScopeCache = new InstrumentationScopeCache($logger); + $this->configCache = new WeakMap(); $this->tracerConfigurator = $tracerConfigurator; } @@ -78,7 +84,10 @@ public function getTracer( $instrumentationScope = new InstrumentationScope($name, $version, $schemaUrl, $this->instrumentationScopeAttributesFactory->builder()->addAll($attributes)->build()); - $tracerConfig = ($this->tracerConfigurator)($instrumentationScope); + $instrumentationScope = $this->instrumentationScopeCache->intern($instrumentationScope); + + /** @noinspection PhpSecondWriteToReadonlyPropertyInspection */ + $tracerConfig = $this->configCache[$instrumentationScope] ??= ($this->tracerConfigurator)($instrumentationScope); return new Tracer($this->tracerState, $instrumentationScope, $tracerConfig); } diff --git a/trace/TracerConfig.php b/trace/TracerConfig.php index 6218415..90f29a6 100644 --- a/trace/TracerConfig.php +++ b/trace/TracerConfig.php @@ -2,6 +2,8 @@ namespace Nevay\OTelSDK\Trace; /** + * @property-read bool $disabled + * * @experimental */ final class TracerConfig { @@ -9,4 +11,10 @@ final class TracerConfig { public function __construct( public bool $disabled = false, ) {} + + public function setDisabled(bool $disabled): self { + $this->disabled = $disabled; + + return $this; + } }