@@ -4,7 +4,60 @@ use tokio::runtime::Handle;
44
55use super :: { RuntimeIntervals , RuntimeMetrics , RuntimeMonitor } ;
66
7- /// A reporter builder
7+ /// A builder for the a [`RuntimeMetricsReporter`] that wraps the RuntimeMonitor, periodically
8+ /// reporting RuntimeMetrics to any configured [metrics-rs] recorder.
9+ ///
10+ /// ### Published Metrics
11+ ///
12+ /// The published metrics are the fields of [RuntimeMetrics], but with the
13+ /// `tokio_` prefix added, for example, `tokio_workers_count`. If desired, you
14+ /// can use the [`with_metrics_transformer`] function to customize the metric names.
15+ ///
16+ /// ### Usage
17+ ///
18+ /// To upload metrics via [metrics-rs], you need to set up a reporter, which
19+ /// is actually what exports the metrics outside of the program. You must set
20+ /// up the reporter before you call [`describe_and_run`].
21+ ///
22+ /// You can find exporters within the [metrics-rs] docs. One such reporter
23+ /// is the [metrics_exporter_prometheus] reporter, which makes metrics visible
24+ /// through Prometheus.
25+ ///
26+ /// You can use it for exampleto export Prometheus metrics by listening on a local Unix socket
27+ /// called `prometheus.sock`, which you can access for debugging by
28+ /// `curl --unix-socket prometheus.sock localhost`, as follows:
29+ ///
30+ /// ```
31+ /// use std::time::Duration;
32+ ///
33+ /// #[tokio::main]
34+ /// async fn main() {
35+ /// metrics_exporter_prometheus::PrometheusBuilder::new()
36+ /// .with_http_uds_listener("prometheus.sock")
37+ /// .install()
38+ /// .unwrap();
39+ /// tokio::task::spawn(
40+ /// tokio_metrics::RuntimeMetricsReporterBuilder::default()
41+ /// // the default metric sampling interval is 30 seconds, which is
42+ /// // too long for quick tests, so have it be 1 second.
43+ /// .with_interval(std::time::Duration::from_secs(1))
44+ /// .describe_and_run(),
45+ /// );
46+ /// // Run some code
47+ /// tokio::task::spawn(async move {
48+ /// for _ in 0..1000 {
49+ /// tokio::time::sleep(Duration::from_millis(10)).await;
50+ /// }
51+ /// })
52+ /// .await
53+ /// .unwrap();
54+ /// }
55+ /// ```
56+ ///
57+ /// [`describe_and_run`]: RuntimeMetricsReporterBuilder::describe_and_run
58+ /// [`with_metrics_transformer`]: RuntimeMetricsReporterBuilder::with_metrics_transformer
59+ /// [metrics-rs]: metrics
60+ /// [metrics_exporter_prometheus]: https://docs.rs/metrics_exporter_prometheus
861pub struct RuntimeMetricsReporterBuilder {
962 interval : Duration ,
1063 metrics_transformer : Box < dyn FnMut ( & ' static str ) -> metrics:: Key + Send > ,
@@ -19,29 +72,108 @@ impl fmt::Debug for RuntimeMetricsReporterBuilder {
1972 }
2073}
2174
75+ const DEFAULT_METRIC_SAMPLING_INTERVAL : Duration = Duration :: from_secs ( 30 ) ;
2276
2377impl Default for RuntimeMetricsReporterBuilder {
2478 fn default ( ) -> Self {
2579 RuntimeMetricsReporterBuilder {
26- interval : Duration :: from_secs ( 30 ) ,
80+ interval : DEFAULT_METRIC_SAMPLING_INTERVAL ,
2781 metrics_transformer : Box :: new ( metrics:: Key :: from_static_name) ,
2882 }
2983 }
3084}
3185
3286impl RuntimeMetricsReporterBuilder {
33- /// Set the interval
87+ /// Set the metric sampling interval, default: 30 seconds.
88+ ///
89+ /// Note that this is the interval on which metrics are *sampled* from
90+ /// the Tokio runtime and then set on the [metrics-rs] reporter. Uploading the
91+ /// metrics upstream is controlled by the reporter set up in the
92+ /// application, and is normally controlled by a different period.
93+ ///
94+ /// For example, if metrics are exported via Prometheus, that
95+ /// normally operates at a pull-based fashion, and the actual collection
96+ /// period is controlled by the Prometheus server, which periodically polls the
97+ /// application's Prometheus exporter to get the latest value of the metrics.
98+ ///
99+ /// [metrics-rs]: metrics
34100 pub fn with_interval ( mut self , interval : Duration ) -> Self {
35101 self . interval = interval;
36102 self
37103 }
38104
39- /// Build the reporter
105+ /// Set a custom "metrics transformer", which is used during `build` to transform the metric
106+ /// names into metric keys, for example to add dimensions. The string metric names used by this reporter
107+ /// all start with `tokio_`. The default transformer is just [`metrics::Key::from_static_name`]
108+ ///
109+ /// For example, to attach a dimension named "application" with value "my_app", and to replace
110+ /// `tokio_` with `my_app_`
111+ /// ```
112+ /// # use metrics::Key;
113+ ///
114+ /// #[tokio::main]
115+ /// async fn main() {
116+ /// metrics_exporter_prometheus::PrometheusBuilder::new()
117+ /// .with_http_uds_listener("prometheus.sock")
118+ /// .install()
119+ /// .unwrap();
120+ /// tokio::task::spawn(
121+ /// tokio_metrics::RuntimeMetricsReporterBuilder::default().with_metrics_transformer(|name| {
122+ /// let name = name.replacen("tokio_", "my_app_", 1);
123+ /// Key::from_parts(name, &[("application", "my_app")])
124+ /// })
125+ /// .describe_and_run()
126+ /// );
127+ /// }
128+ /// ```
129+ pub fn with_metrics_transformer ( mut self , transformer : impl FnMut ( & ' static str ) -> metrics:: Key + Send + ' static ) -> Self {
130+ self . metrics_transformer = Box :: new ( transformer) ;
131+ self
132+ }
133+
134+ /// Build the [`RuntimeMetricsReporter`] for the current Tokio runtime. This function will capture
135+ /// the [`Counter`]s, [`Gauge`]s and [`Histogram`]s from the current [metrics-rs] reporter,
136+ /// so if you are using [`with_local_recorder`], you should wrap this function and [`describe`] with it.
137+ ///
138+ /// For example:
139+ /// ```
140+ /// # use std::sync::Arc;
141+ ///
142+ /// #[tokio::main]
143+ /// async fn main() {
144+ /// let builder = tokio_metrics::RuntimeMetricsReporterBuilder::default();
145+ /// let recorder = Arc::new(metrics_util::debugging::DebuggingRecorder::new());
146+ /// let metrics_reporter = metrics::with_local_recorder(&recorder, || builder.describe().build());
147+ ///
148+ /// // no need to wrap `run()`, since the metrics are already captured
149+ /// tokio::task::spawn(metrics_reporter.run());
150+ /// }
151+ /// ```
152+ ///
153+ ///
154+ /// [`Counter`]: metrics::Counter
155+ /// [`Gauge`]: metrics::Counter
156+ /// [`Histogram`]: metrics::Counter
157+ /// [metrics-rs]: metrics
158+ /// [`with_local_recorder`]: metrics::with_local_recorder
159+ /// [`describe`]: Self::describe
160+ #[ must_use = "reporter does nothing unless run" ]
40161 pub fn build ( self ) -> RuntimeMetricsReporter {
41162 self . build_with_monitor ( RuntimeMonitor :: new ( & Handle :: current ( ) ) )
42163 }
43164
44- /// Build the reporter with a specific [`RuntimeMonitor`]
165+ /// Build the [`RuntimeMetricsReporter`] with a specific [`RuntimeMonitor`]. This function will capture
166+ /// the [`Counter`]s, [`Gauge`]s and [`Histogram`]s from the current [metrics-rs] reporter,
167+ /// so if you are using [`with_local_recorder`], you should wrap this function and [`describe`]
168+ /// with it.
169+ ///
170+ /// [`Counter`]: metrics::Counter
171+ /// [`Gauge`]: metrics::Counter
172+ /// [`Histogram`]: metrics::Counter
173+ /// [metrics-rs]: metrics
174+ /// [`with_local_recorder`]: metrics::with_local_recorder
175+ /// [`describe`]: Self::describe
176+ #[ must_use = "reporter does nothing unless run" ]
45177 pub fn build_with_monitor ( mut self , monitor : RuntimeMonitor ) -> RuntimeMetricsReporter {
46178 RuntimeMetricsReporter {
47179 interval : self . interval ,
@@ -50,24 +182,60 @@ impl RuntimeMetricsReporterBuilder {
50182 }
51183 }
52184
53- /// Describe the metrics. You might not want to run this more than once
185+ /// Call [`describe_counter`] etc. to describe the emitted metrics.
186+ ///
187+ /// Describing metrics makes the reporter attach descriptions and units to them,
188+ /// which makes them easier to use. However, some reporters don't support
189+ /// describing the same metric name more than once, so it is generally a good
190+ /// idea to only call this function once per metric reporter.
191+ ///
192+ /// [`describe_counter`]: metrics::describe_counter
193+ /// [metrics-rs]: metrics
54194 pub fn describe ( mut self ) -> Self {
55195 RuntimeMetricRefs :: describe ( & mut self . metrics_transformer ) ;
56196 self
57197 }
58198
59- /// Run the reporter, describing the metrics beforehand
199+ /// Runs the reporter (within the returned future), [describing] the metrics beforehand.
200+ ///
201+ /// Describing metrics makes the reporter attach descriptions and units to them,
202+ /// which makes them easier to use. However, some reporters don't support
203+ /// describing the same metric name more than once. If you are emitting multiple
204+ /// metrics via a single reporter, try to call [`describe`] once and [`run`] for each
205+ /// runtime metrics reporter.
206+ ///
207+ /// ### Working with a custom reporter
208+ ///
209+ /// If you want to set a local metrics reporter, you shouldn't be calling this method,
210+ /// but you should instead call `.describe().build()` within [`with_local_recorder`] and then
211+ /// call `run` (see the docs on [`build`]).
212+ ///
213+ /// [describing]: Self::describe
214+ /// [`describe`]: Self::describe
215+ /// [`build`]: Self::build.
216+ /// [`run`]: RuntimeMetricsReporter::run
217+ /// [`with_local_recorder`]: metrics::with_local_recorder
60218 pub async fn describe_and_run ( self ) {
61219 self . describe ( ) . build ( ) . run ( ) . await ;
62220 }
63221
64- /// Run the reporter, not describing the metrics beforehand
222+ /// Runs the reporter (within the returned future), not describing the metrics beforehand.
223+ ///
224+ /// ### Working with a custom reporter
225+ ///
226+ /// If you want to set a local metrics reporter, you shouldn't be calling this method,
227+ /// but you should instead call `.describe().build()` within [`with_local_recorder`] and then
228+ /// call [`run`] (see the docs on [`build`]).
229+ ///
230+ /// [`build`]: Self::build
231+ /// [`run`]: RuntimeMetricsReporter::run
232+ /// [`with_local_recorder`]: metrics::with_local_recorder
65233 pub async fn run_without_describing ( self ) {
66234 self . build ( ) . run ( ) . await ;
67235 }
68236}
69237
70- /// A reporter
238+ /// Collects metrics from a Tokio runtime and uploads them to [metrics_rs](metrics).
71239pub struct RuntimeMetricsReporter {
72240 interval : Duration ,
73241 intervals : RuntimeIntervals ,
@@ -270,7 +438,7 @@ impl MyMetricOp for (&metrics::Histogram, Vec<u64>) {
270438 let range = tokio. poll_time_histogram_bucket_range ( i) ;
271439 if * bucket > 0 {
272440 // emit using range.start to avoid very large numbers for open bucket
273- // FIXME: do we want to do something else here
441+ // FIXME: do we want to do something else here?
274442 self . 0 . record_many ( range. start . as_micros ( ) as f64 , * bucket as usize ) ;
275443 }
276444 }
@@ -288,13 +456,13 @@ impl fmt::Debug for RuntimeMetricsReporter {
288456
289457impl RuntimeMetricsReporter
290458{
291- /// Collect and publish metrics once
459+ /// Collect and publish metrics once to the configured [metrics_rs](metrics) reporter.
292460 pub fn run_once ( & mut self ) {
293461 let metrics = self . intervals . next ( ) . expect ( "RuntimeIntervals::next never returns None" ) ;
294462 self . emitter . emit ( metrics, & self . intervals . runtime ) ;
295463 }
296464
297- /// Collect and run metrics.
465+ /// Collect and publish metrics periodically to the configured [metrics_rs](metrics) reporter .
298466 ///
299467 /// You probably want to run this within its own task (using [`tokio::task::spawn`])
300468 pub async fn run ( mut self ) {
0 commit comments