* fix(ci): share quick checks and lint workflows * fix(ci): install actionlint from its verified release * fix(ci): reject dependencies on required quick checks * feat(test): verify the E2E server build and source identity * test(e2e): register verified Darwin test membership * test(e2e): record verified Linux receipt test membership * test(e2e): record compiled Darwin receipt test membership * test(e2e): record compiled Linux receipt test membership * test(e2e): record Darwin e2e-full membership after merging main * fix(test): route scanner/heal evidence E2E runs through the verified server binary The evidence runners built rustfs with plain cargo and then ran e2e_test directly, which now fails without a run receipt. They build through scripts/e2e_binary.py and run the e2e_test invocations under e2e_binary.py run; the obsolete rustfs.features stamp is removed. * docs(e2e): run server-backed e2e commands through the verified binary wrapper * test(e2e): record Linux e2e-full membership from the branch CI listing
7.8 KiB
Storage metrics and observer selection
Use this when: configuring storage dashboards, migrating from replicated per-drive metrics, or diagnosing stale and missing disk observations.
Ownership and identity
The storage collectors have two distinct scopes:
| Scope | Metric families | Meaning |
|---|---|---|
collection_scope="local" |
rustfs_system_drive_*, rustfs_node_disk_* |
Detailed metrics for drives owned by the reporting node; includes configured offline slots. Drive counts are local counts. |
collection_scope="cluster" |
Cluster capacity, health, objects/buckets, erasure sets, and rustfs_cluster_drive_* |
One reporting node's observation of the whole cluster. |
observer identifies the reporting node. server identifies the drive owner;
drive alone is not a unique disk key. Keep rustfs_cluster_id, server, and
drive when grouping drives, and retain pool/set/drive indices for topology.
Drive counters also carry disk_id, so a physical replacement starts a separate
series. Unknown IDs are empty; rustfs_system_drive_info is emitted only when
the ID and topology are known. rustfs_system_drive_present preserves a configured
slot even when its disk is disconnected and its ID is unavailable.
Global drive metrics contain membership, runtime state, capacity and its source.
They do not duplicate remote API counters, error counters, or detailed I/O metrics.
An unreachable peer remains in the global inventory with the storage layer's
unknown/offline state and stale/missing capacity provenance. Missing capacity is
omitted from per-drive byte metrics. Cluster capacity may still include cached
observations: check capacity_stale_drives and capacity_missing_drives before
interpreting it. The existing cluster offline count includes drives not currently
observed online; use global per-drive runtime states to distinguish unknown.
Configure the pipeline
Give every node of a deployment the same stable, unique resource attribute:
OTEL_RESOURCE_ATTRIBUTES=rustfs.cluster.id=production-a
Keep existing resource attributes in the comma-separated value. The example
Compose files accept OTEL_RESOURCE_ATTRIBUTES and default to the development
cluster ID rustfs-dev; set a distinct ID for each deployment. The Collector's
resource_to_telemetry_conversion.enabled: true promotes it to
rustfs_cluster_id on scraped samples. Prometheus external_labels are not a
substitute: they do not add a cluster label to the local time series.
Use the shipped Collector configuration with send_timestamps: true and keep
Prometheus's default honor_timestamps: true. Load
.docker/observability/prometheus-rules/rustfs-storage.yml. Use one ingestion
route per deployment/node; scraping replicas of the same Collector requires a
separate HA deduplication policy. Keep node and Prometheus clocks synchronized.
Storage export callbacks read the current snapshot from memory and stop exporting
removed series. They do not probe storage or run peer RPCs. A successful collection
updates rustfs_storage_snapshot_last_success_timestamp_seconds; OTLP export
alone does not update this value. The source age limit is three times the larger
of the collection interval and RUSTFS_OBS_METER_INTERVAL (with its normal default).
rustfs_storage_snapshot_max_age_seconds exports the remaining validity budget
after deducting collection time from that limit. A slow RPC or usage read therefore
cannot make an already expired observation fresh by completing. An unavailable
source leaves the last success unchanged; a stalled collector stops exporting its
old snapshot after the source age limit.
A Collector may cache a point after RustFS stops exporting it. The recording rules
therefore require both a fresh source and a raw point timestamp at least as new as
that source's last successful collection. This also removes cached optional fields
and old disk IDs when a new snapshot no longer contains them. The timestamp check
must run on the raw selector, before label rewriting or recording the value;
otherwise PromQL can substitute the query evaluation time for the original sample
time. The comparison uses Prometheus millisecond precision so co-published points
are not excluded by submillisecond rounding. Current recording rules preserve the
original name in source_metric.
When adding a storage metric, add its matching rule to that file.
Collection and export are asynchronous, not a transaction across instruments. A snapshot published during an export can briefly mix adjacent observations or withhold a value; the next complete export/collection converges. Source failure visibility is bounded by the published maximum age plus the scrape/rule intervals. Cached capacity has its own observation age and state; a fresh collection does not make that capacity live. Bucket/object counts retain the existing scanner update delay.
Queries and dashboards
The bundled dashboard requires the storage recording rules. Select one Storage cluster and one fresh Cluster observer. Global panels use that observer's complete view. If it expires, the panel shows no data; select another observer. Do not sum replicated global totals or independently take maxima/minima of fields from different observers: they may describe different moments or partitions.
For example, select the observed cluster raw capacity:
rustfs:storage:current{source_metric="rustfs_cluster_capacity_raw_total_bytes",rustfs_cluster_id="production-a",collection_scope="cluster",observer="node1:9000"}
For a per-drive API rate, select owner-scoped counters before rate, then require
a current observation. Sum the resulting rates only across the desired drives:
sum by (rustfs_cluster_id, api) (
rate(rustfs_system_drive_api_calls_total{rustfs_cluster_id="production-a",collection_scope="local"}[5m])
and ignoring(source_metric)
rustfs:storage:current{source_metric="rustfs_system_drive_api_calls_total",rustfs_cluster_id="production-a",collection_scope="local"}
)
A missing series is unknown, not zero. Local totals include only fresh owners and can decrease when a node becomes unreachable. Use a selected global observer for configured topology and its explicit unknown/stale indicators.
Rolling upgrades and validation
Install the rules, resource attribute, and dashboard together. During rolling
upgrades, old releases have no collection_scope; strict new-scope selectors
exclude their replicated series. Local panels initially contain only upgraded
owners. Global panels become available when an upgraded observer publishes a fresh
snapshot. Historical old-label series remain available for retrospective queries.
Do not combine old and new counter histories into one rate.
Run the PromQL regression fixtures from .docker/observability/tests:
promtool test rules storage-rules.test.yml
The native pipeline test in
crates/e2e_test/src/storage_metric_ownership_test.rs requires pinned Collector,
Prometheus, previous-release RustFS, and current RustFS executables. Set
RUSTFS_OTELCOL_BINARY, RUSTFS_PROMETHEUS_BINARY, and
RUSTFS_METRICS_BASELINE_BINARY to those files; scripts/e2e_binary.py run
supplies the current RustFS as CARGO_BIN_EXE_rustfs.
Optionally set RUSTFS_METRICS_E2E_ARTIFACTS to retain logs and Prometheus data.
Run only this external-tool test:
python3 scripts/e2e_binary.py build
python3 scripts/e2e_binary.py run -- cargo test --locked -p e2e_test storage_metric_ownership_pipeline -- --ignored --nocapture
The test first reproduces duplicated global details with the previous release, rolls four nodes forward, verifies owner and disk identity, and checks node loss and recovery while the Collector remains running. SDK unit tests cover removed series, replacements, counter resets, invalid updates, and a stalled collection; PromQL fixtures cover shared paths across clusters/pools and stale cached fields.