1use std::sync::atomic::{AtomicU64, Ordering};
11use std::sync::Arc;
12use std::time::Instant;
13use tokio::sync::RwLock;
14
15#[derive(Debug, Default)]
17pub struct RpcMetrics {
18 pub total_calls: AtomicU64,
20 pub successful_calls: AtomicU64,
22 pub failed_calls: AtomicU64,
24 pub total_latency_ms: AtomicU64,
26 pub retries: AtomicU64,
28}
29
30impl RpcMetrics {
31 pub fn new() -> Self {
32 Self::default()
33 }
34
35 pub fn record_success(&self, latency_ms: u64) {
37 self.total_calls.fetch_add(1, Ordering::Relaxed);
38 self.successful_calls.fetch_add(1, Ordering::Relaxed);
39 self.total_latency_ms
40 .fetch_add(latency_ms, Ordering::Relaxed);
41 }
42
43 pub fn record_failure(&self, latency_ms: u64) {
45 self.total_calls.fetch_add(1, Ordering::Relaxed);
46 self.failed_calls.fetch_add(1, Ordering::Relaxed);
47 self.total_latency_ms
48 .fetch_add(latency_ms, Ordering::Relaxed);
49 }
50
51 pub fn record_retry(&self) {
53 self.retries.fetch_add(1, Ordering::Relaxed);
54 }
55
56 pub fn success_rate(&self) -> f64 {
58 let total = self.total_calls.load(Ordering::Relaxed);
59 if total == 0 {
60 return 100.0;
61 }
62 let successful = self.successful_calls.load(Ordering::Relaxed);
63 (successful as f64 / total as f64) * 100.0
64 }
65
66 pub fn avg_latency_ms(&self) -> f64 {
68 let total = self.total_calls.load(Ordering::Relaxed);
69 if total == 0 {
70 return 0.0;
71 }
72 let latency = self.total_latency_ms.load(Ordering::Relaxed);
73 latency as f64 / total as f64
74 }
75}
76
77#[derive(Debug, Default)]
79pub struct TransactionMetrics {
80 pub submitted: AtomicU64,
82 pub successful: AtomicU64,
84 pub failed: AtomicU64,
86 pub pending: AtomicU64,
88 pub total_gas_used: AtomicU64,
90 pub total_cost_wei: AtomicU64,
92}
93
94impl TransactionMetrics {
95 pub fn new() -> Self {
96 Self::default()
97 }
98
99 pub fn record_submission(&self) {
101 self.submitted.fetch_add(1, Ordering::Relaxed);
102 self.pending.fetch_add(1, Ordering::Relaxed);
103 }
104
105 pub fn record_success(&self, gas_used: u64, cost_wei: u128) {
107 self.successful.fetch_add(1, Ordering::Relaxed);
108 self.pending.fetch_sub(1, Ordering::Relaxed);
109 self.total_gas_used.fetch_add(gas_used, Ordering::Relaxed);
110 self.total_cost_wei
112 .fetch_add((cost_wei & u64::MAX as u128) as u64, Ordering::Relaxed);
113 }
114
115 pub fn record_failure(&self) {
117 self.failed.fetch_add(1, Ordering::Relaxed);
118 self.pending.fetch_sub(1, Ordering::Relaxed);
119 }
120
121 pub fn success_rate(&self) -> f64 {
123 let completed =
124 self.successful.load(Ordering::Relaxed) + self.failed.load(Ordering::Relaxed);
125 if completed == 0 {
126 return 100.0;
127 }
128 let successful = self.successful.load(Ordering::Relaxed);
129 (successful as f64 / completed as f64) * 100.0
130 }
131
132 pub fn avg_gas_used(&self) -> f64 {
134 let successful = self.successful.load(Ordering::Relaxed);
135 if successful == 0 {
136 return 0.0;
137 }
138 let gas = self.total_gas_used.load(Ordering::Relaxed);
139 gas as f64 / successful as f64
140 }
141}
142
143#[derive(Debug, Clone)]
145pub struct GasPriceSnapshot {
146 pub timestamp: Instant,
148 pub base_fee_gwei: f64,
150 pub priority_fee_gwei: f64,
152 pub gas_price_gwei: f64,
154}
155
156pub struct GasMetrics {
158 recent_snapshots: Arc<RwLock<Vec<GasPriceSnapshot>>>,
159 max_snapshots: usize,
160}
161
162impl GasMetrics {
163 pub fn new(max_snapshots: usize) -> Self {
164 Self {
165 recent_snapshots: Arc::new(RwLock::new(Vec::new())),
166 max_snapshots,
167 }
168 }
169
170 pub async fn record_snapshot(&self, snapshot: GasPriceSnapshot) {
172 let mut snapshots = self.recent_snapshots.write().await;
173
174 if snapshots.len() >= self.max_snapshots {
176 snapshots.remove(0);
177 }
178
179 snapshots.push(snapshot);
180 }
181
182 pub async fn avg_base_fee_gwei(&self) -> f64 {
184 let snapshots = self.recent_snapshots.read().await;
185 if snapshots.is_empty() {
186 return 0.0;
187 }
188
189 let sum: f64 = snapshots.iter().map(|s| s.base_fee_gwei).sum();
190 sum / snapshots.len() as f64
191 }
192
193 pub async fn avg_priority_fee_gwei(&self) -> f64 {
195 let snapshots = self.recent_snapshots.read().await;
196 if snapshots.is_empty() {
197 return 0.0;
198 }
199
200 let sum: f64 = snapshots.iter().map(|s| s.priority_fee_gwei).sum();
201 sum / snapshots.len() as f64
202 }
203
204 pub async fn gas_price_trend(&self) -> String {
206 let snapshots = self.recent_snapshots.read().await;
207 if snapshots.len() < 2 {
208 return "unknown".to_string();
209 }
210
211 let recent = &snapshots[snapshots.len() - 1];
212 let older = &snapshots[snapshots.len() / 2];
213
214 let diff_percent =
215 ((recent.base_fee_gwei - older.base_fee_gwei) / older.base_fee_gwei) * 100.0;
216
217 if diff_percent > 10.0 {
218 "increasing".to_string()
219 } else if diff_percent < -10.0 {
220 "decreasing".to_string()
221 } else {
222 "stable".to_string()
223 }
224 }
225}
226
227pub struct MetricsCollector {
229 pub rpc: Arc<RpcMetrics>,
231 pub transactions: Arc<TransactionMetrics>,
233 pub gas: Arc<GasMetrics>,
235 start_time: Instant,
237}
238
239impl MetricsCollector {
240 pub fn new() -> Self {
242 Self {
243 rpc: Arc::new(RpcMetrics::new()),
244 transactions: Arc::new(TransactionMetrics::new()),
245 gas: Arc::new(GasMetrics::new(100)),
246 start_time: Instant::now(),
247 }
248 }
249
250 pub fn uptime_secs(&self) -> u64 {
252 self.start_time.elapsed().as_secs()
253 }
254
255 pub async fn export_prometheus(&self) -> String {
257 let mut output = String::new();
258
259 output.push_str("# HELP apex_evm_rpc_calls_total Total number of RPC calls\n");
261 output.push_str("# TYPE apex_evm_rpc_calls_total counter\n");
262 output.push_str(&format!(
263 "apex_evm_rpc_calls_total {}\n",
264 self.rpc.total_calls.load(Ordering::Relaxed)
265 ));
266
267 output.push_str("# HELP apex_evm_rpc_calls_successful Successful RPC calls\n");
268 output.push_str("# TYPE apex_evm_rpc_calls_successful counter\n");
269 output.push_str(&format!(
270 "apex_evm_rpc_calls_successful {}\n",
271 self.rpc.successful_calls.load(Ordering::Relaxed)
272 ));
273
274 output.push_str("# HELP apex_evm_rpc_calls_failed Failed RPC calls\n");
275 output.push_str("# TYPE apex_evm_rpc_calls_failed counter\n");
276 output.push_str(&format!(
277 "apex_evm_rpc_calls_failed {}\n",
278 self.rpc.failed_calls.load(Ordering::Relaxed)
279 ));
280
281 output.push_str("# HELP apex_evm_rpc_latency_avg Average RPC latency in milliseconds\n");
282 output.push_str("# TYPE apex_evm_rpc_latency_avg gauge\n");
283 output.push_str(&format!(
284 "apex_evm_rpc_latency_avg {}\n",
285 self.rpc.avg_latency_ms()
286 ));
287
288 output.push_str("# HELP apex_evm_rpc_success_rate RPC success rate percentage\n");
289 output.push_str("# TYPE apex_evm_rpc_success_rate gauge\n");
290 output.push_str(&format!(
291 "apex_evm_rpc_success_rate {}\n",
292 self.rpc.success_rate()
293 ));
294
295 output.push_str("# HELP apex_evm_transactions_submitted Total transactions submitted\n");
297 output.push_str("# TYPE apex_evm_transactions_submitted counter\n");
298 output.push_str(&format!(
299 "apex_evm_transactions_submitted {}\n",
300 self.transactions.submitted.load(Ordering::Relaxed)
301 ));
302
303 output.push_str("# HELP apex_evm_transactions_successful Successful transactions\n");
304 output.push_str("# TYPE apex_evm_transactions_successful counter\n");
305 output.push_str(&format!(
306 "apex_evm_transactions_successful {}\n",
307 self.transactions.successful.load(Ordering::Relaxed)
308 ));
309
310 output.push_str("# HELP apex_evm_transactions_failed Failed transactions\n");
311 output.push_str("# TYPE apex_evm_transactions_failed counter\n");
312 output.push_str(&format!(
313 "apex_evm_transactions_failed {}\n",
314 self.transactions.failed.load(Ordering::Relaxed)
315 ));
316
317 output.push_str("# HELP apex_evm_transactions_pending Pending transactions\n");
318 output.push_str("# TYPE apex_evm_transactions_pending gauge\n");
319 output.push_str(&format!(
320 "apex_evm_transactions_pending {}\n",
321 self.transactions.pending.load(Ordering::Relaxed)
322 ));
323
324 output.push_str("# HELP apex_evm_transactions_success_rate Transaction success rate\n");
325 output.push_str("# TYPE apex_evm_transactions_success_rate gauge\n");
326 output.push_str(&format!(
327 "apex_evm_transactions_success_rate {}\n",
328 self.transactions.success_rate()
329 ));
330
331 output.push_str("# HELP apex_evm_gas_avg Average gas used per transaction\n");
332 output.push_str("# TYPE apex_evm_gas_avg gauge\n");
333 output.push_str(&format!(
334 "apex_evm_gas_avg {}\n",
335 self.transactions.avg_gas_used()
336 ));
337
338 output.push_str("# HELP apex_evm_gas_base_fee_avg Average base fee in gwei\n");
340 output.push_str("# TYPE apex_evm_gas_base_fee_avg gauge\n");
341 output.push_str(&format!(
342 "apex_evm_gas_base_fee_avg {}\n",
343 self.gas.avg_base_fee_gwei().await
344 ));
345
346 output.push_str("# HELP apex_evm_gas_priority_fee_avg Average priority fee in gwei\n");
347 output.push_str("# TYPE apex_evm_gas_priority_fee_avg gauge\n");
348 output.push_str(&format!(
349 "apex_evm_gas_priority_fee_avg {}\n",
350 self.gas.avg_priority_fee_gwei().await
351 ));
352
353 output.push_str("# HELP apex_evm_uptime_seconds Uptime in seconds\n");
355 output.push_str("# TYPE apex_evm_uptime_seconds counter\n");
356 output.push_str(&format!("apex_evm_uptime_seconds {}\n", self.uptime_secs()));
357
358 output
359 }
360
361 pub async fn print_summary(&self) {
363 println!("=== Apex EVM Metrics Summary ===");
364 println!("\nRPC Calls:");
365 println!(" Total: {}", self.rpc.total_calls.load(Ordering::Relaxed));
366 println!(
367 " Successful: {}",
368 self.rpc.successful_calls.load(Ordering::Relaxed)
369 );
370 println!(
371 " Failed: {}",
372 self.rpc.failed_calls.load(Ordering::Relaxed)
373 );
374 println!(" Success Rate: {:.2}%", self.rpc.success_rate());
375 println!(" Avg Latency: {:.2}ms", self.rpc.avg_latency_ms());
376 println!(" Retries: {}", self.rpc.retries.load(Ordering::Relaxed));
377
378 println!("\nTransactions:");
379 println!(
380 " Submitted: {}",
381 self.transactions.submitted.load(Ordering::Relaxed)
382 );
383 println!(
384 " Successful: {}",
385 self.transactions.successful.load(Ordering::Relaxed)
386 );
387 println!(
388 " Failed: {}",
389 self.transactions.failed.load(Ordering::Relaxed)
390 );
391 println!(
392 " Pending: {}",
393 self.transactions.pending.load(Ordering::Relaxed)
394 );
395 println!(" Success Rate: {:.2}%", self.transactions.success_rate());
396 println!(" Avg Gas Used: {:.0}", self.transactions.avg_gas_used());
397
398 println!("\nGas Prices:");
399 println!(
400 " Avg Base Fee: {:.2} gwei",
401 self.gas.avg_base_fee_gwei().await
402 );
403 println!(
404 " Avg Priority Fee: {:.2} gwei",
405 self.gas.avg_priority_fee_gwei().await
406 );
407 println!(" Trend: {}", self.gas.gas_price_trend().await);
408
409 println!("\nSystem:");
410 println!(" Uptime: {}s", self.uptime_secs());
411 println!("================================");
412 }
413}
414
415impl Default for MetricsCollector {
416 fn default() -> Self {
417 Self::new()
418 }
419}
420
421#[cfg(test)]
422mod tests {
423 use super::*;
424
425 #[test]
426 fn test_rpc_metrics() {
427 let metrics = RpcMetrics::new();
428
429 metrics.record_success(100);
430 metrics.record_success(200);
431 metrics.record_failure(150);
432
433 assert_eq!(metrics.total_calls.load(Ordering::Relaxed), 3);
434 assert_eq!(metrics.successful_calls.load(Ordering::Relaxed), 2);
435 assert_eq!(metrics.failed_calls.load(Ordering::Relaxed), 1);
436 assert!((metrics.success_rate() - 66.67).abs() < 0.1);
437 assert!((metrics.avg_latency_ms() - 150.0).abs() < 0.1);
438 }
439
440 #[test]
441 fn test_transaction_metrics() {
442 let metrics = TransactionMetrics::new();
443
444 metrics.record_submission();
445 metrics.record_submission();
446 metrics.record_success(21000, 21000000000000);
447 metrics.record_failure();
448
449 assert_eq!(metrics.submitted.load(Ordering::Relaxed), 2);
450 assert_eq!(metrics.successful.load(Ordering::Relaxed), 1);
451 assert_eq!(metrics.failed.load(Ordering::Relaxed), 1);
452 assert_eq!(metrics.success_rate(), 50.0);
453 }
454
455 #[tokio::test]
456 async fn test_metrics_collector_prometheus() {
457 let collector = MetricsCollector::new();
458
459 collector.rpc.record_success(100);
460 collector.transactions.record_submission();
461
462 let output = collector.export_prometheus().await;
463
464 assert!(output.contains("apex_evm_rpc_calls_total"));
465 assert!(output.contains("apex_evm_transactions_submitted"));
466 assert!(output.contains("apex_evm_uptime_seconds"));
467 }
468}