From 8a6dd4750d98f422a85159a0a0654810ccd1bf3a Mon Sep 17 00:00:00 2001 From: Tobias Bachert Date: Sat, 27 Apr 2024 06:59:15 +0200 Subject: [PATCH] Add `Span::addLink()` and `Meter::createGauge()` (#1289) * Add `Span::addLink()` https://opentelemetry.io/docs/specs/otel/trace/api/#add-link * Bump API version to `1.1.x-dev` * Add experimental `Meter::createGauge()` https://opentelemetry.io/docs/specs/otel/metrics/api/#gauge --- src/API/Metrics/GaugeInterface.php | 28 ++++++++++++++++++ src/API/Metrics/MeterInterface.php | 20 +++++++++++++ src/API/Metrics/Noop/NoopGauge.php | 18 ++++++++++++ src/API/Metrics/Noop/NoopMeter.php | 6 ++++ src/API/Trace/NonRecordingSpan.php | 5 ++++ src/API/Trace/SpanInterface.php | 15 ++++++++++ src/API/composer.json | 4 +-- .../DefaultAggregationProviderTrait.php | 2 +- src/SDK/Metrics/Gauge.php | 15 ++++++++++ src/SDK/Metrics/InstrumentType.php | 2 ++ src/SDK/Metrics/Meter.php | 14 +++++++++ src/SDK/Trace/ImmutableSpan.php | 3 +- src/SDK/Trace/Span.php | 29 +++++++++++++++++-- src/SDK/composer.json | 2 +- tests/Integration/SDK/SpanBuilderTest.php | 16 ++++++++++ tests/Unit/SDK/Metrics/MeterTest.php | 20 +++++++++++++ .../MetricReader/ExportingReaderTest.php | 1 + tests/Unit/SDK/Trace/ImmutableSpanTest.php | 1 + 18 files changed, 194 insertions(+), 7 deletions(-) create mode 100644 src/API/Metrics/GaugeInterface.php create mode 100644 src/API/Metrics/Noop/NoopGauge.php create mode 100644 src/SDK/Metrics/Gauge.php diff --git a/src/API/Metrics/GaugeInterface.php b/src/API/Metrics/GaugeInterface.php new file mode 100644 index 000000000..9ae8e4276 --- /dev/null +++ b/src/API/Metrics/GaugeInterface.php @@ -0,0 +1,28 @@ + $attributes + * attributes of the data point + * @param ContextInterface|false|null $context execution context + * + * @see https://opentelemetry.io/docs/specs/otel/metrics/api/#record-1 + */ + public function record(float|int $amount, iterable $attributes = [], ContextInterface|false|null $context = null): void; +} diff --git a/src/API/Metrics/MeterInterface.php b/src/API/Metrics/MeterInterface.php index 192a0cd08..fa12e5814 100644 --- a/src/API/Metrics/MeterInterface.php +++ b/src/API/Metrics/MeterInterface.php @@ -99,6 +99,26 @@ public function createHistogram( array $advisory = [], ): HistogramInterface; + /** + * Creates a `Gauge`. + * + * @param string $name name of the instrument + * @param string|null $unit unit of measure + * @param string|null $description description of the instrument + * @param array $advisory an optional set of recommendations + * @return GaugeInterface created instrument + * + * @see https://opentelemetry.io/docs/specs/otel/metrics/api/#gauge-creation + * + * @experimental + */ + public function createGauge( + string $name, + ?string $unit = null, + ?string $description = null, + array $advisory = [], + ): GaugeInterface; + /** * Creates an `ObservableGauge`. * diff --git a/src/API/Metrics/Noop/NoopGauge.php b/src/API/Metrics/Noop/NoopGauge.php new file mode 100644 index 000000000..5884b522f --- /dev/null +++ b/src/API/Metrics/Noop/NoopGauge.php @@ -0,0 +1,18 @@ + new Aggregation\SumAggregation(true), InstrumentType::UP_DOWN_COUNTER, InstrumentType::ASYNCHRONOUS_UP_DOWN_COUNTER => new Aggregation\SumAggregation(), InstrumentType::HISTOGRAM => new Aggregation\ExplicitBucketHistogramAggregation($advisory['ExplicitBucketBoundaries'] ?? [0, 5, 10, 25, 50, 75, 100, 250, 500, 1000]), - InstrumentType::ASYNCHRONOUS_GAUGE => new Aggregation\LastValueAggregation(), + InstrumentType::GAUGE, InstrumentType::ASYNCHRONOUS_GAUGE => new Aggregation\LastValueAggregation(), default => null, }; // @codeCoverageIgnoreEnd diff --git a/src/SDK/Metrics/Gauge.php b/src/SDK/Metrics/Gauge.php new file mode 100644 index 000000000..b7ab81d0e --- /dev/null +++ b/src/SDK/Metrics/Gauge.php @@ -0,0 +1,15 @@ +writer, $instrument, $referenceCounter); } + public function createGauge(string $name, ?string $unit = null, ?string $description = null, array $advisory = []): GaugeInterface + { + [$instrument, $referenceCounter] = $this->createSynchronousWriter( + InstrumentType::GAUGE, + $name, + $unit, + $description, + $advisory, + ); + + return new Gauge($this->writer, $instrument, $referenceCounter); + } + public function createObservableGauge(string $name, ?string $unit = null, ?string $description = null, $advisory = [], callable ...$callbacks): ObservableGaugeInterface { if (is_callable($advisory)) { diff --git a/src/SDK/Trace/ImmutableSpan.php b/src/SDK/Trace/ImmutableSpan.php index ff86796af..b960941ef 100644 --- a/src/SDK/Trace/ImmutableSpan.php +++ b/src/SDK/Trace/ImmutableSpan.php @@ -26,6 +26,7 @@ public function __construct( private readonly array $links, private readonly array $events, private readonly AttributesInterface $attributes, + private readonly int $totalRecordedLinks, private readonly int $totalRecordedEvents, private readonly StatusDataInterface $status, private readonly int $endEpochNanos, @@ -112,7 +113,7 @@ public function getTotalDroppedEvents(): int public function getTotalDroppedLinks(): int { - return max(0, $this->span->getTotalRecordedLinks() - count($this->links)); + return max(0, $this->totalRecordedLinks - count($this->links)); } public function getStatus(): StatusDataInterface diff --git a/src/SDK/Trace/Span.php b/src/SDK/Trace/Span.php index 0dac95ba6..75d274fc9 100644 --- a/src/SDK/Trace/Span.php +++ b/src/SDK/Trace/Span.php @@ -5,6 +5,7 @@ namespace OpenTelemetry\SDK\Trace; use OpenTelemetry\API\Trace as API; +use OpenTelemetry\API\Trace\SpanContextInterface; use OpenTelemetry\Context\ContextInterface; use OpenTelemetry\SDK\Common\Attribute\AttributesBuilderInterface; use OpenTelemetry\SDK\Common\Dev\Compatibility\Util as BcUtil; @@ -38,8 +39,8 @@ private function __construct( private readonly SpanProcessorInterface $spanProcessor, private readonly ResourceInfo $resource, private AttributesBuilderInterface $attributesBuilder, - private readonly array $links, - private readonly int $totalRecordedLinks, + private array $links, + private int $totalRecordedLinks, private readonly int $startEpochNanos, ) { $this->status = StatusData::unset(); @@ -142,6 +143,29 @@ public function setAttributes(iterable $attributes): self return $this; } + public function addLink(SpanContextInterface $context, iterable $attributes = []): self + { + if ($this->hasEnded) { + return $this; + } + if (!$context->isValid()) { + return $this; + } + if (++$this->totalRecordedLinks > $this->spanLimits->getLinkCountLimit()) { + return $this; + } + + $this->links[] = new Link( + $context, + $this->spanLimits + ->getLinkAttributesFactory() + ->builder($attributes) + ->build(), + ); + + return $this; + } + /** @inheritDoc */ public function addEvent(string $name, iterable $attributes = [], ?int $timestamp = null): self { @@ -260,6 +284,7 @@ public function toSpanData(): SpanDataInterface $this->links, $this->events, $this->attributesBuilder->build(), + $this->totalRecordedLinks, $this->totalRecordedEvents, $this->status, $this->endEpochNanos, diff --git a/src/SDK/composer.json b/src/SDK/composer.json index 599014235..b84b94aa5 100644 --- a/src/SDK/composer.json +++ b/src/SDK/composer.json @@ -19,7 +19,7 @@ "require": { "php": "^8.1", "ext-json": "*", - "open-telemetry/api": "^1.0", + "open-telemetry/api": "~1.0 || ~1.1", "open-telemetry/context": "^1.0", "open-telemetry/sem-conv": "^1.0", "php-http/discovery": "^1.14", diff --git a/tests/Integration/SDK/SpanBuilderTest.php b/tests/Integration/SDK/SpanBuilderTest.php index abcbb91b8..c8eaa0369 100644 --- a/tests/Integration/SDK/SpanBuilderTest.php +++ b/tests/Integration/SDK/SpanBuilderTest.php @@ -68,6 +68,22 @@ public function test_add_link(): void $this->assertCount(2, $span->toSpanData()->getLinks()); } + /** + * @group trace-compliance + */ + public function test_add_link_after_span_creation(): void + { + /** @var Span $span */ + $span = $this + ->tracer + ->spanBuilder(self::SPAN_NAME) + ->addLink($this->sampledSpanContext) + ->startSpan() + ->addLink($this->sampledSpanContext); + + $this->assertCount(2, $span->toSpanData()->getLinks()); + } + public function test_add_link_invalid(): void { /** @var Span $span */ diff --git a/tests/Unit/SDK/Metrics/MeterTest.php b/tests/Unit/SDK/Metrics/MeterTest.php index 531df2e93..166446c7f 100644 --- a/tests/Unit/SDK/Metrics/MeterTest.php +++ b/tests/Unit/SDK/Metrics/MeterTest.php @@ -100,6 +100,26 @@ public function test_create_histogram_advisory(): void ); } + public function test_create_gauge(): void + { + $metricFactory = $this->createMock(MetricFactoryInterface::class); + $metricFactory->expects($this->once())->method('createSynchronousWriter') + ->with( + $this->anything(), + $this->anything(), + new InstrumentationScope('test', null, null, Attributes::create([])), + new Instrument(InstrumentType::GAUGE, 'name', 'unit', 'description'), + $this->anything(), + $this->anything(), + $this->anything(), + ) + ->willReturn([]); + + $meterProvider = $this->createMeterProviderForMetricFactory($metricFactory); + $meter = $meterProvider->getMeter('test'); + $meter->createGauge('name', 'unit', 'description'); + } + public function test_create_up_down_counter(): void { $metricFactory = $this->createMock(MetricFactoryInterface::class); diff --git a/tests/Unit/SDK/Metrics/MetricReader/ExportingReaderTest.php b/tests/Unit/SDK/Metrics/MetricReader/ExportingReaderTest.php index a0997845c..2c16fea28 100644 --- a/tests/Unit/SDK/Metrics/MetricReader/ExportingReaderTest.php +++ b/tests/Unit/SDK/Metrics/MetricReader/ExportingReaderTest.php @@ -50,6 +50,7 @@ public function test_default_aggregation_returns_default_aggregation(): void $this->assertEquals(new SumAggregation(), $reader->defaultAggregation(InstrumentType::UP_DOWN_COUNTER)); $this->assertEquals(new SumAggregation(), $reader->defaultAggregation(InstrumentType::ASYNCHRONOUS_UP_DOWN_COUNTER)); $this->assertEquals(new ExplicitBucketHistogramAggregation([0, 5, 10, 25, 50, 75, 100, 250, 500, 1000]), $reader->defaultAggregation(InstrumentType::HISTOGRAM)); + $this->assertEquals(new LastValueAggregation(), $reader->defaultAggregation(InstrumentType::GAUGE)); $this->assertEquals(new LastValueAggregation(), $reader->defaultAggregation(InstrumentType::ASYNCHRONOUS_GAUGE)); } diff --git a/tests/Unit/SDK/Trace/ImmutableSpanTest.php b/tests/Unit/SDK/Trace/ImmutableSpanTest.php index e4bff29bd..bf3c294e4 100644 --- a/tests/Unit/SDK/Trace/ImmutableSpanTest.php +++ b/tests/Unit/SDK/Trace/ImmutableSpanTest.php @@ -66,6 +66,7 @@ public function test_getters(): void [], [], $this->attributes, + $this->totalRecordedLinks, $this->totalRecordedEvents, $this->status, $this->endEpochNanos,