Repository: endclothing/prometheus_client_php
Branch: master
Commit: b0931c73f920
Files: 61
Total size: 147.6 KB
Directory structure:
gitextract_3xj95wis/
├── .circleci/
│ └── config.yml
├── .gitattributes
├── .gitignore
├── LICENSE
├── README.md
├── composer.json
├── docker-compose.yml
├── examples/
│ ├── flush_adapter.php
│ ├── metrics.php
│ ├── pushgateway.php
│ ├── some_counter.php
│ ├── some_gauge.php
│ └── some_histogram.php
├── nginx/
│ ├── Dockerfile
│ ├── default.conf
│ └── nginx.conf
├── php-fpm/
│ ├── Dockerfile
│ ├── docker-php-ext-apcu-cli.ini
│ └── www.conf
├── phpcs.xml.dist
├── phpunit.xml.dist
├── src/
│ └── Prometheus/
│ ├── Collector.php
│ ├── CollectorRegistry.php
│ ├── Counter.php
│ ├── Exception/
│ │ ├── MetricNotFoundException.php
│ │ ├── MetricsRegistrationException.php
│ │ └── StorageException.php
│ ├── Gauge.php
│ ├── Histogram.php
│ ├── MetricFamilySamples.php
│ ├── PushGateway.php
│ ├── RenderTextFormat.php
│ ├── Sample.php
│ └── Storage/
│ ├── APC.php
│ ├── Adapter.php
│ ├── InMemory.php
│ └── Redis.php
└── tests/
├── Test/
│ ├── BlackBoxPushGatewayTest.php
│ ├── BlackBoxTest.php
│ └── Prometheus/
│ ├── APC/
│ │ ├── CollectorRegistryTest.php
│ │ ├── CounterTest.php
│ │ ├── GaugeTest.php
│ │ └── HistogramTest.php
│ ├── AbstractCollectorRegistryTest.php
│ ├── AbstractCounterTest.php
│ ├── AbstractGaugeTest.php
│ ├── AbstractHistogramTest.php
│ ├── InMemory/
│ │ ├── CollectorRegistryTest.php
│ │ ├── CounterTest.php
│ │ ├── GaugeTest.php
│ │ └── HistogramTest.php
│ ├── PushGatewayTest.php
│ ├── Redis/
│ │ ├── CollectorRegistryTest.php
│ │ ├── CounterTest.php
│ │ ├── CounterWithPrefixTest.php
│ │ ├── GaugeTest.php
│ │ ├── GaugeWithPrefixTest.php
│ │ ├── HistogramTest.php
│ │ └── HistogramWithPrefixTest.php
│ └── Storage/
│ └── RedisTest.php
└── bootstrap.php
================================================
FILE CONTENTS
================================================
================================================
FILE: .circleci/config.yml
================================================
unit-config: &unit-config
environment:
COMPOSER_PREFER_LOWEST: 0
steps:
- checkout
- run:
name: Install latest composer
command: |
php -r "copy('https://raw.githubusercontent.com/composer/getcomposer.org/master/web/installer', 'composer-setup.php');"
php composer-setup.php
php -r "unlink('composer-setup.php');"
mv composer.phar /usr/local/bin/composer
- run:
name: Write COMPOSER_PREFER_LOWEST to file
command: echo "$COMPOSER_PREFER_LOWEST" > ~/COMPOSER_PREFER_LOWEST.txt
# Download and cache dependencies
- restore_cache:
keys:
- v1-composer-cache-{{ checksum "~/COMPOSER_PREFER_LOWEST.txt" }}-{{ checksum "composer.json" }}
# Fall back to using the latest cache if no exact match is found.
- v1-composer-cache-{{ checksum "~/COMPOSER_PREFER_LOWEST.txt" }}
- run:
name: Validate composer files
command: composer validate --strict
- run:
name: Install PHP extensions
command: |
printf '\n' | sudo pecl install redis
sudo docker-php-ext-enable redis
- run:
name: Install composer packages
command: |
if [ $COMPOSER_PREFER_LOWEST -eq "1" ]; then
composer update --no-ansi --no-progress --optimize-autoloader --no-interaction --no-plugins --no-scripts --prefer-dist --prefer-stable --prefer-lowest
else
composer update --no-ansi --no-progress --optimize-autoloader --no-interaction --no-plugins --no-scripts --prefer-dist --prefer-stable
fi
- save_cache:
key: v1-composer-cache-{{ checksum "~/COMPOSER_PREFER_LOWEST.txt" }}-{{ checksum "composer.json" }}
paths:
- ./vendor
- run:
name: Wait for backing services to start
command: dockerize -wait tcp://127.0.0.1:6379 -timeout 30s
- run:
name: Run PHP Code Sniffer
command: ./vendor/bin/phpcs
- run:
name: Run tests
command: ./vendor/bin/phpunit
version: 2.1
jobs:
php74:
<<: *unit-config
docker:
- image: circleci/php:7.4-cli-node
- image: circleci/redis:5-alpine
environment:
COMPOSER_PREFER_LOWEST: 0
php74-lowest:
<<: *unit-config
docker:
- image: circleci/php:7.4-cli-node
- image: circleci/redis:5-alpine
environment:
COMPOSER_PREFER_LOWEST: 1
php73:
<<: *unit-config
docker:
- image: circleci/php:7.3-cli-node
- image: circleci/redis:5-alpine
environment:
COMPOSER_PREFER_LOWEST: 0
php73-lowest:
<<: *unit-config
docker:
- image: circleci/php:7.3-cli-node
- image: circleci/redis:5-alpine
environment:
COMPOSER_PREFER_LOWEST: 1
php72:
<<: *unit-config
docker:
- image: circleci/php:7.2-cli-node
- image: circleci/redis:5-alpine
environment:
COMPOSER_PREFER_LOWEST: 0
php72-lowest:
<<: *unit-config
docker:
- image: circleci/php:7.2-cli-node
- image: circleci/redis:5-alpine
environment:
COMPOSER_PREFER_LOWEST: 1
workflows:
version: 2
units:
jobs:
- php74
- php74-lowest
- php73
- php73-lowest
- php72
- php72-lowest
================================================
FILE: .gitattributes
================================================
* text=auto
/.circleci/ export-ignore
/.github/ export-ignore
/examples/ export-ignore
/nginx/ export-ignore
/php-fpm/ export-ignore
/tests/ export-ignore
.gitattributes export-ignore
.gitignore export-ignore
docker-compose.yml export-ignore
phpcs.xml.dist export-ignore
phpunit.xml.dist export-ignore
================================================
FILE: .gitignore
================================================
/vendor/
*.iml
/.idea/
composer.lock
composer.phar
.phpunit.result.cache
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2016 Jimdo GmbH
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: README.md
================================================
# A prometheus client library written in PHP
[](https://circleci.com/gh/endclothing/prometheus_client_php/tree/master)
This library uses Redis or APCu to do the client side aggregation.
If using Redis, we recommend to run a local Redis instance next to your PHP workers.
## How does it work?
Usually PHP worker processes don't share any state.
You can pick from three adapters.
Redis, APC or an in memory adapter.
While the first needs a separate binary running, the second just needs the [APC](https://pecl.php.net/package/APCU) extension to be installed. If you don't need persistent metrics between requests (e.g. a long running cron job or script) the in memory adapter might be suitable to use.
## Installation
Add as [Composer](https://getcomposer.org/) dependency:
```sh
composer require endclothing/prometheus_client_php
```
## Usage
A simple counter:
```php
\Prometheus\CollectorRegistry::getDefault()
->getOrRegisterCounter('', 'some_quick_counter', 'just a quick measurement')
->inc();
```
Write some enhanced metrics:
```php
$registry = \Prometheus\CollectorRegistry::getDefault();
$counter = $registry->getOrRegisterCounter('test', 'some_counter', 'it increases', ['type']);
$counter->incBy(3, ['blue']);
$gauge = $registry->getOrRegisterGauge('test', 'some_gauge', 'it sets', ['type']);
$gauge->set(2.5, ['blue']);
$histogram = $registry->getOrRegisterHistogram('test', 'some_histogram', 'it observes', ['type'], [0.1, 1, 2, 3.5, 4, 5, 6, 7, 8, 9]);
$histogram->observe(3.5, ['blue']);
```
Manually register and retrieve metrics (these steps are combined in the `getOrRegister...` methods):
```php
$registry = \Prometheus\CollectorRegistry::getDefault();
$counterA = $registry->registerCounter('test', 'some_counter', 'it increases', ['type']);
$counterA->incBy(3, ['blue']);
// once a metric is registered, it can be retrieved using e.g. getCounter:
$counterB = $registry->getCounter('test', 'some_counter')
$counterB->incBy(2, ['red']);
```
Expose the metrics:
```php
$registry = \Prometheus\CollectorRegistry::getDefault();
$renderer = new RenderTextFormat();
$result = $renderer->render($registry->getMetricFamilySamples());
header('Content-type: ' . RenderTextFormat::MIME_TYPE);
echo $result;
```
Change the Redis options (the example shows the defaults):
```php
\Prometheus\Storage\Redis::setDefaultOptions(
[
'host' => '127.0.0.1',
'port' => 6379,
'password' => null,
'timeout' => 0.1, // in seconds
'read_timeout' => '10', // in seconds
'persistent_connections' => false
]
);
```
Using the InMemory storage:
```php
$registry = new CollectorRegistry(new InMemory());
$counter = $registry->registerCounter('test', 'some_counter', 'it increases', ['type']);
$counter->incBy(3, ['blue']);
$renderer = new RenderTextFormat();
$result = $renderer->render($registry->getMetricFamilySamples());
```
### Advanced Usage
#### Advanced Histogram Usage
On passing an empty array for the bucket parameter on instantiation, a set of default buckets will be used instead.
Whilst this is a good base for a typical web application, there is named constructor to assist in the generation of
exponential / geometric buckets.
Eg:
```
Histogram::exponentialBuckets(0.05, 1.5, 10);
```
This will start your buckets with a value of 1.5, grow them by a factor of 1.5 per bucket across a set of 10 buckets.
Also look at the [examples](examples).
## Development
### Dependencies
* PHP ^7.3
* PHP Redis extension
* PHP APCu extension
* [Composer](https://getcomposer.org/doc/00-intro.md#installation-linux-unix-osx)
* Redis
Start a Redis instance:
```
docker-compose up Redis
```
Run the tests:
```
composer install
# when Redis is not listening on localhost:
# export REDIS_HOST=192.168.59.100
./vendor/bin/phpunit
```
## Black box testing
Just start the nginx, fpm & Redis setup with docker-compose:
```
docker-compose up
```
Pick the adapter you want to test.
```
docker-compose run phpunit env ADAPTER=apc vendor/bin/phpunit tests/Test/
docker-compose run phpunit env ADAPTER=redis vendor/bin/phpunit tests/Test/
```
================================================
FILE: composer.json
================================================
{
"name": "endclothing/prometheus_client_php",
"description": "Prometheus instrumentation library for PHP applications.",
"type": "library",
"license": "Apache-2.0",
"authors": [
{
"name": "Daniel Noel-Davies",
"email": "Daniel.Noel-Davies@endclothing.com"
}
],
"replace": {
"jimdo/prometheus_client_php": "*"
},
"require": {
"php": "^7.2",
"ext-json": "*",
"guzzlehttp/guzzle": "^6.3",
"symfony/polyfill-apcu": "^1.6"
},
"require-dev": {
"phpunit/phpunit": "^8.4",
"squizlabs/php_codesniffer": "^3.5"
},
"suggest": {
"ext-redis": "Required if using Redis.",
"ext-apc": "Required if using APCu."
},
"autoload": {
"psr-4": {
"Prometheus\\": "src/Prometheus/"
}
},
"autoload-dev": {
"psr-0": {
"Test\\Prometheus\\": "tests/"
}
},
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
}
},
"config": {
"sort-packages": true
},
"prefer-stable": true
}
================================================
FILE: docker-compose.yml
================================================
nginx:
build: nginx/
links:
- php-fpm
ports:
- 8080:80
php-fpm:
build: php-fpm/
volumes:
- .:/var/www/html
links:
- redis
- pushgateway
environment:
- REDIS_HOST=redis
redis:
image: redis
ports:
- 6379:6379
pushgateway:
image: prom/pushgateway
ports:
- 9091:9091
phpunit:
build: php-fpm/
volumes:
- .:/var/www/html
links:
- redis
- pushgateway
- nginx
environment:
- REDIS_HOST=redis
================================================
FILE: examples/flush_adapter.php
================================================
<?php
require __DIR__ . '/../vendor/autoload.php';
$adapter = $_GET['adapter'];
if ($adapter === 'redis') {
define('REDIS_HOST', $_SERVER['REDIS_HOST'] ?? '127.0.0.1');
$redisAdapter = new Prometheus\Storage\Redis(['host' => REDIS_HOST]);
$redisAdapter->flushRedis();
} elseif ($adapter === 'apc') {
$apcAdapter = new Prometheus\Storage\APC();
$apcAdapter->flushAPC();
} elseif ($adapter === 'in-memory') {
$inMemoryAdapter = new Prometheus\Storage\InMemory();
$inMemoryAdapter->flushMemory();
}
================================================
FILE: examples/metrics.php
================================================
<?php
require __DIR__ . '/../vendor/autoload.php';
use Prometheus\CollectorRegistry;
use Prometheus\RenderTextFormat;
use Prometheus\Storage\Redis;
$adapter = $_GET['adapter'];
if ($adapter === 'redis') {
Redis::setDefaultOptions(['host' => $_SERVER['REDIS_HOST'] ?? '127.0.0.1']);
$adapter = new Prometheus\Storage\Redis();
} elseif ($adapter === 'apc') {
$adapter = new Prometheus\Storage\APC();
} elseif ($adapter === 'in-memory') {
$adapter = new Prometheus\Storage\InMemory();
}
$registry = new CollectorRegistry($adapter);
$renderer = new RenderTextFormat();
$result = $renderer->render($registry->getMetricFamilySamples());
header('Content-type: ' . RenderTextFormat::MIME_TYPE);
echo $result;
================================================
FILE: examples/pushgateway.php
================================================
<?php
require __DIR__ . '/../vendor/autoload.php';
use Prometheus\CollectorRegistry;
use Prometheus\PushGateway;
use Prometheus\Storage\Redis;
$adapter = $_GET['adapter'];
if ($adapter === 'redis') {
Redis::setDefaultOptions(['host' => $_SERVER['REDIS_HOST'] ?? '127.0.0.1']);
$adapter = new Prometheus\Storage\Redis();
} elseif ($adapter === 'apc') {
$adapter = new Prometheus\Storage\APC();
} elseif ($adapter === 'in-memory') {
$adapter = new Prometheus\Storage\InMemory();
}
$registry = new CollectorRegistry($adapter);
$counter = $registry->registerCounter('test', 'some_counter', 'it increases', ['type']);
$counter->incBy(6, ['blue']);
$pushGateway = new PushGateway('192.168.59.100:9091');
$pushGateway->push($registry, 'my_job', ['instance' => 'foo']);
================================================
FILE: examples/some_counter.php
================================================
<?php
require __DIR__ . '/../vendor/autoload.php';
use Prometheus\CollectorRegistry;
use Prometheus\Storage\Redis;
$adapter = $_GET['adapter'];
if ($adapter === 'redis') {
Redis::setDefaultOptions(['host' => $_SERVER['REDIS_HOST'] ?? '127.0.0.1']);
$adapter = new Prometheus\Storage\Redis();
} elseif ($adapter === 'apc') {
$adapter = new Prometheus\Storage\APC();
} elseif ($adapter === 'in-memory') {
$adapter = new Prometheus\Storage\InMemory();
}
$registry = new CollectorRegistry($adapter);
$counter = $registry->registerCounter('test', 'some_counter', 'it increases', ['type']);
$counter->incBy($_GET['c'], ['blue']);
echo "OK\n";
================================================
FILE: examples/some_gauge.php
================================================
<?php
require __DIR__ . '/../vendor/autoload.php';
use Prometheus\CollectorRegistry;
use Prometheus\Storage\Redis;
error_log('c=' . $_GET['c']);
$adapter = $_GET['adapter'];
if ($adapter === 'redis') {
Redis::setDefaultOptions(['host' => $_SERVER['REDIS_HOST'] ?? '127.0.0.1']);
$adapter = new Prometheus\Storage\Redis();
} elseif ($adapter === 'apc') {
$adapter = new Prometheus\Storage\APC();
} elseif ($adapter === 'in-memory') {
$adapter = new Prometheus\Storage\InMemory();
}
$registry = new CollectorRegistry($adapter);
$gauge = $registry->registerGauge('test', 'some_gauge', 'it sets', ['type']);
$gauge->set($_GET['c'], ['blue']);
echo "OK\n";
================================================
FILE: examples/some_histogram.php
================================================
<?php
require __DIR__ . '/../vendor/autoload.php';
use Prometheus\CollectorRegistry;
use Prometheus\Storage\Redis;
error_log('c=' . $_GET['c']);
$adapter = $_GET['adapter'];
if ($adapter === 'redis') {
Redis::setDefaultOptions(['host' => $_SERVER['REDIS_HOST'] ?? '127.0.0.1']);
$adapter = new Prometheus\Storage\Redis();
} elseif ($adapter === 'apc') {
$adapter = new Prometheus\Storage\APC();
} elseif ($adapter === 'in-memory') {
$adapter = new Prometheus\Storage\InMemory();
}
$registry = new CollectorRegistry($adapter);
$histogram = $registry->registerHistogram('test', 'some_histogram', 'it observes', ['type'], [0.1, 1, 2, 3.5, 4, 5, 6, 7, 8, 9]);
$histogram->observe($_GET['c'], ['blue']);
echo "OK\n";
================================================
FILE: nginx/Dockerfile
================================================
FROM nginx
RUN rm /etc/nginx/conf.d/default.conf /etc/nginx/nginx.conf
COPY nginx.conf /etc/nginx/
COPY default.conf /etc/nginx/conf.d/
================================================
FILE: nginx/default.conf
================================================
server {
listen 80;
root /var/www/html;
error_log /var/log/nginx/localhost.error.log;
access_log /var/log/nginx/localhost.access.log;
location ~ ^/.+\.php(/|$) {
fastcgi_pass php-fpm:9000;
fastcgi_split_path_info ^(.+\.php)(/.*)$;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param HTTPS off;
}
}
================================================
FILE: nginx/nginx.conf
================================================
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 4096;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
include /etc/nginx/conf.d/*.conf;
}
================================================
FILE: php-fpm/Dockerfile
================================================
FROM php:5.6-fpm
RUN pecl install redis-2.2.8 && docker-php-ext-enable redis
RUN pecl install apcu-4.0.11 && docker-php-ext-enable apcu
COPY www.conf /usr/local/etc/php-fpm.d/
COPY docker-php-ext-apcu-cli.ini /usr/local/etc/php/conf.d/
================================================
FILE: php-fpm/docker-php-ext-apcu-cli.ini
================================================
apc.enable_cli = On
================================================
FILE: php-fpm/www.conf
================================================
[www]
user = www-data
group = www-data
listen = 127.0.0.1:9000
pm = static
pm.max_children = 20
================================================
FILE: phpcs.xml.dist
================================================
<?xml version="1.0"?>
<ruleset name="PHP_CodeSniffer">
<file>src/</file>
<file>tests/</file>
<file>examples/</file>
<exclude-pattern type="relative">vendor/*</exclude-pattern>
<arg value="nps"/>
<rule ref="PSR12"/>
</ruleset>
================================================
FILE: phpunit.xml.dist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
bootstrap="tests/bootstrap.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnError="false"
stopOnFailure="false">
<testsuites>
<testsuite name="Unit">
<directory>./tests/Test/Prometheus</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./src/Prometheus</directory>
</whitelist>
</filter>
</phpunit>
================================================
FILE: src/Prometheus/Collector.php
================================================
<?php
declare(strict_types=1);
namespace Prometheus;
use InvalidArgumentException;
use Prometheus\Storage\Adapter;
abstract class Collector
{
const RE_METRIC_LABEL_NAME = '/^[a-zA-Z_:][a-zA-Z0-9_:]*$/';
/**
* @var Adapter
*/
protected $storageAdapter;
/**
* @var string
*/
protected $name;
/**
* @var string
*/
protected $help;
/**
* @var array
*/
protected $labels;
/**
* @param Adapter $storageAdapter
* @param string $namespace
* @param string $name
* @param string $help
* @param array $labels
*/
public function __construct(Adapter $storageAdapter, $namespace, $name, $help, $labels = [])
{
$this->storageAdapter = $storageAdapter;
$metricName = ($namespace ? $namespace . '_' : '') . $name;
if (!preg_match(self::RE_METRIC_LABEL_NAME, $metricName)) {
throw new InvalidArgumentException("Invalid metric name: '" . $metricName . "'");
}
$this->name = $metricName;
$this->help = $help;
foreach ($labels as $label) {
if (!preg_match(self::RE_METRIC_LABEL_NAME, $label)) {
throw new InvalidArgumentException("Invalid label name: '" . $label . "'");
}
}
$this->labels = $labels;
}
/**
* @return string
*/
abstract public function getType();
/**
* @return string
*/
public function getName(): string
{
return $this->name;
}
/**
* @return array
*/
public function getLabelNames(): array
{
return $this->labels;
}
/**
* @return string
*/
public function getHelp(): string
{
return $this->help;
}
/**
* @return string
*/
public function getKey(): string
{
return sha1($this->getName() . serialize($this->getLabelNames()));
}
/**
* @param $labels
*/
protected function assertLabelsAreDefinedCorrectly($labels): void
{
if (count($labels) != count($this->labels)) {
throw new InvalidArgumentException(sprintf('Labels are not defined correctly: %s', print_r($labels, true)));
}
}
}
================================================
FILE: src/Prometheus/CollectorRegistry.php
================================================
<?php
declare(strict_types=1);
namespace Prometheus;
use Prometheus\Exception\MetricNotFoundException;
use Prometheus\Exception\MetricsRegistrationException;
use Prometheus\Storage\Adapter;
use Prometheus\Storage\Redis;
class CollectorRegistry
{
/**
* @var CollectorRegistry
*/
private static $defaultRegistry;
/**
* @var Adapter
*/
private $storageAdapter;
/**
* @var Gauge[]
*/
private $gauges = [];
/**
* @var Counter[]
*/
private $counters = [];
/**
* @var Histogram[]
*/
private $histograms = [];
/**
* CollectorRegistry constructor.
* @param Adapter $redisAdapter
*/
public function __construct(Adapter $redisAdapter)
{
$this->storageAdapter = $redisAdapter;
}
/**
* @return CollectorRegistry
*/
public static function getDefault(): CollectorRegistry
{
if (!self::$defaultRegistry) {
return self::$defaultRegistry = new static(new Redis());
}
return self::$defaultRegistry;
}
/**
* @return MetricFamilySamples[]
*/
public function getMetricFamilySamples(): array
{
return $this->storageAdapter->collect();
}
/**
* @param string $namespace e.g. cms
* @param string $name e.g. duration_seconds
* @param string $help e.g. The duration something took in seconds.
* @param array $labels e.g. ['controller', 'action']
* @return Gauge
* @throws MetricsRegistrationException
*/
public function registerGauge($namespace, $name, $help, $labels = []): Gauge
{
$metricIdentifier = self::metricIdentifier($namespace, $name);
if (isset($this->gauges[$metricIdentifier])) {
throw new MetricsRegistrationException("Metric already registered");
}
$this->gauges[$metricIdentifier] = new Gauge(
$this->storageAdapter,
$namespace,
$name,
$help,
$labels
);
return $this->gauges[$metricIdentifier];
}
/**
* @param string $namespace
* @param string $name
* @return Gauge
* @throws MetricNotFoundException
*/
public function getGauge($namespace, $name): Gauge
{
$metricIdentifier = self::metricIdentifier($namespace, $name);
if (!isset($this->gauges[$metricIdentifier])) {
throw new MetricNotFoundException("Metric not found:" . $metricIdentifier);
}
return $this->gauges[$metricIdentifier];
}
/**
* @param string $namespace e.g. cms
* @param string $name e.g. duration_seconds
* @param string $help e.g. The duration something took in seconds.
* @param array $labels e.g. ['controller', 'action']
* @return Gauge
* @throws MetricsRegistrationException
*/
public function getOrRegisterGauge($namespace, $name, $help, $labels = []): Gauge
{
try {
$gauge = $this->getGauge($namespace, $name);
} catch (MetricNotFoundException $e) {
$gauge = $this->registerGauge($namespace, $name, $help, $labels);
}
return $gauge;
}
/**
* @param string $namespace e.g. cms
* @param string $name e.g. requests
* @param string $help e.g. The number of requests made.
* @param array $labels e.g. ['controller', 'action']
* @return Counter
* @throws MetricsRegistrationException
*/
public function registerCounter($namespace, $name, $help, $labels = []): Counter
{
$metricIdentifier = self::metricIdentifier($namespace, $name);
if (isset($this->counters[$metricIdentifier])) {
throw new MetricsRegistrationException("Metric already registered");
}
$this->counters[$metricIdentifier] = new Counter(
$this->storageAdapter,
$namespace,
$name,
$help,
$labels
);
return $this->counters[self::metricIdentifier($namespace, $name)];
}
/**
* @param string $namespace
* @param string $name
* @return Counter
* @throws MetricNotFoundException
*/
public function getCounter($namespace, $name): Counter
{
$metricIdentifier = self::metricIdentifier($namespace, $name);
if (!isset($this->counters[$metricIdentifier])) {
throw new MetricNotFoundException("Metric not found:" . $metricIdentifier);
}
return $this->counters[self::metricIdentifier($namespace, $name)];
}
/**
* @param string $namespace e.g. cms
* @param string $name e.g. requests
* @param string $help e.g. The number of requests made.
* @param array $labels e.g. ['controller', 'action']
* @return Counter
* @throws MetricsRegistrationException
*/
public function getOrRegisterCounter($namespace, $name, $help, $labels = []): Counter
{
try {
$counter = $this->getCounter($namespace, $name);
} catch (MetricNotFoundException $e) {
$counter = $this->registerCounter($namespace, $name, $help, $labels);
}
return $counter;
}
/**
* @param string $namespace e.g. cms
* @param string $name e.g. duration_seconds
* @param string $help e.g. A histogram of the duration in seconds.
* @param array $labels e.g. ['controller', 'action']
* @param array $buckets e.g. [100, 200, 300]
* @return Histogram
* @throws MetricsRegistrationException
*/
public function registerHistogram($namespace, $name, $help, $labels = [], $buckets = null): Histogram
{
$metricIdentifier = self::metricIdentifier($namespace, $name);
if (isset($this->histograms[$metricIdentifier])) {
throw new MetricsRegistrationException("Metric already registered");
}
$this->histograms[$metricIdentifier] = new Histogram(
$this->storageAdapter,
$namespace,
$name,
$help,
$labels,
$buckets
);
return $this->histograms[$metricIdentifier];
}
/**
* @param string $namespace
* @param string $name
* @return Histogram
* @throws MetricNotFoundException
*/
public function getHistogram($namespace, $name): Histogram
{
$metricIdentifier = self::metricIdentifier($namespace, $name);
if (!isset($this->histograms[$metricIdentifier])) {
throw new MetricNotFoundException("Metric not found:" . $metricIdentifier);
}
return $this->histograms[self::metricIdentifier($namespace, $name)];
}
/**
* @param string $namespace e.g. cms
* @param string $name e.g. duration_seconds
* @param string $help e.g. A histogram of the duration in seconds.
* @param array $labels e.g. ['controller', 'action']
* @param array $buckets e.g. [100, 200, 300]
* @return Histogram
* @throws MetricsRegistrationException
*/
public function getOrRegisterHistogram($namespace, $name, $help, $labels = [], $buckets = null): Histogram
{
try {
$histogram = $this->getHistogram($namespace, $name);
} catch (MetricNotFoundException $e) {
$histogram = $this->registerHistogram($namespace, $name, $help, $labels, $buckets);
}
return $histogram;
}
/**
* @param $namespace
* @param $name
* @return string
*/
private static function metricIdentifier($namespace, $name): string
{
return $namespace . ":" . $name;
}
}
================================================
FILE: src/Prometheus/Counter.php
================================================
<?php
declare(strict_types=1);
namespace Prometheus;
use Prometheus\Storage\Adapter;
class Counter extends Collector
{
const TYPE = 'counter';
/**
* @return string
*/
public function getType(): string
{
return self::TYPE;
}
/**
* @param array $labels e.g. ['status', 'opcode']
*/
public function inc(array $labels = []): void
{
$this->incBy(1, $labels);
}
/**
* @param int $count e.g. 2
* @param array $labels e.g. ['status', 'opcode']
*/
public function incBy($count, array $labels = []): void
{
$this->assertLabelsAreDefinedCorrectly($labels);
$this->storageAdapter->updateCounter(
[
'name' => $this->getName(),
'help' => $this->getHelp(),
'type' => $this->getType(),
'labelNames' => $this->getLabelNames(),
'labelValues' => $labels,
'value' => $count,
'command' => Adapter::COMMAND_INCREMENT_INTEGER,
]
);
}
}
================================================
FILE: src/Prometheus/Exception/MetricNotFoundException.php
================================================
<?php
namespace Prometheus\Exception;
use Exception;
/**
* Exception thrown if a metric can't be found in the CollectorRegistry.
*/
class MetricNotFoundException extends Exception
{
}
================================================
FILE: src/Prometheus/Exception/MetricsRegistrationException.php
================================================
<?php
namespace Prometheus\Exception;
use Exception;
/**
* Exception thrown if an error occurs during metrics registration.
*/
class MetricsRegistrationException extends Exception
{
}
================================================
FILE: src/Prometheus/Exception/StorageException.php
================================================
<?php
namespace Prometheus\Exception;
use Exception;
/**
* Exception thrown if an error occurs during metrics storage.
*/
class StorageException extends Exception
{
}
================================================
FILE: src/Prometheus/Gauge.php
================================================
<?php
declare(strict_types=1);
namespace Prometheus;
use Prometheus\Storage\Adapter;
class Gauge extends Collector
{
const TYPE = 'gauge';
/**
* @param double $value e.g. 123
* @param array $labels e.g. ['status', 'opcode']
*/
public function set(float $value, array $labels = []): void
{
$this->assertLabelsAreDefinedCorrectly($labels);
$this->storageAdapter->updateGauge(
[
'name' => $this->getName(),
'help' => $this->getHelp(),
'type' => $this->getType(),
'labelNames' => $this->getLabelNames(),
'labelValues' => $labels,
'value' => $value,
'command' => Adapter::COMMAND_SET,
]
);
}
/**
* @return string
*/
public function getType(): string
{
return self::TYPE;
}
/**
* @param array $labels
*/
public function inc($labels = []): void
{
$this->incBy(1, $labels);
}
/**
* @param $value
* @param array $labels
*/
public function incBy($value, array $labels = []): void
{
$this->assertLabelsAreDefinedCorrectly($labels);
$this->storageAdapter->updateGauge(
[
'name' => $this->getName(),
'help' => $this->getHelp(),
'type' => $this->getType(),
'labelNames' => $this->getLabelNames(),
'labelValues' => $labels,
'value' => $value,
'command' => Adapter::COMMAND_INCREMENT_FLOAT,
]
);
}
/**
* @param array $labels
*/
public function dec($labels = []): void
{
$this->decBy(1, $labels);
}
/**
* @param $value
* @param array $labels
*/
public function decBy($value, $labels = []): void
{
$this->incBy(-$value, $labels);
}
}
================================================
FILE: src/Prometheus/Histogram.php
================================================
<?php
declare(strict_types=1);
namespace Prometheus;
use InvalidArgumentException;
use Prometheus\Storage\Adapter;
class Histogram extends Collector
{
const TYPE = 'histogram';
/**
* @var array|null
*/
private $buckets;
/**
* @param Adapter $adapter
* @param string $namespace
* @param string $name
* @param string $help
* @param array $labels
* @param array $buckets
*/
public function __construct(Adapter $adapter, $namespace, $name, $help, $labels = [], $buckets = null)
{
parent::__construct($adapter, $namespace, $name, $help, $labels);
if (null === $buckets) {
$buckets = self::getDefaultBuckets();
}
if (0 == count($buckets)) {
throw new InvalidArgumentException("Histogram must have at least one bucket.");
}
for ($i = 0; $i < count($buckets) - 1; $i++) {
if ($buckets[$i] >= $buckets[$i + 1]) {
throw new InvalidArgumentException(
"Histogram buckets must be in increasing order: " .
$buckets[$i] . " >= " . $buckets[$i + 1]
);
}
}
if (in_array('le', $labels, true)) {
throw new \InvalidArgumentException("Histogram cannot have a label named 'le'.");
}
$this->buckets = $buckets;
}
/**
* List of default buckets suitable for typical web application latency metrics
* @return array
*/
public static function getDefaultBuckets(): array
{
return [
0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1.0, 2.5, 5.0, 7.5, 10.0,
];
}
/**
* @param float $start
* @param float $growthFactor
* @param int $numberOfBuckets
* @return array
*/
public static function exponentialBuckets(float $start, float $growthFactor, int $numberOfBuckets): array
{
if ($numberOfBuckets < 1) {
throw new InvalidArgumentException('Number of buckets must be a positive integer');
}
if ($start <= 0) {
throw new InvalidArgumentException('The starting position of a set of buckets must be a positive integer');
}
if ($growthFactor <= 1) {
throw new InvalidArgumentException('The growth factor must greater than 1');
}
$buckets = [];
for ($i = 0; $i < $numberOfBuckets; $i++) {
$buckets[$i] = $start;
$start *= $growthFactor;
}
return $buckets;
}
/**
* @param double $value e.g. 123
* @param array $labels e.g. ['status', 'opcode']
*/
public function observe(float $value, array $labels = []): void
{
$this->assertLabelsAreDefinedCorrectly($labels);
$this->storageAdapter->updateHistogram(
[
'value' => $value,
'name' => $this->getName(),
'help' => $this->getHelp(),
'type' => $this->getType(),
'labelNames' => $this->getLabelNames(),
'labelValues' => $labels,
'buckets' => $this->buckets,
]
);
}
/**
* @return string
*/
public function getType(): string
{
return self::TYPE;
}
}
================================================
FILE: src/Prometheus/MetricFamilySamples.php
================================================
<?php
declare(strict_types=1);
namespace Prometheus;
class MetricFamilySamples
{
/**
* @var mixed
*/
private $name;
/**
* @var string
*/
private $type;
/**
* @var string
*/
private $help;
/**
* @var array
*/
private $labelNames;
/**
* @var array
*/
private $samples = [];
/**
* @param array $data
*/
public function __construct(array $data)
{
$this->name = $data['name'];
$this->type = $data['type'];
$this->help = $data['help'];
$this->labelNames = $data['labelNames'];
foreach ($data['samples'] as $sampleData) {
$this->samples[] = new Sample($sampleData);
}
}
/**
* @return string
*/
public function getName(): string
{
return $this->name;
}
/**
* @return string
*/
public function getType(): string
{
return $this->type;
}
/**
* @return string
*/
public function getHelp(): string
{
return $this->help;
}
/**
* @return Sample[]
*/
public function getSamples(): array
{
return $this->samples;
}
/**
* @return array
*/
public function getLabelNames(): array
{
return $this->labelNames;
}
/**
* @return bool
*/
public function hasLabelNames(): bool
{
return !empty($this->labelNames);
}
}
================================================
FILE: src/Prometheus/PushGateway.php
================================================
<?php
declare(strict_types=1);
namespace Prometheus;
use GuzzleHttp\Client;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Exception\GuzzleException;
use RuntimeException;
class PushGateway
{
/**
* @var string
*/
private $address;
/**
* @var ClientInterface
*/
private $client;
/**
* PushGateway constructor.
* @param string $address host:port of the push gateway
* @param ClientInterface $client
*/
public function __construct($address, ClientInterface $client = null)
{
$this->address = $address;
$this->client = $client ?? new Client();
}
/**
* Pushes all metrics in a Collector, replacing all those with the same job.
* Uses HTTP PUT.
* @param CollectorRegistry $collectorRegistry
* @param string $job
* @param array $groupingKey
* @throws GuzzleException
*/
public function push(CollectorRegistry $collectorRegistry, string $job, array $groupingKey = []): void
{
$this->doRequest($collectorRegistry, $job, $groupingKey, 'put');
}
/**
* Pushes all metrics in a Collector, replacing only previously pushed metrics of the same name and job.
* Uses HTTP POST.
* @param CollectorRegistry $collectorRegistry
* @param $job
* @param $groupingKey
* @throws GuzzleException
*/
public function pushAdd(CollectorRegistry $collectorRegistry, string $job, array $groupingKey = []): void
{
$this->doRequest($collectorRegistry, $job, $groupingKey, 'post');
}
/**
* Deletes metrics from the Push Gateway.
* Uses HTTP POST.
* @param string $job
* @param array $groupingKey
* @throws GuzzleException
*/
public function delete(string $job, array $groupingKey = []): void
{
$this->doRequest(null, $job, $groupingKey, 'delete');
}
/**
* @param CollectorRegistry $collectorRegistry
* @param string $job
* @param array $groupingKey
* @param string $method
* @throws GuzzleException
*/
private function doRequest(CollectorRegistry $collectorRegistry, string $job, array $groupingKey, $method): void
{
$url = "http://" . $this->address . "/metrics/job/" . $job;
if (!empty($groupingKey)) {
foreach ($groupingKey as $label => $value) {
$url .= "/" . $label . "/" . $value;
}
}
$requestOptions = [
'headers' => [
'Content-Type' => RenderTextFormat::MIME_TYPE,
],
'connect_timeout' => 10,
'timeout' => 20,
];
if ($method != 'delete') {
$renderer = new RenderTextFormat();
$requestOptions['body'] = $renderer->render($collectorRegistry->getMetricFamilySamples());
}
$response = $this->client->request($method, $url, $requestOptions);
$statusCode = $response->getStatusCode();
if (!in_array($statusCode, [200, 202])) {
$msg = "Unexpected status code "
. $statusCode
. " received from push gateway "
. $this->address . ": " . $response->getBody();
throw new RuntimeException($msg);
}
}
}
================================================
FILE: src/Prometheus/RenderTextFormat.php
================================================
<?php
declare(strict_types=1);
namespace Prometheus;
class RenderTextFormat
{
const MIME_TYPE = 'text/plain; version=0.0.4';
/**
* @param MetricFamilySamples[] $metrics
* @return string
*/
public function render(array $metrics): string
{
usort($metrics, function (MetricFamilySamples $a, MetricFamilySamples $b) {
return strcmp($a->getName(), $b->getName());
});
$lines = [];
foreach ($metrics as $metric) {
$lines[] = "# HELP " . $metric->getName() . " {$metric->getHelp()}";
$lines[] = "# TYPE " . $metric->getName() . " {$metric->getType()}";
foreach ($metric->getSamples() as $sample) {
$lines[] = $this->renderSample($metric, $sample);
}
}
return implode("\n", $lines) . "\n";
}
/**
* @param MetricFamilySamples $metric
* @param Sample $sample
* @return string
*/
private function renderSample(MetricFamilySamples $metric, Sample $sample): string
{
$escapedLabels = [];
$labelNames = $metric->getLabelNames();
if ($metric->hasLabelNames() || $sample->hasLabelNames()) {
$labels = array_combine(array_merge($labelNames, $sample->getLabelNames()), $sample->getLabelValues());
foreach ($labels as $labelName => $labelValue) {
$escapedLabels[] = $labelName . '="' . $this->escapeLabelValue($labelValue) . '"';
}
return $sample->getName() . '{' . implode(',', $escapedLabels) . '} ' . $sample->getValue();
}
return $sample->getName() . ' ' . $sample->getValue();
}
/**
* @param string $v
* @return string
*/
private function escapeLabelValue($v): string
{
$v = str_replace("\\", "\\\\", $v);
$v = str_replace("\n", "\\n", $v);
$v = str_replace("\"", "\\\"", $v);
return $v;
}
}
================================================
FILE: src/Prometheus/Sample.php
================================================
<?php
declare(strict_types=1);
namespace Prometheus;
class Sample
{
/**
* @var string
*/
private $name;
/**
* @var array
*/
private $labelNames;
/**
* @var array
*/
private $labelValues;
/**
* @var int|double
*/
private $value;
/**
* Sample constructor.
* @param array $data
*/
public function __construct(array $data)
{
$this->name = $data['name'];
$this->labelNames = $data['labelNames'];
$this->labelValues = $data['labelValues'];
$this->value = $data['value'];
}
/**
* @return string
*/
public function getName(): string
{
return $this->name;
}
/**
* @return array
*/
public function getLabelNames(): array
{
return (array)$this->labelNames;
}
/**
* @return array
*/
public function getLabelValues(): array
{
return (array)$this->labelValues;
}
/**
* @return int|double
*/
public function getValue(): string
{
return (string) $this->value;
}
/**
* @return bool
*/
public function hasLabelNames(): bool
{
return !empty($this->labelNames);
}
}
================================================
FILE: src/Prometheus/Storage/APC.php
================================================
<?php
declare(strict_types=1);
namespace Prometheus\Storage;
use APCUIterator;
use Prometheus\MetricFamilySamples;
use RuntimeException;
class APC implements Adapter
{
const PROMETHEUS_PREFIX = 'prom';
/**
* @return MetricFamilySamples[]
*/
public function collect(): array
{
$metrics = $this->collectHistograms();
$metrics = array_merge($metrics, $this->collectGauges());
$metrics = array_merge($metrics, $this->collectCounters());
return $metrics;
}
/**
* @param array $data
*/
public function updateHistogram(array $data): void
{
// Initialize the sum
$sumKey = $this->histogramBucketValueKey($data, 'sum');
$new = apcu_add($sumKey, $this->toInteger(0));
// If sum does not exist, assume a new histogram and store the metadata
if ($new) {
apcu_store($this->metaKey($data), json_encode($this->metaData($data)));
}
// Atomically increment the sum
// Taken from https://github.com/prometheus/client_golang/blob/66058aac3a83021948e5fb12f1f408ff556b9037/prometheus/value.go#L91
$done = false;
while (!$done) {
$old = apcu_fetch($sumKey);
$done = apcu_cas($sumKey, $old, $this->toInteger($this->fromInteger($old) + $data['value']));
}
// Figure out in which bucket the observation belongs
$bucketToIncrease = '+Inf';
foreach ($data['buckets'] as $bucket) {
if ($data['value'] <= $bucket) {
$bucketToIncrease = $bucket;
break;
}
}
// Initialize and increment the bucket
apcu_add($this->histogramBucketValueKey($data, $bucketToIncrease), 0);
apcu_inc($this->histogramBucketValueKey($data, $bucketToIncrease));
}
/**
* @param array $data
*/
public function updateGauge(array $data): void
{
$valueKey = $this->valueKey($data);
if ($data['command'] == Adapter::COMMAND_SET) {
apcu_store($valueKey, $this->toInteger($data['value']));
apcu_store($this->metaKey($data), json_encode($this->metaData($data)));
} else {
$new = apcu_add($valueKey, $this->toInteger(0));
if ($new) {
apcu_store($this->metaKey($data), json_encode($this->metaData($data)));
}
// Taken from https://github.com/prometheus/client_golang/blob/66058aac3a83021948e5fb12f1f408ff556b9037/prometheus/value.go#L91
$done = false;
while (!$done) {
$old = apcu_fetch($valueKey);
$done = apcu_cas($valueKey, $old, $this->toInteger($this->fromInteger($old) + $data['value']));
}
}
}
/**
* @param array $data
*/
public function updateCounter(array $data): void
{
$new = apcu_add($this->valueKey($data), 0);
if ($new) {
apcu_store($this->metaKey($data), json_encode($this->metaData($data)));
}
apcu_inc($this->valueKey($data), $data['value']);
}
/**
* @return void
*/
public function flushAPC(): void
{
apcu_clear_cache();
}
/**
* @param array $data
* @return string
*/
private function metaKey(array $data): string
{
return implode(':', [self::PROMETHEUS_PREFIX, $data['type'], $data['name'], 'meta']);
}
/**
* @param array $data
* @return string
*/
private function valueKey(array $data): string
{
return implode(':', [
self::PROMETHEUS_PREFIX,
$data['type'],
$data['name'],
$this->encodeLabelValues($data['labelValues']),
'value',
]);
}
/**
* @param array $data
* @return string
*/
private function histogramBucketValueKey(array $data, $bucket): string
{
return implode(':', [
self::PROMETHEUS_PREFIX,
$data['type'],
$data['name'],
$this->encodeLabelValues($data['labelValues']),
$bucket,
'value',
]);
}
/**
* @param array $data
* @return array
*/
private function metaData(array $data): array
{
$metricsMetaData = $data;
unset($metricsMetaData['value']);
unset($metricsMetaData['command']);
unset($metricsMetaData['labelValues']);
return $metricsMetaData;
}
/**
* @return array
*/
private function collectCounters(): array
{
$counters = [];
foreach (new APCUIterator('/^prom:counter:.*:meta/') as $counter) {
$metaData = json_decode($counter['value'], true);
$data = [
'name' => $metaData['name'],
'help' => $metaData['help'],
'type' => $metaData['type'],
'labelNames' => $metaData['labelNames'],
];
foreach (new APCUIterator('/^prom:counter:' . $metaData['name'] . ':.*:value/') as $value) {
$parts = explode(':', $value['key']);
$labelValues = $parts[3];
$data['samples'][] = [
'name' => $metaData['name'],
'labelNames' => [],
'labelValues' => $this->decodeLabelValues($labelValues),
'value' => $value['value'],
];
}
$this->sortSamples($data['samples']);
$counters[] = new MetricFamilySamples($data);
}
return $counters;
}
/**
* @return array
*/
private function collectGauges(): array
{
$gauges = [];
foreach (new APCUIterator('/^prom:gauge:.*:meta/') as $gauge) {
$metaData = json_decode($gauge['value'], true);
$data = [
'name' => $metaData['name'],
'help' => $metaData['help'],
'type' => $metaData['type'],
'labelNames' => $metaData['labelNames'],
];
foreach (new APCUIterator('/^prom:gauge:' . $metaData['name'] . ':.*:value/') as $value) {
$parts = explode(':', $value['key']);
$labelValues = $parts[3];
$data['samples'][] = [
'name' => $metaData['name'],
'labelNames' => [],
'labelValues' => $this->decodeLabelValues($labelValues),
'value' => $this->fromInteger($value['value']),
];
}
$this->sortSamples($data['samples']);
$gauges[] = new MetricFamilySamples($data);
}
return $gauges;
}
/**
* @return array
*/
private function collectHistograms(): array
{
$histograms = [];
foreach (new APCUIterator('/^prom:histogram:.*:meta/') as $histogram) {
$metaData = json_decode($histogram['value'], true);
$data = [
'name' => $metaData['name'],
'help' => $metaData['help'],
'type' => $metaData['type'],
'labelNames' => $metaData['labelNames'],
'buckets' => $metaData['buckets'],
];
// Add the Inf bucket so we can compute it later on
$data['buckets'][] = '+Inf';
$histogramBuckets = [];
foreach (new APCUIterator('/^prom:histogram:' . $metaData['name'] . ':.*:value/') as $value) {
$parts = explode(':', $value['key']);
$labelValues = $parts[3];
$bucket = $parts[4];
// Key by labelValues
$histogramBuckets[$labelValues][$bucket] = $value['value'];
}
// Compute all buckets
$labels = array_keys($histogramBuckets);
sort($labels);
foreach ($labels as $labelValues) {
$acc = 0;
$decodedLabelValues = $this->decodeLabelValues($labelValues);
foreach ($data['buckets'] as $bucket) {
$bucket = (string)$bucket;
if (!isset($histogramBuckets[$labelValues][$bucket])) {
$data['samples'][] = [
'name' => $metaData['name'] . '_bucket',
'labelNames' => ['le'],
'labelValues' => array_merge($decodedLabelValues, [$bucket]),
'value' => $acc,
];
} else {
$acc += $histogramBuckets[$labelValues][$bucket];
$data['samples'][] = [
'name' => $metaData['name'] . '_' . 'bucket',
'labelNames' => ['le'],
'labelValues' => array_merge($decodedLabelValues, [$bucket]),
'value' => $acc,
];
}
}
// Add the count
$data['samples'][] = [
'name' => $metaData['name'] . '_count',
'labelNames' => [],
'labelValues' => $decodedLabelValues,
'value' => $acc,
];
// Add the sum
$data['samples'][] = [
'name' => $metaData['name'] . '_sum',
'labelNames' => [],
'labelValues' => $decodedLabelValues,
'value' => $this->fromInteger($histogramBuckets[$labelValues]['sum']),
];
}
$histograms[] = new MetricFamilySamples($data);
}
return $histograms;
}
/**
* @param mixed $val
* @return int
*/
private function toInteger($val): int
{
return unpack('Q', pack('d', $val))[1];
}
/**
* @param mixed $val
* @return float
*/
private function fromInteger($val): float
{
return unpack('d', pack('Q', $val))[1];
}
/**
* @param array $samples
*/
private function sortSamples(array &$samples): void
{
usort($samples, function ($a, $b) {
return strcmp(implode("", $a['labelValues']), implode("", $b['labelValues']));
});
}
/**
* @param array $values
* @return string
* @throws RuntimeException
*/
private function encodeLabelValues(array $values): string
{
$json = json_encode($values);
if (false === $json) {
throw new RuntimeException(json_last_error_msg());
}
return base64_encode($json);
}
/**
* @param string $values
* @return array
* @throws RuntimeException
*/
private function decodeLabelValues($values): array
{
$json = base64_decode($values, true);
if (false === $json) {
throw new RuntimeException('Cannot base64 decode label values');
}
$decodedValues = json_decode($json, true);
if (false === $decodedValues) {
throw new RuntimeException(json_last_error_msg());
}
return $decodedValues;
}
}
================================================
FILE: src/Prometheus/Storage/Adapter.php
================================================
<?php
declare(strict_types=1);
namespace Prometheus\Storage;
use Prometheus\MetricFamilySamples;
interface Adapter
{
const COMMAND_INCREMENT_INTEGER = 1;
const COMMAND_INCREMENT_FLOAT = 2;
const COMMAND_SET = 3;
/**
* @return MetricFamilySamples[]
*/
public function collect();
/**
* @param array $data
* @return void
*/
public function updateHistogram(array $data): void;
/**
* @param array $data
* @return void
*/
public function updateGauge(array $data): void;
/**
* @param array $data
* @return void
*/
public function updateCounter(array $data): void;
}
================================================
FILE: src/Prometheus/Storage/InMemory.php
================================================
<?php
declare(strict_types=1);
namespace Prometheus\Storage;
use Prometheus\MetricFamilySamples;
use RuntimeException;
class InMemory implements Adapter
{
private $counters = [];
private $gauges = [];
private $histograms = [];
/**
* @return MetricFamilySamples[]
*/
public function collect(): array
{
$metrics = $this->internalCollect($this->counters);
$metrics = array_merge($metrics, $this->internalCollect($this->gauges));
$metrics = array_merge($metrics, $this->collectHistograms());
return $metrics;
}
public function flushMemory(): void
{
$this->counters = [];
$this->gauges = [];
$this->histograms = [];
}
/**
* @return array
*/
private function collectHistograms(): array
{
$histograms = [];
foreach ($this->histograms as $histogram) {
$metaData = $histogram['meta'];
$data = [
'name' => $metaData['name'],
'help' => $metaData['help'],
'type' => $metaData['type'],
'labelNames' => $metaData['labelNames'],
'buckets' => $metaData['buckets'],
];
// Add the Inf bucket so we can compute it later on
$data['buckets'][] = '+Inf';
$histogramBuckets = [];
foreach ($histogram['samples'] as $key => $value) {
$parts = explode(':', $key);
$labelValues = $parts[2];
$bucket = $parts[3];
// Key by labelValues
$histogramBuckets[$labelValues][$bucket] = $value;
}
// Compute all buckets
$labels = array_keys($histogramBuckets);
sort($labels);
foreach ($labels as $labelValues) {
$acc = 0;
$decodedLabelValues = $this->decodeLabelValues($labelValues);
foreach ($data['buckets'] as $bucket) {
$bucket = (string)$bucket;
if (!isset($histogramBuckets[$labelValues][$bucket])) {
$data['samples'][] = [
'name' => $metaData['name'] . '_bucket',
'labelNames' => ['le'],
'labelValues' => array_merge($decodedLabelValues, [$bucket]),
'value' => $acc,
];
} else {
$acc += $histogramBuckets[$labelValues][$bucket];
$data['samples'][] = [
'name' => $metaData['name'] . '_' . 'bucket',
'labelNames' => ['le'],
'labelValues' => array_merge($decodedLabelValues, [$bucket]),
'value' => $acc,
];
}
}
// Add the count
$data['samples'][] = [
'name' => $metaData['name'] . '_count',
'labelNames' => [],
'labelValues' => $decodedLabelValues,
'value' => $acc,
];
// Add the sum
$data['samples'][] = [
'name' => $metaData['name'] . '_sum',
'labelNames' => [],
'labelValues' => $decodedLabelValues,
'value' => $histogramBuckets[$labelValues]['sum'],
];
}
$histograms[] = new MetricFamilySamples($data);
}
return $histograms;
}
/**
* @param array $metrics
* @return array
*/
private function internalCollect(array $metrics): array
{
$result = [];
foreach ($metrics as $metric) {
$metaData = $metric['meta'];
$data = [
'name' => $metaData['name'],
'help' => $metaData['help'],
'type' => $metaData['type'],
'labelNames' => $metaData['labelNames'],
];
foreach ($metric['samples'] as $key => $value) {
$parts = explode(':', $key);
$labelValues = $parts[2];
$data['samples'][] = [
'name' => $metaData['name'],
'labelNames' => [],
'labelValues' => $this->decodeLabelValues($labelValues),
'value' => $value,
];
}
$this->sortSamples($data['samples']);
$result[] = new MetricFamilySamples($data);
}
return $result;
}
/**
* @param array $data
* @return void
*/
public function updateHistogram(array $data): void
{
// Initialize the sum
$metaKey = $this->metaKey($data);
if (array_key_exists($metaKey, $this->histograms) === false) {
$this->histograms[$metaKey] = [
'meta' => $this->metaData($data),
'samples' => [],
];
}
$sumKey = $this->histogramBucketValueKey($data, 'sum');
if (array_key_exists($sumKey, $this->histograms[$metaKey]['samples']) === false) {
$this->histograms[$metaKey]['samples'][$sumKey] = 0;
}
$this->histograms[$metaKey]['samples'][$sumKey] += $data['value'];
$bucketToIncrease = '+Inf';
foreach ($data['buckets'] as $bucket) {
if ($data['value'] <= $bucket) {
$bucketToIncrease = $bucket;
break;
}
}
$bucketKey = $this->histogramBucketValueKey($data, $bucketToIncrease);
if (array_key_exists($bucketKey, $this->histograms[$metaKey]['samples']) === false) {
$this->histograms[$metaKey]['samples'][$bucketKey] = 0;
}
$this->histograms[$metaKey]['samples'][$bucketKey] += 1;
}
/**
* @param array $data
*/
public function updateGauge(array $data): void
{
$metaKey = $this->metaKey($data);
$valueKey = $this->valueKey($data);
if (array_key_exists($metaKey, $this->gauges) === false) {
$this->gauges[$metaKey] = [
'meta' => $this->metaData($data),
'samples' => [],
];
}
if (array_key_exists($valueKey, $this->gauges[$metaKey]['samples']) === false) {
$this->gauges[$metaKey]['samples'][$valueKey] = 0;
}
if ($data['command'] === Adapter::COMMAND_SET) {
$this->gauges[$metaKey]['samples'][$valueKey] = $data['value'];
} else {
$this->gauges[$metaKey]['samples'][$valueKey] += $data['value'];
}
}
/**
* @param array $data
*/
public function updateCounter(array $data): void
{
$metaKey = $this->metaKey($data);
$valueKey = $this->valueKey($data);
if (array_key_exists($metaKey, $this->counters) === false) {
$this->counters[$metaKey] = [
'meta' => $this->metaData($data),
'samples' => [],
];
}
if (array_key_exists($valueKey, $this->counters[$metaKey]['samples']) === false) {
$this->counters[$metaKey]['samples'][$valueKey] = 0;
}
if ($data['command'] === Adapter::COMMAND_SET) {
$this->counters[$metaKey]['samples'][$valueKey] = 0;
} else {
$this->counters[$metaKey]['samples'][$valueKey] += $data['value'];
}
}
/**
* @param array $data
* @param string $bucket
* @return string
*/
private function histogramBucketValueKey(array $data, $bucket): string
{
return implode(':', [
$data['type'],
$data['name'],
$this->encodeLabelValues($data['labelValues']),
$bucket,
]);
}
/**
* @param array $data
*
* @return string
*/
private function metaKey(array $data): string
{
return implode(':', [
$data['type'],
$data['name'],
'meta'
]);
}
/**
* @param array $data
*
* @return string
*/
private function valueKey(array $data): string
{
return implode(':', [
$data['type'],
$data['name'],
$this->encodeLabelValues($data['labelValues']),
'value'
]);
}
/**
* @param array $data
*
* @return array
*/
private function metaData(array $data): array
{
$metricsMetaData = $data;
unset($metricsMetaData['value']);
unset($metricsMetaData['command']);
unset($metricsMetaData['labelValues']);
return $metricsMetaData;
}
/**
* @param array $samples
*/
private function sortSamples(array &$samples): void
{
usort($samples, function ($a, $b) {
return strcmp(implode("", $a['labelValues']), implode("", $b['labelValues']));
});
}
/**
* @param array $values
* @return string
* @throws RuntimeException
*/
private function encodeLabelValues(array $values): string
{
$json = json_encode($values);
if (false === $json) {
throw new RuntimeException(json_last_error_msg());
}
return base64_encode($json);
}
/**
* @param string $values
* @return array
* @throws RuntimeException
*/
private function decodeLabelValues($values): array
{
$json = base64_decode($values, true);
if (false === $json) {
throw new RuntimeException('Cannot base64 decode label values');
}
$decodedValues = json_decode($json, true);
if (false === $decodedValues) {
throw new RuntimeException(json_last_error_msg());
}
return $decodedValues;
}
}
================================================
FILE: src/Prometheus/Storage/Redis.php
================================================
<?php
declare(strict_types=1);
namespace Prometheus\Storage;
use InvalidArgumentException;
use Prometheus\Counter;
use Prometheus\Exception\StorageException;
use Prometheus\Gauge;
use Prometheus\Histogram;
use Prometheus\MetricFamilySamples;
class Redis implements Adapter
{
const PROMETHEUS_METRIC_KEYS_SUFFIX = '_METRIC_KEYS';
/**
* @var array
*/
private static $defaultOptions = [
'host' => '127.0.0.1',
'port' => 6379,
'timeout' => 0.1,
'read_timeout' => '10',
'persistent_connections' => false,
'password' => null,
];
/**
* @var string
*/
private static $prefix = 'PROMETHEUS_';
/**
* @var array
*/
private $options = [];
/**
* @var \Redis
*/
private $redis;
/**
* @var boolean
*/
private $connectionInitialized = false;
/**
* Redis constructor.
* @param array $options
*/
public function __construct(array $options = [])
{
$this->options = array_merge(self::$defaultOptions, $options);
$this->redis = new \Redis();
}
public static function fromExistingConnection(\Redis $redis): self
{
if ($redis->isConnected() === false) {
throw new StorageException('Connection to Redis server not established');
}
$self = new self();
$self->connectionInitialized = true;
$self->redis = $redis;
return $self;
}
/**
* @param array $options
*/
public static function setDefaultOptions(array $options): void
{
self::$defaultOptions = array_merge(self::$defaultOptions, $options);
}
/**
* @param $prefix
*/
public static function setPrefix($prefix): void
{
self::$prefix = $prefix;
}
/**
* @throws StorageException
*/
public function flushRedis(): void
{
$this->openConnection();
$this->redis->flushAll();
}
/**
* @return MetricFamilySamples[]
* @throws StorageException
*/
public function collect(): array
{
$this->openConnection();
$metrics = $this->collectHistograms();
$metrics = array_merge($metrics, $this->collectGauges());
$metrics = array_merge($metrics, $this->collectCounters());
return array_map(
function (array $metric) {
return new MetricFamilySamples($metric);
},
$metrics
);
}
/**
* @throws StorageException
*/
private function openConnection(): void
{
if ($this->connectionInitialized === true) {
return;
}
$connectionStatus = $this->connectToServer();
if ($connectionStatus === false) {
throw new StorageException("Can't connect to Redis server", 0);
}
if ($this->options['password']) {
$this->redis->auth($this->options['password']);
}
if (isset($this->options['database'])) {
$this->redis->select($this->options['database']);
}
$this->redis->setOption(\Redis::OPT_READ_TIMEOUT, $this->options['read_timeout']);
}
/**
* @return bool
*/
private function connectToServer(): bool
{
try {
if ($this->options['persistent_connections']) {
return $this->redis->pconnect(
$this->options['host'],
$this->options['port'],
$this->options['timeout']
);
}
return $this->redis->connect($this->options['host'], $this->options['port'], $this->options['timeout']);
} catch (\RedisException $e) {
return false;
}
}
/**
* @param array $data
* @throws StorageException
*/
public function updateHistogram(array $data): void
{
$this->openConnection();
$bucketToIncrease = '+Inf';
foreach ($data['buckets'] as $bucket) {
if ($data['value'] <= $bucket) {
$bucketToIncrease = $bucket;
break;
}
}
$metaData = $data;
unset($metaData['value']);
unset($metaData['labelValues']);
$this->redis->eval(
<<<LUA
local increment = redis.call('hIncrByFloat', KEYS[1], ARGV[1], ARGV[3])
redis.call('hIncrBy', KEYS[1], ARGV[2], 1)
if increment == ARGV[3] then
redis.call('hSet', KEYS[1], '__meta', ARGV[4])
redis.call('sAdd', KEYS[2], KEYS[1])
end
LUA
,
[
$this->toMetricKey($data),
self::$prefix . Histogram::TYPE . self::PROMETHEUS_METRIC_KEYS_SUFFIX,
json_encode(['b' => 'sum', 'labelValues' => $data['labelValues']]),
json_encode(['b' => $bucketToIncrease, 'labelValues' => $data['labelValues']]),
$data['value'],
json_encode($metaData),
],
2
);
}
/**
* @param array $data
* @throws StorageException
*/
public function updateGauge(array $data): void
{
$this->openConnection();
$metaData = $data;
unset($metaData['value']);
unset($metaData['labelValues']);
unset($metaData['command']);
$this->redis->eval(
<<<LUA
local result = redis.call(ARGV[1], KEYS[1], ARGV[2], ARGV[3])
if ARGV[1] == 'hSet' then
if result == 1 then
redis.call('hSet', KEYS[1], '__meta', ARGV[4])
redis.call('sAdd', KEYS[2], KEYS[1])
end
else
if result == ARGV[3] then
redis.call('hSet', KEYS[1], '__meta', ARGV[4])
redis.call('sAdd', KEYS[2], KEYS[1])
end
end
LUA
,
[
$this->toMetricKey($data),
self::$prefix . Gauge::TYPE . self::PROMETHEUS_METRIC_KEYS_SUFFIX,
$this->getRedisCommand($data['command']),
json_encode($data['labelValues']),
$data['value'],
json_encode($metaData),
],
2
);
}
/**
* @param array $data
* @throws StorageException
*/
public function updateCounter(array $data): void
{
$this->openConnection();
$metaData = $data;
unset($metaData['value']);
unset($metaData['labelValues']);
unset($metaData['command']);
$this->redis->eval(
<<<LUA
local result = redis.call(ARGV[1], KEYS[1], ARGV[3], ARGV[2])
if result == tonumber(ARGV[2]) then
redis.call('hMSet', KEYS[1], '__meta', ARGV[4])
redis.call('sAdd', KEYS[2], KEYS[1])
end
return result
LUA
,
[
$this->toMetricKey($data),
self::$prefix . Counter::TYPE . self::PROMETHEUS_METRIC_KEYS_SUFFIX,
$this->getRedisCommand($data['command']),
$data['value'],
json_encode($data['labelValues']),
json_encode($metaData),
],
2
);
}
/**
* @return array
*/
private function collectHistograms(): array
{
$keys = $this->redis->sMembers(self::$prefix . Histogram::TYPE . self::PROMETHEUS_METRIC_KEYS_SUFFIX);
sort($keys);
$histograms = [];
foreach ($keys as $key) {
$raw = $this->redis->hGetAll(str_replace($this->redis->_prefix(''), '', $key));
$histogram = json_decode($raw['__meta'], true);
unset($raw['__meta']);
$histogram['samples'] = [];
// Add the Inf bucket so we can compute it later on
$histogram['buckets'][] = '+Inf';
$allLabelValues = [];
foreach (array_keys($raw) as $k) {
$d = json_decode($k, true);
if ($d['b'] == 'sum') {
continue;
}
$allLabelValues[] = $d['labelValues'];
}
// We need set semantics.
// This is the equivalent of array_unique but for arrays of arrays.
$allLabelValues = array_map("unserialize", array_unique(array_map("serialize", $allLabelValues)));
sort($allLabelValues);
foreach ($allLabelValues as $labelValues) {
// Fill up all buckets.
// If the bucket doesn't exist fill in values from
// the previous one.
$acc = 0;
foreach ($histogram['buckets'] as $bucket) {
$bucketKey = json_encode(['b' => $bucket, 'labelValues' => $labelValues]);
if (!isset($raw[$bucketKey])) {
$histogram['samples'][] = [
'name' => $histogram['name'] . '_bucket',
'labelNames' => ['le'],
'labelValues' => array_merge($labelValues, [$bucket]),
'value' => $acc,
];
} else {
$acc += $raw[$bucketKey];
$histogram['samples'][] = [
'name' => $histogram['name'] . '_bucket',
'labelNames' => ['le'],
'labelValues' => array_merge($labelValues, [$bucket]),
'value' => $acc,
];
}
}
// Add the count
$histogram['samples'][] = [
'name' => $histogram['name'] . '_count',
'labelNames' => [],
'labelValues' => $labelValues,
'value' => $acc,
];
// Add the sum
$histogram['samples'][] = [
'name' => $histogram['name'] . '_sum',
'labelNames' => [],
'labelValues' => $labelValues,
'value' => $raw[json_encode(['b' => 'sum', 'labelValues' => $labelValues])],
];
}
$histograms[] = $histogram;
}
return $histograms;
}
/**
* @return array
*/
private function collectGauges(): array
{
$keys = $this->redis->sMembers(self::$prefix . Gauge::TYPE . self::PROMETHEUS_METRIC_KEYS_SUFFIX);
sort($keys);
$gauges = [];
foreach ($keys as $key) {
$raw = $this->redis->hGetAll(str_replace($this->redis->_prefix(''), '', $key));
$gauge = json_decode($raw['__meta'], true);
unset($raw['__meta']);
$gauge['samples'] = [];
foreach ($raw as $k => $value) {
$gauge['samples'][] = [
'name' => $gauge['name'],
'labelNames' => [],
'labelValues' => json_decode($k, true),
'value' => $value,
];
}
usort($gauge['samples'], function ($a, $b) {
return strcmp(implode("", $a['labelValues']), implode("", $b['labelValues']));
});
$gauges[] = $gauge;
}
return $gauges;
}
/**
* @return array
*/
private function collectCounters(): array
{
$keys = $this->redis->sMembers(self::$prefix . Counter::TYPE . self::PROMETHEUS_METRIC_KEYS_SUFFIX);
sort($keys);
$counters = [];
foreach ($keys as $key) {
$raw = $this->redis->hGetAll(str_replace($this->redis->_prefix(''), '', $key));
$counter = json_decode($raw['__meta'], true);
unset($raw['__meta']);
$counter['samples'] = [];
foreach ($raw as $k => $value) {
$counter['samples'][] = [
'name' => $counter['name'],
'labelNames' => [],
'labelValues' => json_decode($k, true),
'value' => $value,
];
}
usort($counter['samples'], function ($a, $b) {
return strcmp(implode("", $a['labelValues']), implode("", $b['labelValues']));
});
$counters[] = $counter;
}
return $counters;
}
/**
* @param int $cmd
* @return string
*/
private function getRedisCommand(int $cmd): string
{
switch ($cmd) {
case Adapter::COMMAND_INCREMENT_INTEGER:
return 'hIncrBy';
case Adapter::COMMAND_INCREMENT_FLOAT:
return 'hIncrByFloat';
case Adapter::COMMAND_SET:
return 'hSet';
default:
throw new InvalidArgumentException("Unknown command");
}
}
/**
* @param array $data
* @return string
*/
private function toMetricKey(array $data): string
{
return implode(':', [self::$prefix, $data['type'], $data['name']]);
}
}
================================================
FILE: tests/Test/BlackBoxPushGatewayTest.php
================================================
<?php
namespace Test;
use GuzzleHttp\Client;
use PHPUnit\Framework\TestCase;
use Prometheus\CollectorRegistry;
use Prometheus\PushGateway;
use Prometheus\Storage\APC;
class BlackBoxPushGatewayTest extends TestCase
{
/**
* @test
*/
public function pushGatewayShouldWork()
{
$adapter = new APC();
$registry = new CollectorRegistry($adapter);
$counter = $registry->registerCounter('test', 'some_counter', 'it increases', ['type']);
$counter->incBy(6, ['blue']);
$pushGateway = new PushGateway('pushgateway:9091');
$pushGateway->push($registry, 'my_job', ['instance' => 'foo']);
$httpClient = new Client();
$metrics = $httpClient->get("http://pushgateway:9091/metrics")->getBody()->getContents();
$this->assertContains(
'# HELP test_some_counter it increases
# TYPE test_some_counter counter
test_some_counter{instance="foo",job="my_job",type="blue"} 6',
$metrics
);
$pushGateway->delete('my_job', ['instance' => 'foo']);
$httpClient = new Client();
$metrics = $httpClient->get("http://pushgateway:9091/metrics")->getBody()->getContents();
$this->assertNotContains(
'# HELP test_some_counter it increases
# TYPE test_some_counter counter
test_some_counter{instance="foo",job="my_job",type="blue"} 6',
$metrics
);
}
}
================================================
FILE: tests/Test/BlackBoxTest.php
================================================
<?php
namespace Test;
use GuzzleHttp\Client;
use GuzzleHttp\Promise;
use PHPUnit\Framework\TestCase;
class BlackBoxTest extends TestCase
{
/**
* @var Client
*/
private $client;
/**
* @var string
*/
private $adapter;
public function setUp()
{
$this->adapter = getenv('ADAPTER');
$this->client = new Client(['base_uri' => 'http://nginx:80/']);
$this->client->get('/examples/flush_adapter.php?adapter=' . $this->adapter);
}
/**
* @test
*/
public function gaugesShouldBeOverwritten()
{
$start = microtime(true);
$promises = [
$this->client->getAsync('/examples/some_gauge.php?c=0&adapter=' . $this->adapter),
$this->client->getAsync('/examples/some_gauge.php?c=1&adapter=' . $this->adapter),
$this->client->getAsync('/examples/some_gauge.php?c=2&adapter=' . $this->adapter),
];
Promise\settle($promises)->wait();
$end = microtime(true);
echo "\ntime: " . ($end - $start) . "\n";
$metricsResult = $this->client->get('/examples/metrics.php?adapter=' . $this->adapter);
$body = (string)$metricsResult->getBody();
echo "\nbody: " . $body . "\n";
$this->assertThat(
$body,
$this->logicalOr(
$this->stringContains('test_some_gauge{type="blue"} 0'),
$this->stringContains('test_some_gauge{type="blue"} 1'),
$this->stringContains('test_some_gauge{type="blue"} 2')
)
);
}
/**
* @test
*/
public function countersShouldIncrementAtomically()
{
$start = microtime(true);
$promises = [];
$sum = 0;
for ($i = 0; $i < 1100; $i++) {
$promises[] = $this->client->getAsync('/examples/some_counter.php?c=' . $i . '&adapter=' . $this->adapter);
$sum += $i;
}
Promise\settle($promises)->wait();
$end = microtime(true);
echo "\ntime: " . ($end - $start) . "\n";
$metricsResult = $this->client->get('/examples/metrics.php?adapter=' . $this->adapter);
$body = (string)$metricsResult->getBody();
$this->assertThat($body, $this->stringContains('test_some_counter{type="blue"} ' . $sum));
}
/**
* @test
*/
public function histogramsShouldIncrementAtomically()
{
$start = microtime(true);
$promises = [
$this->client->getAsync('/examples/some_histogram.php?c=0&adapter=' . $this->adapter),
$this->client->getAsync('/examples/some_histogram.php?c=1&adapter=' . $this->adapter),
$this->client->getAsync('/examples/some_histogram.php?c=2&adapter=' . $this->adapter),
$this->client->getAsync('/examples/some_histogram.php?c=3&adapter=' . $this->adapter),
$this->client->getAsync('/examples/some_histogram.php?c=4&adapter=' . $this->adapter),
$this->client->getAsync('/examples/some_histogram.php?c=5&adapter=' . $this->adapter),
$this->client->getAsync('/examples/some_histogram.php?c=6&adapter=' . $this->adapter),
$this->client->getAsync('/examples/some_histogram.php?c=7&adapter=' . $this->adapter),
$this->client->getAsync('/examples/some_histogram.php?c=8&adapter=' . $this->adapter),
$this->client->getAsync('/examples/some_histogram.php?c=9&adapter=' . $this->adapter),
];
Promise\settle($promises)->wait();
$end = microtime(true);
echo "\ntime: " . ($end - $start) . "\n";
$metricsResult = $this->client->get('/examples/metrics.php?adapter=' . $this->adapter);
$body = (string)$metricsResult->getBody();
$this->assertThat($body, $this->stringContains(<<<EOF
test_some_histogram_bucket{type="blue",le="0.1"} 1
test_some_histogram_bucket{type="blue",le="1"} 2
test_some_histogram_bucket{type="blue",le="2"} 3
test_some_histogram_bucket{type="blue",le="3.5"} 4
test_some_histogram_bucket{type="blue",le="4"} 5
test_some_histogram_bucket{type="blue",le="5"} 6
test_some_histogram_bucket{type="blue",le="6"} 7
test_some_histogram_bucket{type="blue",le="7"} 8
test_some_histogram_bucket{type="blue",le="8"} 9
test_some_histogram_bucket{type="blue",le="9"} 10
test_some_histogram_bucket{type="blue",le="+Inf"} 10
test_some_histogram_count{type="blue"} 10
test_some_histogram_sum{type="blue"} 45
EOF
));
}
}
================================================
FILE: tests/Test/Prometheus/APC/CollectorRegistryTest.php
================================================
<?php
namespace Test\Prometheus\APC;
use Prometheus\Storage\APC;
use Test\Prometheus\AbstractCollectorRegistryTest;
/**
* @requires extension apc
*/
class CollectorRegistryTest extends AbstractCollectorRegistryTest
{
public function configureAdapter()
{
$this->adapter = new APC();
$this->adapter->flushAPC();
}
}
================================================
FILE: tests/Test/Prometheus/APC/CounterTest.php
================================================
<?php
namespace Test\Prometheus\APC;
use Prometheus\Storage\APC;
use Test\Prometheus\AbstractCounterTest;
/**
* See https://prometheus.io/docs/instrumenting/exposition_formats/
* @requires extension apc
*/
class CounterTest extends AbstractCounterTest
{
public function configureAdapter()
{
$this->adapter = new APC();
$this->adapter->flushAPC();
}
}
================================================
FILE: tests/Test/Prometheus/APC/GaugeTest.php
================================================
<?php
namespace Test\Prometheus\APC;
use Prometheus\Storage\APC;
use Test\Prometheus\AbstractGaugeTest;
/**
* See https://prometheus.io/docs/instrumenting/exposition_formats/
* @requires extension apc
*/
class GaugeTest extends AbstractGaugeTest
{
public function configureAdapter()
{
$this->adapter = new APC();
$this->adapter->flushAPC();
}
}
================================================
FILE: tests/Test/Prometheus/APC/HistogramTest.php
================================================
<?php
namespace Test\Prometheus\APC;
use Prometheus\Storage\APC;
use Test\Prometheus\AbstractHistogramTest;
/**
* See https://prometheus.io/docs/instrumenting/exposition_formats/
* @requires extension apc
*/
class HistogramTest extends AbstractHistogramTest
{
public function configureAdapter()
{
$this->adapter = new APC();
$this->adapter->flushAPC();
}
}
================================================
FILE: tests/Test/Prometheus/AbstractCollectorRegistryTest.php
================================================
<?php
namespace Test\Prometheus;
use PHPUnit\Framework\TestCase;
use Prometheus\CollectorRegistry;
use Prometheus\Histogram;
use Prometheus\RenderTextFormat;
use Prometheus\Storage\Adapter;
use Prometheus\Storage\Redis;
use Prometheus\Exception\MetricsRegistrationException;
use Prometheus\Exception\MetricNotFoundException;
abstract class AbstractCollectorRegistryTest extends TestCase
{
/**
* @var Adapter
*/
public $adapter;
/**
* @var RenderTextFormat
*/
private $renderer;
public function setUp(): void
{
$this->configureAdapter();
$this->renderer = new RenderTextFormat();
}
/**
* @test
*/
public function itShouldSaveGauges()
{
$registry = new CollectorRegistry($this->adapter);
$g = $registry->registerGauge('test', 'some_metric', 'this is for testing', ['foo']);
$g->set(35, ['bbb']);
$g->set(35, ['ddd']);
$g->set(35, ['aaa']);
$g->set(35, ['ccc']);
$registry = new CollectorRegistry($this->adapter);
$this->assertThat(
$this->renderer->render($registry->getMetricFamilySamples()),
$this->equalTo(
<<<EOF
# HELP test_some_metric this is for testing
# TYPE test_some_metric gauge
test_some_metric{foo="aaa"} 35
test_some_metric{foo="bbb"} 35
test_some_metric{foo="ccc"} 35
test_some_metric{foo="ddd"} 35
EOF
)
);
}
/**
* @test
*/
public function itShouldSaveCounters()
{
$registry = new CollectorRegistry($this->adapter);
$metric = $registry->registerCounter('test', 'some_metric', 'this is for testing', ['foo', 'bar']);
$metric->incBy(2, ['lalal', 'lululu']);
$registry->getCounter('test', 'some_metric', ['foo', 'bar'])->inc(['lalal', 'lululu']);
$registry->getCounter('test', 'some_metric', ['foo', 'bar'])->inc(['lalal', 'lvlvlv']);
$registry = new CollectorRegistry($this->adapter);
$this->assertThat(
$this->renderer->render($registry->getMetricFamilySamples()),
$this->equalTo(
<<<EOF
# HELP test_some_metric this is for testing
# TYPE test_some_metric counter
test_some_metric{foo="lalal",bar="lululu"} 3
test_some_metric{foo="lalal",bar="lvlvlv"} 1
EOF
)
);
}
/**
* @test
*/
public function itShouldSaveHistograms()
{
$registry = new CollectorRegistry($this->adapter);
$metric = $registry->registerHistogram(
'test',
'some_metric',
'this is for testing',
['foo', 'bar'],
[0.1, 1, 5, 10]
);
$metric->observe(2, ['lalal', 'lululu']);
$registry->getHistogram('test', 'some_metric', ['foo', 'bar'])->observe(7.1, ['lalal', 'lvlvlv']);
$registry->getHistogram('test', 'some_metric', ['foo', 'bar'])->observe(13, ['lalal', 'lululu']);
$registry->getHistogram('test', 'some_metric', ['foo', 'bar'])->observe(7.1, ['lalal', 'lululu']);
$registry->getHistogram('test', 'some_metric', ['foo', 'bar'])->observe(7.1, ['gnaaha', 'hihihi']);
$registry = new CollectorRegistry($this->adapter);
$this->assertThat(
$this->renderer->render($registry->getMetricFamilySamples()),
$this->equalTo(
<<<EOF
# HELP test_some_metric this is for testing
# TYPE test_some_metric histogram
test_some_metric_bucket{foo="gnaaha",bar="hihihi",le="0.1"} 0
test_some_metric_bucket{foo="gnaaha",bar="hihihi",le="1"} 0
test_some_metric_bucket{foo="gnaaha",bar="hihihi",le="5"} 0
test_some_metric_bucket{foo="gnaaha",bar="hihihi",le="10"} 1
test_some_metric_bucket{foo="gnaaha",bar="hihihi",le="+Inf"} 1
test_some_metric_count{foo="gnaaha",bar="hihihi"} 1
test_some_metric_sum{foo="gnaaha",bar="hihihi"} 7.1
test_some_metric_bucket{foo="lalal",bar="lululu",le="0.1"} 0
test_some_metric_bucket{foo="lalal",bar="lululu",le="1"} 0
test_some_metric_bucket{foo="lalal",bar="lululu",le="5"} 1
test_some_metric_bucket{foo="lalal",bar="lululu",le="10"} 2
test_some_metric_bucket{foo="lalal",bar="lululu",le="+Inf"} 3
test_some_metric_count{foo="lalal",bar="lululu"} 3
test_some_metric_sum{foo="lalal",bar="lululu"} 22.1
test_some_metric_bucket{foo="lalal",bar="lvlvlv",le="0.1"} 0
test_some_metric_bucket{foo="lalal",bar="lvlvlv",le="1"} 0
test_some_metric_bucket{foo="lalal",bar="lvlvlv",le="5"} 0
test_some_metric_bucket{foo="lalal",bar="lvlvlv",le="10"} 1
test_some_metric_bucket{foo="lalal",bar="lvlvlv",le="+Inf"} 1
test_some_metric_count{foo="lalal",bar="lvlvlv"} 1
test_some_metric_sum{foo="lalal",bar="lvlvlv"} 7.1
EOF
)
);
}
/**
* @test
*/
public function itShouldSaveHistogramsWithoutLabels()
{
$registry = new CollectorRegistry($this->adapter);
$metric = $registry->registerHistogram('test', 'some_metric', 'this is for testing');
$metric->observe(2);
$registry->getHistogram('test', 'some_metric')->observe(13);
$registry->getHistogram('test', 'some_metric')->observe(7.1);
$registry = new CollectorRegistry($this->adapter);
$this->assertThat(
$this->renderer->render($registry->getMetricFamilySamples()),
$this->equalTo(
<<<EOF
# HELP test_some_metric this is for testing
# TYPE test_some_metric histogram
test_some_metric_bucket{le="0.005"} 0
test_some_metric_bucket{le="0.01"} 0
test_some_metric_bucket{le="0.025"} 0
test_some_metric_bucket{le="0.05"} 0
test_some_metric_bucket{le="0.075"} 0
test_some_metric_bucket{le="0.1"} 0
test_some_metric_bucket{le="0.25"} 0
test_some_metric_bucket{le="0.5"} 0
test_some_metric_bucket{le="0.75"} 0
test_some_metric_bucket{le="1"} 0
test_some_metric_bucket{le="2.5"} 1
test_some_metric_bucket{le="5"} 1
test_some_metric_bucket{le="7.5"} 2
test_some_metric_bucket{le="10"} 2
test_some_metric_bucket{le="+Inf"} 3
test_some_metric_count 3
test_some_metric_sum 22.1
EOF
)
);
}
/**
* @test
*/
public function itShouldIncreaseACounterWithoutNamespace()
{
$registry = new CollectorRegistry($this->adapter);
$registry
->registerCounter('', 'some_quick_counter', 'just a quick measurement')
->inc()
;
$this->assertThat(
$this->renderer->render($registry->getMetricFamilySamples()),
$this->equalTo(
<<<EOF
# HELP some_quick_counter just a quick measurement
# TYPE some_quick_counter counter
some_quick_counter 1
EOF
)
);
}
/**
* @test
*/
public function itShouldForbidRegisteringTheSameCounterTwice()
{
$registry = new CollectorRegistry($this->adapter);
$registry->registerCounter('foo', 'metric', 'help');
$this->expectException(MetricsRegistrationException::class);
$registry->registerCounter('foo', 'metric', 'help');
}
/**
* @test
*/
public function itShouldForbidRegisteringTheSameCounterWithDifferentLabels()
{
$registry = new CollectorRegistry($this->adapter);
$registry->registerCounter('foo', 'metric', 'help', ["foo", "bar"]);
$this->expectException(MetricsRegistrationException::class);
$registry->registerCounter('foo', 'metric', 'help', ["spam", "eggs"]);
}
/**
* @test
*/
public function itShouldForbidRegisteringTheSameHistogramTwice()
{
$registry = new CollectorRegistry($this->adapter);
$registry->registerHistogram('foo', 'metric', 'help');
$this->expectException(MetricsRegistrationException::class);
$registry->registerHistogram('foo', 'metric', 'help');
}
/**
* @test
*/
public function itShouldForbidRegisteringTheSameHistogramWithDifferentLabels()
{
$registry = new CollectorRegistry($this->adapter);
$registry->registerCounter('foo', 'metric', 'help', ["foo", "bar"]);
$this->expectException(MetricsRegistrationException::class);
$registry->registerCounter('foo', 'metric', 'help', ["spam", "eggs"]);
}
/**
* @test
*/
public function itShouldForbidRegisteringTheSameGaugeTwice()
{
$registry = new CollectorRegistry($this->adapter);
$registry->registerGauge('foo', 'metric', 'help');
$this->expectException(MetricsRegistrationException::class);
$registry->registerGauge('foo', 'metric', 'help');
}
/**
* @test
*/
public function itShouldForbidRegisteringTheSameGaugeWithDifferentLabels()
{
$registry = new CollectorRegistry($this->adapter);
$registry->registerGauge('foo', 'metric', 'help', ["foo", "bar"]);
$this->expectException(MetricsRegistrationException::class);
$registry->registerGauge('foo', 'metric', 'help', ["spam", "eggs"]);
}
/**
* @test
*/
public function itShouldThrowAnExceptionWhenGettingANonExistentMetric()
{
$registry = new CollectorRegistry($this->adapter);
$this->expectException(MetricNotFoundException::class);
$registry->getGauge("not_here", "go_away");
}
/**
* @test
*/
public function itShouldNotRegisterACounterTwice()
{
$registry = new CollectorRegistry($this->adapter);
$counterA = $registry->getOrRegisterCounter("foo", "bar", "Help text");
$counterB = $registry->getOrRegisterCounter("foo", "bar", "Help text");
$this->assertSame($counterA, $counterB);
}
/**
* @test
*/
public function itShouldNotRegisterAGaugeTwice()
{
$registry = new CollectorRegistry($this->adapter);
$gaugeA = $registry->getOrRegisterGauge("foo", "bar", "Help text");
$gaugeB = $registry->getOrRegisterGauge("foo", "bar", "Help text");
$this->assertSame($gaugeA, $gaugeB);
}
/**
* @test
*/
public function itShouldNotRegisterAHistogramTwice()
{
$registry = new CollectorRegistry($this->adapter);
$histogramA = $registry->getOrRegisterHistogram("foo", "bar", "Help text");
$histogramB = $registry->getOrRegisterHistogram("foo", "bar", "Help text");
$this->assertSame($histogramA, $histogramB);
}
abstract public function configureAdapter();
}
================================================
FILE: tests/Test/Prometheus/AbstractCounterTest.php
================================================
<?php
namespace Test\Prometheus;
use InvalidArgumentException;
use PHPUnit\Framework\TestCase;
use Prometheus\Counter;
use Prometheus\MetricFamilySamples;
use Prometheus\Sample;
use Prometheus\Storage\Adapter;
/**
* See https://prometheus.io/docs/instrumenting/exposition_formats/
*/
abstract class AbstractCounterTest extends TestCase
{
/**
* @var Adapter
*/
public $adapter;
public function setUp(): void
{
$this->configureAdapter();
}
abstract public function configureAdapter();
/**
* @test
*/
public function itShouldIncreaseWithLabels()
{
$counter = new Counter($this->adapter, 'test', 'some_metric', 'this is for testing', ['foo', 'bar']);
$counter->inc(['lalal', 'lululu']);
$counter->inc(['lalal', 'lululu']);
$counter->inc(['lalal', 'lululu']);
$this->assertThat(
$this->adapter->collect(),
$this->equalTo(
[
new MetricFamilySamples(
[
'type' => Counter::TYPE,
'help' => 'this is for testing',
'name' => 'test_some_metric',
'labelNames' => ['foo', 'bar'],
'samples' => [
[
'labelValues' => ['lalal', 'lululu'],
'value' => 3,
'name' => 'test_some_metric',
'labelNames' => [],
],
],
]
),
]
)
);
}
/**
* @test
*/
public function itShouldIncreaseWithoutLabelWhenNoLabelsAreDefined()
{
$counter = new Counter($this->adapter, 'test', 'some_metric', 'this is for testing');
$counter->inc();
$this->assertThat(
$this->adapter->collect(),
$this->equalTo(
[
new MetricFamilySamples(
[
'type' => Counter::TYPE,
'help' => 'this is for testing',
'name' => 'test_some_metric',
'labelNames' => [],
'samples' => [
[
'labelValues' => [],
'value' => 1,
'name' => 'test_some_metric',
'labelNames' => [],
],
],
]
),
]
)
);
}
/**
* @test
*/
public function itShouldIncreaseTheCounterByAnArbitraryInteger()
{
$counter = new Counter($this->adapter, 'test', 'some_metric', 'this is for testing', ['foo', 'bar']);
$counter->inc(['lalal', 'lululu']);
$counter->incBy(123, ['lalal', 'lululu']);
$this->assertThat(
$this->adapter->collect(),
$this->equalTo(
[
new MetricFamilySamples(
[
'type' => Counter::TYPE,
'help' => 'this is for testing',
'name' => 'test_some_metric',
'labelNames' => ['foo', 'bar'],
'samples' => [
[
'labelValues' => ['lalal', 'lululu'],
'value' => 124,
'name' => 'test_some_metric',
'labelNames' => [],
],
],
]
),
]
)
);
}
/**
* @test
*/
public function itShouldRejectInvalidMetricsNames()
{
$this->expectException(InvalidArgumentException::class);
new Counter($this->adapter, 'test', 'some metric invalid metric', 'help');
}
/**
* @test
*/
public function itShouldRejectInvalidLabelNames()
{
$this->expectException(InvalidArgumentException::class);
new Counter($this->adapter, 'test', 'some_metric', 'help', ['invalid label']);
}
/**
* @test
* @dataProvider labelValuesDataProvider
*
* @param mixed $value The label value
*/
public function isShouldAcceptAnySequenceOfBasicLatinCharactersForLabelValues($value)
{
$label = 'foo';
$histogram = new Counter($this->adapter, 'test', 'some_metric', 'help', [$label]);
$histogram->inc([$value]);
$metrics = $this->adapter->collect();
$this->assertIsArray($metrics);
$this->assertCount(1, $metrics);
$this->assertContainsOnlyInstancesOf(MetricFamilySamples::class, $metrics);
$metric = reset($metrics);
$samples = $metric->getSamples();
$this->assertContainsOnlyInstancesOf(Sample::class, $samples);
foreach ($samples as $sample) {
$labels = array_combine(
array_merge($metric->getLabelNames(), $sample->getLabelNames()),
$sample->getLabelValues()
);
$this->assertEquals($value, $labels[$label]);
}
}
/**
* @return array
* @see isShouldAcceptArbitraryLabelValues
*/
public function labelValuesDataProvider()
{
$cases = [];
// Basic Latin
// See https://en.wikipedia.org/wiki/List_of_Unicode_characters#Basic_Latin
for ($i = 32; $i <= 121; $i++) {
$cases['ASCII code ' . $i] = [chr($i)];
}
return $cases;
}
}
================================================
FILE: tests/Test/Prometheus/AbstractGaugeTest.php
================================================
<?php
namespace Test\Prometheus;
use InvalidArgumentException;
use PHPUnit\Framework\TestCase;
use Prometheus\Gauge;
use Prometheus\MetricFamilySamples;
use Prometheus\Sample;
use Prometheus\Storage\Adapter;
/**
* See https://prometheus.io/docs/instrumenting/exposition_formats/
*/
abstract class AbstractGaugeTest extends TestCase
{
/**
* @var Adapter
*/
public $adapter;
public function setUp(): void
{
$this->configureAdapter();
}
abstract public function configureAdapter();
/**
* @test
*/
public function itShouldAllowSetWithLabels()
{
$gauge = new Gauge($this->adapter, 'test', 'some_metric', 'this is for testing', ['foo', 'bar']);
$gauge->set(123, ['lalal', 'lululu']);
$this->assertThat(
$this->adapter->collect(),
$this->equalTo(
[
new MetricFamilySamples(
[
'name' => 'test_some_metric',
'help' => 'this is for testing',
'type' => Gauge::TYPE,
'labelNames' => ['foo', 'bar'],
'samples' => [
[
'name' => 'test_some_metric',
'labelNames' => [],
'labelValues' => ['lalal', 'lululu'],
'value' => 123,
],
],
]
),
]
)
);
$this->assertThat($gauge->getHelp(), $this->equalTo('this is for testing'));
$this->assertThat($gauge->getType(), $this->equalTo(Gauge::TYPE));
}
/**
* @test
*/
public function itShouldAllowSetWithoutLabelWhenNoLabelsAreDefined()
{
$gauge = new Gauge($this->adapter, 'test', 'some_metric', 'this is for testing');
$gauge->set(123);
$this->assertThat(
$this->adapter->collect(),
$this->equalTo(
[
new MetricFamilySamples(
[
'name' => 'test_some_metric',
'help' => 'this is for testing',
'type' => Gauge::TYPE,
'labelNames' => [],
'samples' => [
[
'name' => 'test_some_metric',
'labelNames' => [],
'labelValues' => [],
'value' => 123,
],
],
]
),
]
)
);
$this->assertThat($gauge->getHelp(), $this->equalTo('this is for testing'));
$this->assertThat($gauge->getType(), $this->equalTo(Gauge::TYPE));
}
/**
* @test
*/
public function itShouldAllowSetWithAFloatValue()
{
$gauge = new Gauge($this->adapter, 'test', 'some_metric', 'this is for testing');
$gauge->set(123.5);
$this->assertThat(
$this->adapter->collect(),
$this->equalTo(
[
new MetricFamilySamples(
[
'name' => 'test_some_metric',
'help' => 'this is for testing',
'type' => Gauge::TYPE,
'labelNames' => [],
'samples' => [
[
'name' => 'test_some_metric',
'labelNames' => [],
'labelValues' => [],
'value' => 123.5,
],
],
]
),
]
)
);
$this->assertThat($gauge->getHelp(), $this->equalTo('this is for testing'));
$this->assertThat($gauge->getType(), $this->equalTo(Gauge::TYPE));
}
/**
* @test
*/
public function itShouldIncrementAValue()
{
$gauge = new Gauge($this->adapter, 'test', 'some_metric', 'this is for testing', ['foo', 'bar']);
$gauge->inc(['lalal', 'lululu']);
$gauge->incBy(123, ['lalal', 'lululu']);
$this->assertThat(
$this->adapter->collect(),
$this->equalTo(
[
new MetricFamilySamples(
[
'name' => 'test_some_metric',
'help' => 'this is for testing',
'type' => Gauge::TYPE,
'labelNames' => ['foo', 'bar'],
'samples' => [
[
'name' => 'test_some_metric',
'labelNames' => [],
'labelValues' => ['lalal', 'lululu'],
'value' => 124,
],
],
]
),
]
)
);
}
/**
* @test
*/
public function itShouldIncrementWithFloatValue()
{
$gauge = new Gauge($this->adapter, 'test', 'some_metric', 'this is for testing', ['foo', 'bar']);
$gauge->inc(['lalal', 'lululu']);
$gauge->incBy(123.5, ['lalal', 'lululu']);
$this->assertThat(
$this->adapter->collect(),
$this->equalTo(
[
new MetricFamilySamples(
[
'name' => 'test_some_metric',
'help' => 'this is for testing',
'type' => Gauge::TYPE,
'labelNames' => ['foo', 'bar'],
'samples' => [
[
'name' => 'test_some_metric',
'labelNames' => [],
'labelValues' => ['lalal', 'lululu'],
'value' => 124.5,
],
],
]
),
]
)
);
}
/**
* @test
*/
public function itShouldDecrementAValue()
{
$gauge = new Gauge($this->adapter, 'test', 'some_metric', 'this is for testing', ['foo', 'bar']);
$gauge->dec(['lalal', 'lululu']);
$gauge->decBy(123, ['lalal', 'lululu']);
$this->assertThat(
$this->adapter->collect(),
$this->equalTo(
[
new MetricFamilySamples(
[
'name' => 'test_some_metric',
'help' => 'this is for testing',
'type' => Gauge::TYPE,
'labelNames' => ['foo', 'bar'],
'samples' => [
[
'name' => 'test_some_metric',
'labelNames' => [],
'labelValues' => ['lalal', 'lululu'],
'value' => -124,
],
],
]
),
]
)
);
}
/**
* @test
*/
public function itShouldDecrementWithFloatValue()
{
$gauge = new Gauge($this->adapter, 'test', 'some_metric', 'this is for testing', ['foo', 'bar']);
$gauge->dec(['lalal', 'lululu']);
$gauge->decBy(123, ['lalal', 'lululu']);
$this->assertThat(
$this->adapter->collect(),
$this->equalTo(
[
new MetricFamilySamples(
[
'name' => 'test_some_metric',
'help' => 'this is for testing',
'type' => Gauge::TYPE,
'labelNames' => ['foo', 'bar'],
'samples' => [
[
'name' => 'test_some_metric',
'labelNames' => [],
'labelValues' => ['lalal', 'lululu'],
'value' => -124,
],
],
]
),
]
)
);
}
/**
* @test
*/
public function itShouldOverwriteWhenSettingTwice()
{
$gauge = new Gauge($this->adapter, 'test', 'some_metric', 'this is for testing', ['foo', 'bar']);
$gauge->set(123, ['lalal', 'lululu']);
$gauge->set(321, ['lalal', 'lululu']);
$this->assertThat(
$this->adapter->collect(),
$this->equalTo(
[
new MetricFamilySamples(
[
'name' => 'test_some_metric',
'help' => 'this is for testing',
'type' => Gauge::TYPE,
'labelNames' => ['foo', 'bar'],
'samples' => [
[
'name' => 'test_some_metric',
'labelNames' => [],
'labelValues' => ['lalal', 'lululu'],
'value' => 321,
],
],
]
),
]
)
);
}
/**
* @test
*/
public function itShouldRejectInvalidMetricsNames()
{
$this->expectException(InvalidArgumentException::class);
new Gauge($this->adapter, 'test', 'some metric invalid metric', 'help');
}
/**
* @test
*/
public function itShouldRejectInvalidLabelNames()
{
$this->expectException(InvalidArgumentException::class);
new Gauge($this->adapter, 'test', 'some_metric', 'help', ['invalid label']);
}
/**
* @test
* @dataProvider labelValuesDataProvider
*
* @param mixed $value The label value
*/
public function isShouldAcceptAnySequenceOfBasicLatinCharactersForLabelValues($value)
{
$label = 'foo';
$histogram = new Gauge($this->adapter, 'test', 'some_metric', 'help', [$label]);
$histogram->inc([$value]);
$metrics = $this->adapter->collect();
$this->assertIsArray($metrics);
$this->assertCount(1, $metrics);
$this->assertContainsOnlyInstancesOf(MetricFamilySamples::class, $metrics);
$metric = reset($metrics);
$samples = $metric->getSamples();
$this->assertContainsOnlyInstancesOf(Sample::class, $samples);
foreach ($samples as $sample) {
$labels = array_combine(
array_merge($metric->getLabelNames(), $sample->getLabelNames()),
$sample->getLabelValues()
);
$this->assertEquals($value, $labels[$label]);
}
}
/**
* @return array
* @see isShouldAcceptArbitraryLabelValues
*/
public function labelValuesDataProvider()
{
$cases = [];
// Basic Latin
// See https://en.wikipedia.org/wiki/List_of_Unicode_characters#Basic_Latin
for ($i = 32; $i <= 121; $i++) {
$cases['ASCII code ' . $i] = [chr($i)];
}
return $cases;
}
}
================================================
FILE: tests/Test/Prometheus/AbstractHistogramTest.php
================================================
<?php
namespace Test\Prometheus;
use InvalidArgumentException;
use PHPUnit\Framework\TestCase;
use Prometheus\Histogram;
use Prometheus\MetricFamilySamples;
use Prometheus\Sample;
use Prometheus\Storage\Adapter;
/**
* See https://prometheus.io/docs/instrumenting/exposition_formats/
*/
abstract class AbstractHistogramTest extends TestCase
{
/**
* @var Adapter
*/
public $adapter;
public function setUp(): void
{
$this->configureAdapter();
}
abstract public function configureAdapter();
/**
* @test
*/
public function itShouldObserveWithLabels()
{
$histogram = new Histogram(
$this->adapter,
'test',
'some_metric',
'this is for testing',
['foo', 'bar'],
[100, 200, 300]
);
$histogram->observe(123, ['lalal', 'lululu']);
$histogram->observe(245, ['lalal', 'lululu']);
$this->assertThat(
$this->adapter->collect(),
$this->equalTo(
[
new MetricFamilySamples(
[
'name' => 'test_some_metric',
'help' => 'this is for testing',
'type' => Histogram::TYPE,
'labelNames' => ['foo', 'bar'],
'samples' => [
[
'name' => 'test_some_metric_bucket',
'labelNames' => ['le'],
'labelValues' => ['lalal', 'lululu', 100],
'value' => 0,
],
[
'name' => 'test_some_metric_bucket',
'labelNames' => ['le'],
'labelValues' => ['lalal', 'lululu', 200],
'value' => 1,
],
[
'name' => 'test_some_metric_bucket',
'labelNames' => ['le'],
'labelValues' => ['lalal', 'lululu', 300],
'value' => 2,
],
[
'name' => 'test_some_metric_bucket',
'labelNames' => ['le'],
'labelValues' => ['lalal', 'lululu', '+Inf'],
'value' => 2,
],
[
'name' => 'test_some_metric_count',
'labelNames' => [],
'labelValues' => ['lalal', 'lululu'],
'value' => 2,
],
[
'name' => 'test_some_metric_sum',
'labelNames' => [],
'labelValues' => ['lalal', 'lululu'],
'value' => 368,
],
],
]
),
]
)
);
}
/**
* @test
*/
public function itShouldObserveWithoutLabelWhenNoLabelsAreDefined()
{
$histogram = new Histogram(
$this->adapter,
'test',
'some_metric',
'this is for testing',
[],
[100, 200, 300]
);
$histogram->observe(245);
$this->assertThat(
$this->adapter->collect(),
$this->equalTo(
[
new MetricFamilySamples(
[
'name' => 'test_some_metric',
'help' => 'this is for testing',
'type' => Histogram::TYPE,
'labelNames' => [],
'samples' => [
[
'name' => 'test_some_metric_bucket',
'labelNames' => ['le'],
'labelValues' => [100],
'value' => 0,
],
[
'name' => 'test_some_metric_bucket',
'labelNames' => ['le'],
'labelValues' => [200],
'value' => 0,
],
[
'name' => 'test_some_metric_bucket',
'labelNames' => ['le'],
'labelValues' => [300],
'value' => 1,
],
[
'name' => 'test_some_metric_bucket',
'labelNames' => ['le'],
'labelValues' => ['+Inf'],
'value' => 1,
],
[
'name' => 'test_some_metric_count',
'labelNames' => [],
'labelValues' => [],
'value' => 1,
],
[
'name' => 'test_some_metric_sum',
'labelNames' => [],
'labelValues' => [],
'value' => 245,
],
],
]
),
]
)
);
}
/**
* @test
*/
public function itShouldObserveValuesOfTypeDouble()
{
$histogram = new Histogram(
$this->adapter,
'test',
'some_metric',
'this is for testing',
[],
[0.1, 0.2, 0.3]
);
$histogram->observe(0.11);
$histogram->observe(0.3);
$this->assertThat(
$this->adapter->collect(),
$this->equalTo(
[
new MetricFamilySamples(
[
'name' => 'test_some_metric',
'help' => 'this is for testing',
'type' => Histogram::TYPE,
'labelNames' => [],
'samples' => [
[
'name' => 'test_some_metric_bucket',
'labelNames' => ['le'],
'labelValues' => [0.1],
'value' => 0,
],
[
'name' => 'test_some_metric_bucket',
'labelNames' => ['le'],
'labelValues' => [0.2],
'value' => 1,
],
[
'name' => 'test_some_metric_bucket',
'labelNames' => ['le'],
'labelValues' => [0.3],
'value' => 2,
],
[
'name' => 'test_some_metric_bucket',
'labelNames' => ['le'],
'labelValues' => ['+Inf'],
'value' => 2,
],
[
'name' => 'test_some_metric_count',
'labelNames' => [],
'labelValues' => [],
'value' => 2,
],
[
'name' => 'test_some_metric_sum',
'labelNames' => [],
'labelValues' => [],
'value' => 0.41,
],
],
]
),
]
)
);
}
/**
* @test
*/
public function itShouldProvideDefaultBuckets()
{
// .005, .01, .025, .05, .075, .1, .25, .5, .75, 1.0, 2.5, 5.0, 7.5, 10.0
$histogram = new Histogram(
$this->adapter,
'test',
'some_metric',
'this is for testing',
[]
);
$histogram->observe(0.11);
$histogram->observe(0.03);
$this->assertThat(
$this->adapter->collect(),
$this->equalTo(
[
new MetricFamilySamples(
[
'name' => 'test_some_metric',
'help' => 'this is for testing',
'type' => Histogram::TYPE,
'labelNames' => [],
'samples' => [
[
'name' => 'test_some_metric_bucket',
'labelNames' => ['le'],
'labelValues' => [0.005],
'value' => 0,
],
[
'name' => 'test_some_metric_bucket',
'labelNames' => ['le'],
'labelValues' => [0.01],
'value' => 0,
],
[
'name' => 'test_some_metric_bucket',
'labelNames' => ['le'],
'labelValues' => [0.025],
'value' => 0,
],
[
'name' => 'test_some_metric_bucket',
'labelNames' => ['le'],
'labelValues' => [0.05],
'value' => 1,
],
[
'name' => 'test_some_metric_bucket',
'labelNames' => ['le'],
'labelValues' => [0.075],
'value' => 1,
],
[
'name' => 'test_some_metric_bucket',
'labelNames' => ['le'],
'labelValues' => [0.1],
'value' => 1,
],
[
'name' => 'test_some_metric_bucket',
'labelNames' => ['le'],
'labelValues' => [0.25],
'value' => 2,
],
[
'name' => 'test_some_metric_bucket',
'labelNames' => ['le'],
'labelValues' => [0.5],
'value' => 2,
],
[
'name' => 'test_some_metric_bucket',
'labelNames' => ['le'],
'labelValues' => [0.75],
'value' => 2,
],
[
'name' => 'test_some_metric_bucket',
'labelNames' => ['le'],
'labelValues' => [1.0],
'value' => 2,
],
[
'name' => 'test_some_metric_bucket',
'labelNames' => ['le'],
'labelValues' => [2.5],
'value' => 2,
],
[
'name' => 'test_some_metric_bucket',
'labelNames' => ['le'],
'labelValues' => [5],
'value' => 2,
],
[
'name' => 'test_some_metric_bucket',
'labelNames' => ['le'],
'labelValues' => [7.5],
'value' => 2,
],
[
'name' => 'test_some_metric_bucket',
'labelNames' => ['le'],
'labelValues' => [10],
'value' => 2,
],
[
'name' => 'test_some_metric_bucket',
'labelNames' => ['le'],
'labelValues' => ['+Inf'],
'value' => 2,
],
[
'name' => 'test_some_metric_count',
'labelNames' => [],
'labelValues' => [],
'value' => 2,
],
[
'name' => 'test_some_metric_sum',
'labelNames' => [],
'labelValues' => [],
'value' => 0.14,
],
],
]
),
]
)
);
}
/**
* @test
*/
public function itShouldThrowAnExceptionWhenTheBucketSizesAreNotIncreasing()
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Histogram buckets must be in increasing order');
new Histogram($this->adapter, 'test', 'some_metric', 'this is for testing', [], [1, 1]);
}
/**
* @test
*/
public function itShouldThrowAnExceptionWhenThereIsLessThanOneBucket()
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Histogram must have at least one bucket');
new Histogram($this->adapter, 'test', 'some_metric', 'this is for testing', [], []);
}
/**
* @test
*/
public function itShouldThrowAnExceptionWhenThereIsALabelNamedLe()
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Histogram cannot have a label named');
new Histogram($this->adapter, 'test', 'some_metric', 'this is for testing', ['le'], [1]);
}
/**
* @test
*/
public function itShouldRejectInvalidMetricsNames()
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Invalid metric name');
new Histogram($this->adapter, 'test', 'some invalid metric', 'help', [], [1]);
}
/**
* @test
*/
public function itShouldRejectInvalidLabelNames()
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Invalid label name');
new Histogram($this->adapter, 'test', 'some_metric', 'help', ['invalid label'], [1]);
}
/**
* @test
* @dataProvider labelValuesDataProvider
*
* @param mixed $value The label value
*/
public function isShouldAcceptAnySequenceOfBasicLatinCharactersForLabelValues($value)
{
$label = 'foo';
$histogram = new Histogram($this->adapter, 'test', 'some_metric', 'help', [$label], [1]);
$histogram->observe(1, [$value]);
$metrics = $this->adapter->collect();
$this->assertIsArray($metrics);
$this->assertCount(1, $metrics);
$this->assertContainsOnlyInstancesOf(MetricFamilySamples::class, $metrics);
$metric = reset($metrics);
$samples = $metric->getSamples();
$this->assertContainsOnlyInstancesOf(Sample::class, $samples);
foreach ($samples as $sample) {
$labels = array_combine(
array_merge($metric->getLabelNames(), $sample->getLabelNames()),
$sample->getLabelValues()
);
$this->assertEquals($value, $labels[$label]);
}
}
/**
* @test
*/
public function itShouldBeAbleToGenerateExponentialBucketsGivenSpecificBounds()
{
$start = 0.05;
$growthFactor = 1.5;
$numberOfbuckets = 14;
$generatedBuckets = Histogram::exponentialBuckets($start, $growthFactor, $numberOfbuckets);
$expectedBuckets = [
0.05,
0.075,
0.1125,
0.16875,
0.253125,
0.3796875,
0.56953125,
0.854296875,
1.2814453125,
1.92216796875,
2.883251953125,
4.3248779296875,
6.4873168945313,
9.7309753417969,
];
$this->assertEquals($generatedBuckets, $expectedBuckets);
}
/**
* @return array
* @see isShouldAcceptArbitraryLabelValues
*/
public function labelValuesDataProvider()
{
$cases = [];
// Basic Latin
// See https://en.wikipedia.org/wiki/List_of_Unicode_characters#Basic_Latin
for ($i = 32; $i <= 121; $i++) {
$cases['ASCII code ' . $i] = [chr($i)];
}
return $cases;
}
}
================================================
FILE: tests/Test/Prometheus/InMemory/CollectorRegistryTest.php
================================================
<?php
namespace Test\Prometheus\InMemory;
use Prometheus\Storage\InMemory;
use Test\Prometheus\AbstractCollectorRegistryTest;
class CollectorRegistryTest extends AbstractCollectorRegistryTest
{
public function configureAdapter()
{
$this->adapter = new InMemory();
$this->adapter->flushMemory();
}
}
================================================
FILE: tests/Test/Prometheus/InMemory/CounterTest.php
================================================
<?php
namespace Test\Prometheus\InMemory;
use Prometheus\Storage\InMemory;
use Test\Prometheus\AbstractCounterTest;
/**
* See https://prometheus.io/docs/instrumenting/exposition_formats/
*/
class CounterTest extends AbstractCounterTest
{
public function configureAdapter()
{
$this->adapter = new InMemory();
$this->adapter->flushMemory();
}
}
================================================
FILE: tests/Test/Prometheus/InMemory/GaugeTest.php
================================================
<?php
namespace Test\Prometheus\InMemory;
use Prometheus\Storage\InMemory;
use Test\Prometheus\AbstractGaugeTest;
/**
* See https://prometheus.io/docs/instrumenting/exposition_formats/
*/
class GaugeTest extends AbstractGaugeTest
{
public function configureAdapter()
{
$this->adapter = new InMemory();
$this->adapter->flushMemory();
}
}
================================================
FILE: tests/Test/Prometheus/InMemory/HistogramTest.php
================================================
<?php
namespace Test\Prometheus\InMemory;
use Prometheus\Storage\InMemory;
use Test\Prometheus\AbstractHistogramTest;
/**
* See https://prometheus.io/docs/instrumenting/exposition_formats/
*/
class HistogramTest extends AbstractHistogramTest
{
public function configureAdapter()
{
$this->adapter = new InMemory();
$this->adapter->flushMemory();
}
}
================================================
FILE: tests/Test/Prometheus/PushGatewayTest.php
================================================
<?php
namespace Test\Prometheus;
use GuzzleHttp\Client;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Response;
use PHPUnit\Framework\TestCase;
use Prometheus\CollectorRegistry;
use Prometheus\MetricFamilySamples;
use Prometheus\PushGateway;
class PushGatewayTest extends TestCase
{
/**
* @test
*
* @doesNotPerformAssertions
*/
public function validResponseShouldNotThrowException(): void
{
$mockedCollectorRegistry = $this->createMock(CollectorRegistry::class);
$mockedCollectorRegistry->method('getMetricFamilySamples')->with()->willReturn([
$this->createMock(MetricFamilySamples::class)
]);
$mockHandler = new MockHandler([
new Response(200),
new Response(202),
]);
$handler = HandlerStack::create($mockHandler);
$client = new Client(['handler' => $handler]);
$pushGateway = new PushGateway('http://foo.bar', $client);
$pushGateway->push($mockedCollectorRegistry, 'foo');
}
/**
* @test
*
* @doesNotPerformAnyAssertions
*/
public function invalidResponseShouldThrowRuntimeException(): void
{
$this->expectException(\RuntimeException::class);
$mockedCollectorRegistry = $this->createMock(CollectorRegistry::class);
$mockedCollectorRegistry->method('getMetricFamilySamples')->with()->willReturn([
$this->createMock(MetricFamilySamples::class)
]);
$mockHandler = new MockHandler([
new Response(201),
new Response(300),
]);
$handler = HandlerStack::create($mockHandler);
$client = new Client(['handler' => $handler]);
$pushGateway = new PushGateway('http://foo.bar', $client);
$pushGateway->push($mockedCollectorRegistry, 'foo');
}
/**
* @test
*/
public function clientGetsDefinedIfNotSpecified(): void
{
$this->expectException(\RuntimeException::class);
$mockedCollectorRegistry = $this->createMock(CollectorRegistry::class);
$mockedCollectorRegistry->method('getMetricFamilySamples')->with()->willReturn([
$this->createMock(MetricFamilySamples::class)
]);
$pushGateway = new PushGateway('http://foo.bar');
$pushGateway->push($mockedCollectorRegistry, 'foo');
}
}
================================================
FILE: tests/Test/Prometheus/Redis/CollectorRegistryTest.php
================================================
<?php
namespace Test\Prometheus\Redis;
use Prometheus\Storage\Redis;
use Test\Prometheus\AbstractCollectorRegistryTest;
/**
* @requires extension redis
*/
class CollectorRegistryTest extends AbstractCollectorRegistryTest
{
public function configureAdapter()
{
$this->adapter = new Redis(['host' => REDIS_HOST]);
$this->adapter->flushRedis();
}
}
================================================
FILE: tests/Test/Prometheus/Redis/CounterTest.php
================================================
<?php
namespace Test\Prometheus\Redis;
use Prometheus\Storage\Redis;
use Test\Prometheus\AbstractCounterTest;
/**
* See https://prometheus.io/docs/instrumenting/exposition_formats/
* @requires extension redis
*/
class CounterTest extends AbstractCounterTest
{
public function configureAdapter()
{
$this->adapter = new Redis(['host' => REDIS_HOST]);
$this->adapter->flushRedis();
}
}
================================================
FILE: tests/Test/Prometheus/Redis/CounterWithPrefixTest.php
================================================
<?php
namespace Test\Prometheus\Redis;
use Prometheus\Storage\Redis;
use Test\Prometheus\AbstractCounterTest;
/**
* See https://prometheus.io/docs/instrumenting/exposition_formats/
* @requires extension redis
*/
class CounterWithPrefixTest extends AbstractCounterTest
{
public function configureAdapter()
{
$connection = new \Redis();
$connection->connect(REDIS_HOST);
$connection->setOption(\Redis::OPT_PREFIX, 'prefix:');
$this->adapter = Redis::fromExistingConnection($connection);
$this->adapter->flushRedis();
}
}
================================================
FILE: tests/Test/Prometheus/Redis/GaugeTest.php
================================================
<?php
namespace Test\Prometheus\Redis;
use Prometheus\Storage\Redis;
use Test\Prometheus\AbstractGaugeTest;
/**
* See https://prometheus.io/docs/instrumenting/exposition_formats/
* @requires extension redis
*/
class GaugeTest extends AbstractGaugeTest
{
public function configureAdapter()
{
$this->adapter = new Redis(['host' => REDIS_HOST]);
$this->adapter->flushRedis();
}
}
================================================
FILE: tests/Test/Prometheus/Redis/GaugeWithPrefixTest.php
================================================
<?php
namespace Test\Prometheus\Redis;
use Prometheus\Storage\Redis;
use Test\Prometheus\AbstractGaugeTest;
/**
* See https://prometheus.io/docs/instrumenting/exposition_formats/
* @requires extension redis
*/
class GaugeWithPrefixTest extends AbstractGaugeTest
{
public function configureAdapter()
{
$connection = new \Redis();
$connection->connect(REDIS_HOST);
$connection->setOption(\Redis::OPT_PREFIX, 'prefix:');
$this->adapter = Redis::fromExistingConnection($connection);
$this->adapter->flushRedis();
}
}
================================================
FILE: tests/Test/Prometheus/Redis/HistogramTest.php
================================================
<?php
namespace Test\Prometheus\Redis;
use Prometheus\Storage\Redis;
use Test\Prometheus\AbstractHistogramTest;
/**
* See https://prometheus.io/docs/instrumenting/exposition_formats/
* @requires extension redis
*/
class HistogramTest extends AbstractHistogramTest
{
public function configureAdapter()
{
$this->adapter = new Redis(['host' => REDIS_HOST]);
$this->adapter->flushRedis();
}
}
================================================
FILE: tests/Test/Prometheus/Redis/HistogramWithPrefixTest.php
================================================
<?php
namespace Test\Prometheus\Redis;
use Prometheus\Storage\Redis;
use Test\Prometheus\AbstractHistogramTest;
/**
* See https://prometheus.io/docs/instrumenting/exposition_formats/
* @requires extension redis
*/
class HistogramWithPrefixTest extends AbstractHistogramTest
{
public function configureAdapter()
{
$connection = new \Redis();
$connection->connect(REDIS_HOST);
$connection->setOption(\Redis::OPT_PREFIX, 'prefix:');
$this->adapter = Redis::fromExistingConnection($connection);
$this->adapter->flushRedis();
}
}
================================================
FILE: tests/Test/Prometheus/Storage/RedisTest.php
================================================
<?php
namespace Prometheus\Storage;
use PHPUnit\Framework\TestCase;
use Prometheus\Exception\StorageException;
/**
* @requires extension redis
*/
class RedisTest extends TestCase
{
/**
* @test
*/
public function itShouldThrowAnExceptionOnConnectionFailure()
{
$redis = new Redis(['host' => '/dev/null']);
$this->expectException(StorageException::class);
$this->expectExceptionMessage("Can't connect to Redis server");
$redis->collect();
$redis->flushRedis();
}
/**
* @test
*/
public function itShouldThrowExceptionWhenInjectedRedisIsNotConnected()
{
$connection = new \Redis();
$this->expectException(StorageException::class);
$this->expectExceptionMessage('Connection to Redis server not established');
Redis::fromExistingConnection($connection);
}
}
================================================
FILE: tests/bootstrap.php
================================================
<?php
error_reporting(-1);
date_default_timezone_set('UTC');
$autoload = __DIR__ . '/../vendor/autoload.php';
if (!file_exists($autoload)) {
echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" . PHP_EOL;
echo " You need to execute `composer install` before running the tests. " . PHP_EOL;
echo " Vendors are required for complete test execution. " . PHP_EOL;
echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" . PHP_EOL . PHP_EOL;
exit(1);
}
$loader = require $autoload;
$loader->add('Test\\Prometheus', __DIR__);
define('REDIS_HOST', isset($_SERVER['REDIS_HOST']) ? $_SERVER['REDIS_HOST'] : '127.0.0.1');
gitextract_3xj95wis/
├── .circleci/
│ └── config.yml
├── .gitattributes
├── .gitignore
├── LICENSE
├── README.md
├── composer.json
├── docker-compose.yml
├── examples/
│ ├── flush_adapter.php
│ ├── metrics.php
│ ├── pushgateway.php
│ ├── some_counter.php
│ ├── some_gauge.php
│ └── some_histogram.php
├── nginx/
│ ├── Dockerfile
│ ├── default.conf
│ └── nginx.conf
├── php-fpm/
│ ├── Dockerfile
│ ├── docker-php-ext-apcu-cli.ini
│ └── www.conf
├── phpcs.xml.dist
├── phpunit.xml.dist
├── src/
│ └── Prometheus/
│ ├── Collector.php
│ ├── CollectorRegistry.php
│ ├── Counter.php
│ ├── Exception/
│ │ ├── MetricNotFoundException.php
│ │ ├── MetricsRegistrationException.php
│ │ └── StorageException.php
│ ├── Gauge.php
│ ├── Histogram.php
│ ├── MetricFamilySamples.php
│ ├── PushGateway.php
│ ├── RenderTextFormat.php
│ ├── Sample.php
│ └── Storage/
│ ├── APC.php
│ ├── Adapter.php
│ ├── InMemory.php
│ └── Redis.php
└── tests/
├── Test/
│ ├── BlackBoxPushGatewayTest.php
│ ├── BlackBoxTest.php
│ └── Prometheus/
│ ├── APC/
│ │ ├── CollectorRegistryTest.php
│ │ ├── CounterTest.php
│ │ ├── GaugeTest.php
│ │ └── HistogramTest.php
│ ├── AbstractCollectorRegistryTest.php
│ ├── AbstractCounterTest.php
│ ├── AbstractGaugeTest.php
│ ├── AbstractHistogramTest.php
│ ├── InMemory/
│ │ ├── CollectorRegistryTest.php
│ │ ├── CounterTest.php
│ │ ├── GaugeTest.php
│ │ └── HistogramTest.php
│ ├── PushGatewayTest.php
│ ├── Redis/
│ │ ├── CollectorRegistryTest.php
│ │ ├── CounterTest.php
│ │ ├── CounterWithPrefixTest.php
│ │ ├── GaugeTest.php
│ │ ├── GaugeWithPrefixTest.php
│ │ ├── HistogramTest.php
│ │ └── HistogramWithPrefixTest.php
│ └── Storage/
│ └── RedisTest.php
└── bootstrap.php
SYMBOL INDEX (224 symbols across 39 files)
FILE: src/Prometheus/Collector.php
class Collector (line 10) | abstract class Collector
method __construct (line 41) | public function __construct(Adapter $storageAdapter, $namespace, $name...
method getType (line 61) | abstract public function getType();
method getName (line 66) | public function getName(): string
method getLabelNames (line 74) | public function getLabelNames(): array
method getHelp (line 82) | public function getHelp(): string
method getKey (line 90) | public function getKey(): string
method assertLabelsAreDefinedCorrectly (line 98) | protected function assertLabelsAreDefinedCorrectly($labels): void
FILE: src/Prometheus/CollectorRegistry.php
class CollectorRegistry (line 12) | class CollectorRegistry
method __construct (line 43) | public function __construct(Adapter $redisAdapter)
method getDefault (line 51) | public static function getDefault(): CollectorRegistry
method getMetricFamilySamples (line 62) | public function getMetricFamilySamples(): array
method registerGauge (line 75) | public function registerGauge($namespace, $name, $help, $labels = []):...
method getGauge (line 97) | public function getGauge($namespace, $name): Gauge
method getOrRegisterGauge (line 114) | public function getOrRegisterGauge($namespace, $name, $help, $labels =...
method registerCounter (line 132) | public function registerCounter($namespace, $name, $help, $labels = []...
method getCounter (line 154) | public function getCounter($namespace, $name): Counter
method getOrRegisterCounter (line 171) | public function getOrRegisterCounter($namespace, $name, $help, $labels...
method registerHistogram (line 190) | public function registerHistogram($namespace, $name, $help, $labels = ...
method getHistogram (line 213) | public function getHistogram($namespace, $name): Histogram
method getOrRegisterHistogram (line 231) | public function getOrRegisterHistogram($namespace, $name, $help, $labe...
method metricIdentifier (line 246) | private static function metricIdentifier($namespace, $name): string
FILE: src/Prometheus/Counter.php
class Counter (line 9) | class Counter extends Collector
method getType (line 16) | public function getType(): string
method inc (line 24) | public function inc(array $labels = []): void
method incBy (line 33) | public function incBy($count, array $labels = []): void
FILE: src/Prometheus/Exception/MetricNotFoundException.php
class MetricNotFoundException (line 10) | class MetricNotFoundException extends Exception
FILE: src/Prometheus/Exception/MetricsRegistrationException.php
class MetricsRegistrationException (line 10) | class MetricsRegistrationException extends Exception
FILE: src/Prometheus/Exception/StorageException.php
class StorageException (line 10) | class StorageException extends Exception
FILE: src/Prometheus/Gauge.php
class Gauge (line 9) | class Gauge extends Collector
method set (line 17) | public function set(float $value, array $labels = []): void
method getType (line 37) | public function getType(): string
method inc (line 45) | public function inc($labels = []): void
method incBy (line 54) | public function incBy($value, array $labels = []): void
method dec (line 74) | public function dec($labels = []): void
method decBy (line 83) | public function decBy($value, $labels = []): void
FILE: src/Prometheus/Histogram.php
class Histogram (line 10) | class Histogram extends Collector
method __construct (line 27) | public function __construct(Adapter $adapter, $namespace, $name, $help...
method getDefaultBuckets (line 57) | public static function getDefaultBuckets(): array
method exponentialBuckets (line 70) | public static function exponentialBuckets(float $start, float $growthF...
method observe (line 98) | public function observe(float $value, array $labels = []): void
method getType (line 118) | public function getType(): string
FILE: src/Prometheus/MetricFamilySamples.php
class MetricFamilySamples (line 7) | class MetricFamilySamples
method __construct (line 37) | public function __construct(array $data)
method getName (line 51) | public function getName(): string
method getType (line 59) | public function getType(): string
method getHelp (line 67) | public function getHelp(): string
method getSamples (line 75) | public function getSamples(): array
method getLabelNames (line 83) | public function getLabelNames(): array
method hasLabelNames (line 91) | public function hasLabelNames(): bool
FILE: src/Prometheus/PushGateway.php
class PushGateway (line 12) | class PushGateway
method __construct (line 29) | public function __construct($address, ClientInterface $client = null)
method push (line 43) | public function push(CollectorRegistry $collectorRegistry, string $job...
method pushAdd (line 56) | public function pushAdd(CollectorRegistry $collectorRegistry, string $...
method delete (line 68) | public function delete(string $job, array $groupingKey = []): void
method doRequest (line 80) | private function doRequest(CollectorRegistry $collectorRegistry, strin...
FILE: src/Prometheus/RenderTextFormat.php
class RenderTextFormat (line 7) | class RenderTextFormat
method render (line 15) | public function render(array $metrics): string
method renderSample (line 38) | private function renderSample(MetricFamilySamples $metric, Sample $sam...
method escapeLabelValue (line 57) | private function escapeLabelValue($v): string
FILE: src/Prometheus/Sample.php
class Sample (line 7) | class Sample
method __construct (line 33) | public function __construct(array $data)
method getName (line 44) | public function getName(): string
method getLabelNames (line 52) | public function getLabelNames(): array
method getLabelValues (line 60) | public function getLabelValues(): array
method getValue (line 68) | public function getValue(): string
method hasLabelNames (line 76) | public function hasLabelNames(): bool
FILE: src/Prometheus/Storage/APC.php
class APC (line 11) | class APC implements Adapter
method collect (line 18) | public function collect(): array
method updateHistogram (line 29) | public function updateHistogram(array $data): void
method updateGauge (line 65) | public function updateGauge(array $data): void
method updateCounter (line 88) | public function updateCounter(array $data): void
method flushAPC (line 100) | public function flushAPC(): void
method metaKey (line 109) | private function metaKey(array $data): string
method valueKey (line 118) | private function valueKey(array $data): string
method histogramBucketValueKey (line 133) | private function histogramBucketValueKey(array $data, $bucket): string
method metaData (line 149) | private function metaData(array $data): array
method collectCounters (line 161) | private function collectCounters(): array
method collectGauges (line 191) | private function collectGauges(): array
method collectHistograms (line 222) | private function collectHistograms(): array
method toInteger (line 298) | private function toInteger($val): int
method fromInteger (line 307) | private function fromInteger($val): float
method sortSamples (line 315) | private function sortSamples(array &$samples): void
method encodeLabelValues (line 327) | private function encodeLabelValues(array $values): string
method decodeLabelValues (line 341) | private function decodeLabelValues($values): array
FILE: src/Prometheus/Storage/Adapter.php
type Adapter (line 9) | interface Adapter
method collect (line 18) | public function collect();
method updateHistogram (line 24) | public function updateHistogram(array $data): void;
method updateGauge (line 30) | public function updateGauge(array $data): void;
method updateCounter (line 36) | public function updateCounter(array $data): void;
FILE: src/Prometheus/Storage/InMemory.php
class InMemory (line 10) | class InMemory implements Adapter
method collect (line 20) | public function collect(): array
method flushMemory (line 28) | public function flushMemory(): void
method collectHistograms (line 38) | private function collectHistograms(): array
method internalCollect (line 114) | private function internalCollect(array $metrics): array
method updateHistogram (line 145) | public function updateHistogram(array $data): void
method updateGauge (line 181) | public function updateGauge(array $data): void
method updateCounter (line 204) | public function updateCounter(array $data): void
method histogramBucketValueKey (line 229) | private function histogramBucketValueKey(array $data, $bucket): string
method metaKey (line 244) | private function metaKey(array $data): string
method valueKey (line 258) | private function valueKey(array $data): string
method metaData (line 273) | private function metaData(array $data): array
method sortSamples (line 285) | private function sortSamples(array &$samples): void
method encodeLabelValues (line 297) | private function encodeLabelValues(array $values): string
method decodeLabelValues (line 311) | private function decodeLabelValues($values): array
FILE: src/Prometheus/Storage/Redis.php
class Redis (line 14) | class Redis implements Adapter
method __construct (line 54) | public function __construct(array $options = [])
method fromExistingConnection (line 60) | public static function fromExistingConnection(\Redis $redis): self
method setDefaultOptions (line 76) | public static function setDefaultOptions(array $options): void
method setPrefix (line 84) | public static function setPrefix($prefix): void
method flushRedis (line 92) | public function flushRedis(): void
method collect (line 102) | public function collect(): array
method openConnection (line 119) | private function openConnection(): void
method connectToServer (line 144) | private function connectToServer(): bool
method updateHistogram (line 165) | public function updateHistogram(array $data): void
method updateGauge (line 205) | public function updateGauge(array $data): void
method updateCounter (line 245) | public function updateCounter(array $data): void
method collectHistograms (line 277) | private function collectHistograms(): array
method collectGauges (line 354) | private function collectGauges(): array
method collectCounters (line 383) | private function collectCounters(): array
method getRedisCommand (line 413) | private function getRedisCommand(int $cmd): string
method toMetricKey (line 431) | private function toMetricKey(array $data): string
FILE: tests/Test/BlackBoxPushGatewayTest.php
class BlackBoxPushGatewayTest (line 11) | class BlackBoxPushGatewayTest extends TestCase
method pushGatewayShouldWork (line 16) | public function pushGatewayShouldWork()
FILE: tests/Test/BlackBoxTest.php
class BlackBoxTest (line 9) | class BlackBoxTest extends TestCase
method setUp (line 21) | public function setUp()
method gaugesShouldBeOverwritten (line 31) | public function gaugesShouldBeOverwritten()
method countersShouldIncrementAtomically (line 61) | public function countersShouldIncrementAtomically()
method histogramsShouldIncrementAtomically (line 84) | public function histogramsShouldIncrementAtomically()
FILE: tests/Test/Prometheus/APC/CollectorRegistryTest.php
class CollectorRegistryTest (line 11) | class CollectorRegistryTest extends AbstractCollectorRegistryTest
method configureAdapter (line 14) | public function configureAdapter()
FILE: tests/Test/Prometheus/APC/CounterTest.php
class CounterTest (line 12) | class CounterTest extends AbstractCounterTest
method configureAdapter (line 14) | public function configureAdapter()
FILE: tests/Test/Prometheus/APC/GaugeTest.php
class GaugeTest (line 12) | class GaugeTest extends AbstractGaugeTest
method configureAdapter (line 14) | public function configureAdapter()
FILE: tests/Test/Prometheus/APC/HistogramTest.php
class HistogramTest (line 12) | class HistogramTest extends AbstractHistogramTest
method configureAdapter (line 14) | public function configureAdapter()
FILE: tests/Test/Prometheus/AbstractCollectorRegistryTest.php
class AbstractCollectorRegistryTest (line 14) | abstract class AbstractCollectorRegistryTest extends TestCase
method setUp (line 26) | public function setUp(): void
method itShouldSaveGauges (line 35) | public function itShouldSaveGauges()
method itShouldSaveCounters (line 66) | public function itShouldSaveCounters()
method itShouldSaveHistograms (line 92) | public function itShouldSaveHistograms()
method itShouldSaveHistogramsWithoutLabels (line 145) | public function itShouldSaveHistogramsWithoutLabels()
method itShouldIncreaseACounterWithoutNamespace (line 186) | public function itShouldIncreaseACounterWithoutNamespace()
method itShouldForbidRegisteringTheSameCounterTwice (line 210) | public function itShouldForbidRegisteringTheSameCounterTwice()
method itShouldForbidRegisteringTheSameCounterWithDifferentLabels (line 222) | public function itShouldForbidRegisteringTheSameCounterWithDifferentLa...
method itShouldForbidRegisteringTheSameHistogramTwice (line 234) | public function itShouldForbidRegisteringTheSameHistogramTwice()
method itShouldForbidRegisteringTheSameHistogramWithDifferentLabels (line 246) | public function itShouldForbidRegisteringTheSameHistogramWithDifferent...
method itShouldForbidRegisteringTheSameGaugeTwice (line 258) | public function itShouldForbidRegisteringTheSameGaugeTwice()
method itShouldForbidRegisteringTheSameGaugeWithDifferentLabels (line 270) | public function itShouldForbidRegisteringTheSameGaugeWithDifferentLabe...
method itShouldThrowAnExceptionWhenGettingANonExistentMetric (line 282) | public function itShouldThrowAnExceptionWhenGettingANonExistentMetric()
method itShouldNotRegisterACounterTwice (line 293) | public function itShouldNotRegisterACounterTwice()
method itShouldNotRegisterAGaugeTwice (line 305) | public function itShouldNotRegisterAGaugeTwice()
method itShouldNotRegisterAHistogramTwice (line 317) | public function itShouldNotRegisterAHistogramTwice()
method configureAdapter (line 327) | abstract public function configureAdapter();
FILE: tests/Test/Prometheus/AbstractCounterTest.php
class AbstractCounterTest (line 15) | abstract class AbstractCounterTest extends TestCase
method setUp (line 22) | public function setUp(): void
method configureAdapter (line 27) | abstract public function configureAdapter();
method itShouldIncreaseWithLabels (line 32) | public function itShouldIncreaseWithLabels()
method itShouldIncreaseWithoutLabelWhenNoLabelsAreDefined (line 66) | public function itShouldIncreaseWithoutLabelWhenNoLabelsAreDefined()
method itShouldIncreaseTheCounterByAnArbitraryInteger (line 98) | public function itShouldIncreaseTheCounterByAnArbitraryInteger()
method itShouldRejectInvalidMetricsNames (line 131) | public function itShouldRejectInvalidMetricsNames()
method itShouldRejectInvalidLabelNames (line 140) | public function itShouldRejectInvalidLabelNames()
method isShouldAcceptAnySequenceOfBasicLatinCharactersForLabelValues (line 152) | public function isShouldAcceptAnySequenceOfBasicLatinCharactersForLabe...
method labelValuesDataProvider (line 180) | public function labelValuesDataProvider()
FILE: tests/Test/Prometheus/AbstractGaugeTest.php
class AbstractGaugeTest (line 15) | abstract class AbstractGaugeTest extends TestCase
method setUp (line 22) | public function setUp(): void
method configureAdapter (line 27) | abstract public function configureAdapter();
method itShouldAllowSetWithLabels (line 32) | public function itShouldAllowSetWithLabels()
method itShouldAllowSetWithoutLabelWhenNoLabelsAreDefined (line 66) | public function itShouldAllowSetWithoutLabelWhenNoLabelsAreDefined()
method itShouldAllowSetWithAFloatValue (line 100) | public function itShouldAllowSetWithAFloatValue()
method itShouldIncrementAValue (line 134) | public function itShouldIncrementAValue()
method itShouldIncrementWithFloatValue (line 167) | public function itShouldIncrementWithFloatValue()
method itShouldDecrementAValue (line 200) | public function itShouldDecrementAValue()
method itShouldDecrementWithFloatValue (line 233) | public function itShouldDecrementWithFloatValue()
method itShouldOverwriteWhenSettingTwice (line 266) | public function itShouldOverwriteWhenSettingTwice()
method itShouldRejectInvalidMetricsNames (line 299) | public function itShouldRejectInvalidMetricsNames()
method itShouldRejectInvalidLabelNames (line 308) | public function itShouldRejectInvalidLabelNames()
method isShouldAcceptAnySequenceOfBasicLatinCharactersForLabelValues (line 320) | public function isShouldAcceptAnySequenceOfBasicLatinCharactersForLabe...
method labelValuesDataProvider (line 348) | public function labelValuesDataProvider()
FILE: tests/Test/Prometheus/AbstractHistogramTest.php
class AbstractHistogramTest (line 15) | abstract class AbstractHistogramTest extends TestCase
method setUp (line 22) | public function setUp(): void
method configureAdapter (line 27) | abstract public function configureAdapter();
method itShouldObserveWithLabels (line 32) | public function itShouldObserveWithLabels()
method itShouldObserveWithoutLabelWhenNoLabelsAreDefined (line 102) | public function itShouldObserveWithoutLabelWhenNoLabelsAreDefined()
method itShouldObserveValuesOfTypeDouble (line 171) | public function itShouldObserveValuesOfTypeDouble()
method itShouldProvideDefaultBuckets (line 241) | public function itShouldProvideDefaultBuckets()
method itShouldThrowAnExceptionWhenTheBucketSizesAreNotIncreasing (line 378) | public function itShouldThrowAnExceptionWhenTheBucketSizesAreNotIncrea...
method itShouldThrowAnExceptionWhenThereIsLessThanOneBucket (line 388) | public function itShouldThrowAnExceptionWhenThereIsLessThanOneBucket()
method itShouldThrowAnExceptionWhenThereIsALabelNamedLe (line 398) | public function itShouldThrowAnExceptionWhenThereIsALabelNamedLe()
method itShouldRejectInvalidMetricsNames (line 408) | public function itShouldRejectInvalidMetricsNames()
method itShouldRejectInvalidLabelNames (line 418) | public function itShouldRejectInvalidLabelNames()
method isShouldAcceptAnySequenceOfBasicLatinCharactersForLabelValues (line 431) | public function isShouldAcceptAnySequenceOfBasicLatinCharactersForLabe...
method itShouldBeAbleToGenerateExponentialBucketsGivenSpecificBounds (line 458) | public function itShouldBeAbleToGenerateExponentialBucketsGivenSpecifi...
method labelValuesDataProvider (line 490) | public function labelValuesDataProvider()
FILE: tests/Test/Prometheus/InMemory/CollectorRegistryTest.php
class CollectorRegistryTest (line 8) | class CollectorRegistryTest extends AbstractCollectorRegistryTest
method configureAdapter (line 10) | public function configureAdapter()
FILE: tests/Test/Prometheus/InMemory/CounterTest.php
class CounterTest (line 11) | class CounterTest extends AbstractCounterTest
method configureAdapter (line 14) | public function configureAdapter()
FILE: tests/Test/Prometheus/InMemory/GaugeTest.php
class GaugeTest (line 11) | class GaugeTest extends AbstractGaugeTest
method configureAdapter (line 14) | public function configureAdapter()
FILE: tests/Test/Prometheus/InMemory/HistogramTest.php
class HistogramTest (line 11) | class HistogramTest extends AbstractHistogramTest
method configureAdapter (line 14) | public function configureAdapter()
FILE: tests/Test/Prometheus/PushGatewayTest.php
class PushGatewayTest (line 14) | class PushGatewayTest extends TestCase
method validResponseShouldNotThrowException (line 21) | public function validResponseShouldNotThrowException(): void
method invalidResponseShouldThrowRuntimeException (line 44) | public function invalidResponseShouldThrowRuntimeException(): void
method clientGetsDefinedIfNotSpecified (line 67) | public function clientGetsDefinedIfNotSpecified(): void
FILE: tests/Test/Prometheus/Redis/CollectorRegistryTest.php
class CollectorRegistryTest (line 11) | class CollectorRegistryTest extends AbstractCollectorRegistryTest
method configureAdapter (line 13) | public function configureAdapter()
FILE: tests/Test/Prometheus/Redis/CounterTest.php
class CounterTest (line 12) | class CounterTest extends AbstractCounterTest
method configureAdapter (line 14) | public function configureAdapter()
FILE: tests/Test/Prometheus/Redis/CounterWithPrefixTest.php
class CounterWithPrefixTest (line 12) | class CounterWithPrefixTest extends AbstractCounterTest
method configureAdapter (line 14) | public function configureAdapter()
FILE: tests/Test/Prometheus/Redis/GaugeTest.php
class GaugeTest (line 12) | class GaugeTest extends AbstractGaugeTest
method configureAdapter (line 14) | public function configureAdapter()
FILE: tests/Test/Prometheus/Redis/GaugeWithPrefixTest.php
class GaugeWithPrefixTest (line 12) | class GaugeWithPrefixTest extends AbstractGaugeTest
method configureAdapter (line 14) | public function configureAdapter()
FILE: tests/Test/Prometheus/Redis/HistogramTest.php
class HistogramTest (line 12) | class HistogramTest extends AbstractHistogramTest
method configureAdapter (line 14) | public function configureAdapter()
FILE: tests/Test/Prometheus/Redis/HistogramWithPrefixTest.php
class HistogramWithPrefixTest (line 12) | class HistogramWithPrefixTest extends AbstractHistogramTest
method configureAdapter (line 14) | public function configureAdapter()
FILE: tests/Test/Prometheus/Storage/RedisTest.php
class RedisTest (line 11) | class RedisTest extends TestCase
method itShouldThrowAnExceptionOnConnectionFailure (line 16) | public function itShouldThrowAnExceptionOnConnectionFailure()
method itShouldThrowExceptionWhenInjectedRedisIsNotConnected (line 30) | public function itShouldThrowExceptionWhenInjectedRedisIsNotConnected()
Condensed preview — 61 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (161K chars).
[
{
"path": ".circleci/config.yml",
"chars": 3286,
"preview": "unit-config: &unit-config\n environment:\n COMPOSER_PREFER_LOWEST: 0\n steps:\n - checkout\n\n - run:\n name:"
},
{
"path": ".gitattributes",
"chars": 386,
"preview": "* text=auto\n/.circleci/ export-ignore\n/.github/ export-ignore\n/examples/ export-ignore\n/nginx"
},
{
"path": ".gitignore",
"chars": 73,
"preview": "/vendor/\n*.iml\n/.idea/\ncomposer.lock\ncomposer.phar\n.phpunit.result.cache\n"
},
{
"path": "LICENSE",
"chars": 11340,
"preview": " Apache License\n Version 2.0, January 2004\n "
},
{
"path": "README.md",
"chars": 4216,
"preview": "# A prometheus client library written in PHP\n\n[ {\n defin"
},
{
"path": "examples/metrics.php",
"chars": 722,
"preview": "<?php\n\nrequire __DIR__ . '/../vendor/autoload.php';\n\nuse Prometheus\\CollectorRegistry;\nuse Prometheus\\RenderTextFormat;\n"
},
{
"path": "examples/pushgateway.php",
"chars": 785,
"preview": "<?php\n\nrequire __DIR__ . '/../vendor/autoload.php';\n\nuse Prometheus\\CollectorRegistry;\nuse Prometheus\\PushGateway;\nuse P"
},
{
"path": "examples/some_counter.php",
"chars": 659,
"preview": "<?php\n\nrequire __DIR__ . '/../vendor/autoload.php';\n\nuse Prometheus\\CollectorRegistry;\nuse Prometheus\\Storage\\Redis;\n\n$a"
},
{
"path": "examples/some_gauge.php",
"chars": 676,
"preview": "<?php\n\nrequire __DIR__ . '/../vendor/autoload.php';\n\nuse Prometheus\\CollectorRegistry;\nuse Prometheus\\Storage\\Redis;\n\n\ne"
},
{
"path": "examples/some_histogram.php",
"chars": 735,
"preview": "<?php\n\nrequire __DIR__ . '/../vendor/autoload.php';\n\nuse Prometheus\\CollectorRegistry;\nuse Prometheus\\Storage\\Redis;\n\ner"
},
{
"path": "nginx/Dockerfile",
"chars": 137,
"preview": "FROM nginx\n\nRUN rm /etc/nginx/conf.d/default.conf /etc/nginx/nginx.conf\nCOPY nginx.conf /etc/nginx/\nCOPY default.conf /e"
},
{
"path": "nginx/default.conf",
"chars": 417,
"preview": "server {\n listen 80;\n\n root /var/www/html;\n\n error_log /var/log/nginx/localhost.error.log;\n access_log /var/"
},
{
"path": "nginx/nginx.conf",
"chars": 642,
"preview": "user nginx;\nworker_processes 1;\n\nerror_log /var/log/nginx/error.log warn;\npid /var/run/nginx.pid;\n\n\nevents {\n "
},
{
"path": "php-fpm/Dockerfile",
"chars": 238,
"preview": "FROM php:5.6-fpm\n\nRUN pecl install redis-2.2.8 && docker-php-ext-enable redis\nRUN pecl install apcu-4.0.11 && docker-php"
},
{
"path": "php-fpm/docker-php-ext-apcu-cli.ini",
"chars": 20,
"preview": "apc.enable_cli = On\n"
},
{
"path": "php-fpm/www.conf",
"chars": 96,
"preview": "[www]\nuser = www-data\ngroup = www-data\nlisten = 127.0.0.1:9000\npm = static\npm.max_children = 20\n"
},
{
"path": "phpcs.xml.dist",
"chars": 253,
"preview": "<?xml version=\"1.0\"?>\n<ruleset name=\"PHP_CodeSniffer\">\n <file>src/</file>\n <file>tests/</file>\n <file>examples/"
},
{
"path": "phpunit.xml.dist",
"chars": 734,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit backupGlobals=\"false\"\n backupStaticAttributes=\"false\"\n b"
},
{
"path": "src/Prometheus/Collector.php",
"chars": 2243,
"preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Prometheus;\n\nuse InvalidArgumentException;\nuse Prometheus\\Storage\\Adapter;\n\na"
},
{
"path": "src/Prometheus/CollectorRegistry.php",
"chars": 7628,
"preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Prometheus;\n\nuse Prometheus\\Exception\\MetricNotFoundException;\nuse Prometheus"
},
{
"path": "src/Prometheus/Counter.php",
"chars": 1084,
"preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Prometheus;\n\nuse Prometheus\\Storage\\Adapter;\n\nclass Counter extends Collector"
},
{
"path": "src/Prometheus/Exception/MetricNotFoundException.php",
"chars": 190,
"preview": "<?php\n\nnamespace Prometheus\\Exception;\n\nuse Exception;\n\n/**\n * Exception thrown if a metric can't be found in the Collec"
},
{
"path": "src/Prometheus/Exception/MetricsRegistrationException.php",
"chars": 190,
"preview": "<?php\n\nnamespace Prometheus\\Exception;\n\nuse Exception;\n\n/**\n * Exception thrown if an error occurs during metrics regist"
},
{
"path": "src/Prometheus/Exception/StorageException.php",
"chars": 173,
"preview": "<?php\n\nnamespace Prometheus\\Exception;\n\nuse Exception;\n\n/**\n * Exception thrown if an error occurs during metrics storag"
},
{
"path": "src/Prometheus/Gauge.php",
"chars": 1955,
"preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Prometheus;\n\nuse Prometheus\\Storage\\Adapter;\n\nclass Gauge extends Collector\n{"
},
{
"path": "src/Prometheus/Histogram.php",
"chars": 3329,
"preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Prometheus;\n\nuse InvalidArgumentException;\nuse Prometheus\\Storage\\Adapter;\n\nc"
},
{
"path": "src/Prometheus/MetricFamilySamples.php",
"chars": 1484,
"preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Prometheus;\n\nclass MetricFamilySamples\n{\n /**\n * @var mixed\n */\n "
},
{
"path": "src/Prometheus/PushGateway.php",
"chars": 3267,
"preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Prometheus;\n\nuse GuzzleHttp\\Client;\nuse GuzzleHttp\\ClientInterface;\nuse Guzzl"
},
{
"path": "src/Prometheus/RenderTextFormat.php",
"chars": 1949,
"preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Prometheus;\n\nclass RenderTextFormat\n{\n const MIME_TYPE = 'text/plain; vers"
},
{
"path": "src/Prometheus/Sample.php",
"chars": 1263,
"preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Prometheus;\n\nclass Sample\n{\n /**\n * @var string\n */\n private $n"
},
{
"path": "src/Prometheus/Storage/APC.php",
"chars": 11268,
"preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Prometheus\\Storage;\n\nuse APCUIterator;\nuse Prometheus\\MetricFamilySamples;\nus"
},
{
"path": "src/Prometheus/Storage/Adapter.php",
"chars": 666,
"preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Prometheus\\Storage;\n\nuse Prometheus\\MetricFamilySamples;\n\ninterface Adapter\n{"
},
{
"path": "src/Prometheus/Storage/InMemory.php",
"chars": 9959,
"preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Prometheus\\Storage;\n\nuse Prometheus\\MetricFamilySamples;\nuse RuntimeException"
},
{
"path": "src/Prometheus/Storage/Redis.php",
"chars": 12984,
"preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Prometheus\\Storage;\n\nuse InvalidArgumentException;\nuse Prometheus\\Counter;\nus"
},
{
"path": "tests/Test/BlackBoxPushGatewayTest.php",
"chars": 1415,
"preview": "<?php\n\nnamespace Test;\n\nuse GuzzleHttp\\Client;\nuse PHPUnit\\Framework\\TestCase;\nuse Prometheus\\CollectorRegistry;\nuse Pro"
},
{
"path": "tests/Test/BlackBoxTest.php",
"chars": 4459,
"preview": "<?php\n\nnamespace Test;\n\nuse GuzzleHttp\\Client;\nuse GuzzleHttp\\Promise;\nuse PHPUnit\\Framework\\TestCase;\n\nclass BlackBoxTe"
},
{
"path": "tests/Test/Prometheus/APC/CollectorRegistryTest.php",
"chars": 348,
"preview": "<?php\n\nnamespace Test\\Prometheus\\APC;\n\nuse Prometheus\\Storage\\APC;\nuse Test\\Prometheus\\AbstractCollectorRegistryTest;\n\n/"
},
{
"path": "tests/Test/Prometheus/APC/CounterTest.php",
"chars": 385,
"preview": "<?php\n\nnamespace Test\\Prometheus\\APC;\n\nuse Prometheus\\Storage\\APC;\nuse Test\\Prometheus\\AbstractCounterTest;\n\n/**\n * See "
},
{
"path": "tests/Test/Prometheus/APC/GaugeTest.php",
"chars": 379,
"preview": "<?php\n\nnamespace Test\\Prometheus\\APC;\n\nuse Prometheus\\Storage\\APC;\nuse Test\\Prometheus\\AbstractGaugeTest;\n\n/**\n * See ht"
},
{
"path": "tests/Test/Prometheus/APC/HistogramTest.php",
"chars": 391,
"preview": "<?php\n\nnamespace Test\\Prometheus\\APC;\n\nuse Prometheus\\Storage\\APC;\nuse Test\\Prometheus\\AbstractHistogramTest;\n\n/**\n * Se"
},
{
"path": "tests/Test/Prometheus/AbstractCollectorRegistryTest.php",
"chars": 10374,
"preview": "<?php\n\nnamespace Test\\Prometheus;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Prometheus\\CollectorRegistry;\nuse Prometheus\\Hist"
},
{
"path": "tests/Test/Prometheus/AbstractCounterTest.php",
"chars": 5991,
"preview": "<?php\n\nnamespace Test\\Prometheus;\n\nuse InvalidArgumentException;\nuse PHPUnit\\Framework\\TestCase;\nuse Prometheus\\Counter;"
},
{
"path": "tests/Test/Prometheus/AbstractGaugeTest.php",
"chars": 12178,
"preview": "<?php\n\nnamespace Test\\Prometheus;\n\nuse InvalidArgumentException;\nuse PHPUnit\\Framework\\TestCase;\nuse Prometheus\\Gauge;\nu"
},
{
"path": "tests/Test/Prometheus/AbstractHistogramTest.php",
"chars": 19273,
"preview": "<?php\n\nnamespace Test\\Prometheus;\n\nuse InvalidArgumentException;\nuse PHPUnit\\Framework\\TestCase;\nuse Prometheus\\Histogra"
},
{
"path": "tests/Test/Prometheus/InMemory/CollectorRegistryTest.php",
"chars": 330,
"preview": "<?php\n\nnamespace Test\\Prometheus\\InMemory;\n\nuse Prometheus\\Storage\\InMemory;\nuse Test\\Prometheus\\AbstractCollectorRegist"
},
{
"path": "tests/Test/Prometheus/InMemory/CounterTest.php",
"chars": 377,
"preview": "<?php\n\nnamespace Test\\Prometheus\\InMemory;\n\nuse Prometheus\\Storage\\InMemory;\nuse Test\\Prometheus\\AbstractCounterTest;\n\n/"
},
{
"path": "tests/Test/Prometheus/InMemory/GaugeTest.php",
"chars": 371,
"preview": "<?php\n\nnamespace Test\\Prometheus\\InMemory;\n\nuse Prometheus\\Storage\\InMemory;\nuse Test\\Prometheus\\AbstractGaugeTest;\n\n/**"
},
{
"path": "tests/Test/Prometheus/InMemory/HistogramTest.php",
"chars": 383,
"preview": "<?php\n\nnamespace Test\\Prometheus\\InMemory;\n\nuse Prometheus\\Storage\\InMemory;\nuse Test\\Prometheus\\AbstractHistogramTest;\n"
},
{
"path": "tests/Test/Prometheus/PushGatewayTest.php",
"chars": 2396,
"preview": "<?php\n\nnamespace Test\\Prometheus;\n\nuse GuzzleHttp\\Client;\nuse GuzzleHttp\\Handler\\MockHandler;\nuse GuzzleHttp\\HandlerStac"
},
{
"path": "tests/Test/Prometheus/Redis/CollectorRegistryTest.php",
"chars": 379,
"preview": "<?php\n\nnamespace Test\\Prometheus\\Redis;\n\nuse Prometheus\\Storage\\Redis;\nuse Test\\Prometheus\\AbstractCollectorRegistryTest"
},
{
"path": "tests/Test/Prometheus/Redis/CounterTest.php",
"chars": 417,
"preview": "<?php\n\nnamespace Test\\Prometheus\\Redis;\n\nuse Prometheus\\Storage\\Redis;\nuse Test\\Prometheus\\AbstractCounterTest;\n\n/**\n * "
},
{
"path": "tests/Test/Prometheus/Redis/CounterWithPrefixTest.php",
"chars": 579,
"preview": "<?php\n\nnamespace Test\\Prometheus\\Redis;\n\nuse Prometheus\\Storage\\Redis;\nuse Test\\Prometheus\\AbstractCounterTest;\n\n/**\n * "
},
{
"path": "tests/Test/Prometheus/Redis/GaugeTest.php",
"chars": 411,
"preview": "<?php\n\nnamespace Test\\Prometheus\\Redis;\n\nuse Prometheus\\Storage\\Redis;\nuse Test\\Prometheus\\AbstractGaugeTest;\n\n/**\n * Se"
},
{
"path": "tests/Test/Prometheus/Redis/GaugeWithPrefixTest.php",
"chars": 573,
"preview": "<?php\n\nnamespace Test\\Prometheus\\Redis;\n\nuse Prometheus\\Storage\\Redis;\nuse Test\\Prometheus\\AbstractGaugeTest;\n\n/**\n * Se"
},
{
"path": "tests/Test/Prometheus/Redis/HistogramTest.php",
"chars": 423,
"preview": "<?php\n\nnamespace Test\\Prometheus\\Redis;\n\nuse Prometheus\\Storage\\Redis;\nuse Test\\Prometheus\\AbstractHistogramTest;\n\n/**\n "
},
{
"path": "tests/Test/Prometheus/Redis/HistogramWithPrefixTest.php",
"chars": 585,
"preview": "<?php\n\nnamespace Test\\Prometheus\\Redis;\n\nuse Prometheus\\Storage\\Redis;\nuse Test\\Prometheus\\AbstractHistogramTest;\n\n/**\n "
},
{
"path": "tests/Test/Prometheus/Storage/RedisTest.php",
"chars": 886,
"preview": "<?php\n\nnamespace Prometheus\\Storage;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Prometheus\\Exception\\StorageException;\n\n/**\n *"
},
{
"path": "tests/bootstrap.php",
"chars": 673,
"preview": "<?php\n\nerror_reporting(-1);\ndate_default_timezone_set('UTC');\n$autoload = __DIR__ . '/../vendor/autoload.php';\nif (!file"
}
]
About this extraction
This page contains the full source code of the endclothing/prometheus_client_php GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 61 files (147.6 KB), approximately 36.3k tokens, and a symbol index with 224 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.