Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 63 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,78 @@ Init in your application:

require_once __DIR__ . '/vendor/autoload.php';

// Create a Telemetry instance using OpenTelemetry adapter.
use Utopia\Telemetry\Adapter\OpenTelemetry;

$telemetry = new OpenTelemetry('http://localhost:4138', 'namespace', 'app', 'unique-instance-name');
$counter = $telemetry->createUpDownCounter('http.server.active_requests', '{request}');
$telemetry = new OpenTelemetry('http://localhost:4318/v1/metrics', 'namespace', 'app', 'unique-instance-id');

$counter->add(1);
$counter->add(2);

// Periodically collect metrics and send them to the configured OpenTelemetry endpoint.
// Periodically collect and export metrics to the configured OpenTelemetry endpoint.
$telemetry->collect();

// Example using Swoole
\Swoole\Timer::tick(60_000, fn () => $telemetry->collect());
```

## Metric Types

### Counter

A monotonically increasing counter. Only positive increments are allowed.

```php
$counter = $telemetry->createCounter('http.server.requests', '{request}', 'Total HTTP requests');

$counter->add(1);
$counter->add(1, ['method' => 'GET', 'status' => '200']);
```

### UpDownCounter

A counter that can increase or decrease. Useful for tracking values like active connections.

```php
$upDownCounter = $telemetry->createUpDownCounter('http.server.active_requests', '{request}', 'Active HTTP requests');

$upDownCounter->add(1); // request started
$upDownCounter->add(-1); // request finished
```

### Histogram

Records a distribution of values. Useful for measuring latency or payload sizes.

```php
$histogram = $telemetry->createHistogram('http.server.request.duration', 'ms', 'HTTP request duration');

$histogram->record(142);
$histogram->record(98.5, ['route' => '/api/users']);
```

### Gauge

Records an instantaneous measurement. Useful for values that can arbitrarily go up or down.

```php
$gauge = $telemetry->createGauge('system.memory.usage', 'By', 'Memory usage');

$gauge->record(1_073_741_824);
$gauge->record(536_870_912, ['host' => 'server-1']);
```

### ObservableGauge

An asynchronous gauge whose value is collected via a callback at export time. Useful for values that are expensive to compute or come from an external source (e.g. CPU usage, queue depth).

```php
$observableGauge = $telemetry->createObservableGauge('process.cpu.usage', '%', 'CPU usage');

