mirror of
https://gitee.com/hyperf/hyperf.git
synced 2024-11-29 18:27:44 +08:00
Merge branch 'master' into pr/827
This commit is contained in:
commit
8e32291fcd
@ -38,6 +38,6 @@ before_script:
|
|||||||
- composer config -g process-timeout 900 && composer update
|
- composer config -g process-timeout 900 && composer update
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- composer analyse src/di src/json-rpc src/tracer
|
- composer analyse src/di src/json-rpc src/tracer src/metric
|
||||||
- composer test -- --exclude-group NonCoroutine
|
- composer test -- --exclude-group NonCoroutine
|
||||||
- vendor/bin/phpunit --group NonCoroutine
|
- vendor/bin/phpunit --group NonCoroutine
|
||||||
|
22
CHANGELOG.md
22
CHANGELOG.md
@ -1,24 +1,44 @@
|
|||||||
# v1.1.4 - TBD
|
# v1.1.5 - TBD
|
||||||
|
|
||||||
|
## Added
|
||||||
|
|
||||||
|
- [#832](https://github.com/hyperf/hyperf/pull/832) Added `Hyperf\Utils\Codec\Json`.
|
||||||
|
- [#833](https://github.com/hyperf/hyperf/pull/833) Added `Hyperf\Utils\Backoff`.
|
||||||
|
|
||||||
|
## Fixed
|
||||||
|
|
||||||
|
- [#835](https://github.com/hyperf/hyperf/pull/835) Fixed `Request::inputs` default value does not works.
|
||||||
|
|
||||||
|
## Optimized
|
||||||
|
|
||||||
|
- [#832](https://github.com/hyperf/hyperf/pull/832) Optimized that response will throw a exception when json format failed.
|
||||||
|
|
||||||
|
# v1.1.4 - 2019-10-31
|
||||||
|
|
||||||
## Added
|
## Added
|
||||||
|
|
||||||
- [#778](https://github.com/hyperf/hyperf/pull/778) Added `PUT` and `DELETE` for `Hyperf\Testing\Client`.
|
- [#778](https://github.com/hyperf/hyperf/pull/778) Added `PUT` and `DELETE` for `Hyperf\Testing\Client`.
|
||||||
|
- [#784](https://github.com/hyperf/hyperf/pull/784) Add Metric Component
|
||||||
- [#795](https://github.com/hyperf/hyperf/pull/795) Added `restartInterval` for `AbstractProcess`.
|
- [#795](https://github.com/hyperf/hyperf/pull/795) Added `restartInterval` for `AbstractProcess`.
|
||||||
|
- [#804](https://github.com/hyperf/hyperf/pull/804) Added `BeforeHandle` `AfterHandle` and `FailToHandle` for command.
|
||||||
|
|
||||||
## Fixed
|
## Fixed
|
||||||
|
|
||||||
- [#779](https://github.com/hyperf/hyperf/pull/779) Fixed bug that JPG file cannot be verified.
|
- [#779](https://github.com/hyperf/hyperf/pull/779) Fixed bug that JPG file cannot be verified.
|
||||||
- [#787](https://github.com/hyperf/hyperf/pull/787) Fixed bug that "--class" option does not exist.
|
- [#787](https://github.com/hyperf/hyperf/pull/787) Fixed bug that "--class" option does not exist.
|
||||||
- [#795](https://github.com/hyperf/hyperf/pull/795) Fixed process not restart when throw an exception.
|
- [#795](https://github.com/hyperf/hyperf/pull/795) Fixed process not restart when throw an exception.
|
||||||
|
- [#796](https://github.com/hyperf/hyperf/pull/796) Fixed `config_etcd.enable` does not works.
|
||||||
|
|
||||||
## Optimized
|
## Optimized
|
||||||
|
|
||||||
- [#781](https://github.com/hyperf/hyperf/pull/781) Publish validation language package according to translation setting.
|
- [#781](https://github.com/hyperf/hyperf/pull/781) Publish validation language package according to translation setting.
|
||||||
|
- [#796](https://github.com/hyperf/hyperf/pull/796) Don't remake HandlerStack for etcd.
|
||||||
- [#797](https://github.com/hyperf/hyperf/pull/797) Use channel to communicate, instead of sharing mem
|
- [#797](https://github.com/hyperf/hyperf/pull/797) Use channel to communicate, instead of sharing mem
|
||||||
|
|
||||||
## Changed
|
## Changed
|
||||||
|
|
||||||
- [#793](https://github.com/hyperf/hyperf/pull/793) Changed `protected` to `public` for `Pool::getConnectionsInChannel`.
|
- [#793](https://github.com/hyperf/hyperf/pull/793) Changed `protected` to `public` for `Pool::getConnectionsInChannel`.
|
||||||
|
- [#811](https://github.com/hyperf/hyperf/pull/811) Command `di:init-proxy` does not clear the runtime cache, If you want to delete them, use `vendor/bin/init-proxy.sh` instead.
|
||||||
|
|
||||||
# v1.1.3 - 2019-10-24
|
# v1.1.3 - 2019-10-24
|
||||||
|
|
||||||
|
@ -12,11 +12,11 @@
|
|||||||
"php": ">=7.2",
|
"php": ">=7.2",
|
||||||
"ext-json": "*",
|
"ext-json": "*",
|
||||||
"ext-swoole": ">=4.4",
|
"ext-swoole": ">=4.4",
|
||||||
"egulias/email-validator": "^2.1",
|
|
||||||
"bandwidth-throttle/token-bucket": "^2.0",
|
"bandwidth-throttle/token-bucket": "^2.0",
|
||||||
"doctrine/annotations": "^1.6",
|
"doctrine/annotations": "^1.6",
|
||||||
"doctrine/inflector": "^1.3",
|
"doctrine/inflector": "^1.3",
|
||||||
"doctrine/instantiator": "^1.0",
|
"doctrine/instantiator": "^1.0",
|
||||||
|
"egulias/email-validator": "^2.1",
|
||||||
"elasticsearch/elasticsearch": "^6.1",
|
"elasticsearch/elasticsearch": "^6.1",
|
||||||
"fig/http-message-util": "^1.1.2",
|
"fig/http-message-util": "^1.1.2",
|
||||||
"google/protobuf": "^3.6.1",
|
"google/protobuf": "^3.6.1",
|
||||||
@ -37,9 +37,11 @@
|
|||||||
"psr/log": "^1.0",
|
"psr/log": "^1.0",
|
||||||
"psr/simple-cache": "^1.0",
|
"psr/simple-cache": "^1.0",
|
||||||
"squizlabs/php_codesniffer": "^3.4",
|
"squizlabs/php_codesniffer": "^3.4",
|
||||||
|
"start-point/etcd-php": "^1.1",
|
||||||
"symfony/console": "^4.2",
|
"symfony/console": "^4.2",
|
||||||
"symfony/finder": "^4.1",
|
"symfony/finder": "^4.1",
|
||||||
"vlucas/phpdotenv": "^3.1"
|
"vlucas/phpdotenv": "^3.1",
|
||||||
|
"endclothing/prometheus_client_php": "^0.9.1"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"doctrine/common": "@stable",
|
"doctrine/common": "@stable",
|
||||||
@ -52,7 +54,9 @@
|
|||||||
"phpunit/phpunit": "^7.0.0",
|
"phpunit/phpunit": "^7.0.0",
|
||||||
"swoft/swoole-ide-helper": "dev-master",
|
"swoft/swoole-ide-helper": "dev-master",
|
||||||
"symfony/property-access": "^4.3",
|
"symfony/property-access": "^4.3",
|
||||||
"symfony/serializer": "^4.3"
|
"symfony/serializer": "^4.3",
|
||||||
|
"influxdb/influxdb-php": "^1.15.0",
|
||||||
|
"domnikl/statsd": "^3.0.1"
|
||||||
},
|
},
|
||||||
"replace": {
|
"replace": {
|
||||||
"hyperf/amqp": "self.version",
|
"hyperf/amqp": "self.version",
|
||||||
@ -85,6 +89,7 @@
|
|||||||
"hyperf/http-server": "self.version",
|
"hyperf/http-server": "self.version",
|
||||||
"hyperf/logger": "self.version",
|
"hyperf/logger": "self.version",
|
||||||
"hyperf/memory": "self.version",
|
"hyperf/memory": "self.version",
|
||||||
|
"hyperf/metric": "self.version",
|
||||||
"hyperf/model-cache": "self.version",
|
"hyperf/model-cache": "self.version",
|
||||||
"hyperf/paginator": "self.version",
|
"hyperf/paginator": "self.version",
|
||||||
"hyperf/pool": "self.version",
|
"hyperf/pool": "self.version",
|
||||||
@ -147,6 +152,7 @@
|
|||||||
"Hyperf\\LoadBalancer\\": "src/load-balancer/src/",
|
"Hyperf\\LoadBalancer\\": "src/load-balancer/src/",
|
||||||
"Hyperf\\Logger\\": "src/logger/src/",
|
"Hyperf\\Logger\\": "src/logger/src/",
|
||||||
"Hyperf\\Memory\\": "src/memory/src/",
|
"Hyperf\\Memory\\": "src/memory/src/",
|
||||||
|
"Hyperf\\Metric\\": "src/metric/src/",
|
||||||
"Hyperf\\ModelCache\\": "src/model-cache/src/",
|
"Hyperf\\ModelCache\\": "src/model-cache/src/",
|
||||||
"Hyperf\\ModelListener\\": "src/model-listener/src/",
|
"Hyperf\\ModelListener\\": "src/model-listener/src/",
|
||||||
"Hyperf\\Paginator\\": "src/paginator/src/",
|
"Hyperf\\Paginator\\": "src/paginator/src/",
|
||||||
@ -207,6 +213,7 @@
|
|||||||
"HyperfTest\\JsonRpc\\": "src/json-rpc/tests/",
|
"HyperfTest\\JsonRpc\\": "src/json-rpc/tests/",
|
||||||
"HyperfTest\\LoadBalancer\\": "src/load-balancer/tests/",
|
"HyperfTest\\LoadBalancer\\": "src/load-balancer/tests/",
|
||||||
"HyperfTest\\Logger\\": "src/logger/tests/",
|
"HyperfTest\\Logger\\": "src/logger/tests/",
|
||||||
|
"HyperfTest\\Metric\\": "src/metric/tests/",
|
||||||
"HyperfTest\\ModelCache\\": "src/model-cache/tests/",
|
"HyperfTest\\ModelCache\\": "src/model-cache/tests/",
|
||||||
"HyperfTest\\ModelListener\\": "src/model-listener/tests/",
|
"HyperfTest\\ModelListener\\": "src/model-listener/tests/",
|
||||||
"HyperfTest\\Paginator\\": "src/paginator/tests/",
|
"HyperfTest\\Paginator\\": "src/paginator/tests/",
|
||||||
@ -261,6 +268,7 @@
|
|||||||
"Hyperf\\LoadBalancer\\ConfigProvider",
|
"Hyperf\\LoadBalancer\\ConfigProvider",
|
||||||
"Hyperf\\Logger\\ConfigProvider",
|
"Hyperf\\Logger\\ConfigProvider",
|
||||||
"Hyperf\\Memory\\ConfigProvider",
|
"Hyperf\\Memory\\ConfigProvider",
|
||||||
|
"Hyperf\\Metric\\ConfigProvider",
|
||||||
"Hyperf\\ModelCache\\ConfigProvider",
|
"Hyperf\\ModelCache\\ConfigProvider",
|
||||||
"Hyperf\\ModelListener\\ConfigProvider",
|
"Hyperf\\ModelListener\\ConfigProvider",
|
||||||
"Hyperf\\Paginator\\ConfigProvider",
|
"Hyperf\\Paginator\\ConfigProvider",
|
||||||
|
@ -30,17 +30,17 @@ runtime/container/proxy/
|
|||||||
|
|
||||||
Re-genenrate command
|
Re-genenrate command
|
||||||
```
|
```
|
||||||
php bin/hyperf.php di:init-proxy
|
vendor/bin/init-proxy.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
So the command to run unit test can use the following instead
|
So the command to run unit test can use the following instead
|
||||||
```
|
```
|
||||||
php bin/hyperf.php di:init-proxy && composer test
|
vendor/bin/init-proxy.sh && composer test
|
||||||
```
|
```
|
||||||
|
|
||||||
Similarly, the command to start the server can also use the following instead
|
Similarly, the command to start the server can also use the following instead
|
||||||
```
|
```
|
||||||
php bin/hyperf.php di:init-proxy && php bin/hyperf.php start
|
vendor/bin/init-proxy.sh && php bin/hyperf.php start
|
||||||
```
|
```
|
||||||
|
|
||||||
## Docker build failure
|
## Docker build failure
|
||||||
|
@ -68,4 +68,4 @@ class FooAspect extends AbstractAspect
|
|||||||
|
|
||||||
在部署生产环境时,我们可能会希望 Hyperf 提前将所有代理类提前生成,而不是使用时动态的生成,可以通过 `php bin/hyperf.php di:init-proxy` 命令来生成所有代理类,该命令会忽视现有的代理类缓存,全部重新生成。
|
在部署生产环境时,我们可能会希望 Hyperf 提前将所有代理类提前生成,而不是使用时动态的生成,可以通过 `php bin/hyperf.php di:init-proxy` 命令来生成所有代理类,该命令会忽视现有的代理类缓存,全部重新生成。
|
||||||
|
|
||||||
基于以上,我们可以将生成代理类的命令和启动服务的命令结合起来,`php bin/hyperf.php di:init-proxy && php bin/hyperf.php start` 来达到自动重新生成所有代理类缓存然后启动服务的目的。
|
基于以上,我们可以将生成代理类的命令和启动服务的命令结合起来,`vendor/bin/init-proxy.sh && php bin/hyperf.php start` 来达到自动重新生成所有代理类缓存然后启动服务的目的。
|
||||||
|
@ -117,4 +117,5 @@
|
|||||||
- [yurunsoft/pay-sdk](https://github.com/Yurunsoft/PaySDK) 支持 Swoole 协程的支付宝/微信支付SDK
|
- [yurunsoft/pay-sdk](https://github.com/Yurunsoft/PaySDK) 支持 Swoole 协程的支付宝/微信支付SDK
|
||||||
- [yurunsoft/yurun-oauth-login](https://github.com/Yurunsoft/YurunOAuthLogin) 支持 Swoole 协程的第三方登录授权 SDK(QQ、微信、微博、Github、Gitee等)
|
- [yurunsoft/yurun-oauth-login](https://github.com/Yurunsoft/YurunOAuthLogin) 支持 Swoole 协程的第三方登录授权 SDK(QQ、微信、微博、Github、Gitee等)
|
||||||
- [overtrue/wechat](zh/sdks/wechat) EasyWeChat,一个流行的微信 SDK,非微信官方 SDK
|
- [overtrue/wechat](zh/sdks/wechat) EasyWeChat,一个流行的微信 SDK,非微信官方 SDK
|
||||||
|
- [Yurunsoft/PHPMailer-Swoole](https://github.com/Yurunsoft/PHPMailer-Swoole) Swoole环境下的 PHPMailer
|
||||||
|
|
||||||
|
@ -203,7 +203,7 @@ $result = parallel([
|
|||||||
|
|
||||||
use Hyperf\Utils\Coroutine\Concurrent;
|
use Hyperf\Utils\Coroutine\Concurrent;
|
||||||
|
|
||||||
$concurrent = new Concurrent(10, 1);
|
$concurrent = new Concurrent(10);
|
||||||
|
|
||||||
for ($i = 0; $i < 15; ++$i) {
|
for ($i = 0; $i < 15; ++$i) {
|
||||||
$concurrent->create(function () use ($count) {
|
$concurrent->create(function () use ($count) {
|
||||||
|
@ -260,7 +260,7 @@ class UserServiceFactory
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`UserService` 也许在构造函数提供一个参数接收对应的值:
|
`UserService` 也可以在构造函数提供一个参数接收对应的值:
|
||||||
|
|
||||||
```php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
310
doc/zh/metric.md
Normal file
310
doc/zh/metric.md
Normal file
@ -0,0 +1,310 @@
|
|||||||
|
# 服务监控
|
||||||
|
|
||||||
|
微服务治理的一个核心需求便是服务可观察性。作为微服务的牧羊人,要做到时刻掌握各项服务的健康状态,并非易事。云原生时代这一领域内涌现出了诸多解决方案。本组件对可观察性当中的重要支柱遥测与监控进行了抽象,方便使用者与既有基础设施快速结合,同时避免供应商锁定。
|
||||||
|
|
||||||
|
## 安装
|
||||||
|
|
||||||
|
### 通过 Composer 安装组件
|
||||||
|
|
||||||
|
```bash
|
||||||
|
composer require hyperf/metric
|
||||||
|
```
|
||||||
|
|
||||||
|
[hyperf/metric](https://github.com/hyperf/metric) 组件默认安装了 [Prometheus](https://prometheus.io/) 相关依赖。如果要使用 [StatsD](https://github.com/statsd/statsd) 或 [InfluxDB](http://influxdb.com),还需要执行下面的命令安装对应的依赖:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# StatsD 所需依赖
|
||||||
|
composer require domnikl/statsd
|
||||||
|
# InfluxDB 所需依赖
|
||||||
|
composer require influxdb/influxdb-php
|
||||||
|
```
|
||||||
|
|
||||||
|
### 增加组件配置
|
||||||
|
|
||||||
|
如文件不存在,可执行下面的命令增加 `config/autoload/metric.php` 配置文件:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
php bin/hyperf.php vendor:publish hyperf/metric
|
||||||
|
```
|
||||||
|
|
||||||
|
## 使用
|
||||||
|
|
||||||
|
### 配置
|
||||||
|
|
||||||
|
#### 选项
|
||||||
|
|
||||||
|
* `default`:配置文件内的 `default` 对应的值则为使用的驱动名称。驱动的具体配置在 `metric` 项下定义,使用与 `key` 相同的驱动。
|
||||||
|
|
||||||
|
```php
|
||||||
|
'default' => env('TELEMETRY_DRIVER', 'prometheus'),
|
||||||
|
```
|
||||||
|
|
||||||
|
* `use_standalone_process`: 是否使用 `独立监控进程`。推荐开启。关闭后将在 `Worker进程` 中处理指标收集与上报。
|
||||||
|
```php
|
||||||
|
'use_standalone_process' => env('TELEMETRY_USE_STANDALONE_PROCESS', true),
|
||||||
|
```
|
||||||
|
|
||||||
|
* `enable_default_metric`: 是否统计默认指标。默认指标包括内存占用、系统 CPU 负载以及Swoole Server 指标和 Swoole Coroutine 指标。
|
||||||
|
```php
|
||||||
|
'enable_default_metric' => env('TELEMETRY_ENABLE_DEFAULT_TELEMETRY', true),
|
||||||
|
```
|
||||||
|
|
||||||
|
* `default_metric_interval`: 默认指标推送周期,单位为秒,下同。
|
||||||
|
```php
|
||||||
|
'default_metric_interval' => env('DEFAULT_METRIC_INTERVAL', 5),
|
||||||
|
```
|
||||||
|
#### 配置 Prometheus
|
||||||
|
|
||||||
|
使用 Prometheus 时,在配置文件中的 `metric` 项增加 Prometheus 的具体配置。
|
||||||
|
|
||||||
|
```php
|
||||||
|
use Hyperf\Metric\Adapter\Prometheus\Constants;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'default' => env('TELEMETRY_DRIVER', 'prometheus'),
|
||||||
|
'use_standalone_process' => env('TELEMETRY_USE_STANDALONE_PROCESS', true),
|
||||||
|
'enable_default_metric' => env('TELEMETRY_ENABLE_DEFAULT_TELEMETRY', true),
|
||||||
|
'default_metric_interval' => env('DEFAULT_METRIC_INTERVAL', 5),
|
||||||
|
'metric' => [
|
||||||
|
'prometheus' => [
|
||||||
|
'driver' => Hyperf\Metric\Adapter\Prometheus\MetricFactory::class,
|
||||||
|
'mode' => Constants::SCRAPE_MODE,
|
||||||
|
'namespace' => env('APP_NAME', 'skeleton'),
|
||||||
|
'scrape_host' => env('PROMETHEUS_SCRAPE_HOST', '0.0.0.0'),
|
||||||
|
'scrape_port' => env('PROMETHEUS_SCRAPE_PORT', '9502'),
|
||||||
|
'scrape_path' => env('PROMETHEUS_SCRAPE_PATH', '/metrics'),
|
||||||
|
'push_host' => env('PROMETHEUS_PUSH_HOST', '0.0.0.0'),
|
||||||
|
'push_port' => env('PROMETHEUS_PUSH_PORT', '9091'),
|
||||||
|
'push_interval' => env('PROMETHEUS_PUSH_INTERVAL', 5),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
Prometheus 有两种工作模式,爬模式与推模式(通过 Prometheus Pushgateway ),本组件均可支持。
|
||||||
|
|
||||||
|
使用爬模式(Prometheus 官方推荐)时需设置:
|
||||||
|
|
||||||
|
```php
|
||||||
|
'mode' => Constants::SCRAPE_MODE
|
||||||
|
```
|
||||||
|
|
||||||
|
并配置爬取地址 `scrape_host`、爬取端口 `scrape_port`、爬取路径 `scrape_path`。Prometheus 可以在对应配置下以HTTP访问形式拉取全部指标。
|
||||||
|
|
||||||
|
> 注意:爬模式下,必须启用独立进程,即 use_standalone_process = true。
|
||||||
|
|
||||||
|
使用推模式时需设置:
|
||||||
|
|
||||||
|
```php
|
||||||
|
'mode' => Constants::PUSH_MODE
|
||||||
|
```
|
||||||
|
|
||||||
|
并配置推送地址 `push_host`、推送端口 `push_port`、推送间隔 `push_interval`。只建议离线任务使用推模式。
|
||||||
|
|
||||||
|
#### 配置 StatsD
|
||||||
|
|
||||||
|
使用 StatsD 时,在配置文件中的 `metric` 项增加 StatsD 的具体配置。
|
||||||
|
|
||||||
|
```php
|
||||||
|
return [
|
||||||
|
'default' => env('TELEMETRY_DRIVER', 'statd'),
|
||||||
|
'use_standalone_process' => env('TELEMETRY_USE_STANDALONE_PROCESS', true),
|
||||||
|
'enable_default_metric' => env('TELEMETRY_ENABLE_DEFAULT_TELEMETRY', true),
|
||||||
|
'metric' => [
|
||||||
|
'statsd' => [
|
||||||
|
'driver' => Hyperf\Metric\Adapter\StatsD\MetricFactory::class,
|
||||||
|
'namespace' => env('APP_NAME', 'skeleton'),
|
||||||
|
'udp_host' => env('STATSD_UDP_HOST', '127.0.0.1'),
|
||||||
|
'udp_port' => env('STATSD_UDP_PORT', '8125'),
|
||||||
|
'enable_batch' => env('STATSD_ENABLE_BATCH', true),
|
||||||
|
'push_interval' => env('STATSD_PUSH_INTERVAL', 5),
|
||||||
|
'sample_rate' => env('STATSD_SAMPLE_RATE', 1.0),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
StatsD 目前只支持 UDP 模式,需要配置 UDP 地址 `udp_host`,UDP 端口 `udp_port`、是否批量推送 `enable_batch`(减少请求次数)、批量推送间隔 `push_interval` 以及采样率`sample_rate`。
|
||||||
|
|
||||||
|
#### 配置 InfluxDB
|
||||||
|
|
||||||
|
使用 InfluxDB 时,在配置文件中的 `metric` 项增加 InfluxDB 的具体配置。
|
||||||
|
|
||||||
|
```php
|
||||||
|
return [
|
||||||
|
'default' => env('TELEMETRY_DRIVER', 'influxdb'),
|
||||||
|
'use_standalone_process' => env('TELEMETRY_USE_STANDALONE_PROCESS', true),
|
||||||
|
'enable_default_metric' => env('TELEMETRY_ENABLE_DEFAULT_TELEMETRY', true),
|
||||||
|
'metric' => [
|
||||||
|
'influxdb' => [
|
||||||
|
'driver' => Hyperf\Metric\Adapter\InfluxDB\MetricFactory::class,
|
||||||
|
'namespace' => env('APP_NAME', 'skeleton'),
|
||||||
|
'host' => env('INFLUXDB_HOST', '127.0.0.1'),
|
||||||
|
'port' => env('INFLUXDB_PORT', '8086'),
|
||||||
|
'username' => env('INFLUXDB_USERNAME', ''),
|
||||||
|
'password' => env('INFLUXDB_PASSWORD', ''),
|
||||||
|
'dbname' => env('INFLUXDB_DBNAME', true),
|
||||||
|
'push_interval' => env('INFLUXDB_PUSH_INTERVAL', 5),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
InfluxDB 使用默认的 HTTP 模式,需要配置地址 `host`,UDP端口 `port`、用户名 `username`、密码 `password`、`dbname` 数据表以及批量推送间隔 `push_interval`。
|
||||||
|
|
||||||
|
### 基本抽象
|
||||||
|
|
||||||
|
遥测组件对常用的三种数据类型进行了抽象,以确保解耦具体实现。
|
||||||
|
|
||||||
|
三种类型分别为:
|
||||||
|
|
||||||
|
* 计数器(Counter): 用于描述单向递增的某种指标。如 HTTP 请求计数。
|
||||||
|
|
||||||
|
```php
|
||||||
|
interface CounterInterface
|
||||||
|
{
|
||||||
|
public function with(string ...$labelValues): self;
|
||||||
|
|
||||||
|
public function add(int $delta);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
* 测量器(Gauge):用于描述某种随时间发生增减变化的指标。如连接池内的可用连接数。
|
||||||
|
|
||||||
|
```php
|
||||||
|
interface GaugeInterface
|
||||||
|
{
|
||||||
|
public function with(string ...$labelValues): self;
|
||||||
|
|
||||||
|
public function set(float $value);
|
||||||
|
|
||||||
|
public function add(float $delta);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
* 直方图(Histogram):用于描述对某一事件的持续观测后产生的统计学分布,通常表示为百分位数或分桶。如HTTP请求延迟。
|
||||||
|
|
||||||
|
```php
|
||||||
|
interface HistogramInterface
|
||||||
|
{
|
||||||
|
public function with(string ...$labelValues): self;
|
||||||
|
|
||||||
|
public function put(float $sample);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 配置中间件
|
||||||
|
|
||||||
|
配置完驱动之后,只需配置一下中间件就能启用请求 Histogram 统计功能。
|
||||||
|
打开 `config/autoload/middlewares.php` 文件,示例为在 `http` Server 中启用中间件。
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'http' => [
|
||||||
|
\Hyperf\Metric\Middleware\MetricMiddeware::class,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
### 自定义使用
|
||||||
|
|
||||||
|
通过HTTP中间件遥测仅仅是本组件用途的冰山一角,您可以注入 `Hyperf\Metric\Contract\MetricFactoryInterface` 类来自行遥测业务数据。比如:创建的订单数量、广告的点击数量等。
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Model\Order;
|
||||||
|
use Hyperf\Metric\Contract\MetricFactoryInterface;
|
||||||
|
|
||||||
|
class IndexController extends AbstractController
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @Inject
|
||||||
|
* @var MetricFactoryInterface
|
||||||
|
*/
|
||||||
|
private $metricFactory;
|
||||||
|
|
||||||
|
public function create(Order $order)
|
||||||
|
{
|
||||||
|
$counter = $this->metricFactory->makeCounter('order_created', ['order_type']);
|
||||||
|
$counter->with($order->type)->add(1);
|
||||||
|
// 订单逻辑...
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`MetricFactoryInterface` 中包含如下工厂方法来生成对应的三种基本统计类型。
|
||||||
|
|
||||||
|
```php
|
||||||
|
public function makeCounter($name, $labelNames): CounterInterface;
|
||||||
|
|
||||||
|
public function makeGauge($name, $labelNames): GaugeInterface;
|
||||||
|
|
||||||
|
public function makeHistogram($name, $labelNames): HistogramInterface;
|
||||||
|
```
|
||||||
|
|
||||||
|
上述例子是统计请求范围内的产生的指标。有时候我们需要统计的指标是面向完整生命周期的,比如统计异步队列长度或库存商品数量。此种场景下可以监听 `MetricFactoryReady` 事件。
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Listener;
|
||||||
|
|
||||||
|
use Hyperf\Event\Contract\ListenerInterface;
|
||||||
|
use Hyperf\Metric\Event\MetricFactoryReady;
|
||||||
|
use Psr\Container\ContainerInterface;
|
||||||
|
use Redis;
|
||||||
|
|
||||||
|
class OnMetricFactoryReady implements ListenerInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var ContainerInterface
|
||||||
|
*/
|
||||||
|
protected $container;
|
||||||
|
|
||||||
|
public function __construct(ContainerInterface $container)
|
||||||
|
{
|
||||||
|
$this->container = $container;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function listen(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
MetricFactoryReady::class,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function process(object $event)
|
||||||
|
{
|
||||||
|
$redis = $this->container->get(Redis::class);
|
||||||
|
$gauge = $event
|
||||||
|
->factory
|
||||||
|
->makeGauge('queue_length', ['driver'])
|
||||||
|
->with('redis');
|
||||||
|
while (true) {
|
||||||
|
$length = $redis->llen('queue');
|
||||||
|
$gauge->set($length);
|
||||||
|
sleep(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> 工程上讲,直接从 Redis 查询队列长度不太合适,应该通过队列驱动 `DriverInterface` 接口下的 `info()` 方法来获取队列长度。这里只做简易演示。您可以在本组件源码的`src/Listener` 文件夹下找到完整例子。
|
||||||
|
|
||||||
|
### 注解
|
||||||
|
|
||||||
|
您可以使用 `@Counter(name="stat_name_here")` 和 `@Histogram(name="stat_name_here")` 来统计切面的调用次数和运行时间。
|
||||||
|
|
||||||
|
关于注解的使用请参阅[注解章节](https://doc.hyperf.io/#/zh/annotation)。
|
@ -24,28 +24,33 @@ swoole.use_shortname = 'Off'
|
|||||||
代理类缓存一旦生成,将不会再重新覆盖。所以当你修改了已经生成代理类的文件时,需要手动清理。
|
代理类缓存一旦生成,将不会再重新覆盖。所以当你修改了已经生成代理类的文件时,需要手动清理。
|
||||||
|
|
||||||
代理类位置如下
|
代理类位置如下
|
||||||
|
|
||||||
```
|
```
|
||||||
runtime/container/proxy/
|
runtime/container/proxy/
|
||||||
```
|
```
|
||||||
|
|
||||||
重新生成缓存命令,新缓存会覆盖原目录
|
重新生成缓存命令,新缓存会覆盖原目录
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
php bin/hyperf.php di:init-proxy
|
vendor/bin/init-proxy.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
删除代理类缓存
|
删除代理类缓存
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
rm -rf ./runtime/container/proxy
|
rm -rf ./runtime/container/proxy
|
||||||
```
|
```
|
||||||
|
|
||||||
所以单测命令可以使用以下代替:
|
所以单测命令可以使用以下代替:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
php bin/hyperf.php di:init-proxy && composer test
|
vendor/bin/init-proxy.sh && composer test
|
||||||
```
|
```
|
||||||
|
|
||||||
同理,启动命令可以使用以下代替
|
同理,启动命令可以使用以下代替
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
php bin/hyperf.php di:init-proxy && php bin/hyperf.php start
|
vendor/bin/init-proxy.sh && php bin/hyperf.php start
|
||||||
```
|
```
|
||||||
|
|
||||||
## PHP7.3下预先生成代理的脚本 执行失败
|
## PHP7.3下预先生成代理的脚本 执行失败
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
在 Hyperf 里可通过 `Hyperf\HttpServer\Contract\ResponseInterface` 接口类来注入 `Response` 代理对象对响应进行处理,默认返回 `Hyperf\HttpServer\Response` 对象,该对象可直接调用所有 `Psr\Http\Message\ResponseInterface` 的方法。
|
在 Hyperf 里可通过 `Hyperf\HttpServer\Contract\ResponseInterface` 接口类来注入 `Response` 代理对象对响应进行处理,默认返回 `Hyperf\HttpServer\Response` 对象,该对象可直接调用所有 `Psr\Http\Message\ResponseInterface` 的方法。
|
||||||
|
|
||||||
|
> 注意 PSR-7 标准为 响应(Response) 进行了 immutable 机制 的设计,所有以 with 开头的方法的返回值都是一个新对象,不会修改原对象的值
|
||||||
|
|
||||||
## 返回 Json 格式
|
## 返回 Json 格式
|
||||||
|
|
||||||
`Hyperf\HttpServer\Contract\ResponseInterface` 提供了 `json($data)` 方法用于快速返回 `Json` 格式,并设置 `Content-Type` 为 `application/json`,`$data` 接受一个数组或为一个实现了 `Hyperf\Utils\Contracts\Arrayable` 接口的对象。
|
`Hyperf\HttpServer\Contract\ResponseInterface` 提供了 `json($data)` 方法用于快速返回 `Json` 格式,并设置 `Content-Type` 为 `application/json`,`$data` 接受一个数组或为一个实现了 `Hyperf\Utils\Contracts\Arrayable` 接口的对象。
|
||||||
|
@ -60,6 +60,7 @@
|
|||||||
* [服务限流](zh/rate-limit.md)
|
* [服务限流](zh/rate-limit.md)
|
||||||
* [配置中心](zh/config-center.md)
|
* [配置中心](zh/config-center.md)
|
||||||
* [调用链追踪](zh/tracer.md)
|
* [调用链追踪](zh/tracer.md)
|
||||||
|
* [服务监控](zh/metric.md)
|
||||||
|
|
||||||
* 其它组件
|
* 其它组件
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ require BASE_PATH . '/config/container.php';
|
|||||||
|
|
||||||
```
|
```
|
||||||
# 重新生成代理类
|
# 重新生成代理类
|
||||||
php bin/hyperf.php di:init-proxy
|
vendor/bin/init-proxy.sh
|
||||||
# 运行单元测试
|
# 运行单元测试
|
||||||
composer test
|
composer test
|
||||||
```
|
```
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
<directory suffix="Test.php">./src/di/tests</directory>
|
<directory suffix="Test.php">./src/di/tests</directory>
|
||||||
<directory suffix="Test.php">./src/dispatcher/tests</directory>
|
<directory suffix="Test.php">./src/dispatcher/tests</directory>
|
||||||
<directory suffix="Test.php">./src/elasticsearch/tests</directory>
|
<directory suffix="Test.php">./src/elasticsearch/tests</directory>
|
||||||
|
<directory suffix="Test.php">./src/etcd/tests</directory>
|
||||||
<directory suffix="Test.php">./src/event/tests</directory>
|
<directory suffix="Test.php">./src/event/tests</directory>
|
||||||
<directory suffix="Test.php">./src/exception-handler/tests</directory>
|
<directory suffix="Test.php">./src/exception-handler/tests</directory>
|
||||||
<directory suffix="Test.php">./src/grpc-client/tests</directory>
|
<directory suffix="Test.php">./src/grpc-client/tests</directory>
|
||||||
@ -30,6 +31,7 @@
|
|||||||
<directory suffix="Test.php">./src/http-server/tests</directory>
|
<directory suffix="Test.php">./src/http-server/tests</directory>
|
||||||
<directory suffix="Test.php">./src/json-rpc/tests</directory>
|
<directory suffix="Test.php">./src/json-rpc/tests</directory>
|
||||||
<directory suffix="Test.php">./src/logger/tests</directory>
|
<directory suffix="Test.php">./src/logger/tests</directory>
|
||||||
|
<directory suffix="Test.php">./src/metric/tests</directory>
|
||||||
<directory suffix="Test.php">./src/model-cache/tests</directory>
|
<directory suffix="Test.php">./src/model-cache/tests</directory>
|
||||||
<directory suffix="Test.php">./src/paginator/tests</directory>
|
<directory suffix="Test.php">./src/paginator/tests</directory>
|
||||||
<directory suffix="Test.php">./src/pool/tests</directory>
|
<directory suffix="Test.php">./src/pool/tests</directory>
|
||||||
|
@ -20,8 +20,9 @@
|
|||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=7.2",
|
"php": ">=7.2",
|
||||||
"symfony/console": "^4.2",
|
"hyperf/utils": "~1.1.0",
|
||||||
"hyperf/utils": "~1.1.0"
|
"psr/event-dispatcher": "^1.0",
|
||||||
|
"symfony/console": "^4.2"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"malukenho/docheader": "^0.1.6",
|
"malukenho/docheader": "^0.1.6",
|
||||||
@ -32,6 +33,9 @@
|
|||||||
"suggest": {
|
"suggest": {
|
||||||
"hyperf/di": "Required to use annotations."
|
"hyperf/di": "Required to use annotations."
|
||||||
},
|
},
|
||||||
|
"config": {
|
||||||
|
"sort-packages": true
|
||||||
|
},
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-master": "1.1-dev"
|
"dev-master": "1.1-dev"
|
||||||
|
@ -15,6 +15,7 @@ namespace Hyperf\Command;
|
|||||||
use Hyperf\Utils\Contracts\Arrayable;
|
use Hyperf\Utils\Contracts\Arrayable;
|
||||||
use Hyperf\Utils\Coroutine;
|
use Hyperf\Utils\Coroutine;
|
||||||
use Hyperf\Utils\Str;
|
use Hyperf\Utils\Str;
|
||||||
|
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||||
use Symfony\Component\Console\Command\Command as SymfonyCommand;
|
use Symfony\Component\Console\Command\Command as SymfonyCommand;
|
||||||
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
|
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
|
||||||
use Symfony\Component\Console\Helper\Table;
|
use Symfony\Component\Console\Helper\Table;
|
||||||
@ -58,6 +59,11 @@ abstract class Command extends SymfonyCommand
|
|||||||
*/
|
*/
|
||||||
protected $coroutine = true;
|
protected $coroutine = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var null|EventDispatcherInterface
|
||||||
|
*/
|
||||||
|
protected $eventDispatcher;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var int
|
* @var int
|
||||||
*/
|
*/
|
||||||
@ -381,13 +387,25 @@ abstract class Command extends SymfonyCommand
|
|||||||
|
|
||||||
protected function execute(InputInterface $input, OutputInterface $output)
|
protected function execute(InputInterface $input, OutputInterface $output)
|
||||||
{
|
{
|
||||||
if ($this->coroutine && ! Coroutine::inCoroutine()) {
|
$callback = function () {
|
||||||
run(function () {
|
try {
|
||||||
|
$this->eventDispatcher && $this->eventDispatcher->dispatch(new Event\BeforeHandle($this));
|
||||||
call([$this, 'handle']);
|
call([$this, 'handle']);
|
||||||
}, $this->hookFlags);
|
$this->eventDispatcher && $this->eventDispatcher->dispatch(new Event\AfterHandle($this));
|
||||||
|
} catch (\Throwable $exception) {
|
||||||
|
if (! $this->eventDispatcher) {
|
||||||
|
throw $exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->eventDispatcher->dispatch(new Event\FailToHandle($this, $exception));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if ($this->coroutine && ! Coroutine::inCoroutine()) {
|
||||||
|
run($callback, $this->hookFlags);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return call([$this, 'handle']);
|
|
||||||
|
return $callback();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
17
src/command/src/Event/AfterHandle.php
Normal file
17
src/command/src/Event/AfterHandle.php
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hyperf\Command\Event;
|
||||||
|
|
||||||
|
class AfterHandle extends Event
|
||||||
|
{
|
||||||
|
}
|
17
src/command/src/Event/BeforeHandle.php
Normal file
17
src/command/src/Event/BeforeHandle.php
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hyperf\Command\Event;
|
||||||
|
|
||||||
|
class BeforeHandle extends Event
|
||||||
|
{
|
||||||
|
}
|
33
src/command/src/Event/Event.php
Normal file
33
src/command/src/Event/Event.php
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hyperf\Command\Event;
|
||||||
|
|
||||||
|
use Hyperf\Command\Command;
|
||||||
|
|
||||||
|
abstract class Event
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var Command
|
||||||
|
*/
|
||||||
|
protected $command;
|
||||||
|
|
||||||
|
public function __construct(Command $command)
|
||||||
|
{
|
||||||
|
$this->command = $command;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCommand(): Command
|
||||||
|
{
|
||||||
|
return $this->command;
|
||||||
|
}
|
||||||
|
}
|
36
src/command/src/Event/FailToHandle.php
Normal file
36
src/command/src/Event/FailToHandle.php
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hyperf\Command\Event;
|
||||||
|
|
||||||
|
use Hyperf\Command\Command;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
class FailToHandle extends Event
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var Throwable
|
||||||
|
*/
|
||||||
|
protected $throwable;
|
||||||
|
|
||||||
|
public function __construct(Command $command, Throwable $throwable)
|
||||||
|
{
|
||||||
|
parent::__construct($command);
|
||||||
|
|
||||||
|
$this->throwable = $throwable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getThrowable(): Throwable
|
||||||
|
{
|
||||||
|
return $this->throwable;
|
||||||
|
}
|
||||||
|
}
|
@ -74,6 +74,10 @@ class BootProcessListener implements ListenerInterface
|
|||||||
|
|
||||||
public function process(object $event)
|
public function process(object $event)
|
||||||
{
|
{
|
||||||
|
if (! $this->config->get('config_etcd.enable', false)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if ($config = $this->client->pull()) {
|
if ($config = $this->client->pull()) {
|
||||||
$configurations = $this->format($config);
|
$configurations = $this->format($config);
|
||||||
foreach ($configurations as $kv) {
|
foreach ($configurations as $kv) {
|
||||||
|
@ -75,6 +75,10 @@ class OnPipeMessageListener implements ListenerInterface
|
|||||||
*/
|
*/
|
||||||
public function process(object $event)
|
public function process(object $event)
|
||||||
{
|
{
|
||||||
|
if (! $this->config->get('config_etcd.enable', false)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (property_exists($event, 'data') && $event->data instanceof PipeMessage) {
|
if (property_exists($event, 'data') && $event->data instanceof PipeMessage) {
|
||||||
/** @var PipeMessage $data */
|
/** @var PipeMessage $data */
|
||||||
$data = $event->data;
|
$data = $event->data;
|
||||||
|
19
src/di/bin/init-proxy.sh
Executable file
19
src/di/bin/init-proxy.sh
Executable file
@ -0,0 +1,19 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
basepath=$(cd `dirname $0`; pwd)
|
||||||
|
|
||||||
|
echo ../../
|
||||||
|
|
||||||
|
if [ ! -f "composer.lock" ]; then
|
||||||
|
echo "Not found composer.lock, please composer install first."
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -rf runtime
|
||||||
|
|
||||||
|
echo "Runtime cleared"
|
||||||
|
|
||||||
|
php bin/hyperf.php di:init-proxy
|
||||||
|
|
||||||
|
echo "Finish!"
|
||||||
|
|
@ -56,6 +56,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"bin": [
|
"bin": [
|
||||||
|
"bin/init-proxy.sh"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"cs-fix": "php-cs-fixer fix $1",
|
"cs-fix": "php-cs-fixer fix $1",
|
||||||
|
@ -18,8 +18,6 @@ use Hyperf\Di\Annotation\Scanner;
|
|||||||
use Hyperf\Di\Container;
|
use Hyperf\Di\Container;
|
||||||
use Psr\Container\ContainerInterface;
|
use Psr\Container\ContainerInterface;
|
||||||
use Symfony\Component\Console\Exception\LogicException;
|
use Symfony\Component\Console\Exception\LogicException;
|
||||||
use Symfony\Component\Finder\Finder;
|
|
||||||
use Symfony\Component\Finder\SplFileInfo;
|
|
||||||
|
|
||||||
class InitProxyCommand extends Command
|
class InitProxyCommand extends Command
|
||||||
{
|
{
|
||||||
@ -49,23 +47,13 @@ class InitProxyCommand extends Command
|
|||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
|
$this->warn('This command does not clear the runtime cache, If you want to delete them, use `vendor/bin/init-proxy.sh` instead.');
|
||||||
|
|
||||||
$this->createAopProxies();
|
$this->createAopProxies();
|
||||||
|
|
||||||
$this->output->writeln('<info>Proxy class create success.</info>');
|
$this->output->writeln('<info>Proxy class create success.</info>');
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function clearRuntime($paths)
|
|
||||||
{
|
|
||||||
$finder = new Finder();
|
|
||||||
$finder->files()->in($paths)->name(['*.php', '*.cache']);
|
|
||||||
|
|
||||||
/** @var SplFileInfo $file */
|
|
||||||
foreach ($finder as $file) {
|
|
||||||
$path = $file->getRealPath();
|
|
||||||
@unlink($path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getScanDir()
|
protected function getScanDir()
|
||||||
{
|
{
|
||||||
if (! defined('BASE_PATH')) {
|
if (! defined('BASE_PATH')) {
|
||||||
@ -91,11 +79,6 @@ class InitProxyCommand extends Command
|
|||||||
{
|
{
|
||||||
$scanDirs = $this->getScanDir();
|
$scanDirs = $this->getScanDir();
|
||||||
|
|
||||||
$runtime = BASE_PATH . '/runtime/container/';
|
|
||||||
if (is_dir($runtime)) {
|
|
||||||
$this->clearRuntime($runtime);
|
|
||||||
}
|
|
||||||
|
|
||||||
$meta = $this->scanner->scan($scanDirs);
|
$meta = $this->scanner->scan($scanDirs);
|
||||||
$classCollection = array_keys($meta);
|
$classCollection = array_keys($meta);
|
||||||
|
|
||||||
|
@ -20,7 +20,9 @@
|
|||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=7.2",
|
"php": ">=7.2",
|
||||||
"hyperf/utils": "~1.1.0"
|
"hyperf/guzzle": "~1.1.0",
|
||||||
|
"hyperf/utils": "~1.1.0",
|
||||||
|
"start-point/etcd-php": "^1.1"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"friendsofphp/php-cs-fixer": "^2.14",
|
"friendsofphp/php-cs-fixer": "^2.14",
|
||||||
@ -28,8 +30,6 @@
|
|||||||
"swoft/swoole-ide-helper": "dev-master"
|
"swoft/swoole-ide-helper": "dev-master"
|
||||||
},
|
},
|
||||||
"suggest": {
|
"suggest": {
|
||||||
"start-point/etcd-php": "Etcd v3 http client.",
|
|
||||||
"linkorb/etcd-php": "Etcd v2 http client."
|
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"sort-packages": true
|
"sort-packages": true
|
||||||
|
@ -14,8 +14,7 @@ namespace Hyperf\Etcd;
|
|||||||
|
|
||||||
use GuzzleHttp\HandlerStack;
|
use GuzzleHttp\HandlerStack;
|
||||||
use Hyperf\Contract\ConfigInterface;
|
use Hyperf\Contract\ConfigInterface;
|
||||||
use Hyperf\Guzzle\PoolHandler;
|
use Hyperf\Guzzle\HandlerStackFactory;
|
||||||
use Hyperf\Guzzle\RetryMiddleware;
|
|
||||||
use Hyperf\Utils\Coroutine;
|
use Hyperf\Utils\Coroutine;
|
||||||
|
|
||||||
abstract class Client
|
abstract class Client
|
||||||
@ -35,37 +34,33 @@ abstract class Client
|
|||||||
*/
|
*/
|
||||||
protected $options = [];
|
protected $options = [];
|
||||||
|
|
||||||
protected $client;
|
/**
|
||||||
|
* @var HandlerStack[]
|
||||||
|
*/
|
||||||
|
protected $stacks;
|
||||||
|
|
||||||
public function __construct(ConfigInterface $config)
|
/**
|
||||||
|
* @var HandlerStackFactory
|
||||||
|
*/
|
||||||
|
protected $factory;
|
||||||
|
|
||||||
|
public function __construct(ConfigInterface $config, HandlerStackFactory $factory)
|
||||||
{
|
{
|
||||||
$uri = $config->get('etcd.uri', 'http://127.0.0.1:2379');
|
$uri = $config->get('etcd.uri', 'http://127.0.0.1:2379');
|
||||||
$version = $config->get('etcd.version', 'v3beta');
|
$version = $config->get('etcd.version', 'v3beta');
|
||||||
|
|
||||||
$this->options = $config->get('etcd.options', []);
|
$this->options = $config->get('etcd.options', []);
|
||||||
$this->baseUri = sprintf('%s/%s/', $uri, $version);
|
$this->baseUri = sprintf('%s/%s/', $uri, $version);
|
||||||
|
$this->factory = $factory;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getDefaultHandler()
|
protected function getDefaultHandler()
|
||||||
{
|
{
|
||||||
$handler = null;
|
$id = (int) Coroutine::inCoroutine();
|
||||||
if (Coroutine::inCoroutine()) {
|
if ($this->stacks[$id] instanceof HandlerStack) {
|
||||||
$handler = make(PoolHandler::class, [
|
return $this->stacks[$id];
|
||||||
'option' => [
|
|
||||||
'max_connections' => 50,
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retry Middleware
|
return $this->stacks[$id] = $this->factory->create();
|
||||||
$retry = make(RetryMiddleware::class, [
|
|
||||||
'retries' => 1,
|
|
||||||
'delay' => 10,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$stack = HandlerStack::create($handler);
|
|
||||||
$stack->push($retry->getMiddleware(), 'retry');
|
|
||||||
|
|
||||||
return $stack;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ namespace Hyperf\Etcd;
|
|||||||
|
|
||||||
use Hyperf\Contract\ConfigInterface;
|
use Hyperf\Contract\ConfigInterface;
|
||||||
use Hyperf\Etcd\Exception\ClientNotFindException;
|
use Hyperf\Etcd\Exception\ClientNotFindException;
|
||||||
|
use Hyperf\Guzzle\HandlerStackFactory;
|
||||||
use Psr\Container\ContainerInterface;
|
use Psr\Container\ContainerInterface;
|
||||||
|
|
||||||
class KVFactory
|
class KVFactory
|
||||||
@ -23,7 +24,7 @@ class KVFactory
|
|||||||
$config = $container->get(ConfigInterface::class);
|
$config = $container->get(ConfigInterface::class);
|
||||||
$version = $config->get('etcd.version');
|
$version = $config->get('etcd.version');
|
||||||
|
|
||||||
$params = ['config' => $config];
|
$params = ['config' => $config, 'factory' => $container->get(HandlerStackFactory::class)];
|
||||||
|
|
||||||
switch ($version) {
|
switch ($version) {
|
||||||
case 'v3':
|
case 'v3':
|
||||||
|
@ -12,13 +12,25 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace HyperfTest\Etcd;
|
namespace HyperfTest\Etcd;
|
||||||
|
|
||||||
|
use GuzzleHttp\Client;
|
||||||
use Hyperf\Config\Config;
|
use Hyperf\Config\Config;
|
||||||
use Hyperf\Contract\ConfigInterface;
|
use Hyperf\Contract\ConfigInterface;
|
||||||
|
use Hyperf\Di\Container;
|
||||||
use Hyperf\Etcd\KVFactory;
|
use Hyperf\Etcd\KVFactory;
|
||||||
use Hyperf\Etcd\KVInterface;
|
use Hyperf\Etcd\KVInterface;
|
||||||
|
use Hyperf\Etcd\V3\EtcdClient;
|
||||||
|
use Hyperf\Etcd\V3\KV;
|
||||||
|
use Hyperf\Guzzle\HandlerStackFactory;
|
||||||
|
use Hyperf\Guzzle\PoolHandler;
|
||||||
|
use Hyperf\Pool\Channel;
|
||||||
|
use Hyperf\Pool\PoolOption;
|
||||||
|
use Hyperf\Pool\SimplePool\Connection;
|
||||||
|
use Hyperf\Pool\SimplePool\Pool;
|
||||||
|
use Hyperf\Pool\SimplePool\PoolFactory;
|
||||||
|
use Hyperf\Utils\ApplicationContext;
|
||||||
|
use HyperfTest\Etcd\Stub\GuzzleClientStub;
|
||||||
use Mockery;
|
use Mockery;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Psr\Container\ContainerInterface;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
@ -32,6 +44,29 @@ class KVTest extends TestCase
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function testGetKyFromFactory()
|
public function testGetKyFromFactory()
|
||||||
|
{
|
||||||
|
$kv = $this->getKVClient();
|
||||||
|
|
||||||
|
$this->assertInstanceOf(KVInterface::class, $kv);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPutAndGet()
|
||||||
|
{
|
||||||
|
$kv = $this->getKVClient();
|
||||||
|
|
||||||
|
$res = $kv->put('/test/test2', 'Hello World!');
|
||||||
|
|
||||||
|
$this->assertArrayHasKey('header', $res);
|
||||||
|
|
||||||
|
$res = $kv->get('/test/test2');
|
||||||
|
|
||||||
|
$this->assertSame('Hello World!', $res['kvs'][0]['value']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return KVInterface
|
||||||
|
*/
|
||||||
|
protected function getKVClient()
|
||||||
{
|
{
|
||||||
$config = new Config([
|
$config = new Config([
|
||||||
'etcd' => [
|
'etcd' => [
|
||||||
@ -43,12 +78,37 @@ class KVTest extends TestCase
|
|||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$container = Mockery::mock(ContainerInterface::class);
|
$container = Mockery::mock(Container::class);
|
||||||
|
ApplicationContext::setContainer($container);
|
||||||
|
|
||||||
|
$container->shouldReceive('make')->with(EtcdClient::class, Mockery::any())->andReturnUsing(function ($class, $args) {
|
||||||
|
return new EtcdClient($args['client']);
|
||||||
|
});
|
||||||
|
$container->shouldReceive('make')->with(Client::class, Mockery::any())->andReturnUsing(function ($class, $args) {
|
||||||
|
return new GuzzleClientStub($args['config']);
|
||||||
|
});
|
||||||
|
$container->shouldReceive('make')->with(PoolHandler::class, Mockery::any())->andReturnUsing(function ($class, $args) use ($container) {
|
||||||
|
return new PoolHandler(new PoolFactory($container), $args['option']);
|
||||||
|
});
|
||||||
|
$container->shouldReceive('make')->with(Channel::class, Mockery::any())->andReturnUsing(function ($class, $args) {
|
||||||
|
return new Channel($args['size']);
|
||||||
|
});
|
||||||
|
$container->shouldReceive('make')->with(Connection::class, Mockery::any())->andReturnUsing(function ($class, $args) use ($container) {
|
||||||
|
return new Connection($container, $args['pool'], $args['callback']);
|
||||||
|
});
|
||||||
|
$container->shouldReceive('make')->with(PoolOption::class, Mockery::any())->andReturnUsing(function ($class, $args) {
|
||||||
|
return new PoolOption(...array_values($args));
|
||||||
|
});
|
||||||
|
$container->shouldReceive('make')->with(Pool::class, Mockery::any())->andReturnUsing(function ($class, $args) use ($container) {
|
||||||
|
return new Pool($container, $args['callback'], $args['option']);
|
||||||
|
});
|
||||||
|
$container->shouldReceive('make')->with(KV::class, Mockery::any())->andReturnUsing(function ($class, $args) {
|
||||||
|
return new KV(...array_values($args));
|
||||||
|
});
|
||||||
$container->shouldReceive('get')->with(ConfigInterface::class)->andReturn($config);
|
$container->shouldReceive('get')->with(ConfigInterface::class)->andReturn($config);
|
||||||
|
$container->shouldReceive('get')->with(HandlerStackFactory::class)->andReturn(new HandlerStackFactory());
|
||||||
|
|
||||||
$factory = new KVFactory();
|
$factory = new KVFactory();
|
||||||
$kv = $factory($container);
|
return $factory($container);
|
||||||
|
|
||||||
$this->assertInstanceOf(KVInterface::class, $kv);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
39
src/etcd/tests/Stub/GuzzleClientStub.php
Normal file
39
src/etcd/tests/Stub/GuzzleClientStub.php
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace HyperfTest\Etcd\Stub;
|
||||||
|
|
||||||
|
use GuzzleHttp\Client;
|
||||||
|
use GuzzleHttp\Psr7\Response;
|
||||||
|
use GuzzleHttp\Psr7\Stream;
|
||||||
|
|
||||||
|
class GuzzleClientStub extends Client
|
||||||
|
{
|
||||||
|
public function request($method, $uri = '', array $options = [])
|
||||||
|
{
|
||||||
|
if ($uri == 'kv/put') {
|
||||||
|
$stream = fopen('php://temp', 'r+');
|
||||||
|
fwrite($stream, '{"header":{"cluster_id":"11588568905070377092","member_id":"128088275939295631","revision":"10","raft_term":"3"}}');
|
||||||
|
fseek($stream, 0);
|
||||||
|
return new Response(200, [], new Stream($stream));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($uri == 'kv/range') {
|
||||||
|
$stream = fopen('php://temp', 'r+');
|
||||||
|
fwrite($stream, '{"header":{"cluster_id":"11588568905070377092","member_id":"128088275939295631","revision":"12","raft_term":"3"},"kvs":[{"key":"L3Rlc3QvdGVzdDI=","create_revision":"7","mod_revision":"12","version":"6","value":"SGVsbG8gV29ybGQh"}],"count":"1"}');
|
||||||
|
fseek($stream, 0);
|
||||||
|
return new Response(200, [], new Stream($stream));
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::request($method, $uri, $options);
|
||||||
|
}
|
||||||
|
}
|
@ -93,10 +93,9 @@ class Request implements RequestInterface
|
|||||||
public function inputs(array $keys, $default = null): array
|
public function inputs(array $keys, $default = null): array
|
||||||
{
|
{
|
||||||
$data = $this->getInputData();
|
$data = $this->getInputData();
|
||||||
$result = $default ?? [];
|
|
||||||
|
|
||||||
foreach ($keys as $key) {
|
foreach ($keys as $key) {
|
||||||
$result[$key] = data_get($data, $key);
|
$result[$key] = data_get($data, $key, $default[$key] ?? null);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
|
@ -23,6 +23,7 @@ use Hyperf\HttpServer\Exception\Http\FileException;
|
|||||||
use Hyperf\HttpServer\Exception\Http\InvalidResponseException;
|
use Hyperf\HttpServer\Exception\Http\InvalidResponseException;
|
||||||
use Hyperf\Utils\ApplicationContext;
|
use Hyperf\Utils\ApplicationContext;
|
||||||
use Hyperf\Utils\ClearStatCache;
|
use Hyperf\Utils\ClearStatCache;
|
||||||
|
use Hyperf\Utils\Codec\Json;
|
||||||
use Hyperf\Utils\Context;
|
use Hyperf\Utils\Context;
|
||||||
use Hyperf\Utils\Contracts\Arrayable;
|
use Hyperf\Utils\Contracts\Arrayable;
|
||||||
use Hyperf\Utils\Contracts\Jsonable;
|
use Hyperf\Utils\Contracts\Jsonable;
|
||||||
@ -459,19 +460,13 @@ class Response implements PsrResponseInterface, ResponseInterface, Sendable
|
|||||||
*/
|
*/
|
||||||
protected function toJson($data): string
|
protected function toJson($data): string
|
||||||
{
|
{
|
||||||
if (is_array($data)) {
|
try {
|
||||||
return json_encode($data, JSON_UNESCAPED_UNICODE);
|
$result = Json::encode($data);
|
||||||
|
} catch (\Throwable $exception) {
|
||||||
|
throw new EncodingException($exception->getMessage(), $exception->getCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($data instanceof Jsonable) {
|
return $result;
|
||||||
return (string) $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($data instanceof Arrayable) {
|
|
||||||
return json_encode($data->toArray(), JSON_UNESCAPED_UNICODE);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new EncodingException('Error encoding response data to JSON.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -29,6 +29,7 @@ class RequestTest extends TestCase
|
|||||||
{
|
{
|
||||||
Mockery::close();
|
Mockery::close();
|
||||||
Context::set(ServerRequestInterface::class, null);
|
Context::set(ServerRequestInterface::class, null);
|
||||||
|
Context::set('http.request.parsedData', null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRequestHasFile()
|
public function testRequestHasFile()
|
||||||
@ -60,4 +61,38 @@ class RequestTest extends TestCase
|
|||||||
$res = $psrRequest->header('Hyperf-Version', 'v0');
|
$res = $psrRequest->header('Hyperf-Version', 'v0');
|
||||||
$this->assertSame('v1.0', $res);
|
$this->assertSame('v1.0', $res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testRequestInput()
|
||||||
|
{
|
||||||
|
$psrRequest = Mockery::mock(ServerRequestInterface::class);
|
||||||
|
$psrRequest->shouldReceive('getParsedBody')->andReturn(['id' => 1]);
|
||||||
|
$psrRequest->shouldReceive('getQueryParams')->andReturn([]);
|
||||||
|
Context::set(ServerRequestInterface::class, $psrRequest);
|
||||||
|
|
||||||
|
$psrRequest = new Request();
|
||||||
|
$this->assertSame(1, $psrRequest->input('id'));
|
||||||
|
$this->assertSame('Hyperf', $psrRequest->input('name', 'Hyperf'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRequestAll()
|
||||||
|
{
|
||||||
|
$psrRequest = Mockery::mock(ServerRequestInterface::class);
|
||||||
|
$psrRequest->shouldReceive('getParsedBody')->andReturn(['id' => 1]);
|
||||||
|
$psrRequest->shouldReceive('getQueryParams')->andReturn(['name' => 'Hyperf']);
|
||||||
|
Context::set(ServerRequestInterface::class, $psrRequest);
|
||||||
|
|
||||||
|
$psrRequest = new Request();
|
||||||
|
$this->assertSame(['id' => 1, 'name' => 'Hyperf'], $psrRequest->all());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRequestInputs()
|
||||||
|
{
|
||||||
|
$psrRequest = Mockery::mock(ServerRequestInterface::class);
|
||||||
|
$psrRequest->shouldReceive('getParsedBody')->andReturn(['id' => 1]);
|
||||||
|
$psrRequest->shouldReceive('getQueryParams')->andReturn([]);
|
||||||
|
Context::set(ServerRequestInterface::class, $psrRequest);
|
||||||
|
|
||||||
|
$psrRequest = new Request();
|
||||||
|
$this->assertSame(['id' => 1, 'name' => 'Hyperf'], $psrRequest->inputs(['id', 'name'], ['name' => 'Hyperf']));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -179,6 +179,20 @@ class ResponseTest extends TestCase
|
|||||||
$this->assertSame('{"kstring":"string","kint1":1,"kint0":0,"kfloat":0.12345,"kfalse":false,"ktrue":true,"karray":{"kstring":"string","kint1":1,"kint0":0,"kfloat":0.12345,"kfalse":false,"ktrue":true}}', $json->getBody()->getContents());
|
$this->assertSame('{"kstring":"string","kint1":1,"kint0":0,"kfloat":0.12345,"kfalse":false,"ktrue":true,"karray":{"kstring":"string","kint1":1,"kint0":0,"kfloat":0.12345,"kfalse":false,"ktrue":true}}', $json->getBody()->getContents());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testObjectToJson()
|
||||||
|
{
|
||||||
|
$container = Mockery::mock(ContainerInterface::class);
|
||||||
|
ApplicationContext::setContainer($container);
|
||||||
|
|
||||||
|
$psrResponse = new \Hyperf\HttpMessage\Base\Response();
|
||||||
|
Context::set(PsrResponseInterface::class, $psrResponse);
|
||||||
|
|
||||||
|
$response = new Response();
|
||||||
|
$json = $response->json((object) ['id' => 1, 'name' => 'Hyperf']);
|
||||||
|
|
||||||
|
$this->assertSame('{"id":1,"name":"Hyperf"}', $json->getBody()->getContents());
|
||||||
|
}
|
||||||
|
|
||||||
public function testPsrResponse()
|
public function testPsrResponse()
|
||||||
{
|
{
|
||||||
$container = Mockery::mock(ContainerInterface::class);
|
$container = Mockery::mock(ContainerInterface::class);
|
||||||
|
1
src/metric/.gitattributes
vendored
Normal file
1
src/metric/.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/tests export-ignore
|
4
src/metric/.gitignore
vendored
Normal file
4
src/metric/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
/vendor/
|
||||||
|
composer.lock
|
||||||
|
*.cache
|
||||||
|
*.log
|
21
src/metric/LICENSE
Normal file
21
src/metric/LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) Hyperf
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
64
src/metric/composer.json
Normal file
64
src/metric/composer.json
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
{
|
||||||
|
"name": "hyperf/metric",
|
||||||
|
"license": "MIT",
|
||||||
|
"keywords": [
|
||||||
|
"php",
|
||||||
|
"hyperf",
|
||||||
|
"prometheus",
|
||||||
|
"statsd",
|
||||||
|
"metrics",
|
||||||
|
"influxdb"
|
||||||
|
],
|
||||||
|
"description": "hyperf metric component",
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.2",
|
||||||
|
"psr/http-message": "^1.0",
|
||||||
|
"endclothing/prometheus_client_php": "^0.9.1",
|
||||||
|
"psr/container": "^1.0",
|
||||||
|
"psr/event-dispatcher": "^1.0",
|
||||||
|
"hyperf/contract": "~1.1.0",
|
||||||
|
"hyperf/utils": "~1.1.0",
|
||||||
|
"hyperf/guzzle": "~1.1.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"malukenho/docheader": "^0.1.6",
|
||||||
|
"mockery/mockery": "^1.0",
|
||||||
|
"phpunit/phpunit": "^7.0.0",
|
||||||
|
"friendsofphp/php-cs-fixer": "^2.9",
|
||||||
|
"influxdb/influxdb-php": "^1.15.0",
|
||||||
|
"domnikl/statsd": "^3.0.1"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"domnikl/statsd": "Required to use StatdD driver.",
|
||||||
|
"influxdb/influxdb-php": "Required to use InfluxDB driver.",
|
||||||
|
"hyperf/di": "Required to use annotations.",
|
||||||
|
"hyperf/event": "Required to use listeners for default metrics.",
|
||||||
|
"hyperf/process": "Required to use standalone process, or you have to roll your own"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Hyperf\\Metric\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload-dev": {
|
||||||
|
"psr-4": {
|
||||||
|
"HyperfTest\\Metric\\": "tests/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"sort-packages": true
|
||||||
|
},
|
||||||
|
"bin": [],
|
||||||
|
"scripts": {
|
||||||
|
"cs-fix": "php-cs-fixer fix $1",
|
||||||
|
"test": "phpunit --colors=always"
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "1.1-dev"
|
||||||
|
},
|
||||||
|
"hyperf": {
|
||||||
|
"config": "Hyperf\\Metric\\ConfigProvider"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
53
src/metric/publish/metric.php
Normal file
53
src/metric/publish/metric.php
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
use Hyperf\Metric\Adapter\Prometheus\Constants;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'default' => env('METRIC_DRIVER', 'prometheus'),
|
||||||
|
'use_standalone_process' => env('METRIC_USE_STANDALONE_PROCESS', true),
|
||||||
|
'enable_default_metric' => env('METRIC_ENABLE_DEFAULT_METRIC', true),
|
||||||
|
'default_metric_interval' => env('DEFAULT_METRIC_INTERVAL', 5),
|
||||||
|
'metric' => [
|
||||||
|
'prometheus' => [
|
||||||
|
'driver' => Hyperf\Metric\Adapter\Prometheus\MetricFactory::class,
|
||||||
|
'mode' => Constants::SCRAPE_MODE,
|
||||||
|
'namespace' => env('APP_NAME', 'skeleton'),
|
||||||
|
'scrape_host' => env('PROMETHEUS_SCRAPE_HOST', '0.0.0.0'),
|
||||||
|
'scrape_port' => env('PROMETHEUS_SCRAPE_PORT', '9502'),
|
||||||
|
'scrape_path' => env('PROMETHEUS_SCRAPE_PATH', '/metrics'),
|
||||||
|
'push_host' => env('PROMETHEUS_PUSH_HOST', '0.0.0.0'),
|
||||||
|
'push_port' => env('PROMETHEUS_PUSH_PORT', '9091'),
|
||||||
|
'push_interval' => env('PROMETHEUS_PUSH_INTERVAL', 5),
|
||||||
|
],
|
||||||
|
'statsd' => [
|
||||||
|
'driver' => Hyperf\Metric\Adapter\StatsD\MetricFactory::class,
|
||||||
|
'namespace' => env('APP_NAME', 'skeleton'),
|
||||||
|
'udp_host' => env('STATSD_UDP_HOST', '127.0.0.1'),
|
||||||
|
'udp_port' => env('STATSD_UDP_PORT', '8125'),
|
||||||
|
'enable_batch' => env('STATSD_ENABLE_BATCH', true),
|
||||||
|
'push_interval' => env('STATSD_PUSH_INTERVAL', 5),
|
||||||
|
'sample_rate' => env('STATSD_SAMPLE_RATE', 1.0),
|
||||||
|
],
|
||||||
|
'influxdb' => [
|
||||||
|
'driver' => Hyperf\Metric\Adapter\InfluxDB\MetricFactory::class,
|
||||||
|
'namespace' => env('APP_NAME', 'skeleton'),
|
||||||
|
'host' => env('INFLUXDB_HOST', '127.0.0.1'),
|
||||||
|
'port' => env('INFLUXDB_PORT', '8086'),
|
||||||
|
'username' => env('INFLUXDB_USERNAME', ''),
|
||||||
|
'password' => env('INFLUXDB_PASSWORD', ''),
|
||||||
|
'dbname' => env('INFLUXDB_DBNAME', true),
|
||||||
|
'push_interval' => env('INFLUXDB_PUSH_INTERVAL', 5),
|
||||||
|
'auto_create_db' => env('INFLUXDB_AUTO_CREATE_DB', true),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
145
src/metric/src/Adapter/InfluxDB/MetricFactory.php
Normal file
145
src/metric/src/Adapter/InfluxDB/MetricFactory.php
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hyperf\Metric\Adapter\InfluxDB;
|
||||||
|
|
||||||
|
use Hyperf\Contract\ConfigInterface;
|
||||||
|
use Hyperf\Guzzle\ClientFactory as GuzzleClientFactory;
|
||||||
|
use Hyperf\Metric\Adapter\Prometheus\Counter;
|
||||||
|
use Hyperf\Metric\Adapter\Prometheus\Gauge;
|
||||||
|
use Hyperf\Metric\Adapter\Prometheus\Histogram;
|
||||||
|
use Hyperf\Metric\Contract\CounterInterface;
|
||||||
|
use Hyperf\Metric\Contract\GaugeInterface;
|
||||||
|
use Hyperf\Metric\Contract\HistogramInterface;
|
||||||
|
use Hyperf\Metric\Contract\MetricFactoryInterface;
|
||||||
|
use InfluxDB\Client;
|
||||||
|
use InfluxDB\Database;
|
||||||
|
use InfluxDB\Database\RetentionPolicy;
|
||||||
|
use InfluxDB\Driver\DriverInterface;
|
||||||
|
use InfluxDB\Point;
|
||||||
|
use Prometheus\CollectorRegistry;
|
||||||
|
use Prometheus\Sample;
|
||||||
|
use Swoole\Coroutine;
|
||||||
|
|
||||||
|
class MetricFactory implements MetricFactoryInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var ConfigInterface
|
||||||
|
*/
|
||||||
|
private $config;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var CollectorRegistry
|
||||||
|
*/
|
||||||
|
private $registry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var guzzleClientFactory
|
||||||
|
*/
|
||||||
|
private $guzzleClientFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $name;
|
||||||
|
|
||||||
|
public function __construct(ConfigInterface $config, CollectorRegistry $registry, GuzzleClientFactory $guzzleClientFactory)
|
||||||
|
{
|
||||||
|
$this->config = $config;
|
||||||
|
$this->registry = $registry;
|
||||||
|
$this->guzzleClientFactory = $guzzleClientFactory;
|
||||||
|
$this->name = $this->config->get('metric.default');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function makeCounter(string $name, ?array $labelNames = []): CounterInterface
|
||||||
|
{
|
||||||
|
return new Counter(
|
||||||
|
$this->registry,
|
||||||
|
$this->getNamespace(),
|
||||||
|
$name,
|
||||||
|
'count ' . str_replace('_', ' ', $name),
|
||||||
|
$labelNames
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function makeGauge(string $name, ?array $labelNames = []): GaugeInterface
|
||||||
|
{
|
||||||
|
return new Gauge(
|
||||||
|
$this->registry,
|
||||||
|
$this->getNamespace(),
|
||||||
|
$name,
|
||||||
|
'gauge ' . str_replace('_', ' ', $name),
|
||||||
|
$labelNames
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function makeHistogram(string $name, ?array $labelNames = []): HistogramInterface
|
||||||
|
{
|
||||||
|
return new Histogram(
|
||||||
|
$this->registry,
|
||||||
|
$this->getNamespace(),
|
||||||
|
$name,
|
||||||
|
'measure ' . str_replace('_', ' ', $name),
|
||||||
|
$labelNames
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
$host = $this->config->get("metric.metric.{$this->name}.host");
|
||||||
|
$port = $this->config->get("metric.metric.{$this->name}.port");
|
||||||
|
$username = $this->config->get("metric.metric.{$this->name}.username");
|
||||||
|
$password = $this->config->get("metric.metric.{$this->name}.password");
|
||||||
|
$dbname = $this->config->get("metric.metric.{$this->name}.dbname");
|
||||||
|
$interval = (float) $this->config->get("metric.metric.{$this->name}.push_interval", 5);
|
||||||
|
$create = $this->config->get("metric.metric.{$this->name}.auto_create_db");
|
||||||
|
$client = new Client($host, $port, $username, $password);
|
||||||
|
$guzzleClient = $this->guzzleClientFactory->create([
|
||||||
|
'connect_timeout' => $client->getConnectTimeout(),
|
||||||
|
'timeout' => $client->getTimeout(),
|
||||||
|
'base_uri' => $client->getBaseURI(),
|
||||||
|
'verify' => $client->getVerifySSL(),
|
||||||
|
]);
|
||||||
|
$client->setDriver(make(DriverInterface::class, ['client' => $guzzleClient]));
|
||||||
|
$database = $client->selectDB($dbname);
|
||||||
|
if (! $database->exists() && $create) {
|
||||||
|
$database->create(new RetentionPolicy($dbname, '1d', 1, true));
|
||||||
|
}
|
||||||
|
while (true) {
|
||||||
|
Coroutine::sleep($interval);
|
||||||
|
$points = [];
|
||||||
|
$metrics = $this->registry->getMetricFamilySamples();
|
||||||
|
foreach ($metrics as $metric) {
|
||||||
|
foreach ($metric->getSamples() as $sample) {
|
||||||
|
$points[] = $this->createPoint($sample);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$result = $database->writePoints($points, Database::PRECISION_SECONDS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function createPoint(Sample $sample): Point
|
||||||
|
{
|
||||||
|
return new Point(
|
||||||
|
$sample->getName(),
|
||||||
|
$sample->getValue(),
|
||||||
|
$labels = array_combine($sample->getLabelNames(), $sample->getLabelValues()),
|
||||||
|
[],
|
||||||
|
time()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getNamespace(): string
|
||||||
|
{
|
||||||
|
return $this->config->get("metric.metric.{$this->name}.namespace");
|
||||||
|
}
|
||||||
|
}
|
20
src/metric/src/Adapter/Prometheus/Constants.php
Normal file
20
src/metric/src/Adapter/Prometheus/Constants.php
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hyperf\Metric\Adapter\Prometheus;
|
||||||
|
|
||||||
|
class Constants
|
||||||
|
{
|
||||||
|
const SCRAPE_MODE = 1;
|
||||||
|
|
||||||
|
const PUSH_MODE = 2;
|
||||||
|
}
|
51
src/metric/src/Adapter/Prometheus/Counter.php
Normal file
51
src/metric/src/Adapter/Prometheus/Counter.php
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hyperf\Metric\Adapter\Prometheus;
|
||||||
|
|
||||||
|
use Hyperf\Metric\Contract\CounterInterface;
|
||||||
|
use Prometheus\CollectorRegistry;
|
||||||
|
|
||||||
|
class Counter implements CounterInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var \Prometheus\CollectorRegistry
|
||||||
|
*/
|
||||||
|
protected $registry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Prometheus\Counter
|
||||||
|
*/
|
||||||
|
protected $counter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
protected $labelValues = [];
|
||||||
|
|
||||||
|
public function __construct(CollectorRegistry $registry, string $namespace, string $name, string $help, array $labelNames)
|
||||||
|
{
|
||||||
|
$this->registry = $registry;
|
||||||
|
$this->counter = $registry->getOrRegisterCounter($namespace, $name, $help, $labelNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function with(string ...$labelValues): CounterInterface
|
||||||
|
{
|
||||||
|
$this->labelValues = $labelValues;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function add(int $delta): void
|
||||||
|
{
|
||||||
|
$this->counter->incBy($delta, $this->labelValues);
|
||||||
|
}
|
||||||
|
}
|
55
src/metric/src/Adapter/Prometheus/Gauge.php
Normal file
55
src/metric/src/Adapter/Prometheus/Gauge.php
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hyperf\Metric\Adapter\Prometheus;
|
||||||
|
|
||||||
|
use Hyperf\Metric\Contract\GaugeInterface;
|
||||||
|
|
||||||
|
class Gauge implements GaugeInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var \Prometheus\CollectorRegistry
|
||||||
|
*/
|
||||||
|
protected $registry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Prometheus\Gauge
|
||||||
|
*/
|
||||||
|
protected $gauge;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
protected $labelValues = [];
|
||||||
|
|
||||||
|
public function __construct(\Prometheus\CollectorRegistry $registry, string $namespace, string $name, string $help, array $labelNames)
|
||||||
|
{
|
||||||
|
$this->registry = $registry;
|
||||||
|
$this->gauge = $registry->getOrRegisterGauge($namespace, $name, $help, $labelNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function with(string ...$labelValues): GaugeInterface
|
||||||
|
{
|
||||||
|
$this->labelValues = $labelValues;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function set(float $value): void
|
||||||
|
{
|
||||||
|
$this->gauge->set($value, $this->labelValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function add(float $delta): void
|
||||||
|
{
|
||||||
|
$this->gauge->incBy($delta, $this->labelValues);
|
||||||
|
}
|
||||||
|
}
|
51
src/metric/src/Adapter/Prometheus/Histogram.php
Normal file
51
src/metric/src/Adapter/Prometheus/Histogram.php
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hyperf\Metric\Adapter\Prometheus;
|
||||||
|
|
||||||
|
use Hyperf\Metric\Contract\HistogramInterface;
|
||||||
|
use Prometheus\CollectorRegistry;
|
||||||
|
|
||||||
|
class Histogram implements HistogramInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var \Prometheus\CollectorRegistry
|
||||||
|
*/
|
||||||
|
protected $registry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Prometheus\Histogram
|
||||||
|
*/
|
||||||
|
protected $histogram;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
protected $labelValues = [];
|
||||||
|
|
||||||
|
public function __construct(CollectorRegistry $registry, string $namespace, string $name, string $help, array $labelNames)
|
||||||
|
{
|
||||||
|
$this->registry = $registry;
|
||||||
|
$this->histogram = $registry->getOrRegisterHistogram($namespace, $name, $help, $labelNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function with(string ...$labelValues): HistogramInterface
|
||||||
|
{
|
||||||
|
$this->labelValues = $labelValues;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function put(float $sample): void
|
||||||
|
{
|
||||||
|
$this->histogram->observe($sample, $this->labelValues);
|
||||||
|
}
|
||||||
|
}
|
169
src/metric/src/Adapter/Prometheus/MetricFactory.php
Normal file
169
src/metric/src/Adapter/Prometheus/MetricFactory.php
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hyperf\Metric\Adapter\Prometheus;
|
||||||
|
|
||||||
|
use Hyperf\Contract\ConfigInterface;
|
||||||
|
use Hyperf\Guzzle\ClientFactory as GuzzleClientFactory;
|
||||||
|
use Hyperf\Metric\Contract\CounterInterface;
|
||||||
|
use Hyperf\Metric\Contract\GaugeInterface;
|
||||||
|
use Hyperf\Metric\Contract\HistogramInterface;
|
||||||
|
use Hyperf\Metric\Contract\MetricFactoryInterface;
|
||||||
|
use Hyperf\Metric\Exception\InvalidArgumentException;
|
||||||
|
use Hyperf\Metric\Exception\RuntimeException;
|
||||||
|
use Prometheus\CollectorRegistry;
|
||||||
|
use Prometheus\RenderTextFormat;
|
||||||
|
use Swoole\Coroutine;
|
||||||
|
use Swoole\Coroutine\Http\Server;
|
||||||
|
|
||||||
|
class MetricFactory implements MetricFactoryInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var ConfigInterface
|
||||||
|
*/
|
||||||
|
private $config;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var CollectorRegistry
|
||||||
|
*/
|
||||||
|
private $registry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var guzzleClientFactory
|
||||||
|
*/
|
||||||
|
private $guzzleClientFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $name;
|
||||||
|
|
||||||
|
public function __construct(ConfigInterface $config, CollectorRegistry $registry, GuzzleClientFactory $guzzleClientFactory)
|
||||||
|
{
|
||||||
|
$this->config = $config;
|
||||||
|
$this->registry = $registry;
|
||||||
|
$this->guzzleClientFactory = $guzzleClientFactory;
|
||||||
|
$this->name = $this->config->get('metric.default');
|
||||||
|
$this->guardConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function makeCounter(string $name, ?array $labelNames = []): CounterInterface
|
||||||
|
{
|
||||||
|
return new Counter(
|
||||||
|
$this->registry,
|
||||||
|
$this->getNamespace(),
|
||||||
|
$name,
|
||||||
|
'count ' . str_replace('_', ' ', $name),
|
||||||
|
$labelNames
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function makeGauge(string $name, ?array $labelNames = []): GaugeInterface
|
||||||
|
{
|
||||||
|
return new Gauge(
|
||||||
|
$this->registry,
|
||||||
|
$this->getNamespace(),
|
||||||
|
$name,
|
||||||
|
'gauge ' . str_replace('_', ' ', $name),
|
||||||
|
$labelNames
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function makeHistogram(string $name, ?array $labelNames = []): HistogramInterface
|
||||||
|
{
|
||||||
|
return new Histogram(
|
||||||
|
$this->registry,
|
||||||
|
$this->getNamespace(),
|
||||||
|
$name,
|
||||||
|
'measure ' . str_replace('_', ' ', $name),
|
||||||
|
$labelNames
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
switch ($this->config->get("metric.metric.{$this->name}.mode")) {
|
||||||
|
case Constants::SCRAPE_MODE:
|
||||||
|
$this->scrapeHandle();
|
||||||
|
break;
|
||||||
|
case Constants::PUSH_MODE:
|
||||||
|
$this->pushHandle();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new InvalidArgumentException('Unsupported Prometheus mode encountered');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function scrapeHandle()
|
||||||
|
{
|
||||||
|
$host = $this->config->get("metric.metric.{$this->name}.scrape_host");
|
||||||
|
$port = $this->config->get("metric.metric.{$this->name}.scrape_port");
|
||||||
|
$path = $this->config->get("metric.metric.{$this->name}.scrape_path");
|
||||||
|
$renderer = new RenderTextFormat();
|
||||||
|
$server = new Server($host, (int) $port, false);
|
||||||
|
$server->handle($path, function ($request, $response) use ($renderer) {
|
||||||
|
$response->header('Content-Type', RenderTextFormat::MIME_TYPE);
|
||||||
|
$response->end($renderer->render($this->registry->getMetricFamilySamples()));
|
||||||
|
});
|
||||||
|
$server->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function pushHandle()
|
||||||
|
{
|
||||||
|
while (true) {
|
||||||
|
$interval = (float) $this->config->get("metric.metric.{$this->name}.push_interval", 5);
|
||||||
|
$host = $this->config->get("metric.metric.{$this->name}.push_host");
|
||||||
|
$port = $this->config->get("metric.metric.{$this->name}.push_port");
|
||||||
|
$this->doRequest("{$host}:{$port}", $this->getNamespace(), 'put');
|
||||||
|
Coroutine::sleep($interval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getNamespace(): string
|
||||||
|
{
|
||||||
|
return $this->config->get("metric.metric.{$this->name}.namespace");
|
||||||
|
}
|
||||||
|
|
||||||
|
private function guardConfig()
|
||||||
|
{
|
||||||
|
if ($this->config->get("metric.metric.{$this->name}.mode") == Constants::SCRAPE_MODE &&
|
||||||
|
$this->config->get('metric.use_standalone_process') == false) {
|
||||||
|
throw new RuntimeException(
|
||||||
|
"Prometheus in scrape mode must be used in conjunction with standalone process. \n Set metric.use_standalone_process to true to avoid this error."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function doRequest(string $address, string $job, string $method)
|
||||||
|
{
|
||||||
|
$url = 'http://' . $address . '/metrics/job/' . $job . '/ip/' . current(swoole_get_local_ip()) . '/pid/' . getmypid();
|
||||||
|
$client = $this->guzzleClientFactory->create();
|
||||||
|
$requestOptions = [
|
||||||
|
'headers' => [
|
||||||
|
'Content-Type' => RenderTextFormat::MIME_TYPE,
|
||||||
|
],
|
||||||
|
'connect_timeout' => 10,
|
||||||
|
'timeout' => 20,
|
||||||
|
];
|
||||||
|
if ($method != 'delete') {
|
||||||
|
$renderer = new RenderTextFormat();
|
||||||
|
$requestOptions['body'] = $renderer->render($this->registry->getMetricFamilySamples());
|
||||||
|
}
|
||||||
|
$response = $client->request($method, $url, $requestOptions);
|
||||||
|
$statusCode = $response->getStatusCode();
|
||||||
|
if ($statusCode != 200 && $statusCode != 202) {
|
||||||
|
$msg = 'Unexpected status code ' . $statusCode . ' received from pushgateway ' . $address . ': ' . $response->getBody();
|
||||||
|
throw new RuntimeException($msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
63
src/metric/src/Adapter/RemoteProxy/Counter.php
Normal file
63
src/metric/src/Adapter/RemoteProxy/Counter.php
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hyperf\Metric\Adapter\RemoteProxy;
|
||||||
|
|
||||||
|
use Hyperf\Metric\Contract\CounterInterface;
|
||||||
|
use Hyperf\Process\ProcessCollector;
|
||||||
|
|
||||||
|
class Counter implements CounterInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected const TARGET_PROCESS_NAME = 'metric';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string[];
|
||||||
|
*/
|
||||||
|
public $labelNames = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
public $labelValues = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public $delta;
|
||||||
|
|
||||||
|
public function __construct(string $name, array $labelNames)
|
||||||
|
{
|
||||||
|
$this->name = $name;
|
||||||
|
$this->labelNames = $labelNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function with(string ...$labelValues): CounterInterface
|
||||||
|
{
|
||||||
|
$this->labelValues = $labelValues;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function add(int $delta): void
|
||||||
|
{
|
||||||
|
$this->delta = $delta;
|
||||||
|
$process = ProcessCollector::get(static::TARGET_PROCESS_NAME)[0];
|
||||||
|
$process->write(serialize($this));
|
||||||
|
}
|
||||||
|
}
|
77
src/metric/src/Adapter/RemoteProxy/Gauge.php
Normal file
77
src/metric/src/Adapter/RemoteProxy/Gauge.php
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hyperf\Metric\Adapter\RemoteProxy;
|
||||||
|
|
||||||
|
use Hyperf\Metric\Contract\GaugeInterface;
|
||||||
|
use Hyperf\Process\ProcessCollector;
|
||||||
|
|
||||||
|
class Gauge implements GaugeInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected const TARGET_PROCESS_NAME = 'metric';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string[];
|
||||||
|
*/
|
||||||
|
public $labelNames = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
public $labelValues = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var null|float
|
||||||
|
*/
|
||||||
|
public $delta;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var null|float
|
||||||
|
*/
|
||||||
|
public $value;
|
||||||
|
|
||||||
|
public function __construct(string $name, array $labelNames)
|
||||||
|
{
|
||||||
|
$this->name = $name;
|
||||||
|
$this->labelNames = $labelNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function with(string ...$labelValues): GaugeInterface
|
||||||
|
{
|
||||||
|
$this->labelValues = $labelValues;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function set(float $value): void
|
||||||
|
{
|
||||||
|
$this->value = $value;
|
||||||
|
$this->delta = null;
|
||||||
|
$process = ProcessCollector::get(static::TARGET_PROCESS_NAME)[0];
|
||||||
|
$process->write(serialize($this));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function add(float $delta): void
|
||||||
|
{
|
||||||
|
$this->delta = $delta;
|
||||||
|
$this->value = null;
|
||||||
|
$process = ProcessCollector::get(static::TARGET_PROCESS_NAME)[0];
|
||||||
|
$process->write(serialize($this));
|
||||||
|
}
|
||||||
|
}
|
63
src/metric/src/Adapter/RemoteProxy/Histogram.php
Normal file
63
src/metric/src/Adapter/RemoteProxy/Histogram.php
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hyperf\Metric\Adapter\RemoteProxy;
|
||||||
|
|
||||||
|
use Hyperf\Metric\Contract\HistogramInterface;
|
||||||
|
use Hyperf\Process\ProcessCollector;
|
||||||
|
|
||||||
|
class Histogram implements HistogramInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected const TARGET_PROCESS_NAME = 'metric';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string[];
|
||||||
|
*/
|
||||||
|
public $labelNames = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
public $labelValues = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var float
|
||||||
|
*/
|
||||||
|
public $sample;
|
||||||
|
|
||||||
|
public function __construct(string $name, array $labelNames)
|
||||||
|
{
|
||||||
|
$this->name = $name;
|
||||||
|
$this->labelNames = $labelNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function with(string ...$labelValues): HistogramInterface
|
||||||
|
{
|
||||||
|
$this->labelValues = $labelValues;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function put(float $sample): void
|
||||||
|
{
|
||||||
|
$this->sample = $sample;
|
||||||
|
$process = ProcessCollector::get(static::TARGET_PROCESS_NAME)[0];
|
||||||
|
$process->write(serialize($this));
|
||||||
|
}
|
||||||
|
}
|
51
src/metric/src/Adapter/RemoteProxy/MetricFactory.php
Normal file
51
src/metric/src/Adapter/RemoteProxy/MetricFactory.php
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hyperf\Metric\Adapter\RemoteProxy;
|
||||||
|
|
||||||
|
use Hyperf\Metric\Contract\CounterInterface;
|
||||||
|
use Hyperf\Metric\Contract\GaugeInterface;
|
||||||
|
use Hyperf\Metric\Contract\HistogramInterface;
|
||||||
|
use Hyperf\Metric\Contract\MetricFactoryInterface;
|
||||||
|
use Hyperf\Metric\Exception\RuntimeException;
|
||||||
|
|
||||||
|
class MetricFactory implements MetricFactoryInterface
|
||||||
|
{
|
||||||
|
public function makeCounter(string $name, ?array $labelNames = []): CounterInterface
|
||||||
|
{
|
||||||
|
return new Counter(
|
||||||
|
$name,
|
||||||
|
$labelNames
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function makeGauge(string $name, ?array $labelNames = []): GaugeInterface
|
||||||
|
{
|
||||||
|
return new Gauge(
|
||||||
|
$name,
|
||||||
|
$labelNames
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function makeHistogram(string $name, ?array $labelNames = []): HistogramInterface
|
||||||
|
{
|
||||||
|
return new Histogram(
|
||||||
|
$name,
|
||||||
|
$labelNames
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
throw new RuntimeException('RemoteProxy adapter cannot handle metrics reporting/serving directly');
|
||||||
|
}
|
||||||
|
}
|
63
src/metric/src/Adapter/StatsD/Counter.php
Normal file
63
src/metric/src/Adapter/StatsD/Counter.php
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hyperf\Metric\Adapter\StatsD;
|
||||||
|
|
||||||
|
use Domnikl\Statsd\Client;
|
||||||
|
use Hyperf\Metric\Contract\CounterInterface;
|
||||||
|
|
||||||
|
class Counter implements CounterInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var \Domnikl\Statsd\Client
|
||||||
|
*/
|
||||||
|
protected $client;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var float
|
||||||
|
*/
|
||||||
|
protected $sampleRate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
protected $labelNames = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
protected $labelValues = [];
|
||||||
|
|
||||||
|
public function __construct(Client $client, string $name, float $sampleRate, array $labelNames)
|
||||||
|
{
|
||||||
|
$this->client = $client;
|
||||||
|
$this->name = $name;
|
||||||
|
$this->sampleRate = $sampleRate;
|
||||||
|
$this->labelNames = $labelNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function with(string ...$labelValues): CounterInterface
|
||||||
|
{
|
||||||
|
$this->labelValues = $labelValues;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function add(int $delta): void
|
||||||
|
{
|
||||||
|
$this->client->count($this->name, $delta, $this->sampleRate, array_combine($this->labelNames, $this->labelValues));
|
||||||
|
}
|
||||||
|
}
|
77
src/metric/src/Adapter/StatsD/Gauge.php
Normal file
77
src/metric/src/Adapter/StatsD/Gauge.php
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hyperf\Metric\Adapter\StatsD;
|
||||||
|
|
||||||
|
use Domnikl\Statsd\Client;
|
||||||
|
use Hyperf\Metric\Contract\GaugeInterface;
|
||||||
|
|
||||||
|
class Gauge implements GaugeInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var \Domnikl\Statsd\Client
|
||||||
|
*/
|
||||||
|
protected $client;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var float
|
||||||
|
*/
|
||||||
|
protected $sampleRate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
protected $labelNames = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
protected $labelValues = [];
|
||||||
|
|
||||||
|
public function __construct(Client $client, string $name, float $sampleRate, array $labelNames)
|
||||||
|
{
|
||||||
|
$this->client = $client;
|
||||||
|
$this->name = $name;
|
||||||
|
$this->sampleRate = $sampleRate;
|
||||||
|
$this->labelNames = $labelNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function with(string ...$labelValues): GaugeInterface
|
||||||
|
{
|
||||||
|
$this->labelValues = $labelValues;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function set(float $value): void
|
||||||
|
{
|
||||||
|
if ($value < 0) {
|
||||||
|
// StatsD gauge doesn't support negative values.
|
||||||
|
$value = 0;
|
||||||
|
}
|
||||||
|
$this->client->gauge($this->name, (string) $value, array_combine($this->labelNames, $this->labelValues));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function add(float $delta): void
|
||||||
|
{
|
||||||
|
if ($delta >= 0) {
|
||||||
|
$deltaStr = '+' . $delta;
|
||||||
|
} else {
|
||||||
|
$deltaStr = (string) $delta;
|
||||||
|
}
|
||||||
|
$this->client->gauge($this->name, $deltaStr, array_combine($this->labelNames, $this->labelValues));
|
||||||
|
}
|
||||||
|
}
|
63
src/metric/src/Adapter/StatsD/Histogram.php
Normal file
63
src/metric/src/Adapter/StatsD/Histogram.php
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hyperf\Metric\Adapter\StatsD;
|
||||||
|
|
||||||
|
use Domnikl\Statsd\Client;
|
||||||
|
use Hyperf\Metric\Contract\HistogramInterface;
|
||||||
|
|
||||||
|
class Histogram implements HistogramInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var \Domnikl\Statsd\Client
|
||||||
|
*/
|
||||||
|
protected $client;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var float
|
||||||
|
*/
|
||||||
|
protected $sampleRate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
protected $labelNames = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
protected $labelValues = [];
|
||||||
|
|
||||||
|
public function __construct(Client $client, string $name, float $sampleRate, array $labelNames)
|
||||||
|
{
|
||||||
|
$this->client = $client;
|
||||||
|
$this->name = $name;
|
||||||
|
$this->sampleRate = $sampleRate;
|
||||||
|
$this->labelNames = $labelNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function with(string ...$labelValues): HistogramInterface
|
||||||
|
{
|
||||||
|
$this->labelValues = $labelValues;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function put(float $sample): void
|
||||||
|
{
|
||||||
|
$this->client->timing($this->name, $sample, $this->sampleRate, array_combine($this->labelNames, $this->labelValues));
|
||||||
|
}
|
||||||
|
}
|
122
src/metric/src/Adapter/StatsD/MetricFactory.php
Normal file
122
src/metric/src/Adapter/StatsD/MetricFactory.php
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hyperf\Metric\Adapter\StatsD;
|
||||||
|
|
||||||
|
use Domnikl\Statsd\Client;
|
||||||
|
use Domnikl\Statsd\Connection;
|
||||||
|
use Hyperf\Contract\ConfigInterface;
|
||||||
|
use Hyperf\Metric\Contract\CounterInterface;
|
||||||
|
use Hyperf\Metric\Contract\GaugeInterface;
|
||||||
|
use Hyperf\Metric\Contract\HistogramInterface;
|
||||||
|
use Hyperf\Metric\Contract\MetricFactoryInterface;
|
||||||
|
use Swoole\Coroutine;
|
||||||
|
|
||||||
|
class MetricFactory implements MetricFactoryInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var ConfigInterface
|
||||||
|
*/
|
||||||
|
private $config;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Client
|
||||||
|
*/
|
||||||
|
private $client;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GuzzleClientFactory.
|
||||||
|
*/
|
||||||
|
private $guzzleClientFactory;
|
||||||
|
|
||||||
|
public function __construct(ConfigInterface $config)
|
||||||
|
{
|
||||||
|
$this->config = $config;
|
||||||
|
$this->client = make(Client::class, [
|
||||||
|
'connection' => $this->getConnection(),
|
||||||
|
'namespace' => $this->getNamespace(),
|
||||||
|
'sampleRateAllMetrics' => $this->getSampleRate(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function makeCounter(string $name, ?array $labelNames = []): CounterInterface
|
||||||
|
{
|
||||||
|
return new Counter(
|
||||||
|
$this->client,
|
||||||
|
$name,
|
||||||
|
$this->getSampleRate(),
|
||||||
|
$labelNames
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function makeGauge(string $name, ?array $labelNames = []): GaugeInterface
|
||||||
|
{
|
||||||
|
return new Gauge(
|
||||||
|
$this->client,
|
||||||
|
$name,
|
||||||
|
$this->getSampleRate(),
|
||||||
|
$labelNames
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function makeHistogram(string $name, ?array $labelNames = []): HistogramInterface
|
||||||
|
{
|
||||||
|
return new Histogram(
|
||||||
|
$this->client,
|
||||||
|
$name,
|
||||||
|
$this->getSampleRate(),
|
||||||
|
$labelNames
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
$name = $this->config->get('metric.default');
|
||||||
|
$interval = (float) $this->config->get("metric.metric.{$name}.push_interval", 5);
|
||||||
|
$batchEnabled = $this->config->get("metric.metric.{$name}.enable_batch") == true;
|
||||||
|
// Block handle from returning.
|
||||||
|
do {
|
||||||
|
if ($batchEnabled) {
|
||||||
|
$this->client->startBatch();
|
||||||
|
Coroutine::sleep((int) $interval);
|
||||||
|
$this->client->endBatch();
|
||||||
|
} else {
|
||||||
|
Coroutine::sleep(5000);
|
||||||
|
}
|
||||||
|
} while (true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getConnection(): Connection
|
||||||
|
{
|
||||||
|
$name = $this->config->get('metric.default');
|
||||||
|
$host = $this->config->get("metric.metric.{$name}.udp_host");
|
||||||
|
$port = $this->config->get("metric.metric.{$name}.udp_port");
|
||||||
|
return make(Connection::class, [
|
||||||
|
'host' => $host,
|
||||||
|
'port' => (int) $port,
|
||||||
|
'timeout' => null,
|
||||||
|
'persistent' => true,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getNamespace(): string
|
||||||
|
{
|
||||||
|
$name = $this->config->get('metric.default');
|
||||||
|
return $this->config->get("metric.metric.{$name}.namespace");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getSampleRate(): float
|
||||||
|
{
|
||||||
|
$name = $this->config->get('metric.default');
|
||||||
|
return $this->config->get("metric.metric.{$name}.sample_rate", 1.0);
|
||||||
|
}
|
||||||
|
}
|
28
src/metric/src/Annotation/Counter.php
Normal file
28
src/metric/src/Annotation/Counter.php
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hyperf\Metric\Annotation;
|
||||||
|
|
||||||
|
use Doctrine\Common\Annotations\Annotation\Target;
|
||||||
|
use Hyperf\Di\Annotation\AbstractAnnotation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Annotation
|
||||||
|
* @Target({"CLASS", "METHOD"})
|
||||||
|
*/
|
||||||
|
class Counter extends AbstractAnnotation
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $name = '';
|
||||||
|
}
|
28
src/metric/src/Annotation/Histogram.php
Normal file
28
src/metric/src/Annotation/Histogram.php
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hyperf\Metric\Annotation;
|
||||||
|
|
||||||
|
use Doctrine\Common\Annotations\Annotation\Target;
|
||||||
|
use Hyperf\Di\Annotation\AbstractAnnotation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Annotation
|
||||||
|
* @Target({"CLASS", "METHOD"})
|
||||||
|
*/
|
||||||
|
class Histogram extends AbstractAnnotation
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $name = '';
|
||||||
|
}
|
75
src/metric/src/Aspect/CounterAnnotationAspect.php
Normal file
75
src/metric/src/Aspect/CounterAnnotationAspect.php
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hyperf\Metric\Aspect;
|
||||||
|
|
||||||
|
use Hyperf\Di\Annotation\Aspect;
|
||||||
|
use Hyperf\Di\Aop\AroundInterface;
|
||||||
|
use Hyperf\Di\Aop\ProceedingJoinPoint;
|
||||||
|
use Hyperf\Metric\Annotation\Counter;
|
||||||
|
use Hyperf\Metric\Contract\MetricFactoryInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Aspect
|
||||||
|
*/
|
||||||
|
class CounterAnnotationAspect implements AroundInterface
|
||||||
|
{
|
||||||
|
public $classes = [];
|
||||||
|
|
||||||
|
public $annotations = [
|
||||||
|
Counter::class,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var MetricFactoryInterface
|
||||||
|
*/
|
||||||
|
private $factory;
|
||||||
|
|
||||||
|
public function __construct(MetricFactoryInterface $factory)
|
||||||
|
{
|
||||||
|
$this->factory = $factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed return the value from process method of ProceedingJoinPoint, or the value that you handled
|
||||||
|
*/
|
||||||
|
public function process(ProceedingJoinPoint $proceedingJoinPoint)
|
||||||
|
{
|
||||||
|
$metadata = $proceedingJoinPoint->getAnnotationMetadata();
|
||||||
|
$source = $this->fromCamelCase($proceedingJoinPoint->className . '::' . $proceedingJoinPoint->methodName);
|
||||||
|
/** @var Counter $annotation */
|
||||||
|
if ($annotation = $metadata->method[Counter::class] ?? null) {
|
||||||
|
$name = $annotation->name ?: $source;
|
||||||
|
} else {
|
||||||
|
$name = $source;
|
||||||
|
}
|
||||||
|
$counter = $this->factory->makeCounter($name, ['class', 'method']);
|
||||||
|
$result = $proceedingJoinPoint->process();
|
||||||
|
$counter
|
||||||
|
->with(
|
||||||
|
$proceedingJoinPoint->className,
|
||||||
|
$proceedingJoinPoint->methodName
|
||||||
|
)
|
||||||
|
->add(1);
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function fromCamelCase(string $input): string
|
||||||
|
{
|
||||||
|
preg_match_all('!([A-Z][A-Z0-9]*(?=$|[A-Z][a-z0-9])|[A-Za-z][a-z0-9]+)!', $input, $matches);
|
||||||
|
$ret = $matches[0];
|
||||||
|
foreach ($ret as &$match) {
|
||||||
|
$match = $match == strtoupper($match) ? strtolower($match) : lcfirst($match);
|
||||||
|
}
|
||||||
|
return implode('_', $ret);
|
||||||
|
}
|
||||||
|
}
|
64
src/metric/src/Aspect/HistogramAnnotationAspect.php
Normal file
64
src/metric/src/Aspect/HistogramAnnotationAspect.php
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hyperf\Metric\Aspect;
|
||||||
|
|
||||||
|
use Hyperf\Di\Annotation\Aspect;
|
||||||
|
use Hyperf\Di\Aop\AroundInterface;
|
||||||
|
use Hyperf\Di\Aop\ProceedingJoinPoint;
|
||||||
|
use Hyperf\Metric\Annotation\Histogram;
|
||||||
|
use Hyperf\Metric\Timer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Aspect
|
||||||
|
*/
|
||||||
|
class HistogramAnnotationAspect implements AroundInterface
|
||||||
|
{
|
||||||
|
public $classes = [];
|
||||||
|
|
||||||
|
public $annotations = [
|
||||||
|
Histogram::class,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed return the value from process method of ProceedingJoinPoint, or the value that you handled
|
||||||
|
*/
|
||||||
|
public function process(ProceedingJoinPoint $proceedingJoinPoint)
|
||||||
|
{
|
||||||
|
$metadata = $proceedingJoinPoint->getAnnotationMetadata();
|
||||||
|
$source = $this->fromCamelCase($proceedingJoinPoint->className . '::' . $proceedingJoinPoint->methodName);
|
||||||
|
/** @var Histogram $annotation */
|
||||||
|
if ($annotation = $metadata->method[Histogram::class] ?? null) {
|
||||||
|
$name = $annotation->name ?: $source;
|
||||||
|
} else {
|
||||||
|
$name = $source;
|
||||||
|
}
|
||||||
|
$timer = new Timer(
|
||||||
|
$name,
|
||||||
|
[
|
||||||
|
'class' => $proceedingJoinPoint->className,
|
||||||
|
'method' => $proceedingJoinPoint->methodName,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
return $proceedingJoinPoint->process();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function fromCamelCase(string $input): string
|
||||||
|
{
|
||||||
|
preg_match_all('!([A-Z][A-Z0-9]*(?=$|[A-Z][a-z0-9])|[A-Za-z][a-z0-9]+)!', $input, $matches);
|
||||||
|
$ret = $matches[0];
|
||||||
|
foreach ($ret as &$match) {
|
||||||
|
$match = $match == strtoupper($match) ? strtolower($match) : lcfirst($match);
|
||||||
|
}
|
||||||
|
return implode('_', $ret);
|
||||||
|
}
|
||||||
|
}
|
59
src/metric/src/ConfigProvider.php
Normal file
59
src/metric/src/ConfigProvider.php
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hyperf\Metric;
|
||||||
|
|
||||||
|
use Domnikl\Statsd\Connection;
|
||||||
|
use Domnikl\Statsd\Connection\UdpSocket;
|
||||||
|
use Hyperf\Metric\Contract\MetricFactoryInterface;
|
||||||
|
use Hyperf\Metric\Listener\OnMetricFactoryReady;
|
||||||
|
use Hyperf\Metric\Listener\OnPipeMessage;
|
||||||
|
use Hyperf\Metric\Listener\OnWorkerStart;
|
||||||
|
use InfluxDB\Driver\DriverInterface;
|
||||||
|
use InfluxDB\Driver\Guzzle;
|
||||||
|
use Prometheus\Storage\Adapter;
|
||||||
|
use Prometheus\Storage\InMemory;
|
||||||
|
|
||||||
|
class ConfigProvider
|
||||||
|
{
|
||||||
|
public function __invoke(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'dependencies' => [
|
||||||
|
MetricFactoryInterface::class => MetricFactoryPicker::class,
|
||||||
|
Adapter::class => InMemory::class,
|
||||||
|
Connection::class => UdpSocket::class,
|
||||||
|
DriverInterface::class => Guzzle::class,
|
||||||
|
],
|
||||||
|
'annotations' => [
|
||||||
|
'scan' => [
|
||||||
|
'paths' => [
|
||||||
|
__DIR__,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'publish' => [
|
||||||
|
[
|
||||||
|
'id' => 'config',
|
||||||
|
'description' => 'The config for metric component.',
|
||||||
|
'source' => __DIR__ . '/../publish/metric.php',
|
||||||
|
'destination' => BASE_PATH . '/config/autoload/metric.php',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'listeners' => [
|
||||||
|
OnPipeMessage::class,
|
||||||
|
OnMetricFactoryReady::class,
|
||||||
|
OnWorkerStart::class,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
24
src/metric/src/Contract/CounterInterface.php
Normal file
24
src/metric/src/Contract/CounterInterface.php
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hyperf\Metric\Contract;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Counter describes a metric that accumulates values monotonically.
|
||||||
|
* An example of a counter is the number of received HTTP requests.
|
||||||
|
*/
|
||||||
|
interface CounterInterface
|
||||||
|
{
|
||||||
|
public function with(string ...$labelValues): self;
|
||||||
|
|
||||||
|
public function add(int $delta): void;
|
||||||
|
}
|
26
src/metric/src/Contract/GaugeInterface.php
Normal file
26
src/metric/src/Contract/GaugeInterface.php
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hyperf\Metric\Contract;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gauge describes a metric that takes specific values over time.
|
||||||
|
* An example of a gauge is the current depth of a job queue.
|
||||||
|
*/
|
||||||
|
interface GaugeInterface
|
||||||
|
{
|
||||||
|
public function with(string ...$labelValues): self;
|
||||||
|
|
||||||
|
public function set(float $value): void;
|
||||||
|
|
||||||
|
public function add(float $delta): void;
|
||||||
|
}
|
26
src/metric/src/Contract/HistogramInterface.php
Normal file
26
src/metric/src/Contract/HistogramInterface.php
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hyperf\Metric\Contract;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Histogram describes a metric that takes repeated observations of the same
|
||||||
|
* kind of thing, and produces a statistical summary of those observations,
|
||||||
|
* typically expressed as quantiles or buckets. An example of a histogram is
|
||||||
|
* HTTP request latencies.
|
||||||
|
*/
|
||||||
|
interface HistogramInterface
|
||||||
|
{
|
||||||
|
public function with(string ...$labelValues): self;
|
||||||
|
|
||||||
|
public function put(float $sample): void;
|
||||||
|
}
|
45
src/metric/src/Contract/MetricFactoryInterface.php
Normal file
45
src/metric/src/Contract/MetricFactoryInterface.php
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hyperf\Metric\Contract;
|
||||||
|
|
||||||
|
interface MetricFactoryInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Create a Counter.
|
||||||
|
* @param string $name name of the metric
|
||||||
|
* @param string[] $labelNames key of your label kvs
|
||||||
|
* @return CounterInterface
|
||||||
|
*/
|
||||||
|
public function makeCounter(string $name, ?array $labelNames = []): CounterInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a Gauge.
|
||||||
|
* @param string $name name of the metric
|
||||||
|
* @param string[] $labelNames key of your label kvs
|
||||||
|
* @return GaugeInterface
|
||||||
|
*/
|
||||||
|
public function makeGauge(string $name, ?array $labelNames = []): GaugeInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a HistogramInterface.
|
||||||
|
* @param string $name name of the metric
|
||||||
|
* @param string[] $labelNames key of your label kvs
|
||||||
|
* @return HistogramInterface
|
||||||
|
*/
|
||||||
|
public function makeHistogram(string $name, ?array $labelNames = []): HistogramInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the metric collecting/reporting/serving tasks.
|
||||||
|
*/
|
||||||
|
public function handle(): void;
|
||||||
|
}
|
29
src/metric/src/Event/MetricFactoryReady.php
Normal file
29
src/metric/src/Event/MetricFactoryReady.php
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hyperf\Metric\Event;
|
||||||
|
|
||||||
|
use Hyperf\Metric\Contract\MetricFactoryInterface;
|
||||||
|
|
||||||
|
class MetricFactoryReady
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* A ready to use factory.
|
||||||
|
* @var MetricFactoryInterface
|
||||||
|
*/
|
||||||
|
public $factory;
|
||||||
|
|
||||||
|
public function __construct(MetricFactoryInterface $factory)
|
||||||
|
{
|
||||||
|
$this->factory = $factory;
|
||||||
|
}
|
||||||
|
}
|
17
src/metric/src/Exception/InvalidArgumentException.php
Normal file
17
src/metric/src/Exception/InvalidArgumentException.php
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hyperf\Metric\Exception;
|
||||||
|
|
||||||
|
class InvalidArgumentException extends \InvalidArgumentException
|
||||||
|
{
|
||||||
|
}
|
17
src/metric/src/Exception/RuntimeException.php
Normal file
17
src/metric/src/Exception/RuntimeException.php
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hyperf\Metric\Exception;
|
||||||
|
|
||||||
|
class RuntimeException extends \RuntimeException
|
||||||
|
{
|
||||||
|
}
|
72
src/metric/src/Listener/DBPoolWatcher.php
Normal file
72
src/metric/src/Listener/DBPoolWatcher.php
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hyperf\Metric\Listener;
|
||||||
|
|
||||||
|
use Hyperf\Contract\ConfigInterface;
|
||||||
|
use Hyperf\DbConnection\Pool\PoolFactory;
|
||||||
|
use Hyperf\Event\Contract\ListenerInterface;
|
||||||
|
use Hyperf\Framework\Event\BeforeWorkerStart;
|
||||||
|
use Hyperf\Metric\Contract\MetricFactoryInterface;
|
||||||
|
use Psr\Container\ContainerInterface;
|
||||||
|
use Swoole\Timer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple mysql connection watcher served as an example.
|
||||||
|
* This listener is not auto enabled.Tweak it to fit your
|
||||||
|
* own need.
|
||||||
|
*/
|
||||||
|
class DBPoolWatcher implements ListenerInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var ContainerInterface
|
||||||
|
*/
|
||||||
|
protected $container;
|
||||||
|
|
||||||
|
public function __construct(ContainerInterface $container)
|
||||||
|
{
|
||||||
|
$this->container = $container;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string[] returns the events that you want to listen
|
||||||
|
*/
|
||||||
|
public function listen(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
BeforeWorkerStart::class,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Periodically scan metrics.
|
||||||
|
*/
|
||||||
|
public function process(object $event)
|
||||||
|
{
|
||||||
|
$workerId = $event->workerId;
|
||||||
|
$pool = $this
|
||||||
|
->container
|
||||||
|
->get(PoolFactory::class)
|
||||||
|
->getPool('default');
|
||||||
|
$gauge = $this
|
||||||
|
->container
|
||||||
|
->get(MetricFactoryInterface::class)
|
||||||
|
->makeGauge('mysql_connections_in_use', ['pool', 'worker'])
|
||||||
|
->with('default', (string) $workerId);
|
||||||
|
|
||||||
|
$config = $this->container->get(ConfigInterface::class);
|
||||||
|
$timerInterval = $config->get('metric.default_metric_interval', 5);
|
||||||
|
Timer::tick($timerInterval * 1000, function () use ($gauge, $pool) {
|
||||||
|
$gauge->set((float) $pool->getCurrentConnections());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
107
src/metric/src/Listener/OnMetricFactoryReady.php
Normal file
107
src/metric/src/Listener/OnMetricFactoryReady.php
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hyperf\Metric\Listener;
|
||||||
|
|
||||||
|
use Hyperf\Contract\ConfigInterface;
|
||||||
|
use Hyperf\Event\Contract\ListenerInterface;
|
||||||
|
use Hyperf\Metric\Contract\MetricFactoryInterface;
|
||||||
|
use Hyperf\Metric\Event\MetricFactoryReady;
|
||||||
|
use Hyperf\Metric\MetricSetter;
|
||||||
|
use Psr\Container\ContainerInterface;
|
||||||
|
use Swoole\Coroutine;
|
||||||
|
use Swoole\Server;
|
||||||
|
use Swoole\Timer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Similar to OnWorkerStart, but this only runs in one process.
|
||||||
|
*/
|
||||||
|
class OnMetricFactoryReady implements ListenerInterface
|
||||||
|
{
|
||||||
|
use MetricSetter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var ContainerInterface
|
||||||
|
*/
|
||||||
|
protected $container;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var MetricFactoryInterface
|
||||||
|
*/
|
||||||
|
protected $factory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var ConfigInterface
|
||||||
|
*/
|
||||||
|
private $config;
|
||||||
|
|
||||||
|
public function __construct(ContainerInterface $container)
|
||||||
|
{
|
||||||
|
$this->container = $container;
|
||||||
|
$this->config = $container->get(ConfigInterface::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string[] returns the events that you want to listen
|
||||||
|
*/
|
||||||
|
public function listen(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
MetricFactoryReady::class,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Periodically scan metrics.
|
||||||
|
*/
|
||||||
|
public function process(object $event)
|
||||||
|
{
|
||||||
|
if (! $this->config->get('metric.enable_default_metric')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->factory = $event->factory;
|
||||||
|
$metrics = $this->factoryMetrics(
|
||||||
|
[],
|
||||||
|
'sys_load',
|
||||||
|
'event_num',
|
||||||
|
'signal_listener_num',
|
||||||
|
'aio_task_num',
|
||||||
|
'aio_worker_num',
|
||||||
|
'c_stack_size',
|
||||||
|
'coroutine_num',
|
||||||
|
'coroutine_peak_num',
|
||||||
|
'coroutine_last_cid',
|
||||||
|
'connection_num',
|
||||||
|
'accept_count',
|
||||||
|
'close_count',
|
||||||
|
'worker_num',
|
||||||
|
'idle_worker_num',
|
||||||
|
'tasking_num',
|
||||||
|
'request_count',
|
||||||
|
'timer_num',
|
||||||
|
'timer_round'
|
||||||
|
);
|
||||||
|
|
||||||
|
$server = $this->container->get(Server::class);
|
||||||
|
$timerInterval = $this->config->get('metric.default_metric_interval', 5);
|
||||||
|
Timer::tick($timerInterval * 1000, function () use ($metrics, $server) {
|
||||||
|
$serverStats = $server->stats();
|
||||||
|
$coroutineStats = Coroutine::stats();
|
||||||
|
$timerStats = Timer::stats();
|
||||||
|
$this->trySet('', $metrics, $serverStats);
|
||||||
|
$this->trySet('', $metrics, $coroutineStats);
|
||||||
|
$this->trySet('timer_', $metrics, $timerStats);
|
||||||
|
$load = sys_getloadavg();
|
||||||
|
$metrics['sys_load']->set(round($load[0] / swoole_cpu_num(), 2));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
74
src/metric/src/Listener/OnPipeMessage.php
Normal file
74
src/metric/src/Listener/OnPipeMessage.php
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hyperf\Metric\Listener;
|
||||||
|
|
||||||
|
use Hyperf\Event\Contract\ListenerInterface;
|
||||||
|
use Hyperf\Metric\Adapter\RemoteProxy\Counter;
|
||||||
|
use Hyperf\Metric\Adapter\RemoteProxy\Gauge;
|
||||||
|
use Hyperf\Metric\Adapter\RemoteProxy\Histogram;
|
||||||
|
use Hyperf\Metric\Contract\MetricFactoryInterface;
|
||||||
|
use Hyperf\Process\Event\PipeMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receives messages in metric process.
|
||||||
|
*/
|
||||||
|
class OnPipeMessage implements ListenerInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var MetricFactoryInterface
|
||||||
|
*/
|
||||||
|
private $factory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string[] returns the events that you want to listen
|
||||||
|
*/
|
||||||
|
public function listen(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
PipeMessage::class,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the Event when the event is triggered, all listeners will
|
||||||
|
* complete before the event is returned to the EventDispatcher.
|
||||||
|
*/
|
||||||
|
public function process(object $event)
|
||||||
|
{
|
||||||
|
$this->factory = make(MetricFactoryInterface::class);
|
||||||
|
if (property_exists($event, 'data') && $event instanceof PipeMessage) {
|
||||||
|
$inner = $event->data;
|
||||||
|
switch (true) {
|
||||||
|
case $inner instanceof Counter:
|
||||||
|
$counter = $this->factory->makeCounter($inner->name, $inner->labelNames);
|
||||||
|
$counter->with(...$inner->labelValues)->add($inner->delta);
|
||||||
|
break;
|
||||||
|
case $inner instanceof Gauge:
|
||||||
|
$gauge = $this->factory->makeGauge($inner->name, $inner->labelNames);
|
||||||
|
if (isset($inner->value)) {
|
||||||
|
$gauge->with(...$inner->labelValues)->set($inner->value);
|
||||||
|
} else {
|
||||||
|
$gauge->with(...$inner->labelValues)->add($inner->delta);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case $inner instanceof Histogram:
|
||||||
|
$histogram = $this->factory->makeHistogram($inner->name, $inner->labelNames);
|
||||||
|
$histogram->with(...$inner->labelValues)->put($inner->sample);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Nothing to do
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
164
src/metric/src/Listener/OnWorkerStart.php
Normal file
164
src/metric/src/Listener/OnWorkerStart.php
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hyperf\Metric\Listener;
|
||||||
|
|
||||||
|
use Hyperf\Contract\ConfigInterface;
|
||||||
|
use Hyperf\Event\Contract\ListenerInterface;
|
||||||
|
use Hyperf\Framework\Event\BeforeWorkerStart;
|
||||||
|
use Hyperf\Metric\Contract\MetricFactoryInterface;
|
||||||
|
use Hyperf\Metric\Event\MetricFactoryReady;
|
||||||
|
use Hyperf\Metric\MetricSetter;
|
||||||
|
use Hyperf\Utils\Coroutine;
|
||||||
|
use Psr\Container\ContainerInterface;
|
||||||
|
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||||
|
use Swoole\Server;
|
||||||
|
use Swoole\Timer;
|
||||||
|
use Throwable;
|
||||||
|
use function gc_status;
|
||||||
|
use function getrusage;
|
||||||
|
use function memory_get_peak_usage;
|
||||||
|
use function memory_get_usage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collect and handle metrics before worker start.
|
||||||
|
*/
|
||||||
|
class OnWorkerStart implements ListenerInterface
|
||||||
|
{
|
||||||
|
use MetricSetter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var ContainerInterface
|
||||||
|
*/
|
||||||
|
protected $container;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var MetricFactoryInterface
|
||||||
|
*/
|
||||||
|
protected $factory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var ConfigInterface
|
||||||
|
*/
|
||||||
|
private $config;
|
||||||
|
|
||||||
|
public function __construct(ContainerInterface $container)
|
||||||
|
{
|
||||||
|
$this->container = $container;
|
||||||
|
$this->config = $container->get(ConfigInterface::class);
|
||||||
|
$this->factory = $container->get(MetricFactoryInterface::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string[] returns the events that you want to listen
|
||||||
|
*/
|
||||||
|
public function listen(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
BeforeWorkerStart::class,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the Event when the event is triggered, all listeners will
|
||||||
|
* complete before the event is returned to the EventDispatcher.
|
||||||
|
*/
|
||||||
|
public function process(object $event)
|
||||||
|
{
|
||||||
|
$workerId = $event->workerId;
|
||||||
|
|
||||||
|
if ($workerId === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If no standalone process is started, we have to handle metrics on worker.
|
||||||
|
*/
|
||||||
|
if (! $this->config->get('metric.use_standalone_process', true)) {
|
||||||
|
$this->spawnHandle();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Allow user to hook up their own metrics logic
|
||||||
|
*/
|
||||||
|
if ($this->shouldFireMetricFactoryReadyEvent($workerId)) {
|
||||||
|
$eventDispatcher = $this->container->get(EventDispatcherInterface::class);
|
||||||
|
$eventDispatcher->dispatch(new MetricFactoryReady($this->factory));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $this->config->get('metric.enable_default_metric', false)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The following metrics MUST be collected in worker.
|
||||||
|
$metrics = $this->factoryMetrics(
|
||||||
|
['worker' => (string) $workerId],
|
||||||
|
'worker_request_count',
|
||||||
|
'worker_dispatch_count',
|
||||||
|
'memory_usage',
|
||||||
|
'memory_peak_usage',
|
||||||
|
'gc_runs',
|
||||||
|
'gc_collected',
|
||||||
|
'gc_threshold',
|
||||||
|
'gc_roots',
|
||||||
|
'ru_oublock',
|
||||||
|
'ru_inblock',
|
||||||
|
'ru_msgsnd',
|
||||||
|
'ru_msgrcv',
|
||||||
|
'ru_maxrss',
|
||||||
|
'ru_ixrss',
|
||||||
|
'ru_idrss',
|
||||||
|
'ru_minflt',
|
||||||
|
'ru_majflt',
|
||||||
|
'ru_nsignals',
|
||||||
|
'ru_nvcsw',
|
||||||
|
'ru_nivcsw',
|
||||||
|
'ru_nswap',
|
||||||
|
'ru_utime_tv_usec',
|
||||||
|
'ru_utime_tv_sec',
|
||||||
|
'ru_stime_tv_usec',
|
||||||
|
'ru_stime_tv_sec'
|
||||||
|
);
|
||||||
|
|
||||||
|
$server = $this->container->get(Server::class);
|
||||||
|
$timerInterval = $this->config->get('metric.default_metric_interval', 5);
|
||||||
|
Timer::tick($timerInterval * 1000, function () use ($metrics, $server) {
|
||||||
|
$serverStats = $server->stats();
|
||||||
|
if (function_exists('gc_status')) {
|
||||||
|
$this->trySet('gc_', $metrics, gc_status());
|
||||||
|
}
|
||||||
|
$this->trySet('', $metrics, getrusage());
|
||||||
|
$metrics['worker_request_count']->set($serverStats['worker_request_count']);
|
||||||
|
$metrics['worker_dispatch_count']->set($serverStats['worker_dispatch_count']);
|
||||||
|
$metrics['memory_usage']->set(memory_get_usage());
|
||||||
|
$metrics['memory_peak_usage']->set(memory_get_peak_usage());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private function shouldFireMetricFactoryReadyEvent(int $workerId): bool
|
||||||
|
{
|
||||||
|
return (! $this->config->get('metric.use_standalone_process'))
|
||||||
|
&& $workerId == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function spawnHandle()
|
||||||
|
{
|
||||||
|
Coroutine::create(function () {
|
||||||
|
try {
|
||||||
|
$this->factory->handle();
|
||||||
|
} catch (Throwable $t) {
|
||||||
|
$this->spawnHandle();
|
||||||
|
throw $t;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
82
src/metric/src/Listener/QueueWatcher.php
Normal file
82
src/metric/src/Listener/QueueWatcher.php
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hyperf\Metric\Listener;
|
||||||
|
|
||||||
|
use Hyperf\AsyncQueue\Driver\DriverFactory;
|
||||||
|
use Hyperf\Contract\ConfigInterface;
|
||||||
|
use Hyperf\Event\Contract\ListenerInterface;
|
||||||
|
use Hyperf\Metric\Event\MetricFactoryReady;
|
||||||
|
use Psr\Container\ContainerInterface;
|
||||||
|
use Swoole\Timer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple redis queue watcher served as an example.
|
||||||
|
* This listener is not auto enabled.Tweak it to fit your
|
||||||
|
* own need.
|
||||||
|
*/
|
||||||
|
class QueueWatcher implements ListenerInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var ContainerInterface
|
||||||
|
*/
|
||||||
|
protected $container;
|
||||||
|
|
||||||
|
public function __construct(ContainerInterface $container)
|
||||||
|
{
|
||||||
|
$this->container = $container;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string[] returns the events that you want to listen
|
||||||
|
*/
|
||||||
|
public function listen(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
MetricFactoryReady::class,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Periodically scan metrics.
|
||||||
|
*/
|
||||||
|
public function process(object $event)
|
||||||
|
{
|
||||||
|
$queue = $this->container->get(DriverFactory::class)->get('default');
|
||||||
|
$waiting = $event
|
||||||
|
->factory
|
||||||
|
->makeGauge('queue_waiting', ['queue'])
|
||||||
|
->with('default');
|
||||||
|
$delayed = $event
|
||||||
|
->factory
|
||||||
|
->makeGauge('queue_delayed', ['queue'])
|
||||||
|
->with('default');
|
||||||
|
$failed = $event
|
||||||
|
->factory
|
||||||
|
->makeGauge('queue_failed', ['queue'])
|
||||||
|
->with('default');
|
||||||
|
$timeout = $event
|
||||||
|
->factory
|
||||||
|
->makeGauge('queue_timeout', ['queue'])
|
||||||
|
->with('default');
|
||||||
|
|
||||||
|
$config = $this->container->get(ConfigInterface::class);
|
||||||
|
$timerInterval = $config->get('metric.default_metric_interval', 5);
|
||||||
|
Timer::tick($timerInterval * 1000, function () use ($waiting, $delayed, $failed, $timeout, $queue) {
|
||||||
|
$info = $queue->info();
|
||||||
|
$waiting->set((float) $info['waiting']);
|
||||||
|
$delayed->set((float) $info['delayed']);
|
||||||
|
$failed->set((float) $info['failed']);
|
||||||
|
$timeout->set((float) $info['timeout']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
72
src/metric/src/Listener/RedisPoolWatcher.php
Normal file
72
src/metric/src/Listener/RedisPoolWatcher.php
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hyperf\Metric\Listener;
|
||||||
|
|
||||||
|
use Hyperf\Contract\ConfigInterface;
|
||||||
|
use Hyperf\Event\Contract\ListenerInterface;
|
||||||
|
use Hyperf\Framework\Event\BeforeWorkerStart;
|
||||||
|
use Hyperf\Metric\Contract\MetricFactoryInterface;
|
||||||
|
use Hyperf\Redis\Pool\PoolFactory;
|
||||||
|
use Psr\Container\ContainerInterface;
|
||||||
|
use Swoole\Timer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple mysql connection watcher served as an example.
|
||||||
|
* This listener is not auto enabled.Tweak it to fit your
|
||||||
|
* own need.
|
||||||
|
*/
|
||||||
|
class RedisPoolWatcher implements ListenerInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var ContainerInterface
|
||||||
|
*/
|
||||||
|
protected $container;
|
||||||
|
|
||||||
|
public function __construct(ContainerInterface $container)
|
||||||
|
{
|
||||||
|
$this->container = $container;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string[] returns the events that you want to listen
|
||||||
|
*/
|
||||||
|
public function listen(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
BeforeWorkerStart::class,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Periodically scan metrics.
|
||||||
|
*/
|
||||||
|
public function process(object $event)
|
||||||
|
{
|
||||||
|
$workerId = $event->workerId;
|
||||||
|
$pool = $this
|
||||||
|
->container
|
||||||
|
->get(PoolFactory::class)
|
||||||
|
->getPool('default');
|
||||||
|
$gauge = $this
|
||||||
|
->container
|
||||||
|
->get(MetricFactoryInterface::class)
|
||||||
|
->makeGauge('redis_connections_in_use', ['pool', 'worker'])
|
||||||
|
->with('default', (string) $workerId);
|
||||||
|
|
||||||
|
$config = $this->container->get(ConfigInterface::class);
|
||||||
|
$timerInterval = $config->get('metric.default_metric_interval', 5);
|
||||||
|
Timer::tick($timerInterval * 1000, function () use ($gauge, $pool) {
|
||||||
|
$gauge->set((float) $pool->getCurrentConnections());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
60
src/metric/src/Metric.php
Normal file
60
src/metric/src/Metric.php
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hyperf\Metric;
|
||||||
|
|
||||||
|
use Hyperf\Metric\Contract\MetricFactoryInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Facade-like, syntax sugar class to create one-off metrics
|
||||||
|
* Beta Feature. API may change.
|
||||||
|
*/
|
||||||
|
class Metric
|
||||||
|
{
|
||||||
|
public static function count(string $name, ?int $delta = 1, ?array $labels = [])
|
||||||
|
{
|
||||||
|
make(MetricFactoryInterface::class)
|
||||||
|
->makeCounter($name, array_keys($labels))
|
||||||
|
->with(...array_values($labels))
|
||||||
|
->add($delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function gauge(string $name, float $value, ?array $labels = [])
|
||||||
|
{
|
||||||
|
make(MetricFactoryInterface::class)
|
||||||
|
->makeGauge($name, array_keys($labels))
|
||||||
|
->with(...array_values($labels))
|
||||||
|
->set($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function shift(string $name, float $delta, ?array $labels = [])
|
||||||
|
{
|
||||||
|
make(MetricFactoryInterface::class)
|
||||||
|
->makeGauge($name, array_keys($labels))
|
||||||
|
->with(...array_values($labels))
|
||||||
|
->add($delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function put(string $name, float $sample, ?array $labels = [])
|
||||||
|
{
|
||||||
|
make(MetricFactoryInterface::class)
|
||||||
|
->makeHistogram($name, array_keys($labels))
|
||||||
|
->with(...array_values($labels))
|
||||||
|
->put($sample);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function time(string $name, callable $func, ?array $args = [], ?array $labels = [])
|
||||||
|
{
|
||||||
|
$timer = new Timer($name, $labels);
|
||||||
|
return $func(...$args);
|
||||||
|
}
|
||||||
|
}
|
50
src/metric/src/MetricFactoryPicker.php
Normal file
50
src/metric/src/MetricFactoryPicker.php
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hyperf\Metric;
|
||||||
|
|
||||||
|
use Hyperf\Contract\ConfigInterface;
|
||||||
|
use Hyperf\Metric\Adapter\Prometheus\MetricFactory as PrometheusFactory;
|
||||||
|
use Hyperf\Metric\Adapter\RemoteProxy\MetricFactory as RemoteFactory;
|
||||||
|
use Hyperf\Metric\Contract\MetricFactoryInterface;
|
||||||
|
use Hyperf\Metric\Exception\InvalidArgumentException;
|
||||||
|
use Psr\Container\ContainerInterface;
|
||||||
|
|
||||||
|
class MetricFactoryPicker
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
public static $inMetricProcess = false;
|
||||||
|
|
||||||
|
public function __invoke(ContainerInterface $container)
|
||||||
|
{
|
||||||
|
$config = $container->get(ConfigInterface::class);
|
||||||
|
$useStandaloneProcess = $config->get('metric.use_standalone_process');
|
||||||
|
// Return a proxy object for workers if user wants to use a dedicated metric process.
|
||||||
|
if ($useStandaloneProcess && ! static::$inMetricProcess) {
|
||||||
|
return $container->get(RemoteFactory::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
$name = $config->get('metric.default');
|
||||||
|
$dedicatedProcess = $config->get('metric.metric.use_standalone_process');
|
||||||
|
$driver = $config->get("metric.metric.{$name}.driver", PrometheusFactory::class);
|
||||||
|
|
||||||
|
$factory = $container->get($driver);
|
||||||
|
if (! ($factory instanceof MetricFactoryInterface)) {
|
||||||
|
throw new InvalidArgumentException(
|
||||||
|
sprintf('The driver %s is not a valid factory.', $driver)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return $factory;
|
||||||
|
}
|
||||||
|
}
|
57
src/metric/src/MetricSetter.php
Normal file
57
src/metric/src/MetricSetter.php
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hyperf\Metric;
|
||||||
|
|
||||||
|
use Hyperf\Metric\Contract\GaugeInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Helper trait to set stats from swoole and kernal.
|
||||||
|
*/
|
||||||
|
trait MetricSetter
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Try to set every stats available to the gauge.
|
||||||
|
* Some of the stats might be missing depending
|
||||||
|
* on the platform.
|
||||||
|
* @param string $prefix
|
||||||
|
* @param array $metrics
|
||||||
|
* @param array $stats
|
||||||
|
*/
|
||||||
|
private function trySet(string $prefix, array $metrics, array $stats): void
|
||||||
|
{
|
||||||
|
foreach (array_keys($stats) as $key) {
|
||||||
|
$metricsKey = \str_replace('.', '_', $prefix . $key);
|
||||||
|
if (array_key_exists($metricsKey, $metrics)) {
|
||||||
|
$metrics[$metricsKey]->set($stats[$key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an array of gauges.
|
||||||
|
* @param array<string, string> $labels
|
||||||
|
* @param array<int, string> $names
|
||||||
|
* @return GaugeInterface[]
|
||||||
|
*/
|
||||||
|
private function factoryMetrics(array $labels, string ...$names): array
|
||||||
|
{
|
||||||
|
$out = [];
|
||||||
|
foreach ($names as $name) {
|
||||||
|
$out[$name] = $this
|
||||||
|
->factory
|
||||||
|
->makeGauge($name, \array_keys($labels))
|
||||||
|
->with(...\array_values($labels));
|
||||||
|
}
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
}
|
53
src/metric/src/Middleware/MetricMiddleware.php
Normal file
53
src/metric/src/Middleware/MetricMiddleware.php
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hyperf\Metric\Middleware;
|
||||||
|
|
||||||
|
use Hyperf\Metric\Contract\MetricFactoryInterface;
|
||||||
|
use Hyperf\Metric\Timer;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use Psr\Http\Server\MiddlewareInterface;
|
||||||
|
use Psr\Http\Server\RequestHandlerInterface;
|
||||||
|
|
||||||
|
class MetricMiddleware implements MiddlewareInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var MetricFactoryInterface
|
||||||
|
*/
|
||||||
|
private $factory;
|
||||||
|
|
||||||
|
public function __construct(MetricFactoryInterface $factory)
|
||||||
|
{
|
||||||
|
$this->factory = $factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process an incoming server request.
|
||||||
|
* Processes an incoming server request in order to produce a response.
|
||||||
|
* If unable to produce the response itself, it may delegate to the provided
|
||||||
|
* request handler to do so.
|
||||||
|
*/
|
||||||
|
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||||
|
{
|
||||||
|
$labels = [
|
||||||
|
'request_status' => '500', //default to 500 incase uncaught exception occur
|
||||||
|
'request_path' => $request->getRequestTarget(),
|
||||||
|
'request_method' => $request->getMethod(),
|
||||||
|
];
|
||||||
|
$timer = new Timer('http_requests', $labels);
|
||||||
|
$response = $handler->handle($request);
|
||||||
|
$labels['request_status'] = (string) $response->getStatusCode();
|
||||||
|
$timer->end($labels);
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
56
src/metric/src/Process/MetricProcess.php
Normal file
56
src/metric/src/Process/MetricProcess.php
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hyperf\Metric\Process;
|
||||||
|
|
||||||
|
use Hyperf\Contract\ConfigInterface;
|
||||||
|
use Hyperf\Metric\Contract\MetricFactoryInterface;
|
||||||
|
use Hyperf\Metric\Event\MetricFactoryReady;
|
||||||
|
use Hyperf\Metric\MetricFactoryPicker;
|
||||||
|
use Hyperf\Process\AbstractProcess;
|
||||||
|
use Hyperf\Process\Annotation\Process;
|
||||||
|
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Metric Process.
|
||||||
|
* @Process
|
||||||
|
*/
|
||||||
|
class MetricProcess extends AbstractProcess
|
||||||
|
{
|
||||||
|
public $name = 'metric';
|
||||||
|
|
||||||
|
public $nums = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var MetricFactoryInterface
|
||||||
|
*/
|
||||||
|
protected $factory;
|
||||||
|
|
||||||
|
public function isEnable(): bool
|
||||||
|
{
|
||||||
|
$config = $this->container->get(ConfigInterface::class);
|
||||||
|
return $config->get('metric.use_standalone_process') ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
MetricFactoryPicker::$inMetricProcess = true;
|
||||||
|
$this->factory = make(MetricFactoryInterface::class);
|
||||||
|
$this
|
||||||
|
->container
|
||||||
|
->get(EventDispatcherInterface::class)
|
||||||
|
->dispatch(new MetricFactoryReady($this->factory));
|
||||||
|
$this
|
||||||
|
->factory
|
||||||
|
->handle();
|
||||||
|
}
|
||||||
|
}
|
74
src/metric/src/Timer.php
Normal file
74
src/metric/src/Timer.php
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hyperf\Metric;
|
||||||
|
|
||||||
|
use Hyperf\Metric\Contract\MetricFactoryInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Syntax sugar class to handle time.
|
||||||
|
*/
|
||||||
|
class Timer
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array<string,string>
|
||||||
|
*/
|
||||||
|
protected $labels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var float
|
||||||
|
*/
|
||||||
|
protected $time;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $ended = false;
|
||||||
|
|
||||||
|
public function __construct(string $name, ?array $default = [])
|
||||||
|
{
|
||||||
|
$this->name = $name;
|
||||||
|
$this->labels = $default;
|
||||||
|
$this->time = microtime(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
$this->end();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function end(?array $labels = []): void
|
||||||
|
{
|
||||||
|
if ($this->ended) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
foreach ($labels as $key => $value) {
|
||||||
|
if (array_key_exists($key, $this->labels)) {
|
||||||
|
$this->labels[$key] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$histogram = make(MetricFactoryInterface::class)
|
||||||
|
->makeHistogram($this->name, array_keys($this->labels))
|
||||||
|
->with(...array_values($this->labels));
|
||||||
|
$d = (float) microtime(true) - $this->time;
|
||||||
|
if ($d < 0) {
|
||||||
|
$d = (float) 0;
|
||||||
|
}
|
||||||
|
$histogram->put($d);
|
||||||
|
$this->ended = true;
|
||||||
|
}
|
||||||
|
}
|
133
src/metric/tests/Cases/MetricFactoryPickerTest.php
Normal file
133
src/metric/tests/Cases/MetricFactoryPickerTest.php
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace HyperfTest\Metric\Cases;
|
||||||
|
|
||||||
|
use Hyperf\Config\Config;
|
||||||
|
use Hyperf\Contract\ConfigInterface;
|
||||||
|
use Hyperf\Di\Container;
|
||||||
|
use Hyperf\Metric\Adapter\Prometheus\MetricFactory as PrometheusFactory;
|
||||||
|
use Hyperf\Metric\Adapter\RemoteProxy\MetricFactory as RemoteFactory;
|
||||||
|
use Hyperf\Metric\Adapter\StatsD\MetricFactory as StatsDFactory;
|
||||||
|
use Hyperf\Metric\MetricFactoryPicker;
|
||||||
|
use Mockery;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
class MetricFactoryPickerTest extends TestCase
|
||||||
|
{
|
||||||
|
public function tearDown()
|
||||||
|
{
|
||||||
|
Mockery::close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPrometheus()
|
||||||
|
{
|
||||||
|
$config = new Config([
|
||||||
|
'metric' => [
|
||||||
|
'default' => 'prometheus',
|
||||||
|
'use_standalone_process' => false,
|
||||||
|
'enable_default_metrics' => true,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
$container = Mockery::mock(Container::class);
|
||||||
|
$container->shouldReceive('get')->with(ConfigInterface::class)->andReturn($config);
|
||||||
|
|
||||||
|
$container->shouldReceive('get')->with(PrometheusFactory::class)->andReturn(Mockery::mock(PrometheusFactory::class));
|
||||||
|
|
||||||
|
$picker = new MetricFactoryPicker();
|
||||||
|
|
||||||
|
$this->assertInstanceOf(PrometheusFactory::class, $picker($container));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testStatsD()
|
||||||
|
{
|
||||||
|
$config = new Config([
|
||||||
|
'metric' => [
|
||||||
|
'default' => 'statsD',
|
||||||
|
'use_standalone_process' => false,
|
||||||
|
'enable_default_metrics' => true,
|
||||||
|
'metric' => [
|
||||||
|
'prometheus' => [
|
||||||
|
'driver' => PrometheusFactory::class,
|
||||||
|
],
|
||||||
|
'statsD' => [
|
||||||
|
'driver' => StatsDFactory::class,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
$container = Mockery::mock(Container::class);
|
||||||
|
$container->shouldReceive('get')->with(ConfigInterface::class)->andReturn($config);
|
||||||
|
$container->shouldReceive('get')->with(StatsDFactory::class)->andReturn(Mockery::mock(StatsDFactory::class));
|
||||||
|
|
||||||
|
$picker = new MetricFactoryPicker();
|
||||||
|
|
||||||
|
$this->assertInstanceOf(StatsDFactory::class, $picker($container));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testProxy()
|
||||||
|
{
|
||||||
|
$config = new Config([
|
||||||
|
'metric' => [
|
||||||
|
'default' => 'statsD',
|
||||||
|
'use_standalone_process' => true,
|
||||||
|
'enable_default_metrics' => true,
|
||||||
|
'metric' => [
|
||||||
|
'prometheus' => [
|
||||||
|
'driver' => PrometheusFactory::class,
|
||||||
|
],
|
||||||
|
'statsD' => [
|
||||||
|
'driver' => StatsDFactory::class,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
$container = Mockery::mock(Container::class);
|
||||||
|
$container->shouldReceive('get')->with(ConfigInterface::class)->andReturn($config);
|
||||||
|
$container->shouldReceive('get')->with(RemoteFactory::class)->andReturn(Mockery::mock(RemoteFactory::class));
|
||||||
|
|
||||||
|
$picker = new MetricFactoryPicker();
|
||||||
|
|
||||||
|
$this->assertInstanceOf(RemoteFactory::class, $picker($container));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testMetricProcess()
|
||||||
|
{
|
||||||
|
$config = new Config([
|
||||||
|
'metric' => [
|
||||||
|
'default' => 'prometheus',
|
||||||
|
'use_standalone_process' => true,
|
||||||
|
'enable_default_metrics' => false,
|
||||||
|
'metric' => [
|
||||||
|
'prometheus' => [
|
||||||
|
'driver' => PrometheusFactory::class,
|
||||||
|
],
|
||||||
|
'statsD' => [
|
||||||
|
'driver' => StatsDFactory::class,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
$container = Mockery::mock(Container::class);
|
||||||
|
$container->shouldReceive('get')->with(ConfigInterface::class)->andReturn($config);
|
||||||
|
$container->shouldReceive('get')->with(PrometheusFactory::class)->andReturn(Mockery::mock(PrometheusFactory::class));
|
||||||
|
|
||||||
|
MetricFactoryPicker::$inMetricProcess = true;
|
||||||
|
$picker = new MetricFactoryPicker();
|
||||||
|
|
||||||
|
$this->assertInstanceOf(PrometheusFactory::class, $picker($container));
|
||||||
|
}
|
||||||
|
}
|
54
src/metric/tests/Cases/MetricFactoryTest.php
Normal file
54
src/metric/tests/Cases/MetricFactoryTest.php
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace HyperfTest\Metric\Cases;
|
||||||
|
|
||||||
|
use Hyperf\Config\Config;
|
||||||
|
use Hyperf\Guzzle\ClientFactory;
|
||||||
|
use Hyperf\Metric\Adapter\Prometheus\Constants;
|
||||||
|
use Hyperf\Metric\Adapter\Prometheus\MetricFactory as PrometheusFactory;
|
||||||
|
use Hyperf\Metric\Exception\RuntimeException;
|
||||||
|
use Mockery;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Prometheus\CollectorRegistry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
class MetricFactoryTest extends TestCase
|
||||||
|
{
|
||||||
|
public function tearDown()
|
||||||
|
{
|
||||||
|
Mockery::close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPrometheusThrows()
|
||||||
|
{
|
||||||
|
$config = new Config([
|
||||||
|
'metric' => [
|
||||||
|
'default' => 'prometheus',
|
||||||
|
'use_standalone_process' => false,
|
||||||
|
'metric' => [
|
||||||
|
'prometheus' => [
|
||||||
|
'driver' => PrometheusFactory::class,
|
||||||
|
'mode' => Constants::SCRAPE_MODE,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
$r = Mockery::mock(CollectorRegistry::class);
|
||||||
|
$c = Mockery::mock(ClientFactory::class);
|
||||||
|
$this->expectException(RuntimeException::class);
|
||||||
|
$p = new PrometheusFactory($config, $r, $c);
|
||||||
|
}
|
||||||
|
}
|
112
src/metric/tests/Cases/OnWorkerStartTest.php
Normal file
112
src/metric/tests/Cases/OnWorkerStartTest.php
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace HyperfTest\Metric\Cases;
|
||||||
|
|
||||||
|
use Hyperf\Config\Config;
|
||||||
|
use Hyperf\Contract\ConfigInterface;
|
||||||
|
use Hyperf\Di\Container;
|
||||||
|
use Hyperf\Metric\Adapter\Prometheus\MetricFactory as PrometheusFactory;
|
||||||
|
use Hyperf\Metric\Contract\MetricFactoryInterface;
|
||||||
|
use Hyperf\Metric\Listener\OnWorkerStart;
|
||||||
|
use Mockery;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
class OnWorkerStartTest extends TestCase
|
||||||
|
{
|
||||||
|
public function tearDown()
|
||||||
|
{
|
||||||
|
Mockery::close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testHandle()
|
||||||
|
{
|
||||||
|
$config = new Config([
|
||||||
|
'metric' => [
|
||||||
|
'default' => 'prometheus',
|
||||||
|
'use_standalone_process' => false,
|
||||||
|
'enable_default_metrics' => false,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$factory = Mockery::mock(PrometheusFactory::class);
|
||||||
|
$factory->shouldReceive('handle')->atLeast()->times(1);
|
||||||
|
$container = Mockery::mock(Container::class);
|
||||||
|
$container->shouldReceive('get')->with(ConfigInterface::class)->andReturn($config);
|
||||||
|
$container->shouldReceive('get')->with(MetricFactoryInterface::class)->andReturn($factory);
|
||||||
|
|
||||||
|
$l = new OnWorkerStart($container);
|
||||||
|
|
||||||
|
$l->process(new class() {
|
||||||
|
public $workerId = 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->assertTrue(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFireEvent()
|
||||||
|
{
|
||||||
|
$config = new Config([
|
||||||
|
'metric' => [
|
||||||
|
'default' => 'prometheus',
|
||||||
|
'use_standalone_process' => false,
|
||||||
|
'enable_default_metrics' => false,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
$factory = Mockery::mock(PrometheusFactory::class);
|
||||||
|
$container = Mockery::mock(Container::class);
|
||||||
|
$factory->shouldReceive('handle')->atLeast()->times(1);
|
||||||
|
$container->shouldReceive('get')->with(ConfigInterface::class)->andReturn($config);
|
||||||
|
$container->shouldReceive('get')->with(MetricFactoryInterface::class)->andReturn($factory);
|
||||||
|
$container->shouldReceive('get')->with(EventDispatcherInterface::class)->andReturn(
|
||||||
|
new class() {
|
||||||
|
public function dispatch()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)->once();
|
||||||
|
$l = new OnWorkerStart($container);
|
||||||
|
$l->process(new class() {
|
||||||
|
public $workerId = 0;
|
||||||
|
});
|
||||||
|
$l->process(new class() {
|
||||||
|
public $workerId = 1;
|
||||||
|
});
|
||||||
|
$this->assertTrue(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNotFireEvent()
|
||||||
|
{
|
||||||
|
$config = new Config([
|
||||||
|
'metric' => [
|
||||||
|
'default' => 'prometheus',
|
||||||
|
'use_standalone_process' => true,
|
||||||
|
'enable_default_metrics' => false,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
$factory = Mockery::mock(PrometheusFactory::class);
|
||||||
|
$container = Mockery::mock(Container::class);
|
||||||
|
$container->shouldReceive('get')->with(ConfigInterface::class)->andReturn($config);
|
||||||
|
$container->shouldReceive('get')->with(MetricFactoryInterface::class)->andReturn($factory);
|
||||||
|
$l = new OnWorkerStart($container);
|
||||||
|
$l->process(new class() {
|
||||||
|
public $workerId = 0;
|
||||||
|
});
|
||||||
|
$this->assertTrue(true);
|
||||||
|
}
|
||||||
|
}
|
73
src/metric/tests/Cases/TimerTest.php
Normal file
73
src/metric/tests/Cases/TimerTest.php
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace HyperfTest\Metric\Cases;
|
||||||
|
|
||||||
|
use Hyperf\Di\Container;
|
||||||
|
use Hyperf\Metric\Contract\HistogramInterface;
|
||||||
|
use Hyperf\Metric\Contract\MetricFactoryInterface;
|
||||||
|
use Hyperf\Metric\Timer;
|
||||||
|
use Hyperf\Utils\ApplicationContext;
|
||||||
|
use Mockery;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
class TimerTest extends TestCase
|
||||||
|
{
|
||||||
|
public function tearDown()
|
||||||
|
{
|
||||||
|
Mockery::close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEnd()
|
||||||
|
{
|
||||||
|
$this->mockContainer();
|
||||||
|
$timer = new Timer('test');
|
||||||
|
$timer->end();
|
||||||
|
$this->assertTrue(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEndCalledTwice()
|
||||||
|
{
|
||||||
|
$this->mockContainer();
|
||||||
|
$timer2 = new Timer('test');
|
||||||
|
$timer2->end();
|
||||||
|
$timer2->end();
|
||||||
|
$this->assertTrue(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEndNotCalled()
|
||||||
|
{
|
||||||
|
$this->mockContainer();
|
||||||
|
$timer3 = new Timer('test');
|
||||||
|
unset($timer3);
|
||||||
|
$this->assertTrue(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function mockContainer()
|
||||||
|
{
|
||||||
|
$container = Mockery::mock(Container::class);
|
||||||
|
$container->shouldReceive('make')->with(MetricFactoryInterface::class, [])->andReturn(new class() {
|
||||||
|
public function makeHistogram($name, $labels)
|
||||||
|
{
|
||||||
|
$histogram = Mockery::mock(HistogramInterface::class);
|
||||||
|
$histogram->shouldReceive('with')->andReturn($histogram);
|
||||||
|
$histogram->shouldReceive('put')->once();
|
||||||
|
return $histogram;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ApplicationContext::setContainer($container);
|
||||||
|
}
|
||||||
|
}
|
13
src/metric/tests/bootstrap.php
Normal file
13
src/metric/tests/bootstrap.php
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once dirname(dirname(__FILE__)) . '/vendor/autoload.php';
|
8
src/metric/tests/ci.ini
Normal file
8
src/metric/tests/ci.ini
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
[opcache]
|
||||||
|
opcache.enable_cli=1
|
||||||
|
|
||||||
|
[redis]
|
||||||
|
extension = "redis.so"
|
||||||
|
|
||||||
|
[swoole]
|
||||||
|
extension = "swoole.so"
|
10
src/metric/tests/swoole.install.sh
Normal file
10
src/metric/tests/swoole.install.sh
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
wget https://github.com/swoole/swoole-src/archive/v${SW_VERSION}.tar.gz -O swoole.tar.gz
|
||||||
|
mkdir -p swoole
|
||||||
|
tar -xf swoole.tar.gz -C swoole --strip-components=1
|
||||||
|
rm swoole.tar.gz
|
||||||
|
cd swoole
|
||||||
|
phpize
|
||||||
|
./configure --enable-openssl --enable-mysqlnd
|
||||||
|
make -j$(nproc)
|
||||||
|
make install
|
85
src/utils/src/Backoff.php
Normal file
85
src/utils/src/Backoff.php
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hyperf\Utils;
|
||||||
|
|
||||||
|
class Backoff
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Max backoff.
|
||||||
|
*/
|
||||||
|
private const CAP = 60 * 1000; // 1 minute
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
private $firstMs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Backoff interval.
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
private $currentMs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int the first backoff in milliseconds
|
||||||
|
*/
|
||||||
|
public function __construct(int $firstMs = 0)
|
||||||
|
{
|
||||||
|
if ($firstMs < 0) {
|
||||||
|
throw new \InvalidArgumentException(
|
||||||
|
'first backoff interval must be greater or equal than 0'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($firstMs > Backoff::CAP) {
|
||||||
|
throw new \InvalidArgumentException(
|
||||||
|
sprintf(
|
||||||
|
'first backoff interval must be less or equal than %d milliseconds',
|
||||||
|
self::CAP
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->firstMs = $firstMs;
|
||||||
|
$this->currentMs = $firstMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sleep until the next execution.
|
||||||
|
*/
|
||||||
|
public function sleep(): void
|
||||||
|
{
|
||||||
|
if ($this->currentMs === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
usleep($this->currentMs * 1000);
|
||||||
|
|
||||||
|
// update backoff using Decorrelated Jitter
|
||||||
|
// see: https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/
|
||||||
|
$this->currentMs = rand($this->firstMs, $this->currentMs * 3);
|
||||||
|
|
||||||
|
if ($this->currentMs > self::CAP) {
|
||||||
|
$this->currentMs = self::CAP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the next backoff for logging, etc.
|
||||||
|
* @return int next backoff
|
||||||
|
*/
|
||||||
|
public function nextBackoff(): int
|
||||||
|
{
|
||||||
|
return $this->currentMs;
|
||||||
|
}
|
||||||
|
}
|
55
src/utils/src/Codec/Json.php
Normal file
55
src/utils/src/Codec/Json.php
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hyperf\Utils\Codec;
|
||||||
|
|
||||||
|
use Hyperf\Utils\Contracts\Arrayable;
|
||||||
|
use Hyperf\Utils\Contracts\Jsonable;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
|
||||||
|
class Json
|
||||||
|
{
|
||||||
|
public static function encode($data, $options = JSON_UNESCAPED_UNICODE): string
|
||||||
|
{
|
||||||
|
if ($data instanceof Jsonable) {
|
||||||
|
return (string) $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($data instanceof Arrayable) {
|
||||||
|
$data = $data->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
$json = json_encode($data, $options);
|
||||||
|
|
||||||
|
static::handleJsonError(json_last_error(), json_last_error_msg());
|
||||||
|
|
||||||
|
return $json;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function decode(string $json, $assoc = true)
|
||||||
|
{
|
||||||
|
$decode = json_decode($json, $assoc);
|
||||||
|
|
||||||
|
static::handleJsonError(json_last_error(), json_last_error_msg());
|
||||||
|
|
||||||
|
return $decode;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function handleJsonError($lastError, $message)
|
||||||
|
{
|
||||||
|
if ($lastError === JSON_ERROR_NONE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidArgumentException($message, $lastError);
|
||||||
|
}
|
||||||
|
}
|
@ -12,6 +12,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
use Hyperf\Utils\ApplicationContext;
|
use Hyperf\Utils\ApplicationContext;
|
||||||
use Hyperf\Utils\Arr;
|
use Hyperf\Utils\Arr;
|
||||||
|
use Hyperf\Utils\Backoff;
|
||||||
use Hyperf\Utils\Collection;
|
use Hyperf\Utils\Collection;
|
||||||
use Hyperf\Utils\Coroutine;
|
use Hyperf\Utils\Coroutine;
|
||||||
use Hyperf\Utils\HigherOrderTapProxy;
|
use Hyperf\Utils\HigherOrderTapProxy;
|
||||||
@ -67,11 +68,12 @@ if (! function_exists('retry')) {
|
|||||||
* Retry an operation a given number of times.
|
* Retry an operation a given number of times.
|
||||||
*
|
*
|
||||||
* @param int $times
|
* @param int $times
|
||||||
* @param int $sleep
|
* @param int $sleep millisecond
|
||||||
* @throws \Throwable
|
* @throws \Throwable
|
||||||
*/
|
*/
|
||||||
function retry($times, callable $callback, $sleep = 0)
|
function retry($times, callable $callback, $sleep = 0)
|
||||||
{
|
{
|
||||||
|
$backoff = new Backoff($sleep);
|
||||||
beginning:
|
beginning:
|
||||||
try {
|
try {
|
||||||
return $callback();
|
return $callback();
|
||||||
@ -79,9 +81,7 @@ if (! function_exists('retry')) {
|
|||||||
if (--$times < 0) {
|
if (--$times < 0) {
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
if ($sleep) {
|
$backoff->sleep();
|
||||||
usleep($sleep * 1000);
|
|
||||||
}
|
|
||||||
goto beginning;
|
goto beginning;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
36
src/utils/tests/BackoffTest.php
Normal file
36
src/utils/tests/BackoffTest.php
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace HyperfTest\Utils;
|
||||||
|
|
||||||
|
use Hyperf\Utils\Backoff;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* @covers \Hyperf\Utils\Backoff
|
||||||
|
*/
|
||||||
|
class BackoffTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testBackoff()
|
||||||
|
{
|
||||||
|
$backoff = new Backoff(1);
|
||||||
|
$backoff->sleep();
|
||||||
|
$firstTick = $backoff->nextBackoff();
|
||||||
|
$this->assertGreaterThanOrEqual(1, $firstTick);
|
||||||
|
$this->assertLessThanOrEqual(3, $firstTick);
|
||||||
|
$backoff->sleep();
|
||||||
|
$secondTick = $backoff->nextBackoff();
|
||||||
|
$this->assertGreaterThanOrEqual(1, $secondTick);
|
||||||
|
$this->assertLessThanOrEqual(3 * $firstTick, $secondTick);
|
||||||
|
}
|
||||||
|
}
|
76
src/utils/tests/Codec/JsonTest.php
Normal file
76
src/utils/tests/Codec/JsonTest.php
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* This file is part of Hyperf.
|
||||||
|
*
|
||||||
|
* @link https://www.hyperf.io
|
||||||
|
* @document https://doc.hyperf.io
|
||||||
|
* @contact group@hyperf.io
|
||||||
|
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace HyperfTest\Utils\Codec;
|
||||||
|
|
||||||
|
use Hyperf\Utils\Codec\Json;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
class JsonTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testEncode()
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
'name' => 'Hyperf',
|
||||||
|
];
|
||||||
|
$json = '{"name":"Hyperf"}';
|
||||||
|
$this->assertSame($json, Json::encode($data));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDecode()
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
'name' => 'Hyperf',
|
||||||
|
];
|
||||||
|
$json = '{"name":"Hyperf"}';
|
||||||
|
$this->assertSame($data, Json::decode($json));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDecodeToObject()
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
'name' => 'Hyperf',
|
||||||
|
];
|
||||||
|
$json = '{"name":"Hyperf"}';
|
||||||
|
$this->assertEquals((object) $data, Json::decode($json, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \InvalidArgumentException
|
||||||
|
* @expectedExceptionMessage Control character error, possibly incorrectly encoded
|
||||||
|
*/
|
||||||
|
public function testDecodeException()
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
'name' => 'Hyperf',
|
||||||
|
];
|
||||||
|
$json = '{"name":"Hyperf}';
|
||||||
|
$this->assertSame($data, Json::decode($json));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testJsonEncodeInCoroutine()
|
||||||
|
{
|
||||||
|
$result = null;
|
||||||
|
go(function () use (&$result) {
|
||||||
|
$result = Json::encode([1, 2, 3]);
|
||||||
|
});
|
||||||
|
$this->assertSame('[1,2,3]', $result);
|
||||||
|
go(function () use (&$result) {
|
||||||
|
$result = Json::decode('[1,2,3]');
|
||||||
|
});
|
||||||
|
$this->assertSame([1, 2, 3], $result);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user