From 48e97de8841b523488a0ec60c1ec0c876f2c2901 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Fri, 29 May 2026 16:33:54 +0530 Subject: [PATCH 1/2] Add lazy telemetry instruments --- src/Telemetry/Counter.php | 42 ++++++++++++++++++++++ src/Telemetry/Gauge.php | 42 ++++++++++++++++++++++ tests/Telemetry/LazyInstrumentTest.php | 49 ++++++++++++++++++++++++++ 3 files changed, 133 insertions(+) create mode 100644 tests/Telemetry/LazyInstrumentTest.php diff --git a/src/Telemetry/Counter.php b/src/Telemetry/Counter.php index c08a5ad..43fc9a2 100644 --- a/src/Telemetry/Counter.php +++ b/src/Telemetry/Counter.php @@ -4,6 +4,48 @@ abstract class Counter { + /** + * @param array $advisory + */ + public static function lazy( + Adapter $telemetry, + string $name, + ?string $unit = null, + ?string $description = null, + array $advisory = [], + ): self { + return new class ($telemetry, $name, $unit, $description, $advisory) extends Counter { + private ?Counter $inner = null; + + /** + * @param array $advisory + */ + public function __construct( + private Adapter $telemetry, + private string $name, + private ?string $unit, + private ?string $description, + private array $advisory, + ) { + } + + /** + * @param iterable|bool|float|int|string|null> $attributes + */ + public function add(float|int $amount, iterable $attributes = []): void + { + $this->inner ??= $this->telemetry->createCounter( + $this->name, + $this->unit, + $this->description, + $this->advisory, + ); + + $this->inner->add($amount, $attributes); + } + }; + } + /** * @param iterable|bool|float|int|string|null> $attributes */ diff --git a/src/Telemetry/Gauge.php b/src/Telemetry/Gauge.php index 245830f..7673521 100644 --- a/src/Telemetry/Gauge.php +++ b/src/Telemetry/Gauge.php @@ -4,6 +4,48 @@ abstract class Gauge { + /** + * @param array $advisory + */ + public static function lazy( + Adapter $telemetry, + string $name, + ?string $unit = null, + ?string $description = null, + array $advisory = [], + ): self { + return new class ($telemetry, $name, $unit, $description, $advisory) extends Gauge { + private ?Gauge $inner = null; + + /** + * @param array $advisory + */ + public function __construct( + private Adapter $telemetry, + private string $name, + private ?string $unit, + private ?string $description, + private array $advisory, + ) { + } + + /** + * @param iterable|bool|float|int|string|null> $attributes + */ + public function record(float|int $amount, iterable $attributes = []): void + { + $this->inner ??= $this->telemetry->createGauge( + $this->name, + $this->unit, + $this->description, + $this->advisory, + ); + + $this->inner->record($amount, $attributes); + } + }; + } + /** * @param iterable|bool|float|int|string|null> $attributes */ diff --git a/tests/Telemetry/LazyInstrumentTest.php b/tests/Telemetry/LazyInstrumentTest.php new file mode 100644 index 0000000..ce725d7 --- /dev/null +++ b/tests/Telemetry/LazyInstrumentTest.php @@ -0,0 +1,49 @@ +assertSame([], $telemetry->counters); + + $counter->add(1, ['event.name' => 'created']); + + $this->assertArrayHasKey('events.total', $telemetry->counters); + $this->assertSame([1], $telemetry->counters['events.total']->values); + + $inner = $telemetry->counters['events.total']; + $counter->add(2); + + $this->assertSame($inner, $telemetry->counters['events.total']); + $this->assertSame([1, 2], $telemetry->counters['events.total']->values); + } + + public function testLazyGaugeCreatesInnerGaugeOnFirstRecord(): void + { + $telemetry = new Test(); + $gauge = Gauge::lazy($telemetry, 'event.timestamp', 's', 'Event timestamp'); + + $this->assertSame([], $telemetry->gauges); + + $gauge->record(123.45, ['event.name' => 'transition']); + + $this->assertArrayHasKey('event.timestamp', $telemetry->gauges); + $this->assertSame([123.45], $telemetry->gauges['event.timestamp']->values); + + $inner = $telemetry->gauges['event.timestamp']; + $gauge->record(456.78); + + $this->assertSame($inner, $telemetry->gauges['event.timestamp']); + $this->assertSame([123.45, 456.78], $telemetry->gauges['event.timestamp']->values); + } +} From 9e60a40eaa27994f9f90dbd8a4c99cd16786c254 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Fri, 29 May 2026 16:39:07 +0530 Subject: [PATCH 2/2] Add lazy factories for remaining instruments --- src/Telemetry/Histogram.php | 42 ++++++++++++++++++++++++++ src/Telemetry/UpDownCounter.php | 42 ++++++++++++++++++++++++++ tests/Telemetry/LazyInstrumentTest.php | 40 ++++++++++++++++++++++++ 3 files changed, 124 insertions(+) diff --git a/src/Telemetry/Histogram.php b/src/Telemetry/Histogram.php index f423bec..6d474f7 100644 --- a/src/Telemetry/Histogram.php +++ b/src/Telemetry/Histogram.php @@ -4,6 +4,48 @@ abstract class Histogram { + /** + * @param array $advisory + */ + public static function lazy( + Adapter $telemetry, + string $name, + ?string $unit = null, + ?string $description = null, + array $advisory = [], + ): self { + return new class ($telemetry, $name, $unit, $description, $advisory) extends Histogram { + private ?Histogram $inner = null; + + /** + * @param array $advisory + */ + public function __construct( + private Adapter $telemetry, + private string $name, + private ?string $unit, + private ?string $description, + private array $advisory, + ) { + } + + /** + * @param iterable|bool|float|int|string|null> $attributes + */ + public function record(float|int $amount, iterable $attributes = []): void + { + $this->inner ??= $this->telemetry->createHistogram( + $this->name, + $this->unit, + $this->description, + $this->advisory, + ); + + $this->inner->record($amount, $attributes); + } + }; + } + /** * @param iterable|bool|float|int|string|null> $attributes */ diff --git a/src/Telemetry/UpDownCounter.php b/src/Telemetry/UpDownCounter.php index 4d1fbce..f9eb368 100644 --- a/src/Telemetry/UpDownCounter.php +++ b/src/Telemetry/UpDownCounter.php @@ -4,6 +4,48 @@ abstract class UpDownCounter { + /** + * @param array $advisory + */ + public static function lazy( + Adapter $telemetry, + string $name, + ?string $unit = null, + ?string $description = null, + array $advisory = [], + ): self { + return new class ($telemetry, $name, $unit, $description, $advisory) extends UpDownCounter { + private ?UpDownCounter $inner = null; + + /** + * @param array $advisory + */ + public function __construct( + private Adapter $telemetry, + private string $name, + private ?string $unit, + private ?string $description, + private array $advisory, + ) { + } + + /** + * @param iterable|bool|float|int|string|null> $attributes + */ + public function add(float|int $amount, iterable $attributes = []): void + { + $this->inner ??= $this->telemetry->createUpDownCounter( + $this->name, + $this->unit, + $this->description, + $this->advisory, + ); + + $this->inner->add($amount, $attributes); + } + }; + } + /** * @param iterable|bool|float|int|string|null> $attributes */ diff --git a/tests/Telemetry/LazyInstrumentTest.php b/tests/Telemetry/LazyInstrumentTest.php index ce725d7..06ec130 100644 --- a/tests/Telemetry/LazyInstrumentTest.php +++ b/tests/Telemetry/LazyInstrumentTest.php @@ -6,6 +6,8 @@ use Utopia\Telemetry\Adapter\Test; use Utopia\Telemetry\Counter; use Utopia\Telemetry\Gauge; +use Utopia\Telemetry\Histogram; +use Utopia\Telemetry\UpDownCounter; class LazyInstrumentTest extends TestCase { @@ -46,4 +48,42 @@ public function testLazyGaugeCreatesInnerGaugeOnFirstRecord(): void $this->assertSame($inner, $telemetry->gauges['event.timestamp']); $this->assertSame([123.45, 456.78], $telemetry->gauges['event.timestamp']->values); } + + public function testLazyHistogramCreatesInnerHistogramOnFirstRecord(): void + { + $telemetry = new Test(); + $histogram = Histogram::lazy($telemetry, 'request.duration', 'ms', 'Request duration'); + + $this->assertSame([], $telemetry->histograms); + + $histogram->record(12.3, ['route' => '/v1/health']); + + $this->assertArrayHasKey('request.duration', $telemetry->histograms); + $this->assertSame([12.3], $telemetry->histograms['request.duration']->values); + + $inner = $telemetry->histograms['request.duration']; + $histogram->record(45.6); + + $this->assertSame($inner, $telemetry->histograms['request.duration']); + $this->assertSame([12.3, 45.6], $telemetry->histograms['request.duration']->values); + } + + public function testLazyUpDownCounterCreatesInnerCounterOnFirstAdd(): void + { + $telemetry = new Test(); + $counter = UpDownCounter::lazy($telemetry, 'active.requests', '{request}', 'Active requests'); + + $this->assertSame([], $telemetry->upDownCounters); + + $counter->add(1, ['route' => '/v1/health']); + + $this->assertArrayHasKey('active.requests', $telemetry->upDownCounters); + $this->assertSame([1], $telemetry->upDownCounters['active.requests']->values); + + $inner = $telemetry->upDownCounters['active.requests']; + $counter->add(-1); + + $this->assertSame($inner, $telemetry->upDownCounters['active.requests']); + $this->assertSame([1, -1], $telemetry->upDownCounters['active.requests']->values); + } }