GithubHelp home page GithubHelp logo

sinkingpoint / openmetrics-parser Goto Github PK

View Code? Open in Web Editor NEW
2.0 2.0 4.0 84 KB

A OpenMetrics/Prometheus text exposition format parser in Rust

License: GNU Lesser General Public License v3.0

Rust 100.00%

openmetrics-parser's Introduction

OpenMetrics-Parser

This is my implementation of a parser for OpenMetrics and Prometheus text exposition formats. It conforms to all the OpenMetrics test cases, and I'm constantly changing the API to be cleaner and better suit my own needs

openmetrics-parser's People

Contributors

sinkingpoint avatar antoinedeschenes avatar samuelallan72 avatar

Stargazers

MOZGIII avatar Micha Hernandez van Leuffen avatar

Watchers

 avatar  avatar

openmetrics-parser's Issues

LabelSets do not need to be complete, only unique.

I have been trying to parse the output of the Prometheus Node Exporter, which uses the Prometheus text exposition format. I created a blanket test case by using wget http://localhost:9100/metrics. (Attached)
After fixing a couple of minor problems (#3, #4) I hit a bigger issue:

---- prometheus::tests::test_prometheus_parser stdout ----
thread 'prometheus::tests::test_prometheus_parser' panicked at src/prometheus/tests.rs:13:13:
failed to parse ./src/prometheus/testdata/node_exporter-v1.7.0.txt: Metrics in family have different label sets: Some(LabelNames { names: ["power_supply", "type"], metric_type: Gauge }) ["ucsi-source-psy-USBC000:001", "USB", "[C] PD PD_PPS"]

This was due to the following metric family:

# HELP node_power_supply_info info of /sys/class/power_supply/<power_supply>.
# TYPE node_power_supply_info gauge
node_power_supply_info{power_supply="AC",type="Mains"} 1
node_power_supply_info{power_supply="ucsi-source-psy-USBC000:001",type="USB",usb_type="[C] PD PD_PPS"} 1
node_power_supply_info{power_supply="ucsi-source-psy-USBC000:002",type="USB",usb_type="C [PD] PD_PPS"} 1
node_power_supply_info{capacity_level="Full",manufacturer="BYD",model_name="DELL FP86V0C",power_supply="BAT0",serial_number="879",status="Full",technology="Li-poly",type="Battery"} 1

This exposed a problem with the openmetrics-parser data model. It currently assumes that every metric in a family has the same set of labels, but this is not always the case. The OpenMetric specification says:

Metrics are defined by a unique LabelSet within a MetricFamily. [...] Metrics with the same name for a given MetricFamily SHOULD have the same set of label names in their LabelSet.

Note the "SHOULD" and not "MUST". It is a best practice, but not a requirement. Prometheus will let you have a different set of labels for metrics in the same family as long as all the label sets are unique.

Changing the data model to support this would be a big and breaking change. I would recommend changing LabelSet from

pub struct LabelSet<'a> {
    label_names: Arc<Vec<String>>,
    label_values: &'a [String],
}

to something like:

pub struct LabelSet {
  labels: HashSet<Arc<String>, String>,
}

I'm happy to help do that, but before I spend any time on this I'd like to make sure this is something you would be willing to do.

Parse even if "should" rules are broken - add a 'lenient' mode or a linter?

It seems in general, the parsers throw errors on metrics which violate “should” rules. See for example:

These ideally should succeed in parsing, because they are valid, and there are plenty of established exporters in the wild that have valid output although they break plenty of "should" rules. I had some ideas to solve this in general, to ensure this information about "should" rules / recommendations are kept, but to enable parsing otherwise valid metrics outputs:

  • log warnings
  • add a linter in addition to the existing parsers
  • a strict and lenient parsing mode
    • strict would only succeed parsing if all "should" rules / recommendations are followed, as current behaviour)
    • lenient would succeed parsing as long as it's valid

Counters missing a _total suffix result in error

A counter should have a _total suffix, but this isn't a hard requirement as far as I can tell. https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels

parse_prometheus fails to parse this, when it should:

# HELP lxd_uptime_seconds The daemon uptime in seconds.
# TYPE lxd_uptime_seconds counter
# EOF
Err(
    InvalidMetric(
        "Counters should have a _total suffix. Got lxd_uptime_seconds",
    ),
)

These may be useful to have as warnings? I wonder if it would be helpful to have two modes for the parser - ie. a parser and a linter:

  1. parse based on the spec requirements ("must") rules.
  2. lint based on all the recommended ("should") rules.

parse_prometheus fails with uppercase in metric name

These metrics fail parsing:

# HELP lxd_memory_Active_anon_bytes The amount of anonymous memory on active LRU list.
# TYPE lxd_memory_Active_anon_bytes gauge
lxd_memory_Active_anon_bytes{name="juju-1e65f3-0",project="default",type="container"} 3.06556928e+08
lxd_memory_Active_anon_bytes{name="juju-3974d6-0",project="default",type="container"} 7.3261056e+07
# EOF

with this error:

Err(
    ParseError(
        " --> 2:3\n  |\n2 | # HELP lxd_memory_Active_anon_bytes The amount of anonymous memory on active LRU list.\n  |   ^---\n  |\n  = expected kw_type",
    ),
)

