Metrics api (#155)
* Metrics API Templating * Metrics API Templating * Adding Style * Added base Metrics Providers * Rework this commit * Minor changes in exporter * Added test for abstract metrics exporter * Fixed namespace in test * Added Metrics interface stub * Added additional check in GlobalMeterProvicerTest * Style fixes * Added fixes according to php stan * Removed comments * Refactored Exporter * Removed unused array from AbstractExporter * Refactored API and aded base files * Installed new dependencies * Added example for Prometheus Metrics * Fixed Phan config * Added UpDownCounter and tests for it * Fixed prometheus misspelling, renamed shortcut command for metrics example * Added docblocks * Refactored structure of metrics prometheus example * Added info in readme how to run metrics prometheus example * Style fixes for counters * Removed unused import in test * Added more typization for metric labels * Added todo for later * Added versioning in MeterProvider * Fix for versioning in MeterProvider * Added ValueRecorder class * Fixed typo in docblock * Removed Assert library * Removed assert library from phpstan config * Fixed style * Removed ValueRecorder from this branch Co-authored-by: Bob Strecansky <bob.strecansky@gmail.com>
This commit is contained in:
parent
ce036e540f
commit
b49df469d6
|
@ -366,6 +366,7 @@ return [
|
|||
'vendor/php-http',
|
||||
'vendor/phan/phan/src/Phan',
|
||||
'vendor/phpunit/phpunit/src',
|
||||
'vendor/endclothing/prometheus_client_php/src',
|
||||
],
|
||||
|
||||
// A list of individual files to include in analysis
|
||||
|
|
5
Makefile
5
Makefile
|
@ -13,6 +13,11 @@ examples: FORCE
|
|||
$(DC_RUN_PHP) php ./examples/AlwaysOnTraceExample.php
|
||||
$(DC_RUN_PHP) php ./examples/AlwaysOffTraceExample.php
|
||||
$(DC_RUN_PHP) php ./examples/JaegerExporterExample.php
|
||||
metrics-prometheus-example:
|
||||
@docker-compose -f docker-compose.prometheus.yaml up -d web
|
||||
@docker-compose -f docker-compose.prometheus.yaml run php-prometheus php /var/www/public/examples/prometheus/PrometheusMetricsExample.php
|
||||
stop-prometheus:
|
||||
@docker-compose -f docker-compose.prometheus.yaml stop
|
||||
bash:
|
||||
$(DC_RUN_PHP) bash
|
||||
style:
|
||||
|
|
13
README.md
13
README.md
|
@ -50,13 +50,24 @@ To make sure the tests in this repo work as you expect, you can use the included
|
|||
Execute `make test` from your bash compatible shell. This will output the test output as well as a test coverage analysis. Code that doesn't pass our currently defined tests will emit a failure in CI
|
||||
|
||||
## Examples
|
||||
|
||||
### Trace
|
||||
You can use the [examples/AlwaysSampleTraceExample.php](/examples/AlwaysOnTraceExample.php) file to test out the reference implementation we have. This example perfoms a sample trace with a grouping of 5 spans and POSTs the result to a local zipkin instance.
|
||||
|
||||
The PHP should execute by itself (if you have a zipkin instance running on localhost), but if you'd like a no-fuss way to test this out with docker and docker-compose, you can perform the following simple steps:
|
||||
|
||||
1.) Install the necessary dependencies by running `make install`. This will install the composer dependencies and store them in `/vendor`
|
||||
2.) Execute the example trace using `make examples`.
|
||||
2.) Execute the example trace using `make examples`.
|
||||
|
||||
Exported spans can be seen in zipkin at [http://127.0.0.1:9411](http://127.0.0.1:9411)
|
||||
|
||||
Exported spans can also be seen in jaeger at [http://127.0.0.1:16686](http://127.0.0.1:16686)
|
||||
|
||||
### Metrics
|
||||
You can use the [examples/prometheus/PrometheusMetricsExample.php](/examples/prometheus/PrometheusMetricsExample.php) file to test out the reference implementation we have. This example will create a counter that will be scraped by local Prometheus instance.
|
||||
|
||||
The easy way to test the example out with docker and docker-compose is:
|
||||
1.) Run `make metrics-prometheus-example`. Make sure that local ports 8080, 6379 and 9090 are available.
|
||||
2.) Open local Prometheus instance: http://localhost:9090
|
||||
3.) Go to Graph section, type "opentelemetry_prometheus_counter" in the search field or select it in the dropdown menu. You will see the counter value. Every other time you run `make metrics-prometheus-example` will increment the counter but remember that Prometheus scrapes values once in 10 seconds.
|
||||
4.) In order to stop docker containers for this example just run `make stop-prometheus`
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Metrics;
|
||||
|
||||
interface Counter extends Metric
|
||||
{
|
||||
/**
|
||||
* Adds value to the counter
|
||||
*
|
||||
* @access public
|
||||
* @param int $value
|
||||
* @return self
|
||||
*/
|
||||
public function add(int $value): Counter;
|
||||
|
||||
/**
|
||||
* Increments value
|
||||
*
|
||||
* @access public
|
||||
* @return self
|
||||
*/
|
||||
public function increment(): Counter;
|
||||
|
||||
/**
|
||||
* Gets the value
|
||||
*
|
||||
* @access public
|
||||
* @return int
|
||||
*/
|
||||
public function getValue(): int;
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Metrics;
|
||||
|
||||
interface Exporter
|
||||
{
|
||||
/**
|
||||
* Possible return values as outlined in the OpenTelemetry spec
|
||||
*/
|
||||
const SUCCESS = 0;
|
||||
const FAILED_NOT_RETRYABLE = 1;
|
||||
const FAILED_RETRYABLE = 2;
|
||||
|
||||
/**
|
||||
* export.
|
||||
*
|
||||
* @access public
|
||||
* @param iterable<Metric> $metrics
|
||||
* @return int
|
||||
*
|
||||
* todo Should we pass a result callback in the 2nd parameter like in JavaScript implementation?
|
||||
*/
|
||||
public function export(iterable $metrics): int;
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Metrics;
|
||||
|
||||
interface LabelableMetric extends Metric
|
||||
{
|
||||
/**
|
||||
* Get $labels
|
||||
* todo: we will probably need a class Label and a typed collection for labels
|
||||
*
|
||||
* @return array<string>
|
||||
*/
|
||||
public function getLabels(): array;
|
||||
|
||||
/**
|
||||
* Set $labels
|
||||
*
|
||||
* @param array<string> $labels
|
||||
* @return self
|
||||
*/
|
||||
public function setLabels(array $labels);
|
||||
|
||||
/**
|
||||
* Set $labels
|
||||
*
|
||||
* @param string $label
|
||||
* @return self
|
||||
*/
|
||||
public function addLabel(string $label);
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Metrics;
|
||||
|
||||
interface Meter
|
||||
{
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Metrics;
|
||||
|
||||
interface MeterProvider
|
||||
{
|
||||
/**
|
||||
* @access public
|
||||
* @param string $name - (required) - This name must identify the instrumentation library
|
||||
* (e.g. io.opentelemetry.contrib.mongodb) and not the instrumented library.
|
||||
* In case an invalid name (null or empty string) is specified, a working default Meter implementation is returned
|
||||
* as a fallback rather than returning null or throwing an exception.
|
||||
* A MeterProvider could also return a no-op Meter here if application owners configure the SDK to suppress
|
||||
* telemetry produced by this library.
|
||||
* @param ?string $version - (optional) - Specifies the version of the instrumentation library (e.g. semver:1.0.0)
|
||||
* @return Meter
|
||||
*/
|
||||
public function getMeter(string $name, ?string $version = null): Meter;
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Metrics;
|
||||
|
||||
interface Metric
|
||||
{
|
||||
/**
|
||||
* Returns metric's name
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string;
|
||||
|
||||
/**
|
||||
* Returns metric's description
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription(): string;
|
||||
|
||||
/**
|
||||
* Returns metric's type
|
||||
*
|
||||
* @access public
|
||||
* @return int
|
||||
*/
|
||||
public function getType(): int;
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Metrics;
|
||||
|
||||
interface MetricKind
|
||||
{
|
||||
public const COUNTER = 1;
|
||||
|
||||
public const UP_DOWN_COUNTER = 2;
|
||||
|
||||
public const VALUE_RECORDER = 3;
|
||||
|
||||
public const SUM_OBSERVER = 4;
|
||||
|
||||
public const UP_DOWN_SUM_OBSERVER = 4;
|
||||
|
||||
public const VALUE_OBSERVER = 5;
|
||||
|
||||
public const TYPES = [
|
||||
self::COUNTER,
|
||||
self::UP_DOWN_COUNTER,
|
||||
self::VALUE_RECORDER,
|
||||
self::SUM_OBSERVER,
|
||||
self::UP_DOWN_SUM_OBSERVER,
|
||||
self::VALUE_OBSERVER,
|
||||
];
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Metrics;
|
||||
|
||||
interface UpDownCounter extends Counter
|
||||
{
|
||||
/**
|
||||
* Subtracts counter value
|
||||
*
|
||||
* @access public
|
||||
* @param int $value
|
||||
* @return UpDownCounter
|
||||
*/
|
||||
public function subtract(int $value) : UpDownCounter;
|
||||
|
||||
/**
|
||||
* Decrements counter value
|
||||
*
|
||||
* @access public
|
||||
* @return UpDownCounter
|
||||
*/
|
||||
public function decrement() : UpDownCounter;
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Metrics;
|
||||
|
||||
interface ValueRecorder
|
||||
{
|
||||
/**
|
||||
* record.
|
||||
*
|
||||
* @access public
|
||||
* @param int|float $value
|
||||
* @return void
|
||||
*/
|
||||
public function record($value) : void;
|
||||
}
|
|
@ -8,7 +8,8 @@
|
|||
"ext-json": "*",
|
||||
"guzzlehttp/guzzle": "^6.5",
|
||||
"psr/http-client": "^1.0",
|
||||
"php-http/guzzle6-adapter": "^2.0"
|
||||
"php-http/guzzle6-adapter": "^2.0",
|
||||
"endclothing/prometheus_client_php": "^1.0"
|
||||
},
|
||||
"authors": [
|
||||
{
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
version: '3.7'
|
||||
services:
|
||||
prometheus:
|
||||
image: prom/prometheus
|
||||
ports:
|
||||
- 9090:9090
|
||||
volumes:
|
||||
- ./docker/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
|
||||
redis:
|
||||
image: redis
|
||||
ports:
|
||||
- 6379:6379
|
||||
web:
|
||||
image: nginx:alpine
|
||||
ports:
|
||||
- 8080:80
|
||||
depends_on:
|
||||
- php-prometheus
|
||||
volumes:
|
||||
- ./docker/prometheus/nginx.conf:/etc/nginx/conf.d/default.conf
|
||||
- ./docker/prometheus/index.php:/var/www/public/index.php
|
||||
php-prometheus:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: docker/prometheus/Dockerfile
|
||||
volumes:
|
||||
- ./:/var/www/public
|
||||
depends_on:
|
||||
- redis
|
||||
- prometheus
|
|
@ -0,0 +1,11 @@
|
|||
FROM php:7.3-fpm-alpine
|
||||
|
||||
RUN apk update && apk add --no-cache \
|
||||
$PHPIZE_DEPS
|
||||
|
||||
RUN pecl channel-update pecl.php.net && \
|
||||
pecl install redis \
|
||||
&& docker-php-ext-enable redis
|
||||
|
||||
# Clean
|
||||
RUN rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /var/cache/*
|
|
@ -0,0 +1,25 @@
|
|||
server {
|
||||
listen 80;
|
||||
index index.php index.html;
|
||||
server_name 127.0.0.1 localhost;
|
||||
root /var/www/public/examples/prometheus;
|
||||
|
||||
access_log /dev/stdout;
|
||||
error_log /dev/stdout debug;
|
||||
|
||||
underscores_in_headers on;
|
||||
|
||||
location / {
|
||||
try_files $uri /index.php?$args;
|
||||
}
|
||||
|
||||
location ~ \.php$ {
|
||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||
fastcgi_pass php-prometheus:9000;
|
||||
fastcgi_index index.php;
|
||||
fastcgi_read_timeout 1000;
|
||||
include fastcgi_params;
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
fastcgi_param PATH_INFO $fastcgi_path_info;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
global:
|
||||
scrape_interval: 15s
|
||||
evaluation_interval: 15s
|
||||
|
||||
# Attach these labels to any time series or alerts when communicating with
|
||||
# external systems (federation, remote storage, Alertmanager).
|
||||
external_labels:
|
||||
monitor: 'prometheus-stack-monitor'
|
||||
|
||||
# Load and evaluate rules in this file every 'evaluation_interval' seconds.
|
||||
# rule_files:
|
||||
# - "prometheus.rules.yml"
|
||||
|
||||
scrape_configs:
|
||||
|
||||
- job_name: 'prometheus'
|
||||
|
||||
scrape_interval: 10s
|
||||
scrape_timeout: 5s
|
||||
|
||||
static_configs:
|
||||
- targets: ['localhost:9090']
|
||||
|
||||
- job_name: 'webserver'
|
||||
|
||||
scrape_interval: 10s
|
||||
scrape_timeout: 5s
|
||||
|
||||
metrics_path: /index.php
|
||||
|
||||
static_configs:
|
||||
- targets: ['web']
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require __DIR__ . '/../../vendor/autoload.php';
|
||||
|
||||
use OpenTelemetry\Sdk\Metrics\Counter;
|
||||
use OpenTelemetry\Sdk\Metrics\Exporters\PrometheusExporter;
|
||||
use Prometheus\CollectorRegistry;
|
||||
use Prometheus\Storage\Redis;
|
||||
|
||||
Redis::setDefaultOptions(
|
||||
[
|
||||
'host' => 'redis',
|
||||
'port' => 6379,
|
||||
'password' => null,
|
||||
'timeout' => 0.1, // in seconds
|
||||
'read_timeout' => '10', // in seconds
|
||||
'persistent_connections' => false,
|
||||
]
|
||||
);
|
||||
|
||||
$counter = new Counter('opentelemetry_prometheus_counter', 'Just a quick measurement');
|
||||
|
||||
$counter->increment();
|
||||
|
||||
$exporter = new PrometheusExporter(CollectorRegistry::getDefault());
|
||||
|
||||
$exporter->export([$counter]);
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require __DIR__ . '/../../vendor/autoload.php';
|
||||
|
||||
\Prometheus\Storage\Redis::setDefaultOptions(
|
||||
[
|
||||
'host' => 'redis',
|
||||
'port' => 6379,
|
||||
'password' => null,
|
||||
'timeout' => 0.1, // in seconds
|
||||
'read_timeout' => '10', // in seconds
|
||||
'persistent_connections' => false,
|
||||
]
|
||||
);
|
||||
|
||||
$registry = \Prometheus\CollectorRegistry::getDefault();
|
||||
|
||||
$renderer = new \Prometheus\RenderTextFormat();
|
||||
$result = $renderer->render($registry->getMetricFamilySamples());
|
||||
|
||||
header('Content-type: ' . \Prometheus\RenderTextFormat::MIME_TYPE);
|
||||
echo $result;
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Sdk\Metrics;
|
||||
|
||||
use OpenTelemetry\Metrics as API;
|
||||
|
||||
abstract class AbstractMetric implements API\Metric
|
||||
{
|
||||
/**
|
||||
* @var string $name
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* @var string $description
|
||||
*/
|
||||
protected $description;
|
||||
|
||||
public function __construct(string $name, string $description = '')
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->description = $description;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Sdk\Metrics;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use OpenTelemetry\Metrics as API;
|
||||
|
||||
class Counter extends AbstractMetric implements API\Counter, API\LabelableMetric
|
||||
{
|
||||
use HasLabels;
|
||||
|
||||
/**
|
||||
* @var int $value
|
||||
*/
|
||||
protected $value = 0;
|
||||
|
||||
/**
|
||||
* Get $type
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getType(): int
|
||||
{
|
||||
return API\MetricKind::COUNTER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current value
|
||||
*
|
||||
* @access public
|
||||
* @return int
|
||||
*/
|
||||
public function getValue(): int
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments the current value
|
||||
*
|
||||
* @access public
|
||||
* @return self
|
||||
*/
|
||||
public function increment(): API\Counter
|
||||
{
|
||||
$this->value++;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the specified value to the current counter's value
|
||||
*
|
||||
* @access public
|
||||
* @return self
|
||||
*/
|
||||
public function add(int $value): API\Counter
|
||||
{
|
||||
if ($value <= 0) {
|
||||
throw new InvalidArgumentException('Only positive numbers can be added to the Counter');
|
||||
}
|
||||
|
||||
$this->value += $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Sdk\Metrics\Exceptions;
|
||||
|
||||
class CantBeExported extends ExportException
|
||||
{
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Sdk\Metrics\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class ExportException extends Exception
|
||||
{
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Sdk\Metrics\Exceptions;
|
||||
|
||||
class RetryableExportException extends ExportException
|
||||
{
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Sdk\Metrics\Exporters;
|
||||
|
||||
use Exception;
|
||||
use OpenTelemetry\Metrics as API;
|
||||
use OpenTelemetry\Sdk\Metrics\Exceptions\RetryableExportException;
|
||||
|
||||
abstract class AbstractExporter implements API\Exporter
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function export(iterable $metrics): int
|
||||
{
|
||||
if (empty($metrics)) {
|
||||
return API\Exporter::SUCCESS;
|
||||
}
|
||||
|
||||
try {
|
||||
foreach ($metrics as $metric) {
|
||||
if (! $metric instanceof API\Metric) {
|
||||
throw new \InvalidArgumentException('Metric must implement ' . API\Metric::class);
|
||||
}
|
||||
}
|
||||
|
||||
$this->doExport($metrics);
|
||||
|
||||
return API\Exporter::SUCCESS;
|
||||
} catch (RetryableExportException $exception) {
|
||||
return API\Exporter::FAILED_RETRYABLE;
|
||||
} catch (Exception $exception) {
|
||||
return API\Exporter::FAILED_NOT_RETRYABLE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends metrics to the destination system
|
||||
*
|
||||
* @access protected
|
||||
* @param iterable<API\Metric> $metrics
|
||||
* @return void
|
||||
*/
|
||||
abstract protected function doExport(iterable $metrics): void;
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Sdk\Metrics\Exporters;
|
||||
|
||||
use OpenTelemetry\Metrics as API;
|
||||
use OpenTelemetry\Sdk\Metrics\Counter;
|
||||
use OpenTelemetry\Sdk\Metrics\Exceptions\CantBeExported;
|
||||
use Prometheus\CollectorRegistry;
|
||||
|
||||
class PrometheusExporter extends AbstractExporter
|
||||
{
|
||||
/**
|
||||
* @var CollectorRegistry $registry
|
||||
*/
|
||||
protected $registry;
|
||||
|
||||
/**
|
||||
* @var string $namespace
|
||||
*/
|
||||
protected $namespace;
|
||||
|
||||
public function __construct(CollectorRegistry $registry, string $namespace = '')
|
||||
{
|
||||
$this->registry = $registry;
|
||||
$this->namespace = $namespace;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function doExport(iterable $metrics): void
|
||||
{
|
||||
foreach ($metrics as $metric) {
|
||||
switch (get_class($metric)) {
|
||||
case Counter::class:
|
||||
$this->exportCounter($metric);
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new CantBeExported('Unknown metrics type: ' . get_class($metric));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function exportCounter(API\Counter $counter): void
|
||||
{
|
||||
$labels = ($counter instanceof API\LabelableMetric) ? $counter->getLabels() : [];
|
||||
|
||||
$record = $this->registry->getOrRegisterCounter(
|
||||
$this->namespace,
|
||||
$counter->getName(),
|
||||
$counter->getDescription(),
|
||||
$labels
|
||||
);
|
||||
|
||||
$record->incBy($counter->getValue(), $labels);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Sdk\Metrics;
|
||||
|
||||
trait HasLabels
|
||||
{
|
||||
/**
|
||||
* @var array $labels
|
||||
*/
|
||||
protected $labels = [];
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getLabels(): array
|
||||
{
|
||||
return $this->labels;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function setLabels(array $labels)
|
||||
{
|
||||
foreach ($labels as $label) {
|
||||
if (! is_string($label)) {
|
||||
throw new \InvalidArgumentException('The label is expected to be a string');
|
||||
}
|
||||
}
|
||||
|
||||
$this->labels = $labels;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function addLabel(string $label)
|
||||
{
|
||||
$this->labels[] = $label;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Sdk\Metrics;
|
||||
|
||||
use OpenTelemetry\Metrics as API;
|
||||
|
||||
class Meter implements API\Meter
|
||||
{
|
||||
// Temp stub
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Sdk\Metrics\Providers;
|
||||
|
||||
use OpenTelemetry\Metrics as API;
|
||||
|
||||
/**
|
||||
* https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/metrics/api.md#global-meter-provider
|
||||
*/
|
||||
final class GlobalMeterProvider
|
||||
{
|
||||
/**
|
||||
* @var API\MeterProvider $globalProvider
|
||||
*/
|
||||
protected static $globalProvider;
|
||||
|
||||
/**
|
||||
* Returns a global instance MeterProvider.
|
||||
* If global instance is missing, new MeterProvider will be lazily created
|
||||
*
|
||||
* @access public static
|
||||
* @return API\MeterProvider
|
||||
*/
|
||||
public static function getGlobalProvider(): API\MeterProvider
|
||||
{
|
||||
if (empty(static::$globalProvider)) {
|
||||
static::$globalProvider = new MeterProvider();
|
||||
}
|
||||
|
||||
return static::$globalProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a global instance of MeterProvider
|
||||
*
|
||||
* @access public static
|
||||
* @param API\MeterProvider $globalProvider
|
||||
* @return void
|
||||
*/
|
||||
public static function setGlobalProvider(API\MeterProvider $globalProvider): void
|
||||
{
|
||||
static::$globalProvider = $globalProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accessor for the global provider
|
||||
*/
|
||||
public static function __callStatic($name, $arguments)
|
||||
{
|
||||
return static::getGlobalProvider()->$name(...$arguments);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Sdk\Metrics\Providers;
|
||||
|
||||
use OpenTelemetry\Metrics as API;
|
||||
use OpenTelemetry\Sdk\Metrics\Meter;
|
||||
|
||||
class MeterProvider implements API\MeterProvider
|
||||
{
|
||||
/**
|
||||
* @var array $meters
|
||||
*/
|
||||
protected $meters = [];
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getMeter(string $name, ?string $version = null): API\Meter
|
||||
{
|
||||
if (empty($this->meters[$name . $version])) {
|
||||
$this->meters[$name . $version] = $this->getCreatedMeter($name, $version);
|
||||
}
|
||||
|
||||
return $this->meters[$name . $version];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Meter instance
|
||||
*
|
||||
* @access protected
|
||||
* @param string $name
|
||||
* @param string|null $version Default: null
|
||||
* @return API\Meter
|
||||
*/
|
||||
protected function getCreatedMeter(string $name, string $version = null): API\Meter
|
||||
{
|
||||
// todo: once the Meter interface and an implementation are done, change this
|
||||
return new Meter();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Sdk\Metrics;
|
||||
|
||||
use OpenTelemetry\Metrics as API;
|
||||
|
||||
class UpDownCounter extends Counter implements API\UpDownCounter
|
||||
{
|
||||
/**
|
||||
* @var int $value
|
||||
*/
|
||||
protected $value = 0;
|
||||
|
||||
/**
|
||||
* Get $type
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getType(): int
|
||||
{
|
||||
return API\MetricKind::UP_DOWN_COUNTER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the specified value to the current counter's value
|
||||
*
|
||||
* @access public
|
||||
* @return self
|
||||
*/
|
||||
public function add(int $value): API\Counter
|
||||
{
|
||||
$this->value += $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function subtract(int $value) : API\UpDownCounter
|
||||
{
|
||||
$this->value -= $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function decrement() : API\UpDownCounter
|
||||
{
|
||||
$this->value--;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Tests\Sdk\Unit\Metrics;
|
||||
|
||||
use OpenTelemetry\Sdk\Metrics\Counter;
|
||||
use OpenTelemetry\Sdk\Metrics\UpDownCounter;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class CounterTest extends TestCase
|
||||
{
|
||||
public function testCounterIncrements()
|
||||
{
|
||||
$counter = new Counter('some_counter');
|
||||
|
||||
$this->assertSame(0, $counter->getValue());
|
||||
|
||||
$counter->increment();
|
||||
|
||||
$this->assertSame(1, $counter->getValue());
|
||||
}
|
||||
|
||||
public function testCounterDoesNotAcceptNegativeNumbers()
|
||||
{
|
||||
$counter = new Counter('some_counter');
|
||||
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
|
||||
$counter->add(-1);
|
||||
}
|
||||
|
||||
public function testUpDownCounterIncrementsAndDecrements()
|
||||
{
|
||||
$counter = new UpDownCounter('some_counter');
|
||||
|
||||
$this->assertSame(0, $counter->getValue());
|
||||
|
||||
$counter->increment();
|
||||
|
||||
$this->assertSame(1, $counter->getValue());
|
||||
|
||||
$counter->decrement();
|
||||
|
||||
$this->assertSame(0, $counter->getValue());
|
||||
}
|
||||
|
||||
public function testUpDownCounterAcceptNegativeNumbers()
|
||||
{
|
||||
$counter = new UpDownCounter('some_up_down_counter');
|
||||
|
||||
$this->assertSame(0, $counter->getValue());
|
||||
|
||||
$counter->add(-1);
|
||||
|
||||
$this->assertSame(-1, $counter->getValue());
|
||||
|
||||
$counter->subtract(3);
|
||||
|
||||
$this->assertSame(-4, $counter->getValue());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Tests\Sdk\Unit\Metrics\Exporters;
|
||||
|
||||
use OpenTelemetry\Metrics as API;
|
||||
use OpenTelemetry\Sdk\Metrics\Exporters\AbstractExporter;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class AbstractExporterTest extends TestCase
|
||||
{
|
||||
public function testEmptyMetricsExportReturnsSuccess()
|
||||
{
|
||||
$this->assertEquals(
|
||||
API\Exporter::SUCCESS,
|
||||
$this->getExporter()->export([])
|
||||
);
|
||||
}
|
||||
|
||||
public function testErrorReturnsIfTryingToExportNotAMetric()
|
||||
{
|
||||
$this->assertEquals(
|
||||
API\Exporter::FAILED_NOT_RETRYABLE,
|
||||
$this->getExporter()->export([1])
|
||||
);
|
||||
}
|
||||
|
||||
protected function getExporter(): AbstractExporter
|
||||
{
|
||||
return new class() extends AbstractExporter {
|
||||
protected function doExport(iterable $metrics): void
|
||||
{
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Tests\Sdk\Unit\Metrics;
|
||||
|
||||
use OpenTelemetry\Sdk\Metrics\HasLabels;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class HasLabelTest extends TestCase
|
||||
{
|
||||
protected $labelable;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->labelable = new class() {
|
||||
use HasLabels;
|
||||
};
|
||||
}
|
||||
|
||||
public function testHasLabelAcceptsValues()
|
||||
{
|
||||
$this->assertEmpty($this->labelable->getLabels());
|
||||
|
||||
$expected = ['label_one', 'label_two'];
|
||||
|
||||
$this->labelable->setLabels($expected);
|
||||
|
||||
$this->assertSame($expected, $this->labelable->getLabels());
|
||||
}
|
||||
|
||||
public function testHasLabelAcceptsOnlyStrings()
|
||||
{
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
|
||||
$this->labelable->setLabels([new \stdClass()]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Tests\Sdk\Unit\Metrics\Providers;
|
||||
|
||||
use OpenTelemetry\Sdk\Metrics\Meter;
|
||||
use OpenTelemetry\Sdk\Metrics\Providers\GlobalMeterProvider;
|
||||
use OpenTelemetry\Sdk\Metrics\Providers\MeterProvider;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class GlobalMeterProvicerTest extends TestCase
|
||||
{
|
||||
public function testGLobalMeterProviderSettersAndGetters()
|
||||
{
|
||||
$defaultProvider = GlobalMeterProvider::getGlobalProvider();
|
||||
|
||||
$this->assertInstanceOf(MeterProvider::class, $defaultProvider);
|
||||
|
||||
$meter = GlobalMeterProvider::getMeter('test');
|
||||
|
||||
$this->assertInstanceOf(Meter::class, $meter);
|
||||
|
||||
$customGlobalProvider = new MeterProvider();
|
||||
|
||||
GlobalMeterProvider::setGlobalProvider($customGlobalProvider);
|
||||
|
||||
$this->assertSame($customGlobalProvider, GlobalMeterProvider::getGlobalProvider());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue