14. Observability
Alerts, audit trail, PanFS events, logs, and metrics, each as a point-in-time snapshot endpoint and a matching Server-Sent Events (SSE) stream endpoint for live updates.
14.1 Metrics and Logs Behind These Endpoints
The endpoints in this chapter are windows onto the data stores of the VDURA observability stack, not a separate data model. Knowing what those stores hold, and how it is named, is what makes the query endpoints useful. This section summarizes it; the full catalog of metric names, labels, alert definitions, and dashboards is in the PanFS Metrics Reference Guide.
Where each endpoint group reads from
| Endpoint group | Backing store | How it reads |
|---|---|---|
metrics/query, metrics/query_range |
VictoriaMetrics (Prometheus-compatible, MetricsQL) | Forwarded to the VictoriaMetrics /api/v1/query and /api/v1/query_range endpoints. Only the documented parameters are accepted; any other name returns 422. |
metrics/{key}, metrics/stream, metrics/{key}/stream, metrics/targets |
VictoriaMetrics | Snapshots and Server-Sent Events streams for named metric keys, and node target discovery in Prometheus HTTP Service Discovery format. |
logs, logs/stream, logs/{logsql_endpoint} |
VictoriaLogs (LogsQL) | The proxy forwards to /select/logsql/{endpoint} on the VictoriaLogs query instance on the director node. |
audit, audit/stream, audit/{request_id}, events, events/stream |
VictoriaLogs | The server owns the LogsQL query, so clients pass no query text. Audit records are deduplicated by request_id and PanFS events by event_id. |
alerts, alerts/stream, storagesets/status |
Alert series in the metrics database | Active alerts evaluated by vmalert and projected from the ALERTS series. |
Metric naming families
Four naming families coexist in the metrics database. Recognizing the family tells you where a metric comes from and how to read it.
| Prefix | Source and reading rules |
|---|---|
magent_local_*, magent_shelf_* |
The PanFS management agent statistics tree, flattened as magent_local_<hw, sw, or sys>_<subsystem>_<stat>. Array statistics get a numeric suffix, and a string statistic becomes a series with the value 1 and the text in a value label. |
panfs_* |
Realm-level inventory and events from the management database. The sample value is a state code and the content is in the labels. Each family has a matching <family>_scrape_success gauge. |
cpu_, mem_, disk_, diskio_, net_, netstat_, nstat_, ethtool_, processes_, procstat_, system_ |
Host operating system metrics from Telegraf input plugins, named <measurement>_<field>. Standard Linux semantics apply, and counters need rate() or increase(). |
ALERTS, ALERTS_FOR_STATE, observability_node_type |
The vmalert output, and the declaration of each node’s role. |
Common labels
Nearly every series carries five labels that identify the emitting node, so any metric can be sliced by node or node role without a join. Realm-level panfs_* inventory metrics describe realm objects rather than the emitting host and carry only node_name.
| Label | Meaning |
|---|---|
host |
Linux hostname of the node. |
host_id |
Stable node UUID assigned by the node identity service. |
ip_address |
Management IP address of the node. |
node_name |
The PanFS realm name of the node, in enclosure,slot form, such as VCH-1,1. |
node_type |
The node role: director_l for a director, vpod for a virtual storage node hosting one OSD. Other values appear on physical realms. |
Reading values correctly
- Counters (host disk and network totals, File Manager operation counts) increase monotonically; use
rate()orincrease(). - Gauges are read directly.
- Info metrics have a constant sample value and carry their content in labels; read them with
last_over_time()or group by the label, never withrate(). - Interval gauges (the
measure_group undermagent_local_sw_iopath_) already represent the last collection interval; sum them across nodes for a realm figure, and do not applyrate()to them. - Management agent and realm metrics arrive about every 20 seconds.
Example queries
Pass any of these as the query parameter of GET /api/v1/observability/metrics/query:
curl -sG "https://<realm-address>/api/v1/observability/metrics/query" \
-H "Authorization: Bearer $TOKEN" \
--data-urlencode 'query=count by (node_type) (system_uptime)'| Question | Expression |
|---|---|
| How many nodes of each role are reporting? | count by (node_type) (system_uptime) |
| Which director holds the realm president role? | panfs_president == 1 |
| Is the master management server reachable? | min(panfs_master_mserver_up) |
| How many realm alerts are active, by severity? | sum by (severity) (panfs_alert_occurrences) |
| How many alerts are firing, by severity? | count by (severity) (ALERTS{alertstate="firing"}) |
| How long has each alert been active? | time() - ALERTS_FOR_STATE |
| Which network interfaces have the link down? | panfs_nic{uplink_down="true"} |
Logs and the audit trail
The audit and event endpoints read structured log lines from VictoriaLogs. The fields of an AuditRecord are the fields of the API audit log (/var/log/panapi-audit.log): client_ip, method, request_id, result, status_code, action, resource, target_id, tenant_id, and user_id. Every log line also carries stream identity fields such as _time, _msg, node_name, host_id, and filename (or unit for journald entries). To go beyond what the audit endpoints offer, query the logs directly through the proxy with LogsQL. For example, to list recent failed API operations:
curl -sG "https://<realm-address>/api/v1/observability/logs/query" \
-H "Authorization: Bearer $TOKEN" \
--data-urlencode 'query=filename:="/var/log/panapi-audit.log" result:=failed' \
--data-urlencode 'limit=20'14.2 Endpoint Reference
Endpoint Summary
| Path | Summary | Method |
|---|---|---|
/api/v1/observability/alerts |
Alerts snapshot (keyed set) | GET |
/api/v1/observability/alerts/stream |
Alerts SSE stream (added/resolved) | GET |
/api/v1/observability/audit |
Audit trail snapshot (last N) | GET |
/api/v1/observability/audit/stream |
Audit SSE stream (append deltas) | GET |
/api/v1/observability/audit/{request_id} |
Audit record with failure diagnostics | GET |
/api/v1/observability/events |
PanFS events snapshot (last N) | GET |
/api/v1/observability/events/stream |
PanFS events SSE stream (append deltas) | GET |
/api/v1/observability/logs |
Logs snapshot (last N) | GET |
/api/v1/observability/logs/stream |
Logs SSE stream (append deltas) | GET |
/api/v1/observability/logs/{logsql_endpoint} |
VictoriaLogs /select/logsql proxy | GET |
/api/v1/observability/metrics/query |
Prometheus instant query proxy | GET |
/api/v1/observability/metrics/query_range |
Prometheus range query proxy | GET |
/api/v1/observability/metrics/stream |
Grouped metric SSE stream (append deltas for several keys) | GET |
/api/v1/observability/metrics/targets |
List metrics targets | GET |
/api/v1/observability/metrics/{key} |
Metric snapshot (range window) | GET |
/api/v1/observability/metrics/{key}/stream |
Metric SSE stream (append deltas) | GET |
/api/v1/observability/storagesets/status |
Storage-set alert status | GET |
GET /api/v1/observability/alerts
Alerts snapshot (keyed set)
The current alert set of the caller’s own tenant, most severe first. A platform caller widens to one tenant with tenant_id, or to every tenant with all_tenants=true; its own tenant is the realm’s telemetry, so the default read is the realm’s health rather than the tenants’ records. A tenant-filtered read returns only records that carry a tenant, so during a rolling upgrade a node that has not taken the new configuration yet produces records no tenant-filtered read returns. That fails closed: it is a completeness limit, never a disclosure.
Authentication: required (PasswordLogin or BearerJWT).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
tenant_id |
query | string (uuid), nullable | no | Platform tenant only. Read the observability records of another tenant instead of your own. |
all_tenants |
query | boolean | no | Platform tenant only. Read the observability records of every tenant. Omitted, a read covers the caller’s own tenant alone - for the platform tenant that is the realm’s own telemetry, which carries the platform tenant id, and not the records tenants own. |
Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Successful Response | array |
| 422 | Validation Error | HTTPValidationError |
GET /api/v1/observability/alerts/stream
Alerts SSE stream (added/resolved)
Deltas for the same feed the alerts snapshot reads, taking the same tenant_id scope. The stream ends itself with one terminal end event whose reason is credential_expired (refresh the token and reconnect), credential_revoked (sign in again), permission_revoked (the caller no longer holds the grant), lifetime_expired (the maximum stream lifetime; reconnect) or recheck_unavailable (the credential could not be re-checked several times running; reconnect with backoff). The checks run on stream ticks, which on a quiet feed happen every 20 seconds, so an end can lag by about that.
Authentication: required (PasswordLogin or BearerJWT).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
tenant_id |
query | string (uuid), nullable | no | Platform tenant only. Read the observability records of another tenant instead of your own. |
all_tenants |
query | boolean | no | Platform tenant only. Read the observability records of every tenant. Omitted, a read covers the caller’s own tenant alone - for the platform tenant that is the realm’s own telemetry, which carries the platform tenant id, and not the records tenants own. |
Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Server-Sent Events stream (text/event-stream). |
- |
| 422 | Validation Error | HTTPValidationError |
GET /api/v1/observability/audit
Audit trail snapshot (last N)
Newest API audit records, newest first, one row per request. The server owns the LogsQL query, so clients pass no query text. Records are deduplicated by their request_id - one record per API call. A tenant caller reads its own tenant’s records; and so does a platform caller by default; a platform caller widens with tenant_id or all_tenants=true. user_id reads one user’s records; the filters compose. only keeps just the records recorded under the named actions, and skip drops them - skip=list,view hides the read-only bulk, leaving the records that changed something, while only=create,delete reads the lifecycle alone. since and until bound the record time inclusively (RFC 3339; a value with no timezone reads as UTC); an inverted range matches nothing. A tenant-filtered read returns only records that carry a tenant, so during a rolling upgrade a node that has not taken the new configuration yet produces records no tenant-filtered read returns. That fails closed: it is a completeness limit, never a disclosure.
Authentication: required (PasswordLogin or BearerJWT).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
user_id |
query | string (uuid), nullable | no | Read one user’s audit records, including what they did while switched into another tenant. Omit it to read every record, whoever made the request. Applied upstream, so the limit is spent on the records asked for; composes with tenant_id. |
skip |
query | string, nullable | no | Comma-separated action names to drop, e.g. skip=list,view. Any action name is accepted; one matching no records excludes nothing. A record carrying no action survives every exclusion. |
only |
query | string, nullable | no | Comma-separated action names to keep, e.g. only=create,delete. Any action name is accepted; a record carrying no action is dropped. Composes with skip, which wins where they overlap. |
since |
query | string (date-time), nullable | no | Oldest record time to return (inclusive, RFC 3339; no timezone reads as UTC). |
until |
query | string (date-time), nullable | no | Newest record time to return (inclusive, RFC 3339; no timezone reads as UTC). |
limit |
query | integer | no | Maximum records to return; unbounded above, so ask only for what you will render. |
tenant_id |
query | string (uuid), nullable | no | Platform tenant only. Read the observability records of another tenant instead of your own. |
all_tenants |
query | boolean | no | Platform tenant only. Read the observability records of every tenant. Omitted, a read covers the caller’s own tenant alone - for the platform tenant that is the realm’s own telemetry, which carries the platform tenant id, and not the records tenants own. |
Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Successful Response | array |
| 422 | Validation Error | HTTPValidationError |
GET /api/v1/observability/audit/stream
Audit SSE stream (append deltas)
Deltas for the same feed the audit snapshot reads, taking the same tenant_id, user_id, skip and only filters - pass the values the snapshot was taken with, so the stream continues it. since/until have no stream counterpart: a stream starts at its baseline and appends from there. The stream ends itself with one terminal end event whose reason is credential_expired (refresh the token and reconnect), credential_revoked (sign in again), permission_revoked (the caller no longer holds the grant), lifetime_expired (the maximum stream lifetime; reconnect) or recheck_unavailable (the credential could not be re-checked several times running; reconnect with backoff). The checks run on stream ticks, which on a quiet feed happen every 20 seconds, so an end can lag by about that.
Authentication: required (PasswordLogin or BearerJWT).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
user_id |
query | string (uuid), nullable | no | Read one user’s audit records, including what they did while switched into another tenant. Omit it to read every record, whoever made the request. Applied upstream, so the limit is spent on the records asked for; composes with tenant_id. |
skip |
query | string, nullable | no | Comma-separated action names to drop, as on the audit snapshot. |
only |
query | string, nullable | no | Comma-separated action names to keep, as on the audit snapshot. |
tenant_id |
query | string (uuid), nullable | no | Platform tenant only. Read the observability records of another tenant instead of your own. |
all_tenants |
query | boolean | no | Platform tenant only. Read the observability records of every tenant. Omitted, a read covers the caller’s own tenant alone - for the platform tenant that is the realm’s own telemetry, which carries the platform tenant id, and not the records tenants own. |
Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Server-Sent Events stream (text/event-stream). |
- |
| 422 | Validation Error | HTTPValidationError |
GET /api/v1/observability/audit/{request_id}
Audit record with failure diagnostics
One audit record plus the correlated pan-api log detail for that request. diagnostics is parsed from the correlated ERROR line when one is found, and is always present, empty when no correlated lines exist. 404 for an unknown or aged-out id, and for a tenant caller also for a record of another tenant or one carrying no tenant, so a tenant cannot probe which ids exist. There is no all-tenants form here, because a request id already names one record: a platform caller reads another tenant’s record by naming it with tenant_id, and gets the same 404 otherwise.
Authentication: required (PasswordLogin or BearerJWT).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
request_id |
path | string | yes | The audit record id, as returned by the audit snapshot. |
tenant_id |
query | string (uuid), nullable | no | Platform tenant only. Read an audit record of another tenant instead of your own. There is no all-tenants form here: a request id already names exactly one record, so the only question is whose record the caller may reach. |
Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Successful Response | AuditRecordDetail |
| 422 | Validation Error | HTTPValidationError |
Response Body (200, AuditRecordDetail)
| Field | Type | Required | Default | Example | Description |
|---|---|---|---|---|---|
action |
string | no | (empty string) | Action performed on that resource, e.g. create. |
|
auth_token_id |
string | no | (empty string) | Id of the API token that authenticated the request; empty for a session. | |
client_ip |
string | no | (empty string) | Client IP address of the request. | |
diagnostics |
AuditDiagnostics object |
no | n/a | Correlated pan-api log detail for this request. | |
duration_ms |
number | no | 0 |
Request duration in milliseconds. | |
id |
string | yes | n/a | The record’s own identifier, which is its request_id. |
|
method |
string | yes | n/a | HTTP method of the audited request. | |
principal_id |
string | no | (empty string) | Who acted, as <tenant id>/<principal id>. Under a tenant switch this is the person who switched, in their own tenant. Empty when the request was not authenticated. |
|
principal_name |
string | no | (empty string) | <tenant name>/<name>, or the bare name when the tenant name was not known. |
|
principal_type |
string | no | (empty string) | user | service-account. |
|
query |
string | no | (empty string) | Query string of the audited request. | |
reason |
any, nullable | no | null |
Reason for a denied or failed result, if recorded. | |
request_body |
any, nullable | no | null |
Request body, secret-redacted at write time. | |
request_id |
string | yes | n/a | Audit request id; the identity used for deduplication. | |
resource |
string | no | (empty string) | Resource the request acted on, e.g. volumes. |
|
result |
string | yes | n/a | Outcome of the request: success | denied | failed. |
|
severity |
string | yes | n/a | Audit-significance severity: INFO | WARNING. |
|
status_code |
integer, nullable | no | null |
HTTP response status code. | |
target_id |
string | no | (empty string) | Id of the specific instance acted on, if any. | |
tenant_id |
string | no | (empty string) | The tenant the credential acts in: the caller’s own, or under a tenant switch the tenant switched into. It differs from the tenant half of principal_id only under a switch. |
|
time |
string | yes | n/a | When the request was audited (RFC 3339, from VictoriaLogs _time). |
|
url |
string | yes | n/a | URL path of the audited request. | |
user_agent |
string | no | (empty string) | Client User-Agent header of the request. |
GET /api/v1/observability/events
PanFS events snapshot (last N)
Newest PanFS management events, newest first, one row per event. The server owns the query, so clients pass no query text. Records are deduplicated by their PanFS event_id, which the feed repeats whenever the events file is re-read, or by their request_id when they come from the audit trail and carry no event_id. A tenant caller reads its own tenant’s events, and so does a platform caller by default; a platform caller widens to one tenant’s with tenant_id. A tenant-filtered read returns only records that carry a tenant, so during a rolling upgrade a node that has not taken the new configuration yet produces records no tenant-filtered read returns. That fails closed: it is a completeness limit, never a disclosure.
Authentication: required (PasswordLogin or BearerJWT).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
limit |
query | integer | no | |
tenant_id |
query | string (uuid), nullable | no | Platform tenant only. Read the observability records of another tenant instead of your own. |
all_tenants |
query | boolean | no | Platform tenant only. Read the observability records of every tenant. Omitted, a read covers the caller’s own tenant alone - for the platform tenant that is the realm’s own telemetry, which carries the platform tenant id, and not the records tenants own. |
Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Successful Response | array |
| 422 | Validation Error | HTTPValidationError |
GET /api/v1/observability/events/stream
PanFS events SSE stream (append deltas)
Deltas for the same feed the snapshot reads, taking the same tenant_id filter. Pass the value the snapshot was taken with, so the stream continues it. The stream ends itself with one terminal end event whose reason is credential_expired (refresh the token and reconnect), credential_revoked (sign in again), permission_revoked (the caller no longer holds the grant), lifetime_expired (the maximum stream lifetime; reconnect) or recheck_unavailable (the credential could not be re-checked several times running; reconnect with backoff). The checks run on stream ticks, which on a quiet feed happen every 20 seconds, so an end can lag by about that.
Authentication: required (PasswordLogin or BearerJWT).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
tenant_id |
query | string (uuid), nullable | no | Platform tenant only. Read the observability records of another tenant instead of your own. |
all_tenants |
query | boolean | no | Platform tenant only. Read the observability records of every tenant. Omitted, a read covers the caller’s own tenant alone - for the platform tenant that is the realm’s own telemetry, which carries the platform tenant id, and not the records tenants own. |
Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Server-Sent Events stream (text/event-stream). |
- |
| 422 | Validation Error | HTTPValidationError |
GET /api/v1/observability/logs
Logs snapshot (last N)
Newest log records matching a LogsQL query. A query the backend cannot parse answers 400 with Invalid query: and the backend’s own diagnosis, which never echoes the query itself. A backend that is down answers 503.
Authentication: required (PasswordLogin or BearerJWT).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
query |
query | string | no | |
limit |
query | integer | no |
Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Successful Response | - |
| 422 | Validation Error | HTTPValidationError |
GET /api/v1/observability/logs/stream
Logs SSE stream (append deltas)
Deltas for the same feed the logs snapshot reads. A query the backend cannot parse answers 400 with Invalid query: and the backend’s own diagnosis, which never echoes the query itself. A backend that is down answers 503. The stream ends itself with one terminal end event whose reason is credential_expired (refresh the token and reconnect), credential_revoked (sign in again), permission_revoked (the caller no longer holds the grant), lifetime_expired (the maximum stream lifetime; reconnect) or recheck_unavailable (the credential could not be re-checked several times running; reconnect with backoff). The checks run on stream ticks, which on a quiet feed happen every 20 seconds, so an end can lag by about that.
Authentication: required (PasswordLogin or BearerJWT).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
query |
query | string | no |
Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Server-Sent Events stream (text/event-stream). |
- |
| 422 | Validation Error | HTTPValidationError |
GET /api/v1/observability/logs/{logsql_endpoint}
VictoriaLogs /select/logsql proxy
Proxies to VictoriaLogs /select/logsql/{endpoint} on the local vlselect instance (co-located on the director node). All query parameters are forwarded as-is (GET only). See VictoriaLogs querying docs for supported endpoints and their parameters.
Authentication: required (PasswordLogin or BearerJWT).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
logsql_endpoint |
path | VictoriaLogsLogsqlEndpoint (query, tail, hits, facets, stats_query, stats_query_range, stream_ids, streams, stream_field_names, stream_field_values, field_names, field_values) |
yes | VictoriaLogs /select/logsql/{endpoint} path segment. See querying docs. |
query |
query | string, nullable | no | LogsQL selector (upstream query). Used by query, tail, hits, facets, stats_query, stats_query_range, stream_ids, streams, stream_field_names, stream_field_values, field_names, field_values. |
start |
query | string, nullable | no | Time range start (Victoria timestamp format). query (optional window), hits, facets, stats_query_range, stream_ids, streams, stream_field_names, stream_field_values, field_names, field_values. |
end |
query | string, nullable | no | Time range end, exclusive [start, end). Same endpoints as start where applicable. |
step |
query | string, nullable | no | Histogram bucket width. hits, stats_query_range (e.g. 1h). |
time |
query | string, nullable | no | Evaluation instant for stats_query (not used by stats_query_range range API). |
limit |
query | string, nullable | no | Cap rows or facet/field results. query, facets, stream_ids, streams, field_values, etc., per VictoriaLogs docs. |
offset |
query | string, nullable | no | Pagination skip on query; timezone offset on hits / stats_query_range; delivery delay on tail - same parameter name, semantics per upstream endpoint. |
timeout |
query | string, nullable | no | Max query duration (e.g. 4.2s). Primarily query. |
format |
query | string, nullable | no | Response shape, e.g. csv for query. |
start_offset |
query | string, nullable | no | Historical lookback before live tail (e.g. 1h). tail only. |
refresh_interval |
query | string, nullable | no | Poll interval for new logs. tail only. |
fields_limit |
query | string, nullable | no | Max number of fields groups on hits. |
ignore_pipes |
query | string, nullable | no | Set to 1 to ignore LogsQL pipes where upstream supports it (hits, facets, …). |
max_values_per_field |
query | string, nullable | no | facets: cap distinct values per field. |
max_value_len |
query | string, nullable | no | facets: max length of field values to consider. |
keep_const_fields |
query | string, nullable | no | facets: set to 1 to include fields with a single constant value. |
field |
query | string, nullable | no | Field name for grouping or lookup. hits (repeatable in raw URL), stream_field_values, field_values. For multiple field= values use repeated query keys (OpenAPI shows a single value). |
filter |
query | string, nullable | no | Substring filter on names or values. stream_field_names, stream_field_values, field_names, field_values. |
timezone |
query | string, nullable | no | Bucket alignment / timezone for hits and stats_query_range when documented upstream. |
extra_filters |
query | string, nullable | no | Extra LogsQL filters applied by VictoriaLogs to all HTTP querying APIs (JSON or filter text). |
extra_stream_filters |
query | string, nullable | no | Extra stream filters applied by VictoriaLogs (JSON or stream filter text). |
hidden_fields_filters |
query | string, nullable | no | Hide specific log fields during execution (VictoriaLogs “hidden fields” API). |
Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Successful response or upstream error body (status may vary). | - |
| 403 | Forbidden. Insufficient permissions. | - |
| 422 | Unknown logsql_endpoint path segment (not a VictoriaLogs logsql endpoint). |
- |
| 503 | Logs backend unreachable or request timed out. | - |
GET /api/v1/observability/metrics/query
Prometheus instant query proxy
Forwards only Prometheus instant-query parameters (query, time, timeout, step) to the Victoria Metrics /api/v1/query endpoint; other query parameter names are rejected. Upstream status, body, and end-to-end headers are forwarded; hop-by-hop headers (RFC 7230) and any names listed in Connection are stripped. Content-Length and Content-Encoding are omitted because the response body is returned already decoded. If Content-Type is missing on a 200 response, application/json is assumed; other status codes are not given a default type. Query parameters are validated with PrometheusInstantQueryParams; extra names yield 422.
Authentication: required (PasswordLogin or BearerJWT).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
query |
query | string, nullable | no | Prometheus expression (MetricsQL / PromQL-compatible instant query). Evaluated by the upstream VictoriaMetrics /api/v1/query endpoint. |
time |
query | string, nullable | no | Evaluation time as Unix timestamp or RFC3339 (Prometheus time). |
timeout |
query | string, nullable | no | Maximum evaluation timeout (Prometheus timeout), e.g. 5s. |
step |
query | string, nullable | no | Optional resolution step (VictoriaMetrics compatibility). Range queries use /api/v1/query_range on the upstream; this route proxies instant queries only. |
Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Successful query or upstream error body (status may vary). | - |
| 403 | Forbidden. Insufficient permissions. | - |
| 422 | Unknown query parameter names (not in the allow-listed instant-query model). | - |
| 503 | Metrics backend unreachable or request timed out. | - |
GET /api/v1/observability/metrics/query_range
Prometheus range query proxy
Forwards Prometheus range-query parameters (query, start, end, step, timeout) to the Victoria Metrics /api/v1/query_range endpoint; other query parameter names are rejected. Upstream status, body, and end-to-end headers are forwarded; hop-by-hop headers (RFC 7230) and any names listed in Connection are stripped. Content-Length and Content-Encoding are omitted because the response body is returned already decoded. If Content-Type is missing on a 200 response, application/json is assumed; other status codes are not given a default type. Query parameters are validated with PrometheusRangeQueryParams; extra names yield 422.
Authentication: required (PasswordLogin or BearerJWT).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
query |
query | string, nullable | no | Prometheus expression (MetricsQL / PromQL-compatible range query). Evaluated by the upstream VictoriaMetrics /api/v1/query_range endpoint. |
start |
query | string, nullable | no | Range start as Unix timestamp or RFC3339 (Prometheus start). |
end |
query | string, nullable | no | Range end as Unix timestamp or RFC3339 (Prometheus end). |
step |
query | string, nullable | no | Resolution step width as duration string (e.g. 60s, 5m) or float seconds. |
timeout |
query | string, nullable | no | Maximum evaluation timeout (Prometheus timeout), e.g. 5s. |
Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Successful query or upstream error body (status may vary). | - |
| 403 | Forbidden. Insufficient permissions. | - |
| 422 | Unknown query parameter names (not in the allow-listed range-query model). | - |
| 503 | Metrics backend unreachable or request timed out. | - |
GET /api/v1/observability/metrics/stream
Grouped metric SSE stream (append deltas for several keys)
Streams append deltas for every key in k over a single connection. Each event carries {"key": "<metric key>", "samples": [...]} so the client can route it to the right chart. Snapshots stay per-key (GET /observability/metrics/{key}).
Grouping keeps concurrent SSE connections low: browsers cap HTTP/1.1 connections at six per host, so one stream per chart would starve ordinary REST calls. Each key is still subscribed as its own broker feed, so clients requesting overlapping sets share one upstream poller per common key.
Authentication: required (PasswordLogin or BearerJWT).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
step |
query | integer | no | Resolution step (seconds); rounded up to a supported tier. |
k |
query | string | yes | Comma-separated metric keys, e.g. disk_throughput,disk_iops. |
Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Server-Sent Events stream (text/event-stream). |
- |
| 404 | One or more keys in k are not registered metrics. |
- |
| 422 | Empty k, or more keys than the per-stream cap. |
- |
GET /api/v1/observability/metrics/targets
List metrics targets
Returns a list of cluster nodes grouped by their type and state in Prometheus HTTP Service Discovery format. Each target is <node management IP>:<port> where the port is the observability agent (telegraf) Prometheus exporter serving /metrics on that node - 9273 by default. __meta_node_state is the PanFS node state, not the health of the scrape endpoint. Nodes that do not run the observability agent are still listed.
Authentication: required (PasswordLogin or BearerJWT).
Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Successful retrieval of metrics targets. | array |
| 400 | Bad request due to invalid parameters or service error. | - |
| 401 | Unauthorized. | - |
| 403 | Forbidden. Insufficient permissions. | - |
| 404 | Requested metrics target or resource not found. | - |
| 500 | Internal server error. | - |
GET /api/v1/observability/metrics/{key}
Metric snapshot (range window)
Range-query snapshot for a named metric over the lookback window.
Authentication: required (PasswordLogin or BearerJWT).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
key |
path | string | yes | |
window |
query | integer | no | Lookback window (seconds). |
step |
query | integer | no | Resolution step (seconds); rounded up to a supported tier. |
Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Successful Response | - |
| 422 | Validation Error | HTTPValidationError |
GET /api/v1/observability/metrics/{key}/stream
Metric SSE stream (append deltas)
SSE: append events as new metric samples arrive (deltas only).
Authentication: required (PasswordLogin or BearerJWT).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
key |
path | string | yes | |
step |
query | integer | no | Resolution step (seconds); rounded up to a supported tier. |
Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Server-Sent Events stream (text/event-stream). |
- |
| 422 | Validation Error | HTTPValidationError |
GET /api/v1/observability/storagesets/status
Storage-set alert status
Active-alert presence per storage set, resolved from one query over all PanFS alerts. Only sets carrying at least one critical or warning alert are listed - an absent set means no alerts. Callers combine this with the set’s node/spare counts to render its status badge.
Authentication: required (PasswordLogin or BearerJWT).
Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Successful Response | array |