Skip to content

Commit

Permalink
Add metrics implementation (#750)
Browse files Browse the repository at this point in the history
* Add metrics implementation

* Apply cs-fixer

* Downgrade to php 7.4

* [TODO] Suppress phan for now

* Add basic example

* [TODO] Remove outdated prometheus example for now

* Add otlp metric converter

* Add metric stream tests

* Downgrade to php 7.4 - fix asynchronous counter instrument type

* Add missing psalm-suppress annotations

* Add `ext-gmp` to composer suggest

* Fix `Sdk` -> `SDK`

* Remove `_bridge.php`

* Add array typehints

* Add `Interface` suffix to interfaces

* Add aggregation / attribute processor / staleness handler tests

* Apply rector

* Simplify filtered attribute processor

* Move instrument deduplication to meter

Allows removing view registry dependency from `MetricFactory`.
Should ideally turn of staleness handling for asynchronous instruments with permanently registered callbacks (drop all `::onStale()` callbacks and prevent addition of new callbacks).

* Allow injecting metric factory

* Release `::onStale()` callbacks if permanent observer callback registered

* Add `MultiObserver` tests

* Add php-doc for exporter temporality

* Resolve phan issues

* Add note about forward compatibility

* Add exemplar tests

* Remove special handling for callbacks being registered multiple times

Was mainly a side-effect of using `spl_object_id()`; lead to inconsistent behavior between providing same and identical callbacks; reverting back to incrementing index, keyspace is large enough.

* Add basic `Meter` / `MeterProvider` tests

* Add view `SelectionCriteria` tests

* Allow `MetricReader`s to configure default aggregation

- move default aggregation handling to metric reader / metric exporter
- move view registration to meter provider constructor
- move exemplar reservoir creation to aggregation to support user implementations
- remove `AttributeProcessor` from view as baggage access was dropped from spec
- deduplicate metric streams

* Add support for weakening `$this` reference of asynchronous callbacks

* Minor improvements

- add missing `Interface` suffix
- move callback destructors to metric observer to not detach if meter and meter provider are out of scope
- simplify `::viewRegistrationRequests()` by readding fallback view

* Add OTLP metric exporter

* Log export failure

* Minor improvements

- rename `MetricObserver::weakMap()` to `::destructors()` to better reflect usage`
- move `ReferenceCounter::acquire()` call to corresponding `MetricObserver::observe()` call for consistency
- cache instrumentation scope id in `Meter` to avoid repeated calls to `serialize()`
- remove obsolete instrument type check from `ViewProjection`, leftover from supporting aggregation per type

* Suppress `PhanAccessMethodInternal` due to being too strict for our usecase

* Add workaround for observer segfault if destruct is initiated by `WeakMap` key going out of scope

* Mark internal classes as internal

* Add in-memory and stream exporter

* Add metric converter test

* Add metric observer tests

* Add view registry tests

* Add metric reader tests

* Improve stream test coverage

* Improve meter test coverage

* Apply rector

* Add delayed staleness handler
  • Loading branch information
Nevay committed Aug 10, 2022
1 parent d0a600f commit 3ac64f6
Show file tree
Hide file tree
Showing 186 changed files with 9,105 additions and 1,596 deletions.
6 changes: 5 additions & 1 deletion .phan/config.php
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,11 @@

// Add any issue types (such as `'PhanUndeclaredMethod'`)
// to this deny-list to inhibit them from being reported.
'suppress_issue_types' => [],
'suppress_issue_types' => [
'PhanAccessClassInternal',
'PhanAccessMethodInternal',
'PhanAccessPropertyInternal',
],

// A regular expression to match files to be excluded
// from parsing and analysis and will not be read at all.
Expand Down
7 changes: 6 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,14 @@ smoke-test-exporter-examples: FORCE ## Run (some) exporter smoke test examples
smoke-test-collector-integration: ## Run smoke test collector integration
docker-compose -f docker-compose.collector.yaml up -d --remove-orphans
# This is slow because it's building the image from scratch (and parts of that, like installing the gRPC extension, are slow)
# This can be sped up by switching to the pre-built images hosted on ghcr.io (and referenced in other docker-compose**.yaml files)
# This can be sped up by switching to the pre-built images hosted on ghcr.io (and referenced in other docker-compose**.yaml files)
docker-compose -f docker-compose.collector.yaml run -e OTEL_EXPORTER_OTLP_ENDPOINT=collector:4317 --rm php php ./examples/traces/features/exporters/otlp_grpc.php
docker-compose -f docker-compose.collector.yaml stop
smoke-test-collector-metrics-integration:
docker-compose -f docker-compose.collector.yaml up -d --force-recreate collector
COMPOSE_IGNORE_ORPHANS=TRUE docker-compose -f docker-compose.yaml run --rm php php ./examples/metrics/features/exporters/otlp.php
docker-compose -f docker-compose.collector.yaml logs collector
docker-compose -f docker-compose.collector.yaml stop collector
smoke-test-prometheus-example: metrics-prometheus-example stop-prometheus
metrics-prometheus-example:
@docker-compose -f docker-compose.prometheus.yaml -p opentelemetry-php_metrics-prometheus-example up -d web
Expand Down
2 changes: 2 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
"php-http/mock-client": "^1.5",
"phpbench/phpbench": "^1.2",
"phpmetrics/phpmetrics": "^2.7",
"phpspec/prophecy-phpunit": "^2.0",
"phpstan/phpstan": "^1.1",
"phpstan/phpstan-mockery": "^1.0",
"phpstan/phpstan-phpunit": "^1.0",
Expand All @@ -100,6 +101,7 @@
"symfony/http-client": "^5.2"
},
"suggest": {
"ext-gmp": "To support unlimited number of synchronous metric readers",
"ext-grpc": "To use the OTLP GRPC Exporter",
"ext-protobuf": "For more performant protobuf/grpc exporting",
"ext-sockets": "To use the Thrift UDP Exporter for the Jaeger Agent"
Expand Down
76 changes: 76 additions & 0 deletions examples/metrics/basic.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php

// Example based on https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/supplementary-guidelines.md#synchronous-example
declare(strict_types=1);

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

use OpenTelemetry\Contrib\Otlp\StreamMetricExporter;
use OpenTelemetry\SDK\Common\Attribute\Attributes;
use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScopeFactory;
use OpenTelemetry\SDK\Common\Time\ClockFactory;
use OpenTelemetry\SDK\Metrics\Aggregation\ExplicitBucketHistogramAggregation;
use OpenTelemetry\SDK\Metrics\Exemplar\ExemplarFilter\WithSampledTraceExemplarFilter;
use OpenTelemetry\SDK\Metrics\MeterProvider;
use OpenTelemetry\SDK\Metrics\MetricReader\ExportingReader;
use OpenTelemetry\SDK\Metrics\StalenessHandler\ImmediateStalenessHandlerFactory;
use OpenTelemetry\SDK\Metrics\View\CriteriaViewRegistry;
use OpenTelemetry\SDK\Metrics\View\SelectionCriteria\InstrumentNameCriteria;
use OpenTelemetry\SDK\Metrics\View\ViewTemplate;
use OpenTelemetry\SDK\Resource\ResourceInfoFactory;

$clock = ClockFactory::getDefault();
$reader = new ExportingReader(new StreamMetricExporter(STDOUT, /*Temporality::CUMULATIVE*/), $clock);

// Let's imagine we export the metrics as Histogram, and to simplify the story we will only have one histogram bucket (-Inf, +Inf):
$views = new CriteriaViewRegistry();
$views->register(
new InstrumentNameCriteria('http.server.duration'),
ViewTemplate::create()
->withAttributeKeys(['http.method', 'http.status_code'])
->withAggregation(new ExplicitBucketHistogramAggregation([])),
);

$meterProvider = new MeterProvider(
null,
ResourceInfoFactory::emptyResource(),
$clock,
Attributes::factory(),
new InstrumentationScopeFactory(Attributes::factory()),
[$reader],
$views,
new WithSampledTraceExemplarFilter(),
new ImmediateStalenessHandlerFactory(),
);

$serverDuration = $meterProvider->getMeter('io.opentelemetry.contrib.php')->createHistogram(
'http.server.duration',
'ms',
'measures the duration inbound HTTP requests',
);

// During the time range (T0, T1]:
$serverDuration->record(50, ['http.method' => 'GET', 'http.status_code' => 200]);
$serverDuration->record(100, ['http.method' => 'GET', 'http.status_code' => 200]);
$serverDuration->record(1, ['http.method' => 'GET', 'http.status_code' => 500]);
$reader->collect();

// During the time range (T1, T2]:
$reader->collect();

// During the time range (T2, T3]:
$serverDuration->record(5, ['http.method' => 'GET', 'http.status_code' => 500]);
$serverDuration->record(2, ['http.method' => 'GET', 'http.status_code' => 500]);
$reader->collect();

// During the time range (T3, T4]:
$serverDuration->record(100, ['http.method' => 'GET', 'http.status_code' => 200]);
$reader->collect();

// During the time range (T4, T5]:
$serverDuration->record(100, ['http.method' => 'GET', 'http.status_code' => 200]);
$serverDuration->record(30, ['http.method' => 'GET', 'http.status_code' => 200]);
$serverDuration->record(50, ['http.method' => 'GET', 'http.status_code' => 200]);
$reader->collect();

$meterProvider->shutdown();
75 changes: 75 additions & 0 deletions examples/metrics/features/exporters/otlp.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php

declare(strict_types=1);

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

use GuzzleHttp\Client;
use GuzzleHttp\Psr7\HttpFactory;
use OpenTelemetry\API\Metrics\ObserverInterface;
use OpenTelemetry\Contrib\OtlpHttp\MetricExporter;
use OpenTelemetry\SDK\Common\Attribute\Attributes;
use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScopeFactory;
use OpenTelemetry\SDK\Common\Time\ClockFactory;
use OpenTelemetry\SDK\Metrics\Exemplar\ExemplarFilter\WithSampledTraceExemplarFilter;
use OpenTelemetry\SDK\Metrics\MeterProvider;
use OpenTelemetry\SDK\Metrics\MetricReader\ExportingReader;
use OpenTelemetry\SDK\Metrics\StalenessHandler\ImmediateStalenessHandlerFactory;
use OpenTelemetry\SDK\Metrics\View\CriteriaViewRegistry;
use OpenTelemetry\SDK\Resource\ResourceInfoFactory;

$clock = ClockFactory::getDefault();
$reader = new ExportingReader(MetricExporter::create(
new Client(),
new HttpFactory(),
new HttpFactory(),
'http://collector:4318/v1/metrics',
), $clock);

$meterProvider = new MeterProvider(
null,
ResourceInfoFactory::defaultResource(),
$clock,
Attributes::factory(),
new InstrumentationScopeFactory(Attributes::factory()),
[$reader],
new CriteriaViewRegistry(),
new WithSampledTraceExemplarFilter(),
new ImmediateStalenessHandlerFactory(),
);

$meter = $meterProvider->getMeter('io.opentelemetry.contrib.php');
$meter
->createObservableUpDownCounter('process.memory.usage', 'By', 'The amount of physical memory in use.')
->observe(static function (ObserverInterface $observer): void {
$observer->observe(memory_get_usage(true));
});

$serverDuration = $meter
->createHistogram('http.server.duration', 'ms', 'measures the duration inbound HTTP requests');

// During the time range (T0, T1]:
$serverDuration->record(50, ['http.method' => 'GET', 'http.status_code' => 200]);
$serverDuration->record(100, ['http.method' => 'GET', 'http.status_code' => 200]);
$serverDuration->record(1, ['http.method' => 'GET', 'http.status_code' => 500]);
$reader->collect();

// During the time range (T1, T2]:
$reader->collect();

// During the time range (T2, T3]:
$serverDuration->record(5, ['http.method' => 'GET', 'http.status_code' => 500]);
$serverDuration->record(2, ['http.method' => 'GET', 'http.status_code' => 500]);
$reader->collect();

// During the time range (T3, T4]:
$serverDuration->record(100, ['http.method' => 'GET', 'http.status_code' => 200]);
$reader->collect();

// During the time range (T4, T5]:
$serverDuration->record(100, ['http.method' => 'GET', 'http.status_code' => 200]);
$serverDuration->record(30, ['http.method' => 'GET', 'http.status_code' => 200]);
$serverDuration->record(50, ['http.method' => 'GET', 'http.status_code' => 200]);
$reader->collect();

$meterProvider->shutdown();
11 changes: 1 addition & 10 deletions examples/metrics/prometheus/prometheus_metrics_example.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@

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

use OpenTelemetry\Contrib\Prometheus\PrometheusExporter;
use OpenTelemetry\SDK\Metrics\Counter;
use Prometheus\CollectorRegistry;
use Prometheus\Storage\Redis;

Redis::setDefaultOptions(
Expand All @@ -20,10 +17,4 @@
]
);

$counter = new Counter('opentelemetry_prometheus_counter', 'Just a quick measurement');

$counter->increment();

$exporter = new PrometheusExporter(CollectorRegistry::getDefault());

$exporter->export([$counter]);
trigger_error('Prometheus exporter currently not supported', E_USER_WARNING);
3 changes: 3 additions & 0 deletions files/collector/otel-collector-config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,6 @@ service:
receivers: [otlp, zipkin]
exporters: [zipkin, logging, newrelic]
processors: [batch]
metrics:
receivers: [otlp]
exporters: [logging]
3 changes: 2 additions & 1 deletion src/API/Common/Instrumentation/InstrumentationTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

use OpenTelemetry\API\Metrics\MeterInterface;
use OpenTelemetry\API\Metrics\MeterProviderInterface;
use OpenTelemetry\API\Metrics\NoopMeter;
use OpenTelemetry\API\Metrics\Noop\NoopMeter;
use OpenTelemetry\API\Trace\NoopTracer;
use OpenTelemetry\API\Trace\TracerInterface;
use OpenTelemetry\API\Trace\TracerProviderInterface;
Expand Down Expand Up @@ -177,6 +177,7 @@ private function initDefaults(): void
{
$this->propagator = new NullPropagator();
$this->tracer = new NoopTracer();
/** @phan-suppress-next-line PhanAccessMethodInternal */
$this->meter = new NoopMeter();
$this->logger = new NullLogger();
}
Expand Down
30 changes: 9 additions & 21 deletions src/API/Metrics/CounterInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,18 @@

namespace OpenTelemetry\API\Metrics;

interface CounterInterface extends MetricInterface
{
/**
* Adds value to the counter
*
* @access public
* @param int $value
* @return self
*/
public function add(int $value): CounterInterface;
use OpenTelemetry\Context\Context;

/**
* Increments value
*
* @access public
* @return self
*/
public function increment(): CounterInterface;
interface CounterInterface
{

/**
* Gets the value
* @param float|int $amount non-negative amount to increment by
* @param iterable<non-empty-string, string|bool|float|int|array|null> $attributes
* attributes of the data point
* @param Context|false|null $context execution context
*
* @access public
* @return int
* @see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/api.md#add
*/
public function getValue(): int;
public function add($amount, iterable $attributes = [], $context = null): void;
}
26 changes: 0 additions & 26 deletions src/API/Metrics/ExporterInterface.php

This file was deleted.

21 changes: 21 additions & 0 deletions src/API/Metrics/HistogramInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\API\Metrics;

use OpenTelemetry\Context\Context;

interface HistogramInterface
{

/**
* @param float|int $amount non-negative amount to record
* @param iterable<non-empty-string, string|bool|float|int|array|null> $attributes
* attributes of the data point
* @param Context|false|null $context execution context
*
* @see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/api.md#record
*/
public function record($amount, iterable $attributes = [], $context = null): void;
}
32 changes: 0 additions & 32 deletions src/API/Metrics/LabelableMetricInterfaceInterface.php

This file was deleted.

0 comments on commit 3ac64f6

Please sign in to comment.