---
title: "@unikvs/metrics"
description: "操作の件数とバイト数、use フックの実行時間を記録し、OTel や Prometheus へ転送できるミドルウェアです。"
---

## 概要 [#overview]

`Metrics` は操作の件数とバイト数、use フックの実行時間を記録し、集計と外部転送を行います。この実行時間にストレージ I/O の遅延は含みません。Middleware

性能の可視化や運用監視に使います。測定値はメモリーに保持され、必要に応じて OTel の計器やカスタム出力先へ転送されます。

- `name` は `Metrics` です。
- `isOpen` は常に `true` です。
- `use` は各操作の前段で `use` フックチェーンの成否と実行時間を測定します。この実行時間にストレージ I/O の遅延は含みません。`encode`・`decode`・ストリームではバイト数も記録します。
- 記録処理は決して例外を投げず、出力先の失敗は無視します。

:::warning[use の成否と遅延はストレージ操作のものではありません]
`use` はストレージ I/O の前に実行され、`next()` が実行するのは後段の `use` フックまでです。そのため、`outcome` と `durationMs` は `use` フックチェーンの成否と実行時間を表し、ストレージ操作の成否や遅延を表しません。

`use` フックの後に実行されるストレージ I/O の失敗は、`use` の記録に反映されません。プレーン値の `set` の書き込み失敗、存在しないキー以外の `get` の読み取り失敗、`stream` の取得や存在しないキーの失敗、`delete`・`has`・`clear`・`copy`・`move` の失敗が該当します。Metrics がエラーとして記録するのは、フック自体の失敗と、ラップ後のストリームの読み取り失敗・取り消しです。存在しないキーの `get` もエラーとしては記録されません。プレーン値の `set` がエンコード中に失敗した場合は、`use` の `success` と `encode` の `error` が並びます。

ストリームの記録は、ストリームの完了、読み取り失敗、取り消しのいずれか早い時点で確定します。完了前に書き込み先の失敗などでストリームが取り消された場合は `error` として記録されます。確定後の失敗は反映されず、ソースがラップ前にロックされていた場合は何も記録されません。

