master b0931c73f920 cached
61 files
147.6 KB
36.3k tokens
224 symbols
1 requests
Download .txt
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

[![CircleCI](https://circleci.com/gh/endclothing/prometheus_client_php/tree/master.svg?style=shield)](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');
Download .txt
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
Download .txt
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[![CircleCI](https://circleci.com/gh/endclothing/prometheus_client_php/tre"
  },
  {
    "path": "composer.json",
    "chars": 1143,
    "preview": "{\n    \"name\": \"endclothing/prometheus_client_php\",\n    \"description\": \"Prometheus instrumentation library for PHP applic"
  },
  {
    "path": "docker-compose.yml",
    "chars": 472,
    "preview": "nginx:\n  build: nginx/\n  links:\n    - php-fpm\n  ports:\n    - 8080:80\n\nphp-fpm:\n  build: php-fpm/\n  volumes:\n    - .:/var"
  },
  {
    "path": "examples/flush_adapter.php",
    "chars": 528,
    "preview": "<?php\n\nrequire __DIR__ . '/../vendor/autoload.php';\n\n$adapter = $_GET['adapter'];\n\nif ($adapter === 'redis') {\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.

Copied to clipboard!