Skip to content

feat(metrics): add Prometheus text format deserialization#1729

Merged
josecelano merged 29 commits into
torrust:developfrom
josecelano:1582-add-prometheus-deserialization-metrics
May 5, 2026
Merged

feat(metrics): add Prometheus text format deserialization#1729
josecelano merged 29 commits into
torrust:developfrom
josecelano:1582-add-prometheus-deserialization-metrics

Conversation

@josecelano
Copy link
Copy Markdown
Member

Summary

Adds deserialization from Prometheus exposition text format to MetricCollection, completing the serialization ↔ deserialization symmetry alongside the existing JSON and to_prometheus() paths.

Closes #1582


What changed

packages/metrics/src/prometheus.rs

  • New PrometheusDeserializable trait (mirrors PrometheusSerializable)
  • New PrometheusDeserializationError enum with fine-grained variants:
    • ParseError — syntax error in the input text
    • UnsupportedType — known but not yet supported type (Histogram, Summary, …)
    • UnknownType — unrecognised metric type
    • ValueMismatch — value type does not match the declared metric type
    • UnknownValuePrometheusValue::Unknown sample (no silent zero-returns)
    • LabelConversion — label name/value conversion failure
    • CollectionError — structural error assembling the MetricCollection

packages/metrics/src/label/set.rs

  • TryFrom<openmetrics_parser::LabelSet<'_>> for LabelSet — replaces ad-hoc inline label conversion

packages/metrics/src/metric_collection/mod.rs

  • Private parse_prometheus_timestamp(t: f64, fallback) -> DurationSinceUnixEpoch — eliminates the duplicated timestamp-parsing block
  • impl PrometheusDeserializable for MetricCollection — parses Counter and Gauge families; uses openmetrics-parser 0.4.4

packages/metrics/Cargo.toml

  • Added openmetrics-parser = "0.4.4"

Tests

  • Timestamp helper: 8 edge-case unit tests (negative, NaN, ±Inf, nanosecond-boundary overflow, zero)
  • TryFrom for LabelSet: 3 unit tests (empty conversion, round-trip, empty-name error)
  • MetricCollection: round-trip serialize → deserialize, error-path tests for unsupported and malformed input
  • 222 tests pass, 0 failed

Prior art

This is a clean re-implementation based on PR #1611 by @naoNao89, incorporating all maintainer feedback that was pending on that PR. @naoNao89 is preserved as the commit author.

josecelano and others added 2 commits May 4, 2026 16:57
- Add `PrometheusDeserializable` trait to `prometheus.rs` alongside
  the existing `PrometheusSerializable` trait
- Add `PrometheusDeserializationError` with fine-grained variants:
  ParseError, UnsupportedType, UnknownType, ValueMismatch,
  UnknownValue, LabelConversion, CollectionError
- Implement `TryFrom<openmetrics_parser::LabelSet>` for `LabelSet`
- Extract private `parse_prometheus_timestamp` helper to eliminate
  duplicated timestamp-parsing logic
- Implement `PrometheusDeserializable for MetricCollection` using
  `openmetrics-parser` 0.4.4; no silent zero-returns for unknown or
  mismatched values
- Add unit tests for the timestamp helper, label conversion, and
  full round-trip serialize → deserialize

Closes torrust#1582
Co-authored-by: josecelano <josecelano@users.noreply.github.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds Prometheus exposition text-format deserialization to the packages/metrics crate by introducing a PrometheusDeserializable trait and implementing it for MetricCollection (currently Counter + Gauge families), aiming for symmetry with existing to_prometheus() serialization.

Changes:

  • Added PrometheusDeserializable + PrometheusDeserializationError to model Prometheus parsing and error reporting.
  • Implemented Prometheus text parsing for MetricCollection using openmetrics-parser and added unit tests (timestamps, label conversion, round-trip, and error paths).
  • Introduced TryFrom<openmetrics_parser::LabelSet> for LabelSet and added openmetrics-parser = "0.4.4" dependency.

Reviewed changes