`Metrics` は `afterUse` を実装していないため、書き込み系操作の成否を別レコードとして出力しません。コアの `use` はストレージ I/O の前に実行されるため、どのミドルウェアでも `use` からストレージ操作の成否を観測できません。コアには、ストレージ I/O の確定後に成否を受け取る [`afterUse`](/unikvs/packages/core/middleware#hooks-after-use) フックがあります。
:::

インストールは次のコマンドで行います。

```package-install
npm install @unikvs/metrics @opentelemetry/api
```

`@opentelemetry/api` は `exporter` に `"otel"` を指定するときのみ必要です。Prometheus 形式の出力 (`toPrometheusString()`) やメモリー集計のみでは不要です。

## 使い方 [#usage]

Metrics は IMiddleware を実装しています。UniKvs には use() で渡します。

```ts
import { Metrics } from "@unikvs/metrics";
```

コンストラクターの引数は次のとおりです。

| Prop | Type | Default | Description |
| - | - | - | - |
| `exporter?` | `[#metrics-options-exporter] MetricsExporterKind \| MetricsExporter` | - | 測定値の転送先です。組み込みのエクスポーター名かカスタム出力関数を指定します。この設定にかかわらず、測定値は常にメモリーに保持されます。 |
| `meterProvider?` | `[#metrics-options-meter-provider] MetricsMeterProvider` | - | exporter が "otel" のときに使う OTel の MeterProvider です。 |
| `registry?` | `[#metrics-options-registry] unknown` | - | 外部の Prometheus 連携のために保持する、不透明なレジストリーハンドルです。Prometheus の公開用テキストは toPrometheusString() から取得できます。 |
| `onRecord?` | `[#metrics-options-on-record] (record: MetricRecord) => void` | - | 測定値を記録するたびに呼び出されるコールバックです。 |
| `clock?` | `[#metrics-options-clock] () => number` | - | ミリ秒を返す時計です。既定では performance.now() を使います。 |

以下は、操作を測定し、集計を取得する例です。

```ts
import { Memory } from "@unikvs/memory";
import { Metrics } from "@unikvs/metrics";
import * as unikvs from "unikvs";

const metrics = new Metrics();
const kvs = new unikvs.Builder<{ k1: unikvs.PlainValue<string> }>()
  .use(metrics)
  .persist(new Memory())
  .build();

await kvs.open();
await kvs.set("k1", "hello");
console.log(metrics.getMetrics().total); // 記録件数
console.log(metrics.toPrometheusString()); // Prometheus 形式
await kvs.close();
```

### `Metrics` [#metrics]

操作のメトリクスを記録するミドルウェアです。測定値はメモリーに保持され、`getMetrics()` と `toPrometheusString()` で取得できます。

### `MetricsOptions` [#metrics-options]

`Metrics` のコンストラクターが受け取るオプションです。エクスポーターや時計を指定できます。

### `MetricsAction` [#metrics-action]

メトリクスミドルウェアが観測する操作名です。`IMiddleware.UseContext["action"]` と等価です。

### `MetricsOutcome` [#metrics-outcome]

観測した `use` フックチェーン、または `encode`・`decode`・ストリームの成否を表します。`"success"` か `"error"` です。`use` の場合はストレージ操作の成否ではありません。

### `MetricsExporterKind` [#metrics-exporter-kind]

名前で選択する組み込みのエクスポーターです。`"otel"` は計器への転送を指し、`"prometheus"` はメモリー保持とテキスト出力を指します。

### `MetricsExporter` [#metrics-exporter]

記録されたすべての測定値を受け取るカスタム出力先です。

### `MetricRecord` [#metric-record]

1 回のフック実行で観測した測定値です。

| Prop | Type | Default | Description |
| - | - | - | - |
| `action` | `[#metric-record-action] MetricsAction` | - | 観測した操作です。 |
| `outcome` | `[#metric-record-outcome] MetricsOutcome` | - | 観測したフックの成否です。use フックの場合はストレージ操作の成否ではありません。 |
| `durationMs` | `[#metric-record-duration-ms] number` | - | 観測したフックチェーンの経過時間 (ミリ秒) です。use の場合は use フックチェーンの実行時間で、ストレージ I/O の遅延は含みません。ストリームの場合は、ラップからソース (変換) の完了・読み取り失敗・取り消しによる確定までの時間で、ストリームの消費時間を含むことがあります。ここでの完了はソース (変換) の完了を指し、`destination` の読み切りを意味しません。 |
| `bytes?` | `[#metric-record-bytes] number \| undefined` | - | 観測したバイト数です。不明な場合は undefined です。 |
| `key` | `[#metric-record-key] string \| undefined` | - | 操作対象のキーです。キーを持たない clear では undefined です。 |

### `MetricsActionSummary` [#metrics-action-summary]

操作名ごとに集計したカウンターです。

| Prop | Type | Default | Description |
| - | - | - | - |
| `action` | `[#metrics-action-summary-action] string` | - | 操作名です。 |
| `count` | `[#metrics-action-summary-count] number` | - | 記録した測定値の総数です。 |
| `success` | `[#metrics-action-summary-success] number` | - | 成功した測定値の数です。 |
| `error` | `[#metrics-action-summary-error] number` | - | 失敗した測定値の数です。 |
| `totalBytes` | `[#metrics-action-summary-total-bytes] number` | - | 観測したバイト数の合計です。 |
| `totalDurationMs` | `[#metrics-action-summary-total-duration-ms] number` | - | 観測した経過時間の合計 (ミリ秒) です。 |

### `MetricsSnapshot` [#metrics-snapshot]

これまでに記録したすべての測定値のスナップショットです。

| Prop | Type | Default | Description |
| - | - | - | - |
| `total` | `[#metrics-snapshot-total] number` | - | 記録した測定値の総数です。 |
| `byAction` | `[#metrics-snapshot-by-action] Record<string, MetricsActionSummary>` | - | 操作名をキーにした集計結果です。 |
| `records` | `[#metrics-snapshot-records] readonly MetricRecord[]` | - | 記録したすべての測定値です (記録順)。 |

## データ型 [#data-types]

| メソッド       | 入力                 | 出力                       |
| -------------- | -------------------- | -------------------------- |
| `encode`       | `unknown`            | `unknown` (透過)           |
| `decode`       | `unknown`            | `unknown` (透過)           |
| `encodeStream` | `unknown` (チャンク) | `unknown` (チャンク、透過) |
| `decodeStream` | `unknown` (チャンク) | `unknown` (チャンク、透過) |

- `use` は操作ごとに 1 件を記録し、バイト数は `undefined` です。`durationMs` は `use` フックチェーンの実行時間で、ストレージ I/O の遅延は含みません。
- `use` フックの後に実行されるストレージ I/O の失敗は、`use` の記録に反映されません。プレーン値の `set` の書き込み失敗、存在しないキー以外の `get` の読み取り失敗、`stream` の取得や存在しないキーの失敗、`delete`・`has`・`clear`・`copy`・`move` の失敗が該当します。
- Metrics がエラーとして記録するのは、フック自体の失敗と、ラップ後のストリームの読み取り失敗・取り消しです。存在しないキーの `get` もエラーとしては記録されません。プレーン値の `set` がエンコード中に失敗した場合は、`use` の `success` と `encode` の `error` が並びます。
- ストリームの記録は、ストリームの完了、読み取り失敗、取り消しのいずれか早い時点で確定します。完了は `success`、読み取り失敗と取り消しは `error` です。完了前に書き込み先の失敗などでストリームが取り消された場合も `error` になります。確定後の失敗は反映されず、ソースがラップ前にロックされていた場合は何も記録されません。
- ストリームの `durationMs` は、ラップからソース (変換) の完了・読み取り失敗・取り消しによる確定までの時間で、ストリームの消費時間を含むことがあります。ここでの完了はソース (変換) の完了を指し、`destination` の読み切りを意味しません。`use`・`encode`・`decode` の `durationMs` はフックチェーンの実行時間です。
- `Metrics` は `afterUse` を実装していないため、書き込み系操作の成否を別レコードとして出力しません。コアの `use` はストレージ I/O の前に実行されるため、どのミドルウェアでも `use` からストレージ操作の成否を観測できません。コアには、ストレージ I/O の確定後に成否を受け取る [`afterUse`](/unikvs/packages/core/middleware#hooks-after-use) フックがあります。
- `encode` と `decode` はデータをそのまま通し、文字列や `ArrayBuffer`、`ArrayBufferView` ではバイト数も記録します。不明な型では `undefined` です。
- ストリームではチャンクをそのまま通し、確定時にそれまでの合計チャンクサイズを 1 件として記録します。
- このミドルウェアはキーラベルを意図的に省略し、`toPrometheusString()` のカーディナリティーが増えないようにしています。キーごとの詳細は `getMetrics()` と `onRecord` から取得できます。

## エラー [#errors]

このパッケージ固有のエラーはありません。

記録処理は例外を投げず、出力先の失敗は無視するため、メトリクスが操作のパイプラインを壊すことはありません。フックの実行中にエラーが発生したときは、`error` を記録してからそのエラーを再送出します。ストリームでは、読み取り中の失敗や取り消しも `error` として記録します。ただし、`use` はストレージ I/O の前に完了するため、ストレージ操作の失敗は `use` の記録には反映されません。
