Skip to content

Commit e3bdc10

Browse files
author
Ariel Ben-Yehuda
committed
add docs
1 parent d679868 commit e3bdc10

File tree

5 files changed

+236
-20
lines changed

5 files changed

+236
-20
lines changed

Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ keywords = ["async", "futures", "metrics", "debugging"]
1818
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tokio_unstable)'] }
1919

2020
[features]
21-
metrics-integration = ["dep:metrics"]
22-
default = ["rt", "metrics-integration"]
21+
default = ["rt"]
22+
metrics-rs-integration = ["dep:metrics"]
2323
rt = ["tokio"]
2424

2525
[dependencies]
@@ -39,6 +39,7 @@ serde_json = "1.0.79"
3939
tokio = { version = "1.41.0", features = ["full", "rt", "time", "macros", "test-util"] }
4040
metrics-util = { version = "0.19", features = ["debugging"] }
4141
metrics = { version = "0.24" }
42+
metrics-exporter-prometheus = { version = "0.16", features = ["uds-listener"] }
4243

4344
[[example]]
4445
name = "runtime"

src/lib.rs

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,54 @@ async fn do_work() {
102102
tokio::time::sleep(Duration::from_millis(100)).await;
103103
}
104104
}
105-
```"##
105+
```
106+
107+
### Monitoring and publishing runtime metrics (unstable)
108+
109+
If the `metrics-rs-integration` feature is additionally enabled, this crate allows
110+
publishing runtime metrics externally via [metrics-rs](metrics) exporters.
111+
112+
For example, you can use [metrics_exporter_prometheus] to make metrics visible
113+
to Prometheus. You can see the [metrics_exporter_prometheus] and [metrics-rs](metrics)
114+
docs for guidance on configuring exporters.
115+
116+
The published metrics are the same as the fields of [RuntimeMetrics], but with
117+
a "tokio_" prefix added, for example `tokio_workers_count`.
118+
119+
[metrics_exporter_prometheus]: https://docs.rs/metrics_exporter_prometheus
120+
[RuntimeMetrics]: crate::RuntimeMetrics
121+
122+
This example exports Prometheus metrics by listening on a local Unix socket
123+
called `prometheus.sock`, which you can access for debugging by
124+
`curl --unix-socket prometheus.sock localhost`.
125+
126+
```
127+
use std::time::Duration;
128+
129+
#[tokio::main]
130+
async fn main() {
131+
metrics_exporter_prometheus::PrometheusBuilder::new()
132+
.with_http_uds_listener("prometheus.sock")
133+
.install()
134+
.unwrap();
135+
tokio::task::spawn(
136+
tokio_metrics::RuntimeMetricsReporterBuilder::default()
137+
// the default metric sampling interval is 30 seconds, which is
138+
// too long for quick tests, so have it be 1 second.
139+
.with_interval(std::time::Duration::from_secs(1))
140+
.describe_and_run(),
141+
);
142+
// Run some code
143+
tokio::task::spawn(async move {
144+
for _ in 0..1000 {
145+
tokio::time::sleep(Duration::from_millis(10)).await;
146+
}
147+
})
148+
.await
149+
.unwrap();
150+
}
151+
```
152+
"##
106153
)]
107154

108155
macro_rules! cfg_rt {
@@ -122,8 +169,8 @@ cfg_rt! {
122169
RuntimeMetrics,
123170
RuntimeMonitor,
124171
};
125-
#[cfg(feature = "metrics-integration")]
126-
pub use runtime::metrics_integration::{RuntimeMetricsReporterBuilder, RuntimeMetricsReporter};
172+
#[cfg(feature = "metrics-rs-integration")]
173+
pub use runtime::metrics_rs_integration::{RuntimeMetricsReporterBuilder, RuntimeMetricsReporter};
127174
}
128175

129176
mod task;

src/runtime.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use std::time::{Duration, Instant};
22
use tokio::runtime;
33

4-
#[cfg(feature = "metrics-integration")]
5-
pub(crate) mod metrics_integration;
4+
#[cfg(feature = "metrics-rs-integration")]
5+
pub(crate) mod metrics_rs_integration;
66

77
#[cfg(any(docsrs, all(tokio_unstable, feature = "rt")))]
88
#[cfg_attr(docsrs, doc(cfg(all(tokio_unstable, feature = "rt"))))]
Lines changed: 180 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,60 @@ use tokio::runtime::Handle;
44

55
use 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
861
pub 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

2377
impl 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

3286
impl 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).
71239
pub 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

289457
impl 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) {

tests/auto_metrics.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ macro_rules! cfg_rt {
99
}
1010

1111
cfg_rt! {
12-
#[cfg(feature = "metrics-integration")]
12+
#[cfg(feature = "metrics-rs-integration")]
1313
#[test]
1414
fn main() {
1515
use metrics_util::debugging::DebugValue;

0 commit comments

Comments
 (0)