$observableGauge->observe(function (callable $observer): void {
// This callback is invoked each time metrics are collected.
$observer(sys_getloadavg()[0] * 100);
$observer(72.4, ['core' => '0']);
$observer(68.1, ['core' => '1']);
});
```

## System Requirements

Utopia Framework requires PHP 8.0 or later. We recommend using the latest PHP version whenever possible.
Expand Down
10 changes: 10 additions & 0 deletions src/Telemetry/Adapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,15 @@ public function createUpDownCounter(
array $advisory = [],
): UpDownCounter;

/**
* @param array<string, mixed> $advisory
*/
public function createObservableGauge(
string $name,
?string $unit = null,
?string $description = null,
array $advisory = [],
): ObservableGauge;

public function collect(): bool;
}
13 changes: 13 additions & 0 deletions src/Telemetry/Adapter/None.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Utopia\Telemetry\Counter;
use Utopia\Telemetry\Gauge;
use Utopia\Telemetry\Histogram;
use Utopia\Telemetry\ObservableGauge;
use Utopia\Telemetry\UpDownCounter;

class None implements Adapter
Expand Down Expand Up @@ -70,6 +71,18 @@ public function add(float|int $amount, iterable $attributes = []): void
};
}

/**
* @param array<string, mixed> $advisory
*/
public function createObservableGauge(string $name, ?string $unit = null, ?string $description = null, array $advisory = []): ObservableGauge
{
return new class () extends ObservableGauge {
public function observe(callable $callback): void
{
}
};
}

public function collect(): bool
{
return true;
Expand Down
42 changes: 39 additions & 3 deletions src/Telemetry/Adapter/OpenTelemetry.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use OpenTelemetry\API\Metrics\GaugeInterface;
use OpenTelemetry\API\Metrics\HistogramInterface;
use OpenTelemetry\API\Metrics\MeterInterface;
use OpenTelemetry\API\Metrics\ObserverInterface;
use OpenTelemetry\API\Metrics\UpDownCounterInterface;
use OpenTelemetry\Contrib\Otlp\ContentTypes;
use OpenTelemetry\Contrib\Otlp\MetricExporter;
Expand All @@ -25,6 +26,7 @@
use Utopia\Telemetry\Counter;
use Utopia\Telemetry\Gauge;
use Utopia\Telemetry\Histogram;
use Utopia\Telemetry\ObservableGauge;
use Utopia\Telemetry\UpDownCounter;

class OpenTelemetry implements Adapter
Expand All @@ -34,13 +36,14 @@ class OpenTelemetry implements Adapter
private MeterInterface $meter;

/**
* @var array<class-string, array<string, Counter|UpDownCounter|Histogram|Gauge>>
* @var array<class-string, array<string, Counter|UpDownCounter|Histogram|Gauge|ObservableGauge>>
*/
private array $meterStorage = [
Counter::class => [],
UpDownCounter::class => [],
Histogram::class => [],
Gauge::class => [],
ObservableGauge::class => [],
];

/**
Expand Down Expand Up @@ -103,12 +106,12 @@ protected function createExporter(TransportInterface $transport): MetricExporter
}

/**
* @template T of Counter|UpDownCounter|Histogram|Gauge
* @template T of Counter|UpDownCounter|Histogram|Gauge|ObservableGauge
* @param class-string<T> $type
* @param callable(): T $creator
* @return T
*/
private function createMeter(string $type, string $name, callable $creator): Counter|UpDownCounter|Histogram|Gauge
private function createMeter(string $type, string $name, callable $creator): Counter|UpDownCounter|Histogram|Gauge|ObservableGauge
{
if (! isset($this->meterStorage[$type][$name])) {
$this->meterStorage[$type][$name] = $creator();
Expand Down Expand Up @@ -222,6 +225,39 @@ public function add(float|int $amount, iterable $attributes = []): void
});
}

/**
* Create an ObservableGauge metric
*
* @param array<string, mixed> $advisory
*/
public function createObservableGauge(string $name, ?string $unit = null, ?string $description = null, array $advisory = []): ObservableGauge
{
return $this->createMeter(ObservableGauge::class, $name, function () use ($name, $unit, $description, $advisory) {
$otelGauge = $this->meter->createObservableGauge($name, $unit, $description, $advisory);

return new class ($otelGauge) extends ObservableGauge {
private ?\Closure $callback = null;

public function __construct(private \OpenTelemetry\API\Metrics\ObservableGaugeInterface $gauge)
{
$this->gauge->observe(function (ObserverInterface $observer): void {
if ($this->callback !== null) {
($this->callback)(function (float|int $value, iterable $attributes = []) use ($observer): void {
/** @var iterable<non-empty-string, array<mixed>|bool|float|int|string|null> $attributes */
$observer->observe($value, $attributes);
});
}
});
}

public function observe(callable $callback): void
{
$this->callback = \Closure::fromCallable($callback);
}
};
});
}

/**
* Collect and export metrics
*/
Expand Down
23 changes: 23 additions & 0 deletions src/Telemetry/Adapter/Test.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Utopia\Telemetry\Counter;
use Utopia\Telemetry\Gauge;
use Utopia\Telemetry\Histogram;
use Utopia\Telemetry\ObservableGauge;
use Utopia\Telemetry\UpDownCounter;

/**
Expand Down Expand Up @@ -33,6 +34,11 @@ class Test implements Adapter
*/
public array $upDownCounters = [];

/**
* @var array<string, ObservableGauge>
*/
public array $observableGauges = [];

/**
* @param array<string, mixed> $advisory
*/
Expand Down Expand Up @@ -125,6 +131,23 @@ public function add(float|int $amount, iterable $attributes = []): void
return $upDownCounter;
}

/**
* @param array<string, mixed> $advisory
*/
public function createObservableGauge(string $name, ?string $unit = null, ?string $description = null, array $advisory = []): ObservableGauge
{
$gauge = new class () extends ObservableGauge {
public ?\Closure $callback = null;

public function observe(callable $callback): void
{
$this->callback = \Closure::fromCallable($callback);
}
};
$this->observableGauges[$name] = $gauge;
return $gauge;
}

public function collect(): bool
{
return true;
Expand Down
16 changes: 16 additions & 0 deletions src/Telemetry/ObservableGauge.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace Utopia\Telemetry;

abstract class ObservableGauge
{
/**
* Register an observation callback that will be invoked during collection.
*
* The callback receives an observer callable that can be called with a value and optional attributes
* to record an observation: $observer(float|int $value, iterable $attributes = [])
*
* @param callable(callable(float|int, iterable<non-empty-string, array<mixed>|bool|float|int|string|null>): void): void $callback
*/
abstract public function observe(callable $callback): void;
}
35 changes: 35 additions & 0 deletions tests/Telemetry/Adapter/OpenTelemetryTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Utopia\Telemetry\Counter;
use Utopia\Telemetry\Gauge;
use Utopia\Telemetry\Histogram;
use Utopia\Telemetry\ObservableGauge;
use Utopia\Telemetry\UpDownCounter;

/**
Expand Down Expand Up @@ -159,6 +160,38 @@ public function testUpDownCounterAdd(): void
$this->assertTrue(true);
}

public function testCreateObservableGauge(): void
{
$gauge = $this->adapter->createObservableGauge(
name: 'test_observable_gauge',
unit: 'bytes',
description: 'Test observable gauge metric'
);

$this->assertInstanceOf(ObservableGauge::class, $gauge);
}

public function testObservableGaugeObserve(): void
{
$gauge = $this->adapter->createObservableGauge('observe_test_gauge');

// Should not throw
$gauge->observe(function (callable $observer): void {
$observer(1024);
$observer(2048.5, ['host' => 'server-1']);
});

$this->assertTrue(true);
}

public function testObservableGaugeCaching(): void
{
$gauge1 = $this->adapter->createObservableGauge('cached_observable_gauge');
$gauge2 = $this->adapter->createObservableGauge('cached_observable_gauge');

$this->assertSame($gauge1, $gauge2);
}

public function testMeterCaching(): void
{
$counter1 = $this->adapter->createCounter('cached_counter');
Expand Down Expand Up @@ -229,10 +262,12 @@ public function testNullOptionalParameters(): void
$histogram = $this->adapter->createHistogram('minimal_histogram');
$gauge = $this->adapter->createGauge('minimal_gauge');
$upDownCounter = $this->adapter->createUpDownCounter('minimal_updown');
$observableGauge = $this->adapter->createObservableGauge('minimal_observable_gauge');

$this->assertInstanceOf(Counter::class, $counter);
$this->assertInstanceOf(Histogram::class, $histogram);
$this->assertInstanceOf(Gauge::class, $gauge);
$this->assertInstanceOf(UpDownCounter::class, $upDownCounter);
$this->assertInstanceOf(ObservableGauge::class, $observableGauge);
}
}
Loading