To reproduce:

use openmetrics_parser::prometheus::parse_prometheus;

fn main() {
    let metrics = parse_prometheus(r#"
# HELP lxd_memory_Active_anon_bytes The amount of anonymous memory on active LRU list.
# TYPE lxd_memory_Active_anon_bytes gauge
lxd_memory_Active_anon_bytes{name="juju-1e65f3-0",project="default",type="container"} 3.06556928e+08
lxd_memory_Active_anon_bytes{name="juju-3974d6-0",project="default",type="container"} 7.3261056e+07
# EOF"#);
    println!("{:#?}", &metrics);
}

Panic when a counter has an starting value of integer and then a float is pushed

Hi!

First of all thanks for this project, specially gravel-gateway. I think that currently is the most usable/helpful Prometheus gateway out there.

Context

I encontered this problem while using gravel-gateway. However seems that the problem is related with the usage of this library, so I open the issue here, if you prefer to have the issue on the other repo, I will move it happily.

Problem

The problem itself is when I push a counter metric and randomly crashes the gateway with this error:

thread 'tokio-runtime-worker' panicked at 'called `Option::unwrap()` on a `None` value', /usr/local/cargo/registry/src/github.com-1ecc6299db9ec823/openmetrics-parser-0.4.2/src/public/model.rs:900:50

Findings

After reading some code (I don't know Rust) and making some tests I found that this happens when a counter has an integer value. E.g:

example_test_counter_total{foo="bar"} 0

And then a float is pushed. e.g:

example_test_counter_total{foo="bar"} 0.01

After this second push, the gateway crashes, but, if the metric starts with a float from the begining, there are no crashes, however is very common to initialize a metric to 0, leading to this use case.

Extra info

More information that could help:

  • Gravel gateway version: v1.6.2
  • Prometheus client: github.com/prometheus/client_golang v1.17.0
  • Using the official process collector leads to this crashes on process_cpu_seconds_total counter metric.

Error on parsing metrics with not-recommended naming

See original bug report: canonical/lxd#13194 . Turns out that the metrics there were valid, but not following a recommended ("should") rule (avoid _total postfix in names):

# HELP lxd_cpu_seconds_total The total number of CPU time used in seconds.
# TYPE lxd_cpu_seconds_total counter
lxd_cpu_seconds_total{cpu="0",mode="system",name="juju-3974d6-0",project="default",type="container"} 96.453461
lxd_cpu_seconds_total{cpu="0",mode="user",name="juju-3974d6-0",project="default",type="container"} 165.58365
lxd_cpu_seconds_total{cpu="0",mode="system",name="juju-1e65f3-0",project="default",type="container"} 1044.756945
lxd_cpu_seconds_total{cpu="0",mode="user",name="juju-1e65f3-0",project="default",type="container"} 1907.128387

parse_openmetrics fails to parse, with

    InvalidMetric(
        "Invalid Name in metric family: lxd_cpu_seconds != lxd_cpu_seconds_total",
    ),

It succeeds parsing this with parse_prometheus though.

Tests are failing on master

$ cargo test
   Compiling openmetrics-parser v0.4.4 (.../openmetrics-parser)
warning: unused import: `crate::prometheus::parse_prometheus`-parser, openmetrics-parser(test)
 --> src/public/tests.rs:1:5
  |
1 | use crate::prometheus::parse_prometheus;
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: `#[warn(unused_imports)]` on by default

warning: `openmetrics-parser` (lib) generated 1 warning (run `cargo fix --lib -p openmetrics-parser` to apply 1 suggestion)
    Finished test [unoptimized + debuginfo] target(s) in 0.70sparser(test)
     Running unittests src/lib.rs (target/debug/deps/openmetrics_parser-4fbb317101af00fa)

running 5 tests
test openmetrics::tests::run_openmetrics_validation ... FAILED
test public::tests::test_label_sets ... ok
test public::tests::test_metric_number_operations ... ok
test prometheus::tests::test_prometheus_parser ... ok
test public::tests::test_render ... ok

failures:

---- openmetrics::tests::run_openmetrics_validation stdout ----
thread 'openmetrics::tests::run_openmetrics_validation' panicked at src/openmetrics/tests.rs:30:5:
assertion failed: tests.is_ok()
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
    openmetrics::tests::run_openmetrics_validation

test result: FAILED. 4 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

error: test failed, to rerun pass `--lib`

Looks like some paths were moved around and the test wasn't updated - ./OpenMetrics/tests/testdata/parsers doesn't exist any more.

Would you be interested in tests in CI?

Error parsing families with zero metrics

Thanks for creating this parser, it's very useful! I've noticed that it gives an error for metric families that contain no metrics:

fn main() {
    openmetrics_parser::openmetrics::parse_openmetrics(
        "# HELP foo My first counter.
# TYPE foo counter
# HELP bar My second counter.
# TYPE bar counter
bar_total 0
# EOF",
    )
    .unwrap();
}

// thread 'main' panicked at src/main.rs:10:6:
// called `Result::unwrap()` on an `Err` value: InvalidMetric("Invalid metric name in family. Family name is foo, but got a metric called bar")
// note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

However, according to the specification, I think that families with zero metrics should be allowed:

A MetricFamily MAY have zero or more Metrics.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.