---
title: "@unikvs/metrics"
description: "Middleware that records operation counts, byte sizes, and use-hook duration, forwardable to OTel or Prometheus."
---

## Overview [#overview]

`Metrics` records operation counts, byte sizes, and use-hook duration, with aggregation and external export. The use-hook duration does not include storage I/O latency.Middleware

Use it for performance visibility and operational monitoring. Measurements stay in memory and can be forwarded to OTel instruments or custom sinks.

- `name` is `Metrics`.
- `isOpen` is always `true`.
- `use` measures each operation's use-hook chain success and duration; storage I/O latency is not included. `encode` / `decode` and streams also record byte sizes.
- Recording never throws; sink failures are ignored.

:::warning[`use` outcomes and latency are not storage-operation outcomes]
`use` runs before storage I/O, and `next()` only runs downstream `use` hooks. Its `outcome` and `durationMs` therefore describe the `use` hook chain, not the storage operation or its latency.

Storage I/O failures that run after the `use` hooks are not reflected in `use` records. This includes plain-value `set` write failures, `get` read failures other than missing keys, `stream` acquisition and missing-key failures, and `delete` / `has` / `clear` / `copy` / `move` failures. Metrics records only failures of the hooks themselves and read failures or cancellations of a wrapped stream; a missing-key `get` is not recorded as an error either. A plain-value `set` that fails during encoding can produce `use` success plus an `encode` error.

Stream records settle at the earliest of stream completion, read failure, or cancellation. A destination failure that cancels the stream before completion is recorded as an `error`. Failures after settlement are not reflected, and a source that is already locked before wrapping records nothing.

