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