use std::{sync::Arc, time::Duration};
use metrique::emf::Emf;
use metrique::writer::{
AttachGlobalEntrySinkExt, Entry, EntryIoStreamExt, FormatExt, GlobalEntrySink,
sink::global_entry_sink,
};
use metrique::{
timers::{Stopwatch, Timer, Timestamp},
unit::Millisecond,
unit_of_work::metrics,
};
global_entry_sink! { ServiceMetrics }
#[metrics(rename_all = "PascalCase")]
#[derive(Default)]
struct RequestMetrics {
operation: &'static str,
#[metrics(timestamp)]
timestamp: Timestamp,
time: Timer,
number_of_ducks: usize,
#[metrics(unit = Millisecond)]
duck_counter_time: Stopwatch,
}
impl RequestMetrics {
fn init(operation: &'static str) -> RequestMetricsGuard {
Self {
operation,
..Default::default()
}
.append_on_drop(ServiceMetrics::sink()) }
}
struct ServerState {
}
#[derive(Entry)]
struct Globals {
region: String,
cell: String,
}
async fn look_at_sky(_state: Arc<ServerState>) {
let mut metrics = RequestMetrics::init("LookAtSky");
let number_of_ducks = {
let _guard = metrics.duck_counter_time.start();
count_ducks().await
};
metrics.number_of_ducks += number_of_ducks;
tokio::time::sleep(Duration::from_millis(234)).await;
}
async fn count_ducks() -> usize {
tokio::time::sleep(Duration::from_millis(1234)).await;
42
}
#[tokio::main]
async fn main() {
let globals = Globals {
region: "us-east-1".to_string(),
cell: "5".to_string(),
};
let _handle = ServiceMetrics::attach_to_stream(
Emf::all_validations("MyApp".to_string(), vec![vec![]])
.output_to_makewriter(|| std::io::stdout().lock())
.merge_globals(globals),
);
let app = Arc::new(ServerState {});
look_at_sky(app).await;
}
#[cfg(test)]
mod test {
use std::time::UNIX_EPOCH;
use metrique::test_util::{self, TestEntrySink};
use metrique_timesource::{TimeSource, set_time_source};
#[tokio::test(start_paused = true)]
async fn looking_at_sky_produces_metrics() {
let _mock_time = set_time_source(TimeSource::tokio(UNIX_EPOCH));
let TestEntrySink { inspector, sink } = test_util::test_entry_sink();
let _guard = crate::ServiceMetrics::set_test_sink(sink);
let app = std::sync::Arc::new(crate::ServerState {});
super::look_at_sky(app.clone()).await;
let entry = inspector.get(0);
assert_eq!(entry.values["Operation"], "LookAtSky");
assert_eq!(entry.metrics["NumberOfDucks"], 42);
assert_eq!(entry.timestamp, Some(UNIX_EPOCH));
assert_eq!(entry.metrics["DuckCounterTime"], 1234);
assert_eq!(entry.metrics["Time"], 1468);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn multithreaded_runtime_sink() {
let _mock_time = set_time_source(TimeSource::tokio(UNIX_EPOCH));
let TestEntrySink { inspector, sink } = test_util::test_entry_sink();
let _guard = crate::ServiceMetrics::set_test_sink_on_current_tokio_runtime(sink);
let app = std::sync::Arc::new(crate::ServerState {});
let handles: Vec<_> = (0..3)
.map(|_| {
let app = app.clone();
tokio::spawn(async move {
super::look_at_sky(app).await;
})
})
.collect();
for handle in handles {
handle.await.unwrap();
}
let entries = inspector.entries();
assert_eq!(entries.len(), 3);
for entry in entries {
assert_eq!(entry.values["Operation"], "LookAtSky");
assert_eq!(entry.metrics["NumberOfDucks"], 42);
}
}
}