Skip to main content

tuwunel_core/metrics/
mod.rs

1//! Collects task, runtime, and request metrics.
2//!
3//! Runtime instrumentation is enabled when the required Tokio facilities are
4//! available. Snapshot helpers expose interval data to diagnostics and
5//! telemetry.
6
7pub mod dump;
8
9use std::sync::{
10	Arc, Mutex,
11	atomic::{AtomicU32, AtomicU64},
12};
13#[cfg(tokio_unstable)]
14use std::{iter::repeat, ops::Range, time::Duration};
15
16#[cfg(tokio_unstable)]
17use smallvec::SmallVec;
18use tokio::runtime;
19#[cfg(tokio_unstable)]
20use tokio_metrics::{RuntimeIntervals, RuntimeMonitor};
21use tokio_metrics::{TaskMetrics, TaskMonitor};
22
23/// Bucket counts sampled from the scheduler latency histogram.
24///
25/// The inline budget matches the default bucket count; a runtime configured
26/// with more buckets spills to the heap.
27#[cfg(tokio_unstable)]
28type Counts = SmallVec<[u64; 20]>;
29
30/// One bucket of the scheduler latency histogram.
31///
32/// The range is the bucket's latency span as configured on the runtime; the
33/// count is how many task schedules landed within it.
34#[cfg(tokio_unstable)]
35type Bucket = (Range<Duration>, u64);
36
37/// Owns process-wide runtime, task, and request measurements.
38///
39/// Task monitoring is available in builds with Tokio's unstable metrics.
40/// Runtime monitoring additionally requires an embedding runtime handle, while
41/// request counters remain independent of either monitor.
42pub struct Metrics {
43	_runtime: Option<runtime::Handle>,
44
45	runtime_metrics: Option<runtime::RuntimeMetrics>,
46
47	task_monitor: Option<TaskMonitor>,
48
49	task_intervals: Mutex<Option<Box<dyn Iterator<Item = TaskMetrics> + Send>>>,
50
51	#[cfg(tokio_unstable)]
52	_runtime_monitor: Option<RuntimeMonitor>,
53
54	#[cfg(tokio_unstable)]
55	runtime_intervals: Mutex<Option<RuntimeIntervals>>,
56
57	#[cfg(tokio_unstable)]
58	sched_histogram_last: Mutex<Counts>,
59
60	// TODO: move stats
61	/// Supplies identifiers for traced requests.
62	///
63	/// The tracing span consumes and increments the sequence when its fields
64	/// are evaluated. It is not an unconditional request-traffic counter.
65	pub requests_count: AtomicU64,
66
67	/// Counts request handlers that have finished in debug builds.
68	///
69	/// The counter is monotonic and remains separate from the gauge of handlers
70	/// still active. Release builds leave it at zero.
71	pub requests_handle_finished: AtomicU64,
72
73	/// Tracks request handlers currently executing in debug builds.
74	///
75	/// The value is a gauge rather than a lifetime total. Release builds leave
76	/// it at zero.
77	pub requests_handle_active: AtomicU32,
78
79	/// Counts request handlers that terminated through panic handling.
80	///
81	/// The counter is scoped to request handling rather than all process
82	/// panics. It is monotonic for the lifetime of the process.
83	pub requests_panic: AtomicU32,
84}
85
86impl Metrics {
87	#[must_use]
88	/// Creates shared metrics state for an optional runtime.
89	///
90	/// A runtime handle enables runtime snapshots. Builds exposing Tokio's
91	/// unstable metrics also install task monitoring independently of that
92	/// handle.
93	pub fn new(runtime: Option<&runtime::Handle>) -> Arc<Self> {
94		#[cfg(tokio_unstable)]
95		let runtime_monitor = runtime.map(RuntimeMonitor::new);
96
97		#[cfg(tokio_unstable)]
98		let runtime_intervals = runtime_monitor
99			.as_ref()
100			.map(RuntimeMonitor::intervals);
101
102		let task_monitor = cfg!(tokio_unstable).then(|| {
103			TaskMonitor::builder()
104				.with_slow_poll_threshold(TaskMonitor::DEFAULT_SLOW_POLL_THRESHOLD)
105				.with_long_delay_threshold(TaskMonitor::DEFAULT_LONG_DELAY_THRESHOLD)
106				.clone()
107				.build()
108		});
109
110		let task_intervals = task_monitor.as_ref().map(
111			|task_monitor| -> Box<dyn Iterator<Item = TaskMetrics> + Send> {
112				Box::new(task_monitor.intervals())
113			},
114		);
115
116		Arc::new(Self {
117			_runtime: runtime.cloned(),
118
119			runtime_metrics: runtime.map(runtime::Handle::metrics),
120
121			task_monitor,
122
123			task_intervals: task_intervals.into(),
124
125			#[cfg(tokio_unstable)]
126			_runtime_monitor: runtime_monitor,
127
128			#[cfg(tokio_unstable)]
129			runtime_intervals: Mutex::new(runtime_intervals),
130
131			#[cfg(tokio_unstable)]
132			sched_histogram_last: Counts::new().into(),
133
134			requests_count: AtomicU64::new(0),
135			requests_handle_finished: AtomicU64::new(0),
136			requests_handle_active: AtomicU32::new(0),
137			requests_panic: AtomicU32::new(0),
138		})
139	}
140
141	#[inline]
142	/// Awaits a future under task instrumentation when a monitor is available.
143	///
144	/// Instrumented futures contribute to task interval snapshots. Builds
145	/// without a task monitor await the future directly with no wrapper.
146	pub async fn instrument<F, Output>(&self, f: F) -> Output
147	where
148		F: Future<Output = Output>,
149	{
150		if let Some(monitor) = self.task_metrics() {
151			monitor.instrument(f).await
152		} else {
153			f.await
154		}
155	}
156
157	/// Advances the task monitor and returns its next interval sample.
158	///
159	/// A server without task monitoring returns `None`. Samples describe only
160	/// futures explicitly passed through [`Self::instrument`].
161	///
162	/// # Panics
163	///
164	/// Panics when the interval mutex is poisoned.
165	pub fn task_interval(&self) -> Option<TaskMetrics> {
166		self.task_intervals
167			.lock()
168			.expect("locked")
169			.as_mut()
170			.and_then(Iterator::next)
171	}
172
173	#[cfg(tokio_unstable)]
174	/// Advances the runtime monitor and returns its next interval sample.
175	///
176	/// The returned value is absent only if the monitor's interval iterator
177	/// ends. Calls require a runtime handle to have been supplied at
178	/// construction.
179	///
180	/// # Panics
181	///
182	/// Panics when the interval mutex is poisoned or no runtime monitor exists.
183	pub fn runtime_interval(&self) -> Option<tokio_metrics::RuntimeMetrics> {
184		self.runtime_intervals
185			.lock()
186			.expect("locked")
187			.as_mut()
188			.map(Iterator::next)
189			.expect("next interval")
190	}
191
192	/// Bucket deltas of the scheduler latency histogram since the last call.
193	///
194	/// Each call diffs the runtime's cumulative per-worker counts against the
195	/// previous sample and replaces it, so the counts are the traffic of one
196	/// interval rather than the totals tokio exposes. Returns `None` when the
197	/// runtime was built without the histogram.
198	///
199	/// # Panics
200	///
201	/// Panics when the scheduler histogram state mutex is poisoned.
202	#[cfg(tokio_unstable)]
203	pub fn sched_histogram_interval(&self) -> Option<impl Iterator<Item = Bucket>> {
204		let metrics = self
205			.runtime_metrics()
206			.filter(|metrics| metrics.schedule_latency_histogram_enabled())?;
207
208		let num_workers = metrics.num_workers();
209		let bucket_total = |bucket| -> u64 {
210			(0..num_workers)
211				.map(|worker| metrics.schedule_latency_histogram_bucket_count(worker, bucket))
212				.sum()
213		};
214
215		let num_buckets = metrics.schedule_latency_histogram_num_buckets();
216		let totals: Counts = (0..num_buckets).map(bucket_total).collect();
217
218		let mut last = self.sched_histogram_last.lock().expect("locked");
219		let deltas: Counts = totals
220			.iter()
221			.zip(last.iter().copied().chain(repeat(0)))
222			.map(|(total, last)| total.saturating_sub(last))
223			.collect();
224
225		*last = totals;
226
227		let ranges = (0..num_buckets)
228			.map(move |bucket| metrics.schedule_latency_histogram_bucket_range(bucket));
229
230		Some(ranges.zip(deltas))
231	}
232
233	#[inline]
234	/// Returns the number of workers in the monitored runtime.
235	///
236	/// A server constructed without a runtime handle reports zero. The value
237	/// comes directly from Tokio's runtime metrics snapshot.
238	pub fn num_workers(&self) -> usize {
239		self.runtime_metrics()
240			.map_or(0, runtime::RuntimeMetrics::num_workers)
241	}
242
243	#[inline]
244	/// Returns the optional task monitor.
245	///
246	/// The monitor exists only when task metrics were enabled at construction.
247	/// It may be used to instrument futures or inspect cumulative task
248	/// measurements.
249	pub fn task_metrics(&self) -> Option<&TaskMonitor> { self.task_monitor.as_ref() }
250
251	#[inline]
252	/// Returns the optional Tokio runtime metrics handle.
253	///
254	/// The handle exists when server construction received an embedding
255	/// runtime. Callers borrow it without advancing any interval monitor.
256	pub fn runtime_metrics(&self) -> Option<&runtime::RuntimeMetrics> {
257		self.runtime_metrics.as_ref()
258	}
259}