# Configuration: metrics

> Source: https://ferron.sh/docs/configuration/observability/metrics

This page documents the metrics emitted by Ferron. Ferron emits OpenTelemetry-style metrics through the observability event system. Each module documents its own metrics, such as:

- **Core HTTP server metrics**: active requests, request duration, and request count. See [HTTP host directives](https://ferron.sh/docs/configuration/server/host.md#metrics).
- **Cache metrics**: cache hits, misses, entries, stores, evictions, and purges, with zone scoping. See [HTTP caching](https://ferron.sh/docs/configuration/content/cache.md#metrics).
- **Rate limiting metrics**: allowed and rejected requests, with zone scoping. See [Rate limiting](https://ferron.sh/docs/configuration/content/rate-limit.md#metrics).
- **Response control metrics**: aborted connections, IP blocks, and status rule matches. See [HTTP response control](https://ferron.sh/docs/configuration/routing/response.md#metrics).
- **Static file metrics**: files served and bytes sent, with compression and cache hit attributes. See [Static file serving](https://ferron.sh/docs/configuration/content/static-files.md#metrics).
- **Rewrite metrics**: applied rewrites and invalid rewrite errors. See [URL rewriting](https://ferron.sh/docs/configuration/routing/rewrite.md#metrics).
- **Proxy metrics**: backend selection, health, connection pooling, and TLS failures. See [Reverse proxying](https://ferron.sh/docs/configuration/proxy/reverse-proxy.md#metrics).
- **CGI/FastCGI/SCGI metrics**: request counts, failures, upstream duration, and stderr errors. See [CGI](https://ferron.sh/docs/configuration/content/cgi.md#metrics), [FastCGI](https://ferron.sh/docs/configuration/content/fastcgi.md#metrics), [SCGI](https://ferron.sh/docs/configuration/content/scgi.md#metrics).

> [!info]
>
> - This page lists core metrics emitted by Ferron. For per-module metrics, see respective configuration pages.
> - For native Prometheus metrics export, see [Prometheus metrics](https://ferron.sh/docs/configuration/observability/prometheus.md).
> - For OTLP export configuration, see [OTLP observability](https://ferron.sh/docs/configuration/observability/otlp.md).
> - For StatsD export configuration, see [StatsD metrics](https://ferron.sh/docs/configuration/observability/statsd.md).

## Understanding metric types

Ferron uses OpenTelemetry metric types. Understanding the type helps you write correct PromQL or OTLP queries:

| Type            | Behavior                                                                | Typical use                                 |
| --------------- | ----------------------------------------------------------------------- | ------------------------------------------- |
| `Counter`       | Monotonically increases. Resets to zero on process restart.             | Request counts, error counts, reload counts |
| `Gauge`         | Can go up or down. Represents an absolute value at time of measurement. | Active connections, queue length, uptime    |
| `UpDownCounter` | Can go up or down. Represents a delta since the last measurement.       | Memory usage, file descriptor count         |

## Exponential histograms

When using the OTLP observability backend, all histogram metrics use Base2 Exponential Histograms instead of fixed linear buckets by default. This provides multiple advantages for latency-sensitive workloads:

- **Automatic bucket allocation**. The SDK computes bucket boundaries using a base-2 exponential formula. You do not need to tune bucket boundaries.
- **High resolution across orders of magnitude**. The default configuration uses 160 buckets from sub-millisecond to 100 seconds with less than 5% relative error. This preserves tail-latency outliers (p99, p99.9) that coarse fixed buckets would mask.
- **No configuration required**. The SDK applies the exponential histogram aggregation at the SDK layer via an OTel View. You do not need to change any configuration.

> [!tip]
> If you do not see histogram metrics in your observability backend, try disabling native exponential histograms using `native_histograms false` in `metrics` subblock (see [Configuration: OTLP observability](https://ferron.sh/docs/configuration/observability/otlp.md)).

The Prometheus observability backend also supports native exponential histograms when you configure `endpoint_native_histograms` and set the output format to `"protobuf"`. In text format, it uses explicit bucket histograms with predefined boundaries.

## Metric exemplars

Ferron supports metric exemplars in Prometheus and OTLP observability backends. When a request carries an active trace context (W3C trace ID and span ID), the observability module attaches an exemplar to the observation. The exemplar links the observation to the specific trace.

Each exemplar contains:

- `trace_id`: the W3C trace ID of the request
- `span_id`: the W3C span ID of the request

Exemplars are available for all counter metrics by default. For Prometheus histograms, exemplars are active only when `endpoint_native_histograms` is `false` (the default), since native histograms and exemplars are mutually exclusive for this module. When you enable native histograms, histogram metrics no longer carry exemplars.

> [!note]
>
> To use metric exemplars with Ferron, you need to configure a tracing backend (for example OTLP) in the web server configuration.

> [!info]
>
> - For Prometheus exemplar configuration options and format details, see [Prometheus metrics](https://ferron.sh/docs/configuration/observability/prometheus.md#metric-exemplars).
> - For OTLP exemplar configuration options and format details, see [OTLP observability](https://ferron.sh/docs/configuration/observability/otlp.md#metrics).

## Process metrics

The `metrics-process` module collects process-level metrics automatically when you configure an observability backend. On Linux it reads `/proc/self/stat`. On Windows it uses the `GetProcessTimes` and `GetProcessMemoryInfo` APIs. On both platforms the collection interval is 1 second.

**Platform support:** Linux and Windows. On other platforms, the module is a no-op.

| Metric                               | Type          | Attributes                          | Description                                                                           |
| ------------------------------------ | ------------- | ----------------------------------- | ------------------------------------------------------------------------------------- |
| `process.cpu.time`                   | Counter       | `cpu.mode` (`"user"` or `"system"`) | Total CPU seconds broken down by different states                                     |
| `process.cpu.utilization`            | Gauge         | `cpu.mode` (`"user"` or `"system"`) | CPU use since the last measurement                                                    |
| `process.memory.usage`               | UpDownCounter | None                                | The change in physical memory (RSS) since the last measurement                        |
| `process.memory.virtual`             | UpDownCounter | None                                | The change in committed virtual memory (VMS) since the last measurement               |
| `process.unix.file_descriptor.count` | UpDownCounter | None                                | The change in number of unix file descriptors since the last measurement (Linux only) |

## Admin API metrics

The `metrics-admin` module collects metrics exposed via admin API automatically when you configure an observability backend.

| Metric                                       | Type    | Attributes | Description                                                              |
| -------------------------------------------- | ------- | ---------- | ------------------------------------------------------------------------ |
| `ferron.admin.uptime`                        | Gauge   | None       | Time since the server started                                            |
| `ferron.admin.connections_active`            | Gauge   | None       | Currently open TCP connections across all HTTP listeners                 |
| `ferron.admin.requests_total`                | Counter | None       | Total HTTP requests served across all listeners                          |
| `ferron.admin.reloads`                       | Counter | None       | Number of configuration reloads performed                                |
| `ferron.admin.observability_events_dropped`  | Counter | None       | Total number of observability events dropped due to backpressure         |
| `ferron.admin.observability_event_queue_len` | Gauge   | None       | Approximate current length of the observability event queue              |
| `ferron.admin.config_mtime`                  | Gauge   | None       | Last modification time of the configuration source (epoch seconds)       |
| `ferron.admin.config_drift`                  | Gauge   | None       | Whether the system detects configuration drift (1 = drift, 0 = no drift) |

### Admin API request metrics

The admin API emits per-request metrics for all endpoints except `/health` (which is a high-frequency probe with low signal per-request).

| Metric                          | Type      | Attributes                                                     | Description                                                      |
| ------------------------------- | --------- | -------------------------------------------------------------- | ---------------------------------------------------------------- |
| `ferron.admin.request.duration` | Histogram | `http.request.method`, `url.path`, `http.response.status_code` | How long admin API requests take in seconds                      |
| `ferron.admin.request.count`    | Counter   | `http.request.method`, `url.path`, `http.response.status_code` | Total number of admin API requests                               |
| `ferron.admin.reload.count`     | Counter   | `http.response.status_code`                                    | Total number of admin config reload attempts (POST /reload only) |

### Admin API runtime metrics

The `metrics-admin` module also collects runtime status metrics when you configure an observability backend.

- `ferron.admin.runtime.primary_threads` (Gauge): number of primary threads (typically equal to CPU count).
- `ferron.admin.runtime.io_uring_supported` (Gauge): whether the current system supports `io_uring` (`1` = yes, `0` = no).
- `ferron.admin.runtime.io_uring_runtime_enabled` (Gauge): whether `io_uring` was successfully enabled at runtime (`1` = yes, `0` = no).

These metrics correspond to the same data that the admin API `GET /status` endpoint exposes. They are available as time-series data for monitoring and alerting.

## Reload metrics

The `metrics-reload` module emits a counter metric around configuration reload lifecycle events when you configure an observability backend.

| Metric           | Type    | Attributes                                                              | Description                                                                  |
| ---------------- | ------- | ----------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| `ferron.reloads` | Counter | `ferron.reload.successful` (bool), `error.message` (string, on failure) | Number of configuration reloads performed, annotated with success or failure |

## HTTP metrics

Ferron also emits common request-path metrics:

| Metric                                         | Type    | Attributes                 | Description                                                                                |
| ---------------------------------------------- | ------- | -------------------------- | ------------------------------------------------------------------------------------------ |
| `ferron.http.server.pre_handler_request_count` | Counter | None                       | Malformed or timed-out requests rejected before the normal HTTP handler completes          |
| `ferron.http.server.redirects`                 | Counter | None                       | Redirects emitted by the core server, including trailing-slash and HTTP-to-HTTPS redirects |
| `ferron.http.server.client_ip_rewrites`        | Counter | None                       | Requests whose client IP was rewritten from a trusted proxy header                         |
| `ferron.http.server.cors_preflights`           | Counter | None                       | CORS preflight requests handled before the main pipeline                                   |
| `ferron.http.server.connection_errors`         | Counter | transport, lifecycle stage | Listener and handshake failures                                                            |

## Prometheus scrape metrics

The Prometheus endpoint emits self-referential metrics about its own scrape performance. Ferron registers them in the same registry that serves `/metrics`. They appear in every scrape response.

| Metric                                      | Type      | Attributes | Description                                         |
| ------------------------------------------- | --------- | ---------- | --------------------------------------------------- |
| `ferron_prometheus_scrape_duration_seconds` | Histogram | None       | How long Prometheus scrape requests take in seconds |
| `ferron_prometheus_scrape_total`            | Counter   | None       | Total number of Prometheus scrape requests          |
| `ferron_prometheus_scrape_errors_total`     | Counter   | None       | Total number of failed Prometheus scrape requests   |

> [!tip]
> Use `ferron_prometheus_scrape_duration_seconds` to monitor scrape latency. If p99 exceeds your Prometheus `scrape_timeout`, consider reducing the number of registered metrics or increasing the timeout.

## TLS metrics

The core server emits per-host TLS metrics for every HTTPS connection. Each metric carries a `ferron.host` attribute (the SNI hostname, or `"_global"` for connections without SNI) for per-host filtering and multi-tenant monitoring.

| Metric                             | Type          | Attributes                                                                               | Description                                        |
| ---------------------------------- | ------------- | ---------------------------------------------------------------------------------------- | -------------------------------------------------- |
| `ferron.tls.certificate_not_after` | Gauge         | `ferron.host`, `ferron.tls.provider`, `crypto.certificate.serial_number`                 | Certificate `notAfter` field as Unix epoch seconds |
| `ferron.tls.handshake.duration`    | Histogram     | `ferron.host`, `tls.protocol.version`, `tls.cipher_suite`, `ferron.tls.handshake.result` | TLS handshake latency                              |
| `ferron.tls.handshake.total`       | Counter       | `ferron.host`, `ferron.tls.handshake.result`                                             | Total TLS handshake attempts                       |
| `ferron.tls.connections.active`    | UpDownCounter | `ferron.host`                                                                            | Currently open TLS connections                     |

## Common alerting patterns

The table below maps core metrics to practical alerting thresholds. Adjust thresholds based on your deployment size and traffic patterns.

| Alert                  | Metric                                       | Suggested threshold            |
| ---------------------- | -------------------------------------------- | ------------------------------ |
| High error rate        | `ferron.http.server.connection_errors`       | Rate > 10/s for 1m             |
| Connection saturation  | `ferron.admin.connections_active`            | Approaching configured limit   |
| Config reload failures | `ferron.admin.reloads`                       | Count > 0 with error attribute |
| Config drift detected  | `ferron.admin.config_drift`                  | Value = 1 for > 1m             |
| Export backpressure    | `ferron.admin.observability_events_dropped`  | Rate > 0                       |
| Queue buildup          | `ferron.admin.observability_event_queue_len` | > 1000                         |
| Memory growth          | `process.memory.usage`                       | Sustained increase over hours  |
| CPU saturation         | `process.cpu.utilization`                    | > 0.9 sustained                |
| TLS handshake failures | `ferron.tls.handshake.total{result="error"}` | Rate > 5/s for 1m              |
| Slow TLS handshakes    | `ferron.tls.handshake.duration` p99          | > 500ms                        |

## Cardinality guidance

Observability sinks may drop events under high load. Ferron exposes `ferron.admin.observability_events_dropped` and `ferron.admin.observability_event_queue_len` to help detect and tune exporter queues.

When using Prometheus or OTLP with baggage promotion, monitor label cardinality. High-cardinality labels can cause significant performance degradation and memory use in Prometheus. Examples include user IDs or request IDs used as metric labels. Use `max_distinct` on baggage keys to cap distinct label values and prevent label explosion. Values exceeding the distinct cap are dropped.