linera_client/
client_metrics.rs1use hdrhistogram::Histogram;
5use linera_core::client::TimingType;
6use tokio::{sync::mpsc, task, time};
7use tracing::{debug, info, warn};
8
9#[derive(Debug, Clone)]
10pub struct TimingConfig {
12 pub enabled: bool,
14 pub report_interval_secs: u64,
16}
17
18#[cfg(not(web))]
19impl Default for TimingConfig {
20 fn default() -> Self {
21 Self {
22 enabled: false,
23 report_interval_secs: 5,
24 }
25 }
26}
27
28#[derive(Debug, thiserror::Error)]
29pub enum ClientMetricsError {
31 #[error("Failed to create histogram: {0}")]
33 HistogramCreationError(#[from] hdrhistogram::CreationError),
34 #[error("Failed to record histogram: {0}")]
36 HistogramRecordError(#[from] hdrhistogram::RecordError),
37}
38
39pub struct ExecuteBlockTimingsHistograms {
41 pub submit_block_proposal_histogram: Histogram<u64>,
43 pub update_validators_histogram: Histogram<u64>,
45}
46
47impl ExecuteBlockTimingsHistograms {
48 pub fn new() -> Result<Self, ClientMetricsError> {
50 Ok(Self {
51 submit_block_proposal_histogram: Histogram::<u64>::new(2)?,
52 update_validators_histogram: Histogram::<u64>::new(2)?,
53 })
54 }
55}
56
57pub struct ExecuteOperationsTimingsHistograms {
59 pub execute_block_histogram: Histogram<u64>,
61 pub execute_block_timings_histograms: ExecuteBlockTimingsHistograms,
63}
64
65impl ExecuteOperationsTimingsHistograms {
66 pub fn new() -> Result<Self, ClientMetricsError> {
68 Ok(Self {
69 execute_block_histogram: Histogram::<u64>::new(2)?,
70 execute_block_timings_histograms: ExecuteBlockTimingsHistograms::new()?,
71 })
72 }
73}
74
75pub struct BlockTimingsHistograms {
77 pub execute_operations_histogram: Histogram<u64>,
79 pub execute_operations_timings_histograms: ExecuteOperationsTimingsHistograms,
81}
82
83impl BlockTimingsHistograms {
84 pub fn new() -> Result<Self, ClientMetricsError> {
86 Ok(Self {
87 execute_operations_histogram: Histogram::<u64>::new(2)?,
88 execute_operations_timings_histograms: ExecuteOperationsTimingsHistograms::new()?,
89 })
90 }
91
92 pub fn record_timing(
94 &mut self,
95 duration_ms: u64,
96 timing_type: TimingType,
97 ) -> Result<(), ClientMetricsError> {
98 match timing_type {
99 TimingType::ExecuteOperations => {
100 self.execute_operations_histogram.record(duration_ms)?;
101 }
102 TimingType::ExecuteBlock => {
103 self.execute_operations_timings_histograms
104 .execute_block_histogram
105 .record(duration_ms)?;
106 }
107 TimingType::SubmitBlockProposal => {
108 self.execute_operations_timings_histograms
109 .execute_block_timings_histograms
110 .submit_block_proposal_histogram
111 .record(duration_ms)?;
112 }
113 TimingType::UpdateValidators => {
114 self.execute_operations_timings_histograms
115 .execute_block_timings_histograms
116 .update_validators_histogram
117 .record(duration_ms)?;
118 }
119 }
120 Ok(())
121 }
122}
123
124#[cfg(not(web))]
126pub struct ClientMetrics {
127 pub timing_config: TimingConfig,
129 pub timing_sender: mpsc::UnboundedSender<(u64, TimingType)>,
131 pub timing_task: task::JoinHandle<()>,
133}
134
135#[cfg(not(web))]
136impl ClientMetrics {
137 pub fn new(timing_config: TimingConfig) -> Self {
139 let (tx, rx) = mpsc::unbounded_channel();
140 let timing_task = tokio::spawn(Self::timing_collection(
141 rx,
142 timing_config.report_interval_secs,
143 ));
144
145 Self {
146 timing_config,
147 timing_sender: tx,
148 timing_task,
149 }
150 }
151
152 async fn timing_collection(
153 mut receiver: mpsc::UnboundedReceiver<(u64, TimingType)>,
154 report_interval_secs: u64,
155 ) {
156 let mut histograms =
157 BlockTimingsHistograms::new().expect("Failed to create timing histograms");
158
159 let mut report_needed = false;
160 let mut report_timer = time::interval(time::Duration::from_secs(report_interval_secs));
161 report_timer.set_missed_tick_behavior(time::MissedTickBehavior::Skip);
162
163 loop {
164 tokio::select! {
165 timing_data = receiver.recv() => {
166 match timing_data {
167 Some((duration_ms, timing_type)) => {
168 if let Err(e) = histograms.record_timing(duration_ms, timing_type) {
169 warn!("Failed to record timing data: {}", e);
170 } else {
171 report_needed = true;
172 }
173 }
174 None => {
175 debug!("Timing collection task shutting down - sender closed");
176 break;
177 }
178 }
179 }
180 _ = report_timer.tick() => {
181 if report_needed {
182 Self::print_timing_report(&histograms);
183 report_needed = false;
184 }
185 }
186 }
187 }
188 }
189
190 fn print_timing_report(histograms: &BlockTimingsHistograms) {
191 for quantile in [0.99, 0.95, 0.90, 0.50] {
192 let formatted_quantile = (quantile * 100.0) as usize;
193
194 info!(
195 "Execute operations p{}: {} ms",
196 formatted_quantile,
197 histograms
198 .execute_operations_histogram
199 .value_at_quantile(quantile)
200 );
201
202 info!(
203 " └─ Execute block p{}: {} ms",
204 formatted_quantile,
205 histograms
206 .execute_operations_timings_histograms
207 .execute_block_histogram
208 .value_at_quantile(quantile)
209 );
210 info!(
211 " ├─ Submit block proposal p{}: {} ms",
212 formatted_quantile,
213 histograms
214 .execute_operations_timings_histograms
215 .execute_block_timings_histograms
216 .submit_block_proposal_histogram
217 .value_at_quantile(quantile)
218 );
219 info!(
220 " └─ Update validators p{}: {} ms",
221 formatted_quantile,
222 histograms
223 .execute_operations_timings_histograms
224 .execute_block_timings_histograms
225 .update_validators_histogram
226 .value_at_quantile(quantile)
227 );
228 }
229 }
230}