From 348ac83ed0f8558623872ea725a68ad146c07f1d Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Fri, 3 May 2024 16:57:19 +0200 Subject: [PATCH] First commit --- .github/FUNDING.yml | 1 + .github/workflows/main.yml | 48 +++ .gitignore | 9 + .php-cs-fixer.dist.php | 19 + LICENSE | 203 +++++++++++ NOTICE | 7 + README.md | 19 + composer.json | 30 ++ .../danog/BetterPrometheus/BetterCollector.md | 60 ++++ .../BetterCollectorRegistry.md | 307 ++++++++++++++++ .../danog/BetterPrometheus/BetterCounter.md | 85 +++++ .../danog/BetterPrometheus/BetterGauge.md | 128 +++++++ .../danog/BetterPrometheus/BetterHistogram.md | 84 +++++ .../danog/BetterPrometheus/BetterSummary.md | 87 +++++ docs/docs/index.md | 25 ++ docs/index.md | 1 + lib/BetterCollector.php | 54 +++ lib/BetterCollectorRegistry.php | 330 ++++++++++++++++++ lib/BetterCounter.php | 57 +++ lib/BetterGauge.php | 92 +++++ lib/BetterHistogram.php | 102 ++++++ lib/BetterSummary.php | 117 +++++++ psalm.xml | 17 + 23 files changed, 1882 insertions(+) create mode 100644 .github/FUNDING.yml create mode 100644 .github/workflows/main.yml create mode 100644 .gitignore create mode 100644 .php-cs-fixer.dist.php create mode 100644 LICENSE create mode 100644 NOTICE create mode 100644 README.md create mode 100644 composer.json create mode 100644 docs/docs/danog/BetterPrometheus/BetterCollector.md create mode 100644 docs/docs/danog/BetterPrometheus/BetterCollectorRegistry.md create mode 100644 docs/docs/danog/BetterPrometheus/BetterCounter.md create mode 100644 docs/docs/danog/BetterPrometheus/BetterGauge.md create mode 100644 docs/docs/danog/BetterPrometheus/BetterHistogram.md create mode 100644 docs/docs/danog/BetterPrometheus/BetterSummary.md create mode 100644 docs/docs/index.md create mode 120000 docs/index.md create mode 100644 lib/BetterCollector.php create mode 100644 lib/BetterCollectorRegistry.php create mode 100644 lib/BetterCounter.php create mode 100644 lib/BetterGauge.php create mode 100644 lib/BetterHistogram.php create mode 100644 lib/BetterSummary.php create mode 100644 psalm.xml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..72fc5ff --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: danog diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..5dfbd0d --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,48 @@ +name: build +on: + pull_request: + push: +jobs: + run: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] + php-versions: ["8.2", "8.3"] + name: PHP ${{ matrix.php-versions }} Test on ${{ matrix.os }} + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + extensions: mbstring, intl, sockets + coverage: xdebug + + - name: Check environment + run: | + php --version + composer --version + + - name: Get composer cache directory + id: composercache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache dependencies + uses: actions/cache@v2 + with: + path: ${{ steps.composercache.outputs.dir }} + key: ${{ matrix.os }}-composer-${{ matrix.php-versions }}-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ matrix.os }}-composer-${{ matrix.php-versions }}- + + - name: Run codestyle check + env: + PHP_CS_FIXER_IGNORE_ENV: 1 + run: | + vendor/bin/php-cs-fixer --diff --dry-run -v fix + + - name: Run Psalm analysis + run: | + vendor/bin/psalm.phar --shepherd diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d2f34cd --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +.vscode +build +composer.lock +phpunit.xml +vendor +.php_cs.cache +coverage +.phpunit* +*.cache diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php new file mode 100644 index 0000000..d03900a --- /dev/null +++ b/.php-cs-fixer.dist.php @@ -0,0 +1,19 @@ + true, + ]); + } +}; + +$config->getFinder() + ->in(__DIR__ . '/lib'); + +$cacheDir = getenv('TRAVIS') ? getenv('HOME') . '/.php-cs-fixer' : __DIR__; + +$config->setCacheFile($cacheDir . '/.php_cs.cache'); + +return $config; diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6b0b127 --- /dev/null +++ b/LICENSE @@ -0,0 +1,203 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..7602292 --- /dev/null +++ b/NOTICE @@ -0,0 +1,7 @@ +better-prometheus - A better Prometheus library for PHP applications. + +Copyright 2024 Daniil Gentili + +Homepage: https://github.com/danog/better-prometheus + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..03de7e7 --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +# better-prometheus + +[![Psalm coverage](https://shepherd.dev/github/danog/better-prometheus/coverage.svg)](https://shepherd.dev/github/danog/better-prometheus) +[![Psalm level 1](https://shepherd.dev/github/danog/better-prometheus/level.svg)](https://shepherd.dev/github/danog/better-prometheus) +![License](https://img.shields.io/github/license/danog/better-prometheus) + +A better Prometheus library for PHP applications. + +Offers a modern, clean PHP 8.1 API, with support for **default label values**, based on and compatible with the original `promphp/prometheus_client_php` library. + +## Installation + +```bash +composer require danog/better-prometheus +``` + +## API documentation + +See [here »](https://github.com/danog/better-prometheus/blob/master/docs/docs/index.md) for the full API documentation. \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..8537e76 --- /dev/null +++ b/composer.json @@ -0,0 +1,30 @@ +{ + "name": "danog/better-prometheus", + "description": "A better Prometheus library for PHP applications", + "type": "library", + "require": { + "php": ">=8.1", + "promphp/prometheus_client_php": "^2.10" + }, + "license": "Apache-2.0", + "autoload": { + "psr-4": { + "danog\\BetterPrometheus\\": "lib/" + } + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "psalm/phar": "dev-master", + "danog/phpdoc": "^0.1.24" + }, + "authors": [ + { + "name": "Daniil Gentili", + "email": "daniil@daniil.it" + } + ], + "scripts": { + "cs": "php-cs-fixer fix -v --diff --dry-run", + "cs-fix": "php-cs-fixer fix -v --diff" + } +} diff --git a/docs/docs/danog/BetterPrometheus/BetterCollector.md b/docs/docs/danog/BetterPrometheus/BetterCollector.md new file mode 100644 index 0000000..2fc757b --- /dev/null +++ b/docs/docs/danog/BetterPrometheus/BetterCollector.md @@ -0,0 +1,60 @@ +--- +title: "danog\\BetterPrometheus\\BetterCollector: A better prometheus collector." +description: "" + +--- +# `danog\BetterPrometheus\BetterCollector` +[Back to index](../../index.md) + +> Author: Daniil Gentili + + +A better prometheus collector. + + + +## Properties +* `$storageAdapter`: `Prometheus\Storage\Adapter` Storage adapter +* `$namespace`: `string` Metric namespace +* `$name`: `string` Metric name +* `$help`: `string` Info about the metric +* `$labels`: `array` Default labels, i.e. ['instance' => 'instance_1'] + +## Method list: +* [`__construct(\Prometheus\Storage\Adapter $storageAdapter, string $namespace, string $name, string $help, array $labels = [])`](#__construct) +* [`addLabels(array $labels): static`](#addLabels) + +## Methods: +### `__construct(\Prometheus\Storage\Adapter $storageAdapter, string $namespace, string $name, string $help, array $labels = [])` + +Constructor. + + +Parameters: + +* `$storageAdapter`: `\Prometheus\Storage\Adapter` +* `$namespace`: `string` +* `$name`: `string` +* `$help`: `string` +* `$labels`: `array` + + +#### See also: +* `\Prometheus\Storage\Adapter` + + + + +### `addLabels(array $labels): static` + +Create a new instance of this collector, with these additional labels. + + +Parameters: + +* `$labels`: `array` + + + +--- +Generated by [danog/phpdoc](https://phpdoc.daniil.it) diff --git a/docs/docs/danog/BetterPrometheus/BetterCollectorRegistry.md b/docs/docs/danog/BetterPrometheus/BetterCollectorRegistry.md new file mode 100644 index 0000000..647ebdd --- /dev/null +++ b/docs/docs/danog/BetterPrometheus/BetterCollectorRegistry.md @@ -0,0 +1,307 @@ +--- +title: "danog\\BetterPrometheus\\BetterCollectorRegistry: " +description: "" + +--- +# `danog\BetterPrometheus\BetterCollectorRegistry` +[Back to index](../../index.md) + +> Author: Daniil Gentili + + + + + + +## Properties +* `$storageAdapter`: `Prometheus\Storage\Adapter` + +## Method list: +* [`__construct(\Prometheus\Storage\Adapter $storageAdapter, bool $registerDefaultMetrics = true)`](#__construct) +* [`wipeStorage(): void`](#wipeStorage) +* [`getMetricFamilySamples(bool $sortMetrics = true): list<\Prometheus\MetricFamilySamples>`](#getMetricFamilySamples) +* [`registerGauge(string $namespace, string $name, string $help, array $labels = []): \danog\BetterPrometheus\BetterGauge`](#registerGauge) +* [`getGauge(string $namespace, string $name): \danog\BetterPrometheus\BetterGauge`](#getGauge) +* [`getOrRegisterGauge(string $namespace, string $name, string $help, array $labels = []): \danog\BetterPrometheus\BetterGauge`](#getOrRegisterGauge) +* [`registerCounter(string $namespace, string $name, string $help, array $labels = []): \danog\BetterPrometheus\BetterCounter`](#registerCounter) +* [`getCounter(string $namespace, string $name): \danog\BetterPrometheus\BetterCounter`](#getCounter) +* [`getOrRegisterCounter(string $namespace, string $name, string $help, array $labels = []): \danog\BetterPrometheus\BetterCounter`](#getOrRegisterCounter) +* [`registerHistogram(string $namespace, string $name, string $help, array $labels = [], (non-empty-list|null) $buckets = NULL): \danog\BetterPrometheus\BetterHistogram`](#registerHistogram) +* [`getHistogram(string $namespace, string $name): \danog\BetterPrometheus\BetterHistogram`](#getHistogram) +* [`getOrRegisterHistogram(string $namespace, string $name, string $help, array $labels = [], (non-empty-list|null) $buckets = NULL): \danog\BetterPrometheus\BetterHistogram`](#getOrRegisterHistogram) +* [`registerSummary(string $namespace, string $name, string $help, array $labels = [], int $maxAgeSeconds = 600, (non-empty-list|null) $quantiles = NULL): \danog\BetterPrometheus\BetterSummary`](#registerSummary) +* [`getSummary(string $namespace, string $name): \danog\BetterPrometheus\BetterSummary`](#getSummary) +* [`getOrRegisterSummary(string $namespace, string $name, string $help, array $labels = [], int $maxAgeSeconds = 600, (non-empty-list|null) $quantiles = NULL): \danog\BetterPrometheus\BetterSummary`](#getOrRegisterSummary) + +## Methods: +### `__construct(\Prometheus\Storage\Adapter $storageAdapter, bool $registerDefaultMetrics = true)` + +CollectorRegistry constructor. + + +Parameters: + +* `$storageAdapter`: `\Prometheus\Storage\Adapter` +* `$registerDefaultMetrics`: `bool` + + +#### See also: +* `\Prometheus\Storage\Adapter` + + + + +### `wipeStorage(): void` + +Removes all previously stored metrics from underlying storage adapter. + + + +### `getMetricFamilySamples(bool $sortMetrics = true): list<\Prometheus\MetricFamilySamples>` + + + + +Parameters: + +* `$sortMetrics`: `bool` + + +#### See also: +* `\Prometheus\MetricFamilySamples` + + + + +### `registerGauge(string $namespace, string $name, string $help, array $labels = []): \danog\BetterPrometheus\BetterGauge` + + + + +Parameters: + +* `$namespace`: `string` e.g. cms +* `$name`: `string` e.g. duration_seconds +* `$help`: `string` e.g. The duration something took in seconds. +* `$labels`: `array` e.g. ['controller' => 'someController', 'action' => 'someAction'] + + +#### See also: +* [`\danog\BetterPrometheus\BetterGauge`: A better prometheus gauge.](../../danog/BetterPrometheus/BetterGauge.md) + + + + +### `getGauge(string $namespace, string $name): \danog\BetterPrometheus\BetterGauge` + + + + +Parameters: + +* `$namespace`: `string` +* `$name`: `string` + + +#### See also: +* [`\danog\BetterPrometheus\BetterGauge`: A better prometheus gauge.](../../danog/BetterPrometheus/BetterGauge.md) + + + + +### `getOrRegisterGauge(string $namespace, string $name, string $help, array $labels = []): \danog\BetterPrometheus\BetterGauge` + + + + +Parameters: + +* `$namespace`: `string` e.g. cms +* `$name`: `string` e.g. duration_seconds +* `$help`: `string` e.g. The duration something took in seconds. +* `$labels`: `array` e.g. ['controller' => 'someController', 'action' => 'someAction'] + + +#### See also: +* [`\danog\BetterPrometheus\BetterGauge`: A better prometheus gauge.](../../danog/BetterPrometheus/BetterGauge.md) + + + + +### `registerCounter(string $namespace, string $name, string $help, array $labels = []): \danog\BetterPrometheus\BetterCounter` + + + + +Parameters: + +* `$namespace`: `string` e.g. cms +* `$name`: `string` e.g. requests +* `$help`: `string` e.g. The number of requests made. +* `$labels`: `array` e.g. ['controller' => 'someController', 'action' => 'someAction'] + + +#### See also: +* [`\danog\BetterPrometheus\BetterCounter`: A better prometheus counter.](../../danog/BetterPrometheus/BetterCounter.md) + + + + +### `getCounter(string $namespace, string $name): \danog\BetterPrometheus\BetterCounter` + + + + +Parameters: + +* `$namespace`: `string` +* `$name`: `string` + + +#### See also: +* [`\danog\BetterPrometheus\BetterCounter`: A better prometheus counter.](../../danog/BetterPrometheus/BetterCounter.md) + + + + +### `getOrRegisterCounter(string $namespace, string $name, string $help, array $labels = []): \danog\BetterPrometheus\BetterCounter` + + + + +Parameters: + +* `$namespace`: `string` e.g. cms +* `$name`: `string` e.g. requests +* `$help`: `string` e.g. The number of requests made. +* `$labels`: `array` e.g. ['controller' => 'someController', 'action' => 'someAction'] + + +#### See also: +* [`\danog\BetterPrometheus\BetterCounter`: A better prometheus counter.](../../danog/BetterPrometheus/BetterCounter.md) + + + + +### `registerHistogram(string $namespace, string $name, string $help, array $labels = [], (non-empty-list|null) $buckets = NULL): \danog\BetterPrometheus\BetterHistogram` + + + + +Parameters: + +* `$namespace`: `string` e.g. cms +* `$name`: `string` e.g. duration_seconds +* `$help`: `string` e.g. A histogram of the duration in seconds. +* `$labels`: `array` e.g. ['controller' => 'someController', 'action' => 'someAction'] +* `$buckets`: `(non-empty-list|null)` e.g. [100, 200, 300] + + +#### See also: +* `non-empty-list` +* [`\danog\BetterPrometheus\BetterHistogram`: A better prometheus histogram.](../../danog/BetterPrometheus/BetterHistogram.md) + + + + +### `getHistogram(string $namespace, string $name): \danog\BetterPrometheus\BetterHistogram` + + + + +Parameters: + +* `$namespace`: `string` +* `$name`: `string` + + +#### See also: +* [`\danog\BetterPrometheus\BetterHistogram`: A better prometheus histogram.](../../danog/BetterPrometheus/BetterHistogram.md) + + + + +### `getOrRegisterHistogram(string $namespace, string $name, string $help, array $labels = [], (non-empty-list|null) $buckets = NULL): \danog\BetterPrometheus\BetterHistogram` + + + + +Parameters: + +* `$namespace`: `string` e.g. cms +* `$name`: `string` e.g. duration_seconds +* `$help`: `string` e.g. A histogram of the duration in seconds. +* `$labels`: `array` e.g. ['controller' => 'someController', 'action' => 'someAction'] +* `$buckets`: `(non-empty-list|null)` e.g. [100, 200, 300] + + +#### See also: +* `non-empty-list` +* [`\danog\BetterPrometheus\BetterHistogram`: A better prometheus histogram.](../../danog/BetterPrometheus/BetterHistogram.md) + + + + +### `registerSummary(string $namespace, string $name, string $help, array $labels = [], int $maxAgeSeconds = 600, (non-empty-list|null) $quantiles = NULL): \danog\BetterPrometheus\BetterSummary` + + + + +Parameters: + +* `$namespace`: `string` e.g. cms +* `$name`: `string` e.g. duration_seconds +* `$help`: `string` e.g. A summary of the duration in seconds. +* `$labels`: `array` e.g. ['controller' => 'someController', 'action' => 'someAction'] +* `$maxAgeSeconds`: `int` e.g. 604800 +* `$quantiles`: `(non-empty-list|null)` e.g. [0.01, 0.5, 0.99] + + +#### See also: +* `non-empty-list` +* [`\danog\BetterPrometheus\BetterSummary`: A better prometheus summary.](../../danog/BetterPrometheus/BetterSummary.md) + + + + +### `getSummary(string $namespace, string $name): \danog\BetterPrometheus\BetterSummary` + + + + +Parameters: + +* `$namespace`: `string` +* `$name`: `string` + + +#### See also: +* [`\danog\BetterPrometheus\BetterSummary`: A better prometheus summary.](../../danog/BetterPrometheus/BetterSummary.md) + + + + +### `getOrRegisterSummary(string $namespace, string $name, string $help, array $labels = [], int $maxAgeSeconds = 600, (non-empty-list|null) $quantiles = NULL): \danog\BetterPrometheus\BetterSummary` + + + + +Parameters: + +* `$namespace`: `string` e.g. cms +* `$name`: `string` e.g. duration_seconds +* `$help`: `string` e.g. A summary of the duration in seconds. +* `$labels`: `array` e.g. ['controller' => 'someController', 'action' => 'someAction'] +* `$maxAgeSeconds`: `int` e.g. 604800 +* `$quantiles`: `(non-empty-list|null)` e.g. [0.01, 0.5, 0.99] + + +#### See also: +* `non-empty-list` +* [`\danog\BetterPrometheus\BetterSummary`: A better prometheus summary.](../../danog/BetterPrometheus/BetterSummary.md) + + + + +--- +Generated by [danog/phpdoc](https://phpdoc.daniil.it) diff --git a/docs/docs/danog/BetterPrometheus/BetterCounter.md b/docs/docs/danog/BetterPrometheus/BetterCounter.md new file mode 100644 index 0000000..1fef30a --- /dev/null +++ b/docs/docs/danog/BetterPrometheus/BetterCounter.md @@ -0,0 +1,85 @@ +--- +title: "danog\\BetterPrometheus\\BetterCounter: A better prometheus counter." +description: "" + +--- +# `danog\BetterPrometheus\BetterCounter` +[Back to index](../../index.md) + +> Author: Daniil Gentili + + +A better prometheus counter. + + + +## Properties +* `$storageAdapter`: `Prometheus\Storage\Adapter` Storage adapter +* `$namespace`: `string` Metric namespace +* `$name`: `string` Metric name +* `$help`: `string` Info about the metric +* `$labels`: `array` Default labels, i.e. ['instance' => 'instance_1'] + +## Method list: +* [`addLabels(array $labels): static`](#addLabels) +* [`inc(array $labels = []): void`](#inc) +* [`incBy((int|float) $count, array $labels = []): void`](#incBy) +* [`__construct(\Prometheus\Storage\Adapter $storageAdapter, string $namespace, string $name, string $help, array $labels = [])`](#__construct) + +## Methods: +### `addLabels(array $labels): static` + + + + +Parameters: + +* `$labels`: `array` + + + +### `inc(array $labels = []): void` + + + + +Parameters: + +* `$labels`: `array` e.g. ['status' => '201', 'opcode' => 'SOME_OP'] + + + +### `incBy((int|float) $count, array $labels = []): void` + + + + +Parameters: + +* `$count`: `(int|float)` e.g. 2 +* `$labels`: `array` e.g. ['status' => '201', 'opcode' => 'SOME_OP'] + + + +### `__construct(\Prometheus\Storage\Adapter $storageAdapter, string $namespace, string $name, string $help, array $labels = [])` + +Constructor. + + +Parameters: + +* `$storageAdapter`: `\Prometheus\Storage\Adapter` +* `$namespace`: `string` +* `$name`: `string` +* `$help`: `string` +* `$labels`: `array` + + +#### See also: +* `\Prometheus\Storage\Adapter` + + + + +--- +Generated by [danog/phpdoc](https://phpdoc.daniil.it) diff --git a/docs/docs/danog/BetterPrometheus/BetterGauge.md b/docs/docs/danog/BetterPrometheus/BetterGauge.md new file mode 100644 index 0000000..17342e0 --- /dev/null +++ b/docs/docs/danog/BetterPrometheus/BetterGauge.md @@ -0,0 +1,128 @@ +--- +title: "danog\\BetterPrometheus\\BetterGauge: A better prometheus gauge." +description: "" + +--- +# `danog\BetterPrometheus\BetterGauge` +[Back to index](../../index.md) + +> Author: Daniil Gentili + + +A better prometheus gauge. + + + +## Properties +* `$storageAdapter`: `Prometheus\Storage\Adapter` Storage adapter +* `$namespace`: `string` Metric namespace +* `$name`: `string` Metric name +* `$help`: `string` Info about the metric +* `$labels`: `array` Default labels, i.e. ['instance' => 'instance_1'] + +## Method list: +* [`addLabels(array $labels): static`](#addLabels) +* [`set((int|double) $value, array $labels = []): void`](#set) +* [`incBy(int|float $value, array $labels = []): void`](#incBy) +* [`inc(array $labels = []): void`](#inc) +* [`dec(array $labels = []): void`](#dec) +* [`decBy(int|float $value, array $labels = []): void`](#decBy) +* [`__construct(\Prometheus\Storage\Adapter $storageAdapter, string $namespace, string $name, string $help, array $labels = [])`](#__construct) + +## Methods: +### `addLabels(array $labels): static` + + + + +Parameters: + +* `$labels`: `array` + + + +### `set((int|double) $value, array $labels = []): void` + + + + +Parameters: + +* `$value`: `(int|double)` e.g. 123 +* `$labels`: `array` e.g. ['status' => '201', 'opcode' => 'SOME_OP'] + + +#### See also: +* `double` + + + + +### `incBy(int|float $value, array $labels = []): void` + + + + +Parameters: + +* `$value`: `int|float` +* `$labels`: `array` e.g. ['status' => '201', 'opcode' => 'SOME_OP'] + + + +### `inc(array $labels = []): void` + + + + +Parameters: + +* `$labels`: `array` e.g. ['status' => '201', 'opcode' => 'SOME_OP'] + + + +### `dec(array $labels = []): void` + + + + +Parameters: + +* `$labels`: `array` e.g. ['status' => '201', 'opcode' => 'SOME_OP'] + + + +### `decBy(int|float $value, array $labels = []): void` + + + + +Parameters: + +* `$value`: `int|float` +* `$labels`: `array` e.g. ['status' => '201', 'opcode' => 'SOME_OP'] + + + +### `__construct(\Prometheus\Storage\Adapter $storageAdapter, string $namespace, string $name, string $help, array $labels = [])` + +Constructor. + + +Parameters: + +* `$storageAdapter`: `\Prometheus\Storage\Adapter` +* `$namespace`: `string` +* `$name`: `string` +* `$help`: `string` +* `$labels`: `array` + + +#### See also: +* `\Prometheus\Storage\Adapter` + + + + +--- +Generated by [danog/phpdoc](https://phpdoc.daniil.it) diff --git a/docs/docs/danog/BetterPrometheus/BetterHistogram.md b/docs/docs/danog/BetterPrometheus/BetterHistogram.md new file mode 100644 index 0000000..962cd4d --- /dev/null +++ b/docs/docs/danog/BetterPrometheus/BetterHistogram.md @@ -0,0 +1,84 @@ +--- +title: "danog\\BetterPrometheus\\BetterHistogram: A better prometheus histogram." +description: "" + +--- +# `danog\BetterPrometheus\BetterHistogram` +[Back to index](../../index.md) + +> Author: Daniil Gentili + + +A better prometheus histogram. + + + + +## Constants +* `danog\BetterPrometheus\BetterHistogram::TYPE`: + +## Properties +* `$storageAdapter`: `Prometheus\Storage\Adapter` Storage adapter +* `$namespace`: `string` Metric namespace +* `$name`: `string` Metric name +* `$help`: `string` Info about the metric +* `$labels`: `array` Default labels, i.e. ['instance' => 'instance_1'] + +## Method list: +* [`__construct(\Prometheus\Storage\Adapter $adapter, string $namespace, string $name, string $help, array $labels = [], (non-empty-list|null) $buckets = NULL)`](#__construct) +* [`addLabels(array $labels): static`](#addLabels) +* [`observe(double $value, array $labels = []): void`](#observe) + +## Methods: +### `__construct(\Prometheus\Storage\Adapter $adapter, string $namespace, string $name, string $help, array $labels = [], (non-empty-list|null) $buckets = NULL)` + + + + +Parameters: + +* `$adapter`: `\Prometheus\Storage\Adapter` +* `$namespace`: `string` +* `$name`: `string` +* `$help`: `string` +* `$labels`: `array` +* `$buckets`: `(non-empty-list|null)` + + +#### See also: +* `\Prometheus\Storage\Adapter` +* `non-empty-list` + + + + +### `addLabels(array $labels): static` + + + + +Parameters: + +* `$labels`: `array` + + + +### `observe(double $value, array $labels = []): void` + + + + +Parameters: + +* `$value`: `double` e.g. 123 +* `$labels`: `array` e.g. ['status' => '201', 'opcode' => 'SOME_OP'] + + +#### See also: +* `double` + + + + +--- +Generated by [danog/phpdoc](https://phpdoc.daniil.it) diff --git a/docs/docs/danog/BetterPrometheus/BetterSummary.md b/docs/docs/danog/BetterPrometheus/BetterSummary.md new file mode 100644 index 0000000..6207292 --- /dev/null +++ b/docs/docs/danog/BetterPrometheus/BetterSummary.md @@ -0,0 +1,87 @@ +--- +title: "danog\\BetterPrometheus\\BetterSummary: A better prometheus summary." +description: "" + +--- +# `danog\BetterPrometheus\BetterSummary` +[Back to index](../../index.md) + +> Author: Daniil Gentili + + +A better prometheus summary. + + + + +## Constants +* `danog\BetterPrometheus\BetterSummary::RESERVED_LABELS`: + +* `danog\BetterPrometheus\BetterSummary::TYPE`: + +## Properties +* `$storageAdapter`: `Prometheus\Storage\Adapter` Storage adapter +* `$namespace`: `string` Metric namespace +* `$name`: `string` Metric name +* `$help`: `string` Info about the metric +* `$labels`: `array` Default labels, i.e. ['instance' => 'instance_1'] + +## Method list: +* [`__construct(\Prometheus\Storage\Adapter $adapter, string $namespace, string $name, string $help, array $labels = [], int $maxAgeSeconds = 600, ?non-empty-list $quantiles = NULL)`](#__construct) +* [`addLabels(array $labels): static`](#addLabels) +* [`observe(double $value, array $labels = []): void`](#observe) + +## Methods: +### `__construct(\Prometheus\Storage\Adapter $adapter, string $namespace, string $name, string $help, array $labels = [], int $maxAgeSeconds = 600, ?non-empty-list $quantiles = NULL)` + + + + +Parameters: + +* `$adapter`: `\Prometheus\Storage\Adapter` +* `$namespace`: `string` +* `$name`: `string` +* `$help`: `string` +* `$labels`: `array` +* `$maxAgeSeconds`: `int` +* `$quantiles`: `?non-empty-list` + + +#### See also: +* `\Prometheus\Storage\Adapter` +* `non-empty-list` + + + + +### `addLabels(array $labels): static` + + + + +Parameters: + +* `$labels`: `array` + + + +### `observe(double $value, array $labels = []): void` + + + + +Parameters: + +* `$value`: `double` e.g. 123 +* `$labels`: `array` e.g. ['status' => '404', 'opcode' => 'SOME_OP'] + + +#### See also: +* `double` + + + + +--- +Generated by [danog/phpdoc](https://phpdoc.daniil.it) diff --git a/docs/docs/index.md b/docs/docs/index.md new file mode 100644 index 0000000..e7c556a --- /dev/null +++ b/docs/docs/index.md @@ -0,0 +1,25 @@ +--- +description: "A better Prometheus library for PHP applications" +title: "danog/better-prometheus" + +--- +# `danog/better-prometheus` + +A better Prometheus library for PHP applications + + + +## Abstract classes +* [\danog\BetterPrometheus\BetterCollector: A better prometheus collector.](danog/BetterPrometheus/BetterCollector.md) + +## Classes +* [\danog\BetterPrometheus\BetterCollectorRegistry](danog/BetterPrometheus/BetterCollectorRegistry.md) +* [\danog\BetterPrometheus\BetterCounter: A better prometheus counter.](danog/BetterPrometheus/BetterCounter.md) +* [\danog\BetterPrometheus\BetterGauge: A better prometheus gauge.](danog/BetterPrometheus/BetterGauge.md) +* [\danog\BetterPrometheus\BetterHistogram: A better prometheus histogram.](danog/BetterPrometheus/BetterHistogram.md) +* [\danog\BetterPrometheus\BetterSummary: A better prometheus summary.](danog/BetterPrometheus/BetterSummary.md) + + + +--- +Generated by [danog/phpdoc](https://phpdoc.daniil.it). \ No newline at end of file diff --git a/docs/index.md b/docs/index.md new file mode 120000 index 0000000..32d46ee --- /dev/null +++ b/docs/index.md @@ -0,0 +1 @@ +../README.md \ No newline at end of file diff --git a/lib/BetterCollector.php b/lib/BetterCollector.php new file mode 100644 index 0000000..3dceba0 --- /dev/null +++ b/lib/BetterCollector.php @@ -0,0 +1,54 @@ + $labels Default labels, i.e. ['instance' => 'instance_1'] */ + public readonly array $labels = [] + ) { + self::assertValidLabels($labels); + $metricName = ($namespace !== '' ? $namespace . '_' : '') . $name; + Collector::assertValidMetricName($metricName); + $this->metricName = $metricName; + } + + /** @param array $labels */ + protected static function assertValidLabels(array $labels): void + { + foreach ($labels as $labelKey => $_) { + Collector::assertValidLabel($labelKey); + } + } + + /** + * Create a new instance of this collector, with these additional labels. + * + * @param array $labels + */ + abstract public function addLabels(array $labels): static; +} diff --git a/lib/BetterCollectorRegistry.php b/lib/BetterCollectorRegistry.php new file mode 100644 index 0000000..6193487 --- /dev/null +++ b/lib/BetterCollectorRegistry.php @@ -0,0 +1,330 @@ + + */ + private array $gauges = []; + + /** + * @var array + */ + private array $counters = []; + + /** + * @var array + */ + private array $histograms = []; + + /** + * @var array + */ + private array $summaries = []; + + /** + * @psalm-suppress UnusedProperty + */ + private ?BetterGauge $defaultGauge = null; + + /** + * CollectorRegistry constructor. + * + */ + public function __construct( + public readonly Adapter $storageAdapter, + bool $registerDefaultMetrics = true + ) { + $this->storageAdapter = $storageAdapter; + if ($registerDefaultMetrics) { + $this->defaultGauge = $this->getOrRegisterGauge( + "", + "php_info", + "Information about the PHP environment.", + ["version" => PHP_VERSION] + ); + $this->defaultGauge->set(1); + } + } + + /** + * Removes all previously stored metrics from underlying storage adapter. + */ + public function wipeStorage(): void + { + $this->storageAdapter->wipeStorage(); + } + + /** + * @psalm-suppress TooManyArguments + * + * @return list + */ + public function getMetricFamilySamples(bool $sortMetrics = true): array + { + /** @var list */ + return $this->storageAdapter->collect($sortMetrics); + } + + /** + * @param string $namespace e.g. cms + * @param string $name e.g. duration_seconds + * @param string $help e.g. The duration something took in seconds. + * @param array $labels e.g. ['controller' => 'someController', 'action' => 'someAction'] + * + * @throws MetricsRegistrationException + */ + public function registerGauge(string $namespace, string $name, string $help, $labels = []): BetterGauge + { + $metricIdentifier = "$namespace:$name"; + if (isset($this->gauges[$metricIdentifier])) { + throw new MetricsRegistrationException("Metric already registered"); + } + $this->gauges[$metricIdentifier] = new BetterGauge( + $this->storageAdapter, + $namespace, + $name, + $help, + $labels + ); + return $this->gauges[$metricIdentifier]; + } + + /** + * @throws MetricNotFoundException + */ + public function getGauge(string $namespace, string $name): BetterGauge + { + $metricIdentifier = "$namespace:$name"; + if (!isset($this->gauges[$metricIdentifier])) { + throw new MetricNotFoundException("Metric not found:" . $metricIdentifier); + } + return $this->gauges[$metricIdentifier]; + } + + /** + * @param string $namespace e.g. cms + * @param string $name e.g. duration_seconds + * @param string $help e.g. The duration something took in seconds. + * @param array $labels e.g. ['controller' => 'someController', 'action' => 'someAction'] + * + * @throws MetricsRegistrationException + */ + public function getOrRegisterGauge(string $namespace, string $name, string $help, $labels = []): BetterGauge + { + try { + $gauge = $this->getGauge($namespace, $name); + } catch (MetricNotFoundException $e) { + $gauge = $this->registerGauge($namespace, $name, $help, $labels); + } + return $gauge; + } + + /** + * @param string $namespace e.g. cms + * @param string $name e.g. requests + * @param string $help e.g. The number of requests made. + * @param array $labels e.g. ['controller' => 'someController', 'action' => 'someAction'] + * + * @throws MetricsRegistrationException + */ + public function registerCounter(string $namespace, string $name, string $help, $labels = []): BetterCounter + { + $metricIdentifier = "$namespace:$name"; + if (isset($this->counters[$metricIdentifier])) { + throw new MetricsRegistrationException("Metric already registered"); + } + $this->counters[$metricIdentifier] = new BetterCounter( + $this->storageAdapter, + $namespace, + $name, + $help, + $labels + ); + return $this->counters["$namespace:$name"]; + } + + /** + * + * @throws MetricNotFoundException + */ + public function getCounter(string $namespace, string $name): BetterCounter + { + $metricIdentifier = "$namespace:$name"; + if (!isset($this->counters[$metricIdentifier])) { + throw new MetricNotFoundException("Metric not found:" . $metricIdentifier); + } + return $this->counters["$namespace:$name"]; + } + + /** + * @param string $namespace e.g. cms + * @param string $name e.g. requests + * @param string $help e.g. The number of requests made. + * @param array $labels e.g. ['controller' => 'someController', 'action' => 'someAction'] + * + * @throws MetricsRegistrationException + */ + public function getOrRegisterCounter(string $namespace, string $name, string $help, $labels = []): BetterCounter + { + try { + $counter = $this->getCounter($namespace, $name); + } catch (MetricNotFoundException $e) { + $counter = $this->registerCounter($namespace, $name, $help, $labels); + } + return $counter; + } + + /** + * @param string $namespace e.g. cms + * @param string $name e.g. duration_seconds + * @param string $help e.g. A histogram of the duration in seconds. + * @param array $labels e.g. ['controller' => 'someController', 'action' => 'someAction'] + * @param non-empty-list|null $buckets e.g. [100, 200, 300] + * + * @throws MetricsRegistrationException + */ + public function registerHistogram( + string $namespace, + string $name, + string $help, + array $labels = [], + ?array $buckets = null + ): BetterHistogram { + $metricIdentifier = "$namespace:$name"; + if (isset($this->histograms[$metricIdentifier])) { + throw new MetricsRegistrationException("Metric already registered"); + } + $this->histograms[$metricIdentifier] = new BetterHistogram( + $this->storageAdapter, + $namespace, + $name, + $help, + $labels, + $buckets + ); + return $this->histograms[$metricIdentifier]; + } + + /** + * + * @throws MetricNotFoundException + */ + public function getHistogram(string $namespace, string $name): BetterHistogram + { + $metricIdentifier = "$namespace:$name"; + if (!isset($this->histograms[$metricIdentifier])) { + throw new MetricNotFoundException("Metric not found:" . $metricIdentifier); + } + return $this->histograms["$namespace:$name"]; + } + + /** + * @param string $namespace e.g. cms + * @param string $name e.g. duration_seconds + * @param string $help e.g. A histogram of the duration in seconds. + * @param array $labels e.g. ['controller' => 'someController', 'action' => 'someAction'] + * @param non-empty-list|null $buckets e.g. [100, 200, 300] + * + * @throws MetricsRegistrationException + */ + public function getOrRegisterHistogram( + string $namespace, + string $name, + string $help, + array $labels = [], + ?array $buckets = null + ): BetterHistogram { + try { + $histogram = $this->getHistogram($namespace, $name); + } catch (MetricNotFoundException $e) { + $histogram = $this->registerHistogram($namespace, $name, $help, $labels, $buckets); + } + return $histogram; + } + + /** + * @param string $namespace e.g. cms + * @param string $name e.g. duration_seconds + * @param string $help e.g. A summary of the duration in seconds. + * @param array $labels e.g. ['controller' => 'someController', 'action' => 'someAction'] + * @param int $maxAgeSeconds e.g. 604800 + * @param non-empty-list|null $quantiles e.g. [0.01, 0.5, 0.99] + * + * @throws MetricsRegistrationException + */ + public function registerSummary( + string $namespace, + string $name, + string $help, + array $labels = [], + int $maxAgeSeconds = 600, + ?array $quantiles = null + ): BetterSummary { + $metricIdentifier = "$namespace:$name"; + if (isset($this->summaries[$metricIdentifier])) { + throw new MetricsRegistrationException("Metric already registered"); + } + $this->summaries[$metricIdentifier] = new BetterSummary( + $this->storageAdapter, + $namespace, + $name, + $help, + $labels, + $maxAgeSeconds, + $quantiles + ); + return $this->summaries[$metricIdentifier]; + } + + /** + * + * @throws MetricNotFoundException + */ + public function getSummary(string $namespace, string $name): BetterSummary + { + $metricIdentifier = "$namespace:$name"; + if (!isset($this->summaries[$metricIdentifier])) { + throw new MetricNotFoundException("Metric not found:" . $metricIdentifier); + } + return $this->summaries["$namespace:$name"]; + } + + /** + * @param string $namespace e.g. cms + * @param string $name e.g. duration_seconds + * @param string $help e.g. A summary of the duration in seconds. + * @param array $labels e.g. ['controller' => 'someController', 'action' => 'someAction'] + * @param int $maxAgeSeconds e.g. 604800 + * @param non-empty-list|null $quantiles e.g. [0.01, 0.5, 0.99] + * + * @throws MetricsRegistrationException + */ + public function getOrRegisterSummary( + string $namespace, + string $name, + string $help, + array $labels = [], + int $maxAgeSeconds = 600, + ?array $quantiles = null + ): BetterSummary { + try { + $summary = $this->getSummary($namespace, $name); + } catch (MetricNotFoundException $e) { + $summary = $this->registerSummary($namespace, $name, $help, $labels, $maxAgeSeconds, $quantiles); + } + return $summary; + } +} diff --git a/lib/BetterCounter.php b/lib/BetterCounter.php new file mode 100644 index 0000000..eb771c0 --- /dev/null +++ b/lib/BetterCounter.php @@ -0,0 +1,57 @@ +storageAdapter, + $this->namespace, + $this->name, + $this->help, + $this->labels + $labels + ); + } + + /** + * @param array $labels e.g. ['status' => '201', 'opcode' => 'SOME_OP'] + */ + public function inc(array $labels = []): void + { + $this->incBy(1, $labels); + } + + /** + * @param int|float $count e.g. 2 + * @param array $labels e.g. ['status' => '201', 'opcode' => 'SOME_OP'] + */ + public function incBy(int|float $count, array $labels = []): void + { + self::assertValidLabels($labels); + $labels = $this->labels + $labels; + $this->storageAdapter->updateCounter( + [ + 'name' => $this->metricName, + 'help' => $this->help, + 'type' => self::TYPE, + 'labelNames' => \array_keys($labels), + 'labelValues' => \array_values($labels), + 'value' => $count, + 'command' => \is_float($count) ? Adapter::COMMAND_INCREMENT_FLOAT : Adapter::COMMAND_INCREMENT_INTEGER, + ] + ); + } +} diff --git a/lib/BetterGauge.php b/lib/BetterGauge.php new file mode 100644 index 0000000..9a13f12 --- /dev/null +++ b/lib/BetterGauge.php @@ -0,0 +1,92 @@ +storageAdapter, + $this->namespace, + $this->name, + $this->help, + $this->labels + $labels + ); + } + + /** + * @param int|double $value e.g. 123 + * @param array $labels e.g. ['status' => '201', 'opcode' => 'SOME_OP'] + */ + public function set(int|float $value, array $labels = []): void + { + self::assertValidLabels($labels); + $labels = $this->labels + $labels; + $this->storageAdapter->updateGauge( + [ + 'name' => $this->name, + 'help' => $this->help, + 'type' => self::TYPE, + 'labelNames' => \array_keys($labels), + 'labelValues' => \array_values($labels), + 'value' => $value, + 'command' => Adapter::COMMAND_SET, + ] + ); + } + /** + * @param array $labels e.g. ['status' => '201', 'opcode' => 'SOME_OP'] + */ + public function incBy(int|float $value, array $labels = []): void + { + self::assertValidLabels($labels); + $labels = $this->labels + $labels; + $this->storageAdapter->updateGauge( + [ + 'name' => $this->metricName, + 'help' => $this->help, + 'type' => self::TYPE, + 'labelNames' => \array_keys($labels), + 'labelValues' => \array_values($labels), + 'value' => $value, + 'command' => \is_float($value) ? Adapter::COMMAND_INCREMENT_FLOAT : Adapter::COMMAND_INCREMENT_INTEGER, + ] + ); + } + + /** + * @param array $labels e.g. ['status' => '201', 'opcode' => 'SOME_OP'] + */ + public function inc(array $labels = []): void + { + $this->incBy(1, $labels); + } + + /** + * @param array $labels e.g. ['status' => '201', 'opcode' => 'SOME_OP'] + */ + public function dec(array $labels = []): void + { + $this->decBy(1, $labels); + } + + /** + * @param array $labels e.g. ['status' => '201', 'opcode' => 'SOME_OP'] + */ + public function decBy(int|float $value, array $labels = []): void + { + $this->incBy(-$value, $labels); + } +} diff --git a/lib/BetterHistogram.php b/lib/BetterHistogram.php new file mode 100644 index 0000000..25b0a8b --- /dev/null +++ b/lib/BetterHistogram.php @@ -0,0 +1,102 @@ + + */ + private readonly array $buckets; + + /** + * @param array $labels + * @param non-empty-list|null $buckets + */ + public function __construct( + Adapter $adapter, + string $namespace, + string $name, + string $help, + array $labels = [], + ?array $buckets = null + ) { + parent::__construct($adapter, $namespace, $name, $help, $labels); + + if (null === $buckets) { + /** @var non-empty-list */ + $buckets = Histogram::getDefaultBuckets(); + } + + /** @psalm-suppress TypeDoesNotContainType */ + if (0 === \count($buckets)) { + throw new InvalidArgumentException("Histogram must have at least one bucket."); + } + + for ($i = 0; $i < \count($buckets) - 1; $i++) { + if ($buckets[$i] >= $buckets[$i + 1]) { + throw new InvalidArgumentException( + "Histogram buckets must be in increasing order: " . + $buckets[$i] . " >= " . $buckets[$i + 1] + ); + } + } + $this->buckets = $buckets; + } + + /** @param array $labels */ + protected static function assertValidLabels(array $labels): void + { + if (isset($labels['le'])) { + throw new \InvalidArgumentException("Histogram cannot have a label named 'le'."); + } + parent::assertValidLabels($labels); + } + + public function addLabels(array $labels): static + { + return new self( + $this->storageAdapter, + $this->namespace, + $this->name, + $this->help, + $this->labels + $labels, + $this->buckets + ); + } + + /** + * @param double $value e.g. 123 + * @param array $labels e.g. ['status' => '201', 'opcode' => 'SOME_OP'] + */ + public function observe(float|int $value, array $labels = []): void + { + self::assertValidLabels($labels); + $labels = $this->labels + $labels; + + $this->storageAdapter->updateHistogram( + [ + 'value' => $value, + 'name' => $this->metricName, + 'help' => $this->help, + 'type' => self::TYPE, + 'labelNames' => \array_keys($labels), + 'labelValues' => \array_values($labels), + 'buckets' => $this->buckets, + ] + ); + } +} diff --git a/lib/BetterSummary.php b/lib/BetterSummary.php new file mode 100644 index 0000000..216f011 --- /dev/null +++ b/lib/BetterSummary.php @@ -0,0 +1,117 @@ + + */ + private readonly array $quantiles; + + /** + * @param array $labels + * @param ?non-empty-list $quantiles + */ + public function __construct( + Adapter $adapter, + string $namespace, + string $name, + string $help, + array $labels = [], + private readonly int $maxAgeSeconds = 600, + ?array $quantiles = null + ) { + parent::__construct($adapter, $namespace, $name, $help, $labels); + + if (null === $quantiles) { + /** @var non-empty-list */ + $quantiles = Summary::getDefaultQuantiles(); + } + + /** @psalm-suppress TypeDoesNotContainType */ + if (0 === \count($quantiles)) { + throw new InvalidArgumentException("Summary must have at least one quantile."); + } + + for ($i = 0; $i < \count($quantiles) - 1; $i++) { + if ($quantiles[$i] >= $quantiles[$i + 1]) { + throw new InvalidArgumentException( + "Summary quantiles must be in increasing order: " . + $quantiles[$i] . " >= " . $quantiles[$i + 1] + ); + } + } + + foreach ($quantiles as $quantile) { + if ($quantile <= 0 || $quantile >= 1) { + throw new InvalidArgumentException("Quantile $quantile invalid: Expected number between 0 and 1."); + } + } + + if ($maxAgeSeconds <= 0) { + throw new InvalidArgumentException("maxAgeSeconds $maxAgeSeconds invalid: Expected number greater than 0."); + } + + $this->quantiles = $quantiles; + } + + public function addLabels(array $labels): static + { + return new self( + $this->storageAdapter, + $this->namespace, + $this->name, + $this->help, + $this->labels + $labels, + $this->maxAgeSeconds, + $this->quantiles + ); + } + + /** @param array $labels */ + protected static function assertValidLabels(array $labels): void + { + if (isset($labels['quantile'])) { + throw new \InvalidArgumentException("Sumamry cannot have a label named 'quantile'."); + } + parent::assertValidLabels($labels); + } + + /** + * @param double $value e.g. 123 + * @param array $labels e.g. ['status' => '404', 'opcode' => 'SOME_OP'] + */ + public function observe(float $value, array $labels = []): void + { + self::assertValidLabels($labels); + $labels = $this->labels + $labels; + + $this->storageAdapter->updateSummary( + [ + 'value' => $value, + 'name' => $this->metricName, + 'help' => $this->help, + 'type' => self::TYPE, + 'labelNames' => \array_keys($labels), + 'labelValues' => \array_values($labels), + 'maxAgeSeconds' => $this->maxAgeSeconds, + 'quantiles' => $this->quantiles, + ] + ); + } +} diff --git a/psalm.xml b/psalm.xml new file mode 100644 index 0000000..9478693 --- /dev/null +++ b/psalm.xml @@ -0,0 +1,17 @@ + + + + + + + + +