1use std::time::Duration;
2
3use synd_support::o11y::metric;
4use tokio_metrics::{RuntimeMetrics, RuntimeMonitor, TaskMetrics, TaskMonitor};
5use tokio_util::sync::CancellationToken;
6
7struct Metrics {
8 runtime_total_polls_count: u64,
9 runtime_busy_duration_secs: f64,
10 gql_mean_poll_duration_secs: f64,
11 gql_mean_slow_poll_duration_secs: f64,
12 gql_mean_first_poll_delay_secs: f64,
13 gql_mean_scheduled_duration_secs: f64,
14 gql_mean_idle_duration_secs: f64,
15}
16
17pub struct Monitors {
18 gql: TaskMonitor,
19}
20
21impl Monitors {
22 pub fn new() -> Self {
23 Self {
24 gql: TaskMonitor::new(),
25 }
26 }
27
28 pub(crate) fn graphql_task_monitor(&self) -> TaskMonitor {
29 self.gql.clone()
30 }
31
32 pub async fn emit_metrics(self, interval: Duration, ct: CancellationToken) {
33 let handle = tokio::runtime::Handle::current();
34 let runtime_monitor = RuntimeMonitor::new(&handle);
35 let intervals = runtime_monitor.intervals().zip(self.gql.intervals());
36
37 for (runtime_metrics, gql_metrics) in intervals {
38 let Metrics {
39 runtime_total_polls_count,
40 runtime_busy_duration_secs,
41 gql_mean_poll_duration_secs,
42 gql_mean_slow_poll_duration_secs,
43 gql_mean_first_poll_delay_secs,
44 gql_mean_scheduled_duration_secs,
45 gql_mean_idle_duration_secs,
46 } = Self::collect_metrics(&runtime_metrics, &gql_metrics);
47
48 metric!(monotonic_counter.runtime.poll = runtime_total_polls_count);
50 metric!(monotonic_counter.runtime.busy_duration = runtime_busy_duration_secs);
51
52 metric!(
54 monotonic_counter.task.graphql.mean_poll_duration = gql_mean_poll_duration_secs
55 );
56 metric!(
57 monotonic_counter.task.graphql.mean_slow_poll_duration =
58 gql_mean_slow_poll_duration_secs
59 );
60
61 metric!(
63 monotonic_counter.task.graphql.mean_first_poll_delay =
64 gql_mean_first_poll_delay_secs,
65 );
66 metric!(
67 monotonic_counter.task.graphql.mean_scheduled_duration =
68 gql_mean_scheduled_duration_secs,
69 );
70
71 metric!(
73 monotonic_counter.task.graphql.mean_idle_duration = gql_mean_idle_duration_secs,
74 );
75
76 tokio::select! {
77 biased;
78 () = ct.cancelled() => break,
80 () = tokio::time::sleep(interval) => ()
81 }
82 }
83 }
84
85 fn collect_metrics(runtime_metrics: &RuntimeMetrics, gql_metrics: &TaskMetrics) -> Metrics {
86 Metrics {
87 runtime_total_polls_count: runtime_metrics.total_polls_count,
88 runtime_busy_duration_secs: runtime_metrics.total_busy_duration.as_secs_f64(),
89 gql_mean_poll_duration_secs: gql_metrics.mean_poll_duration().as_secs_f64(),
90 gql_mean_slow_poll_duration_secs: gql_metrics.mean_slow_poll_duration().as_secs_f64(),
91 gql_mean_first_poll_delay_secs: gql_metrics.mean_first_poll_delay().as_secs_f64(),
92 gql_mean_scheduled_duration_secs: gql_metrics.mean_scheduled_duration().as_secs_f64(),
93 gql_mean_idle_duration_secs: gql_metrics.mean_idle_duration().as_secs_f64(),
94 }
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use std::sync::{Arc, Mutex};
101
102 use tracing::{Event, Subscriber, instrument::WithSubscriber};
103 use tracing_subscriber::{
104 Layer, Registry,
105 layer::{Context, SubscriberExt as _},
106 registry::LookupSpan,
107 };
108
109 use super::*;
110
111 struct TestLayer<F> {
112 on_event: F,
113 }
114
115 impl<S, F> Layer<S> for TestLayer<F>
116 where
117 S: Subscriber + for<'span> LookupSpan<'span>,
118 F: Fn(&Event<'_>) + 'static,
119 {
120 fn on_event(&self, event: &Event<'_>, _ctx: Context<'_, S>) {
121 (self.on_event)(event);
122 }
123 }
124
125 #[tokio::test]
126 async fn emit_metrics() {
127 let events = Arc::new(Mutex::new(Vec::new()));
128 let events_cloned = events.clone();
129 let on_event = move |event: &Event<'_>| {
130 let field = event.fields().next().unwrap().name();
131 events_cloned.lock().unwrap().push(field);
132 };
133 let layer = TestLayer { on_event };
134 let registry = Registry::default().with(layer);
135 let m = Monitors::new();
136 let ct = CancellationToken::new();
137 ct.cancel();
138
139 m.emit_metrics(Duration::from_millis(0), ct)
140 .with_subscriber(registry)
141 .await;
142
143 let events = events.lock().unwrap().clone();
144 insta::with_settings!({
145 description => "metrics which monitor emits",
146 omit_expression => true ,
147 }, {
148 insta::assert_yaml_snapshot!(events);
149 });
150 }
151}