Copilot reviewed 6 out of 7 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
project-words.txt Adds new spelling whitelist entries used by the implementation/docs.
packages/metrics/src/prometheus.rs Introduces deserialization trait and structured error type for Prometheus parsing.
packages/metrics/src/metric_collection/mod.rs Implements MetricCollection::from_prometheus, adds timestamp parsing helper + tests.
packages/metrics/src/label/set.rs Adds TryFrom conversion from openmetrics_parser::LabelSet + tests.
packages/metrics/Cargo.toml Adds the openmetrics-parser dependency.
docs/issues/1582-add-prometheus-deserialization-metrics/ISSUE.md Adds design/spec documentation for the feature.
Cargo.lock Locks new transitive dependencies for openmetrics-parser.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/metrics/src/metric_collection/mod.rs Outdated
Comment thread packages/metrics/src/metric_collection/mod.rs Outdated
Comment thread packages/metrics/src/metric_collection/mod.rs Outdated
Comment thread packages/metrics/src/metric_collection/mod.rs Outdated
Comment thread packages/metrics/src/label/set.rs Outdated
@josecelano josecelano requested a review from Copilot May 4, 2026 16:48
@josecelano josecelano self-assigned this May 4, 2026
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 7 changed files in this pull request and generated 6 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/metrics/src/metric_collection/mod.rs Outdated
Comment thread packages/metrics/src/metric_collection/mod.rs Outdated
Comment thread packages/metrics/src/metric_collection/mod.rs Outdated
Comment thread project-words.txt
Comment thread project-words.txt
Comment thread packages/metrics/src/metric_collection/mod.rs Outdated
@codecov
Copy link
Copy Markdown

codecov Bot commented May 4, 2026

Codecov Report

❌ Patch coverage is 81.24352% with 181 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.69%. Comparing base (0c9d757) to head (19f98d0).
⚠️ Report is 3 commits behind head on develop.

Files with missing lines Patch % Lines
packages/metrics/src/metric_collection/serde.rs 56.85% 124 Missing and 5 partials ⚠️
...ckages/metrics/src/metric_collection/prometheus.rs 86.89% 28 Missing and 18 partials ⚠️
packages/metrics/src/prometheus.rs 50.00% 5 Missing ⚠️
packages/metrics/src/metric_collection/error.rs 97.22% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #1729      +/-   ##
===========================================
+ Coverage    79.66%   79.69%   +0.02%     
===========================================
  Files          354      359       +5     
  Lines        25597    26411     +814     
  Branches     25597    26411     +814     
===========================================
+ Hits         20392    21047     +655     
- Misses        4969     5117     +148     
- Partials       236      247      +11     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

josecelano added 6 commits May 4, 2026 21:04
- Tighten nanosecond overflow assertion from approx epsilon 1e-3 to
  exact equality, catching the 'nanos - 1_000_000_000 → /' mutant.
- Add it_should_accept_a_counter_value_that_is_a_whole_number_float to
  exercise the float-counter match guard (value.is_finite() && >= 0.0
  && fract() == 0.0 && < MAX), killing four related guard mutations.
- Add it_should_reject_a_float_counter_value_equal_to_first_unrepresentable_u64
  to verify the strict-less-than bound (< not <=) for 2^64.
- Exclude mutants.out / mutants.out.old from cspell to prevent 'xyzzy'
  false-positive failures from cargo-mutants artefacts.

Result: 35 mutants tested — 24 caught, 11 unviable, 0 survived.
josecelano added 11 commits May 5, 2026 08:45
…eline

Extract family-iteration logic into exposition_to_metric_collection helper
function and refactor from_prometheus to coordinate three distinct stages:

- Stage 1 (Normalize): ensure_trailing_newline
- Stage 2 (Parse): openmetrics_parser::prometheus::parse_prometheus
- Stage 3 (Convert): exposition_to_metric_collection

The conversion stage encapsulates the dispatch logic for Counter/Gauge types,
improving testability and extensibility. Future metric type support only needs
to modify the conversion stage.

All 253 tests pass. No behavior changes.
@josecelano josecelano marked this pull request as ready for review May 5, 2026 09:52
@josecelano
Copy link
Copy Markdown
Member Author

ACK 19f98d0

@josecelano josecelano merged commit 0e71e40 into torrust:develop May 5, 2026
26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add deserialization from Prometheus text format in metrics package

3 participants