Metrics does not implement `afterUse`, so it emits no separate record for the outcome of mutating operations. Core `use` runs before storage I/O, so no middleware can observe storage-operation outcomes from `use`. Core provides an [`afterUse`](/unikvs/en/packages/core/middleware#hooks-after-use) hook for post-I/O outcomes.
:::

Install with:

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

`@opentelemetry/api` is only needed when `exporter` is `"otel"`. Memory-only aggregation and Prometheus output (`toPrometheusString()`) do not need it.

## Usage [#usage]

Metrics implements IMiddleware. Pass it to UniKvs with use().

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

Constructor arguments:

| Prop | Type | Default | Description |
| - | - | - | - |
| `exporter?` | `[#metrics-options-exporter] MetricsExporterKind \| MetricsExporter` | - | Where to forward measurements. Pass a built-in exporter name or a custom sink. Measurements are always kept in memory regardless of this setting. |
| `meterProvider?` | `[#metrics-options-meter-provider] MetricsMeterProvider` | - | OTel MeterProvider used when exporter is "otel". |
| `registry?` | `[#metrics-options-registry] unknown` | - | Opaque registry handle kept for external Prometheus integration. The exposition text comes from toPrometheusString(). |
| `onRecord?` | `[#metrics-options-on-record] (record: MetricRecord) => void` | - | Called every time a measurement is recorded. |
| `clock?` | `[#metrics-options-clock] () => number` | - | Clock returning milliseconds. Defaults to performance.now(). |

The following measures operations and reads the aggregation:

```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); // record count
console.log(metrics.toPrometheusString()); // Prometheus format
await kvs.close();
```

### `Metrics` [#metrics]

Middleware that records operation metrics. Measurements stay in memory and are available via `getMetrics()` and `toPrometheusString()`.

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

Options accepted by the `Metrics` constructor. Specifies the exporter and clock.

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

Operation names observed by the metrics middleware. Equivalent to `IMiddleware.UseContext["action"]`.

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

Whether the observed `use` hook chain, or the `encode`, `decode`, or stream operation, succeeded or failed. `"success"` or `"error"`. For `use`, this is not the storage operation's outcome.

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

Built-in exporter selected by name. `"otel"` forwards to instruments; `"prometheus"` means in-memory retention plus text output.

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

Custom sink receiving every recorded measurement.

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

Measurement observed for a single hook execution.

| Prop | Type | Default | Description |
| - | - | - | - |
| `action` | `[#metric-record-action] MetricsAction` | - | The observed operation. |
| `outcome` | `[#metric-record-outcome] MetricsOutcome` | - | Whether the observed hook succeeded or failed. For the use hook, this is not the storage operation's outcome. |
| `durationMs` | `[#metric-record-duration-ms] number` | - | Wall-clock duration of the observed hook chain (ms). For use, this is the use-hook chain, not storage I/O latency. For streams, it runs from wrapping until settlement (source/transform completion, read failure, or cancellation) and can include stream consumption time. Completion here means the source/transform finished, not that the destination has been fully drained. |
| `bytes?` | `[#metric-record-bytes] number \| undefined` | - | Observed byte count. Undefined when unknown. |
| `key` | `[#metric-record-key] string \| undefined` | - | Target key. Undefined for keyless clear. |

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

Per-operation-name counters.

| Prop | Type | Default | Description |
| - | - | - | - |
| `action` | `[#metrics-action-summary-action] string` | - | Operation name. |
| `count` | `[#metrics-action-summary-count] number` | - | Total recorded measurements. |
| `success` | `[#metrics-action-summary-success] number` | - | Successful measurements. |
| `error` | `[#metrics-action-summary-error] number` | - | Failed measurements. |
| `totalBytes` | `[#metrics-action-summary-total-bytes] number` | - | Total observed bytes. |
| `totalDurationMs` | `[#metrics-action-summary-total-duration-ms] number` | - | Total observed elapsed time (ms). |

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

Snapshot of all measurements recorded so far.

| Prop | Type | Default | Description |
| - | - | - | - |
| `total` | `[#metrics-snapshot-total] number` | - | Total recorded measurements. |
| `byAction` | `[#metrics-snapshot-by-action] Record<string, MetricsActionSummary>` | - | Aggregation keyed by operation name. |
| `records` | `[#metrics-snapshot-records] readonly MetricRecord[]` | - | All recorded measurements (in record order). |

## Data types [#data-types]

| Method         | Input             | Output                         |
| -------------- | ----------------- | ------------------------------ |
| `encode`       | `unknown`         | `unknown` (passthrough)        |
| `decode`       | `unknown`         | `unknown` (passthrough)        |
| `encodeStream` | `unknown` (chunk) | `unknown` (chunk, passthrough) |
| `decodeStream` | `unknown` (chunk) | `unknown` (chunk, passthrough) |

- `use` records one measurement per operation, with bytes as `undefined`. `durationMs` is the use-hook chain duration and does not include storage I/O latency.
- Storage I/O failures that run after the `use` hooks are not reflected in `use` records. This includes plain-value `set` write failures, `get` read failures other than missing keys, `stream` acquisition and missing-key failures, and `delete` / `has` / `clear` / `copy` / `move` failures.
- Metrics records only failures of the hooks themselves and read failures or cancellations of a wrapped stream; a missing-key `get` is not recorded as an error either. A plain-value `set` that fails during encoding can produce `use` success plus an `encode` error.
- Stream records settle at the earliest of stream completion, read failure, or cancellation. Completion records `success`; read failures and cancellations record `error`. A destination failure that cancels the stream before completion also records `error`. Failures after settlement are not reflected, and a source that is already locked before wrapping records nothing.
- Stream `durationMs` runs from wrapping until settlement (source/transform completion, read failure, or cancellation) and can include stream consumption time. Completion here means the source/transform finished, not that the destination has been fully drained. For `use`, `encode`, and `decode`, `durationMs` is the hook-chain duration.
- Metrics does not implement `afterUse`, so it emits no separate record for mutating-operation outcomes. Core `use` runs before storage I/O, so no middleware can observe storage-operation outcomes from `use`. Core provides an [`afterUse`](/unikvs/en/packages/core/middleware#hooks-after-use) hook for post-I/O outcomes.
- `encode` and `decode` pass data through, also recording bytes for strings, `ArrayBuffer`, and `ArrayBufferView`. Other types record `undefined`.
- Streams pass chunks through and record the total chunk size as one record on settlement.
- This middleware deliberately omits key labels so `toPrometheusString()` cardinality stays flat. Per-key detail is available via `getMetrics()` and `onRecord`.

## Errors [#errors]

This package has no package-specific errors.

Recording never throws and sink failures are ignored, so metrics never break the operation pipeline. When a hook fails, the error is recorded and then rethrown. For streams, read failures and cancellations are also recorded as `error`. However, `use` completes before storage I/O, so storage operation failures are not reflected in `use` records.
