-
Notifications
You must be signed in to change notification settings - Fork 51
Sfauvel/1990/refactor prometheus metrics using struct #1998
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
sfauvel
merged 23 commits into
main
from
sfauvel/1990/refactor_prometheus_metrics_using_struct
Oct 16, 2024
Merged
Changes from all commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
6bc994a
Example using a dedicated struct
sfauvel b5acdcd
Create a wrapper to prometheus Metrics
sfauvel 43f3113
Extract common part from the service file
sfauvel b1e0768
Remove metric constants
sfauvel 941db95
Create a `internal/mithril-metric` crate
sfauvel d38d52d
Move the export function to the `mothril-metric`
sfauvel 1f9da7b
Add a macro to generate the metric service
sfauvel 1c0127e
Rename the `MetricsServiceTrait` to `MetricsServiceExporter`
sfauvel fdd5adc
Fix clippy warning on documentation and cargo.toml sort
sfauvel 404bf86
Rename `MithrilMetric` to `MetricCollector` and `record`to `increment…
sfauvel 7280f43
Split `commons`file to `helper`and `metric` files
sfauvel 1e515d9
Remove feature='full'
sfauvel 141fb1e
Use a generic instead of `Epoch` for `MetricGauge`
sfauvel 6ce5a1e
Use a generic instead of `Epoch` for `MetricGauge`
sfauvel 53bcd54
Move code of the function `metrics_tools::export_metrics` into the `e…
sfauvel 51cad72
The name of the metric must be provided to the macro
sfauvel fa1e5fa
Upgrade versions and fix fmt check warning
sfauvel 97c31a5
Harmonize log messages
sfauvel 441dd74
Add missing documentation
sfauvel cedeb26
Add `Send + Sync` to MetricServiceExporter trait and remove `Copy` fr…
sfauvel 006e9ef
Fix code in documentation
sfauvel 78b1131
Remove obsolete functions from signer MetricsService
sfauvel 98038e1
chore: upgrade crate versions
sfauvel File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| [package] | ||
| name = "mithril-metric" | ||
| version = "0.1.0" | ||
| description = "Common tools to expose metrics." | ||
| authors = { workspace = true } | ||
| edition = { workspace = true } | ||
| homepage = { workspace = true } | ||
| license = { workspace = true } | ||
| repository = { workspace = true } | ||
|
|
||
| [lib] | ||
| crate-type = ["lib", "cdylib", "staticlib"] | ||
|
|
||
| [dependencies] | ||
| anyhow = "1.0.89" | ||
| axum = "0.7.7" | ||
| mithril-common = { path = "../../mithril-common" } | ||
| paste = "1.0.15" | ||
| prometheus = "0.13.4" | ||
| reqwest = { version = "0.12.8", features = ["json", "stream"] } | ||
| slog = { version = "2.7.0", features = [ | ||
| "max_level_trace", | ||
| "release_max_level_debug", | ||
| ] } | ||
| tokio = { version = "1.40.0" } | ||
|
|
||
| [dev-dependencies] | ||
| prometheus-parse = "0.2.5" | ||
| slog-async = "2.8.0" | ||
| slog-term = "2.9.1" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| .PHONY: all build test check doc | ||
|
|
||
| CARGO = cargo | ||
|
|
||
| all: test build | ||
|
|
||
| build: | ||
| ${CARGO} build --release | ||
|
|
||
| test: | ||
| ${CARGO} test | ||
|
|
||
| check: | ||
| ${CARGO} check --release --all-features --all-targets | ||
| ${CARGO} clippy --release --all-features --all-targets | ||
| ${CARGO} fmt --check | ||
|
|
||
| doc: | ||
| ${CARGO} doc --no-deps --open |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| # Mithril-metric | ||
|
|
||
| **This is a work in progress** 🛠 | ||
|
|
||
| This crate contains material to expose metrics. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,244 @@ | ||
| //! Helper to create a metric service. | ||
|
|
||
| /// Create a MetricService. | ||
| /// | ||
| /// To build the service you need to provide the structure name and a list of metrics. | ||
| /// Each metrics is defined by an attribute name, a type, a metric name and a help message. | ||
| /// | ||
| /// The attribute name will be used to create a getter method for the metric. | ||
| /// | ||
| /// Crate that use this macro should have `paste` as dependency. | ||
| /// | ||
| /// # Example of 'build_metrics_service' and metrics usage | ||
| /// | ||
| /// ``` | ||
| /// use slog::Logger; | ||
| /// use mithril_common::{entities::Epoch, StdResult}; | ||
| /// use mithril_metric::build_metrics_service; | ||
| /// use mithril_metric::{MetricCollector, MetricCounter, MetricGauge, MetricsServiceExporter}; | ||
| /// | ||
| /// build_metrics_service!( | ||
| /// MetricsService, | ||
| /// counter_example: MetricCounter( | ||
| /// "custom_counter_example_name", | ||
| /// "Example of a counter metric" | ||
| /// ), | ||
| /// gauge_example: MetricGauge( | ||
| /// "custom_gauge_example_name", | ||
| /// "Example of a gauge metric" | ||
| /// ) | ||
| /// ); | ||
| /// | ||
| /// let service = MetricsService::new(Logger::root(slog::Discard, slog::o!())).unwrap(); | ||
| /// service.get_counter_example().increment(); | ||
| /// service.get_gauge_example().record(Epoch(12)); | ||
| /// ``` | ||
| #[macro_export] | ||
| macro_rules! build_metrics_service { | ||
| ($service:ident, $($metric_attribute:ident:$metric_type:ident($name:literal, $help:literal)),*) => { | ||
| paste::item! { | ||
| /// Metrics service which is responsible for recording and exposing metrics. | ||
| pub struct $service { | ||
| registry: prometheus::Registry, | ||
| $( | ||
| $metric_attribute: $metric_type, | ||
| )* | ||
| } | ||
|
|
||
| impl $service { | ||
| /// Create a new MetricsService instance. | ||
| pub fn new(logger: slog::Logger) -> mithril_common::StdResult<Self> { | ||
|
|
||
| let registry = prometheus::Registry::new(); | ||
|
|
||
| $( | ||
| let $metric_attribute = $metric_type::new( | ||
| logger.clone(), | ||
| $name, | ||
| $help, | ||
| )?; | ||
| registry.register($metric_attribute.collector())?; | ||
| )* | ||
|
|
||
| Ok(Self { | ||
| registry, | ||
| $( | ||
| $metric_attribute, | ||
| )* | ||
| }) | ||
| } | ||
| $( | ||
| /// Get the `$metric_attribute` counter. | ||
| pub fn [<get_ $metric_attribute>](&self) -> &$metric_type { | ||
| &self.$metric_attribute | ||
| } | ||
| )* | ||
| } | ||
|
|
||
| impl MetricsServiceExporter for $service { | ||
| fn export_metrics(&self) -> mithril_common::StdResult<String> { | ||
| Ok(prometheus::TextEncoder::new().encode_to_string(&self.registry.gather())?) | ||
| } | ||
| } | ||
|
|
||
| } | ||
| }; | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| pub mod test_tools { | ||
| use std::{io, sync::Arc}; | ||
|
|
||
| use slog::{Drain, Logger}; | ||
| use slog_async::Async; | ||
| use slog_term::{CompactFormat, PlainDecorator}; | ||
| pub struct TestLogger; | ||
|
|
||
| impl TestLogger { | ||
| fn from_writer<W: io::Write + Send + 'static>(writer: W) -> Logger { | ||
| let decorator = PlainDecorator::new(writer); | ||
| let drain = CompactFormat::new(decorator).build().fuse(); | ||
| let drain = Async::new(drain).build().fuse(); | ||
| Logger::root(Arc::new(drain), slog::o!()) | ||
| } | ||
|
|
||
| pub fn stdout() -> Logger { | ||
| Self::from_writer(slog_term::TestStdoutWriter) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use std::collections::BTreeMap; | ||
|
|
||
| use crate::{MetricCollector, MetricCounter, MetricGauge, MetricsServiceExporter}; | ||
|
|
||
| use super::*; | ||
| use mithril_common::{entities::Epoch, StdResult}; | ||
| use prometheus::{Registry, TextEncoder}; | ||
| use prometheus_parse::Value; | ||
| use slog::Logger; | ||
| use test_tools::TestLogger; | ||
|
|
||
| fn parse_metrics(raw_metrics: &str) -> StdResult<BTreeMap<String, Value>> { | ||
| Ok( | ||
| prometheus_parse::Scrape::parse(raw_metrics.lines().map(|s| Ok(s.to_owned())))? | ||
| .samples | ||
| .into_iter() | ||
| .map(|s| (s.metric, s.value)) | ||
| .collect::<BTreeMap<_, _>>(), | ||
| ) | ||
| } | ||
|
|
||
| pub struct MetricsServiceExample { | ||
| registry: Registry, | ||
| counter_example: MetricCounter, | ||
| gauge_example: MetricGauge, | ||
| } | ||
|
|
||
| impl MetricsServiceExample { | ||
| pub fn new(logger: Logger) -> StdResult<Self> { | ||
| let registry = Registry::new(); | ||
|
|
||
| let counter_example = MetricCounter::new( | ||
| logger.clone(), | ||
| "counter_example", | ||
| "Example of a counter metric", | ||
| )?; | ||
| registry.register(counter_example.collector())?; | ||
|
|
||
| let gauge_example = | ||
| MetricGauge::new(logger.clone(), "gauge_example", "Example of a gauge metric")?; | ||
| registry.register(gauge_example.collector())?; | ||
|
|
||
| Ok(Self { | ||
| registry, | ||
| counter_example, | ||
| gauge_example, | ||
| }) | ||
| } | ||
|
|
||
| /// Get the `counter_example` counter. | ||
| pub fn get_counter_example(&self) -> &MetricCounter { | ||
| &self.counter_example | ||
| } | ||
|
|
||
| /// Get the `gauge_example` counter. | ||
| pub fn get_gauge_example(&self) -> &MetricGauge { | ||
| &self.gauge_example | ||
| } | ||
| } | ||
|
|
||
| impl MetricsServiceExporter for MetricsServiceExample { | ||
| fn export_metrics(&self) -> StdResult<String> { | ||
| Ok(TextEncoder::new().encode_to_string(&self.registry.gather())?) | ||
| } | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_service_creation() { | ||
| let service = MetricsServiceExample::new(TestLogger::stdout()).unwrap(); | ||
| service.get_counter_example().increment(); | ||
| service.get_counter_example().increment(); | ||
| service.get_gauge_example().record(Epoch(12)); | ||
|
|
||
| assert_eq!(2, service.get_counter_example().get()); | ||
| assert_eq!(Epoch(12), Epoch(service.get_gauge_example().get() as u64)); | ||
| } | ||
|
|
||
| build_metrics_service!( | ||
| MetricsServiceExampleBuildWithMacro, | ||
| counter_example: MetricCounter( | ||
| "custom_counter_example_name", | ||
| "Example of a counter metric" | ||
| ), | ||
| gauge_example: MetricGauge( | ||
| "custom_gauge_example_name", | ||
| "Example of a gauge metric" | ||
| ) | ||
| ); | ||
|
|
||
| #[test] | ||
| fn test_service_creation_using_build_metrics_service_macro() { | ||
| let service = MetricsServiceExampleBuildWithMacro::new(TestLogger::stdout()).unwrap(); | ||
| service.get_counter_example().increment(); | ||
| service.get_counter_example().increment(); | ||
| service.get_gauge_example().record(Epoch(12)); | ||
|
|
||
| assert_eq!(2, service.get_counter_example().get()); | ||
| assert_eq!(Epoch(12), Epoch(service.get_gauge_example().get() as u64)); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_build_metrics_service_named_metrics_with_attribute_name() { | ||
| let service = MetricsServiceExampleBuildWithMacro::new(TestLogger::stdout()).unwrap(); | ||
| assert_eq!( | ||
| "custom_counter_example_name", | ||
| service.get_counter_example().name() | ||
| ); | ||
| assert_eq!( | ||
| "custom_gauge_example_name", | ||
| service.get_gauge_example().name() | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_build_metrics_service_provide_a_functional_export_metrics_function() { | ||
| let service = MetricsServiceExampleBuildWithMacro::new(TestLogger::stdout()).unwrap(); | ||
|
|
||
| service.counter_example.increment(); | ||
| service.gauge_example.record(Epoch(12)); | ||
|
|
||
| let exported_metrics = service.export_metrics().unwrap(); | ||
|
|
||
| let parsed_metrics = parse_metrics(&exported_metrics).unwrap(); | ||
|
|
||
| let parsed_metrics_expected = BTreeMap::from([ | ||
| (service.counter_example.name(), Value::Counter(1.0)), | ||
| (service.gauge_example.name(), Value::Gauge(12.0)), | ||
| ]); | ||
|
|
||
| assert_eq!(parsed_metrics_expected, parsed_metrics); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| #![warn(missing_docs)] | ||
|
|
||
| //! metrics module. | ||
| //! This module contains the tools to create a metrics service and a metrics server. | ||
|
|
||
| pub mod helper; | ||
| pub mod metric; | ||
| mod server; | ||
|
|
||
| pub use metric::*; | ||
| pub use server::MetricsServer; | ||
| pub use server::MetricsServiceExporter; | ||
|
|
||
| #[cfg(test)] | ||
| pub use helper::test_tools::TestLogger; | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.