# 如何將 Laravel Octane HTTP 類型 Metrics 整合到 RoadRunner Metrics
###### tags: `Laravel`,`PHP`,`RoadRunner`,`Octane`
RoadRunner 本身就有提供一些基本的 Metrics 供 Prometheus 之類的監控工具監控,例如:`rr_http_workers_ready`、`rr_http_workers_working`,RoadRunner 可以透過簡單的 `.rr.yaml` 設定端口輸出這些 Metrics 數據,供 Prometheus 抓取。
```yaml
metrics:
address: 0.0.0.0:2112 # Prometheus metrics 端口
```
重啟 Server 後開啟 http://localhost:2112/metrics 會出現類似下面的畫面,RoadRunner 提供很多基本 Metrics 數據。

實務上系統常需要監控 Reqeust 數量跟執行時間等等跟 HTTP Reqeust 相關的數據,RoadRunner 雖然有提供 [HTTP Metrics](https://docs.roadrunner.dev/docs/logging-and-observability/metrics#http-metrics) Middleware,可以監控 request 數量跟執行時間等等資訊。
但是如果使用 Laravel Octane 搭配 RoadRunner,RoadRunner 不會參與實際處理 HTTP Reqeust,而是把 Reqeust 轉交由 Laravel Octane 自己的 PHP 進程處理。由於跳過 RoadRunner HTTP Metrics Middleware,所以即使引用了 Middleware,Metrics 頁面並不會出現 `rr_http_request_total`、`rr_http_request_duration_seconds` 等數據。
那 Laravel Octane 要如何處理 HTTP Reqeust Metrics 呢?這有兩種做法:
1. 自行做統計,並另外新增 Metrics Port 和 Path(EX: http://localhost:80/metrics),輸出這些統計資料。
2. 透過 RoadRunner Metrics Plugin,當 Laravel App 層處理 HTTP Reqeust 時,經過 RPC 觸發 RoadRunner Application Metrics,這樣就可以在原本的 http://localhost:2112/metrics 頁面呈現客製化的 Metrics 數據。
接下來要介紹一下第二種做法
## RoadRunner Application Metrics
如果希望沿用原本 RoadRunner Metrics 頁面(http://localhost:2112/metrics),那就要透過 RoadRunner Metrics Plugin 處理 Metrics 數據:
```mermaid
graph LR
A(Laravel 端) --> |RPC| B(RoadRunner Application Metrics)
```
### RoadRunner 設定
首先在 RoadRunner `.rr.yaml` 的 `metrics.collect` 下設定客製化的 Metrics
```yaml
metrics:
address: 0.0.0.0:2112 # Prometheus Metrics 端口
collect:
api_requests_total:
type: counter
help: "Total number of HTTP requests."
labels: ["method", "path"]
api_request_failures_total:
type: counter
help: "Total number of failed HTTP requests (non-2xx responses)."
labels: ["method", "path"]
api_request_duration_seconds:
type: histogram
help: "Request latency in seconds."
labels: ["method", "path"]
# 根據您的需求調整 buckets
buckets: [0.1, 0.3, 0.5, 1, 2]
```
以上例子新增三種客製化的 Application Metrics(`api_requests_total`、`api_request_failures_total`、`api_request_duration_seconds`),顧名思義就是 HTTP Requests 數目、非 200 Http Status Code Requests 數目 跟 API Request 執行時間。
- type:種類有四種:gauge、counter、summary、histogram,差異請參考 ChatGPT 整理表格

以上例子因為是作 Requests 數量統計跟執行時間(duration)分佈,所以使用 `counter` & `histogram` 兩種 Type
- buckets:`histogram` Type 需設定的執行時間分佈區間,設定為 0.1s、0.3s、0.5s、1s、2s,假設有個 Request 執行 0.8s,0.1s、0.3s、0.5s 這三個 buckets 會 +1,如果 0.05s,那就沒有 buckets 會累計了,以此類推。
- labels:可以增加額外的 Metadata,適合拿來做分類統計或過濾,以上例子採用 HTTP Method & Request Path 做分類,也就是 Requests 數量統計跟執行時間(duration)分佈會依照不同 HTTP Method & Request Path 做分類。
### Laravel 監聽事件
設定好 RoadRunner Application Metrics 後,接著要在 Laravel 端觸發 Application Metrics,這邊使用到一個套件 [spiral/roadrunner-metrics](https://github.com/roadrunner-php/metrics),它是一個 PHP 與 RoadRunner Metrics Plugin 介接的橋樑,通過這橋樑提供 RoadRunner Metrics 資料,例如某個 Metrics 統計數量加 1 / 減 1 等等。
- 在 App\Providers 的 AppServiceProvider 註冊 Metrics 實例
```php
$this->app->singleton(MetricsInterface::class, function () {
// 從設定檔讀取 RPC 位址
$rpcAddress = config('octane.rpc');
// 透過 RPC 連線建立 RoadRunner Metrics Client 端
return new Metrics(
RPC::create($rpcAddress)
);
});
```
- 在 App\Listeners 下新增 OctaneMetricsReporter 監聽物件,有三種 Metrics(`api_requests_total`、`api_request_failures_total`、`api_request_duration_seconds`)要觸發
```php
<?php
namespace App\Listeners;
use Spiral\RoadRunner\Metrics\MetricsInterface;
class OctaneMetricsReporter
{
protected MetricsInterface $metrics;
public function __construct(MetricsInterface $metrics)
{
$this->metrics = $metrics;
}
public function handle($event)
{
// 如果沒有 Response 物件,則直接返回
if (!$event->response) {
return;
}
$method = $event->request->getMethod();
// 符合 Route 設定回傳 Route URI,不符合則一率為 unmatched
$path = optional($event->request->route())->uri() ?? 'unmatched';
$labels = [$method, $path];
// 每次請求,將 api_requests_total 計數器加 1
$this->metrics->add('api_requests_total', 1, $labels);
// 如果不是 2xx,則累加 api_request_failures_total
$statusCode = $event->response->getStatusCode();
if ($statusCode < 200 || $statusCode >= 300) {
$this->metrics->add('api_request_failures_total', 1, $labels);
}
// 計算請求執行期間並記錄 api_request_duration_seconds
$startTime = $event->request->server('REQUEST_TIME_FLOAT');
if ($startTime) {
$latencyInSeconds = (microtime(true) - $startTime);
$this->metrics->observe('api_request_duration_seconds', $latencyInSeconds, $labels);
}
}
}
```
- 最後在 config 的 octane.php 新增 RoadRunner RPC Endpoint(預設是 `tcp://127.0.0.1:6001`)及在 RequestTerminated 階段設定監聽事件
```php
return [
...
'listeners' => [
...
RequestTerminated::class => [
// FlushUploadedFiles::class,
App\Listeners\OctaneMetricsReporter::class,
],
],
...
'rpc' => env('ROADRUNNER_RPC', 'tcp://127.0.0.1:6001'),
]
```
完成上述設定後,重啟 Laravel Octane Server,再呼叫一些 api,就能看到剛剛設定客製化 Metrics 了
