Skip to main content

tuwunel_core/metrics/
dump.rs

1//! Exit-time dumps of runtime metrics and resource usage.
2//!
3//! Each file is a small JSON envelope: a `meta` block (pid, timestamp,
4//! version, scope) and a `payload` string holding the Debug output of the
5//! source struct verbatim.
6
7use std::{fs, path::Path, process};
8
9use serde::Serialize;
10#[cfg(tokio_unstable)]
11use tokio_metrics::RuntimeMetrics;
12
13use crate::{
14	Result, debug_info, error,
15	utils::{sys::Usage, time::now_millis},
16	version,
17};
18
19#[cfg(tokio_unstable)]
20const RUNTIME_METRICS_PREFIX: &str = "tuwunel.runtime_metrics";
21const RUNTIME_USAGE_PREFIX: &str = "tuwunel.runtime_usage";
22
23#[derive(Serialize)]
24struct Dump<'a> {
25	meta: DumpMeta,
26	payload: &'a str,
27}
28
29#[derive(Serialize)]
30struct DumpMeta {
31	pid: u32,
32	wrote_at_ms: u64,
33	tuwunel_version: &'static str,
34	scope: &'static str,
35}
36
37impl DumpMeta {
38	fn new(scope: &'static str) -> Self {
39		Self {
40			pid: process::id(),
41			wrote_at_ms: now_millis(),
42			tuwunel_version: version(),
43			scope,
44		}
45	}
46}
47
48#[cfg(tokio_unstable)]
49/// Writes a runtime metrics snapshot into a process-specific JSON file.
50///
51/// The output filename includes the current process identifier. Serialization
52/// and file-system failures are reported through logging instead of being
53/// returned.
54pub fn write_runtime_metrics(dir: &Path, metrics: &RuntimeMetrics) {
55	let pid = process::id();
56	let path = dir.join(format!("{RUNTIME_METRICS_PREFIX}.{pid}.json"));
57	let payload = format!("{metrics:?}");
58	let dump = Dump {
59		meta: DumpMeta::new("runtime_metrics"),
60		payload: &payload,
61	};
62
63	report(&path, "runtime_metrics", write_json(&path, &dump));
64}
65
66/// Writes a resource usage snapshot into a process-specific JSON file.
67///
68/// The output filename includes the current process identifier. Serialization
69/// and file-system failures are reported through logging instead of being
70/// returned.
71pub fn write_resource_usage(dir: &Path, usage: &Usage) {
72	let pid = process::id();
73	let path = dir.join(format!("{RUNTIME_USAGE_PREFIX}.{pid}.json"));
74	let payload = format!("{usage:?}");
75	let dump = Dump {
76		meta: DumpMeta::new("runtime_usage"),
77		payload: &payload,
78	};
79
80	report(&path, "runtime_usage", write_json(&path, &dump));
81}
82
83fn write_json<T: Serialize>(path: &Path, value: &T) -> Result {
84	if let Some(parent) = path.parent()
85		&& !parent.as_os_str().is_empty()
86	{
87		fs::create_dir_all(parent)?;
88	}
89
90	let json = serde_json::to_string_pretty(value)?;
91	fs::write(path, json)?;
92
93	Ok(())
94}
95
96fn report(path: &Path, scope: &'static str, result: Result) {
97	match result {
98		| Ok(()) => debug_info!(?path, %scope, "Wrote metrics."),
99		| Err(error) => error!(?path, %scope, %error, "Failed to write metrics."),
100	}
101}