1use prometheus::{
13 Counter, CounterVec, Gauge, GaugeVec, Histogram, HistogramOpts, HistogramVec,
14 IntCounter, IntCounterVec, IntGauge, IntGaugeVec, Opts, Registry,
15};
16use std::sync::Arc;
17use std::time::Instant;
18
19#[derive(Clone)]
21pub struct PortalisMetrics {
22 pub registry: Arc<Registry>,
24
25 pub translation: TranslationMetrics,
27
28 pub agents: AgentMetrics,
30
31 pub pipeline: PipelineMetrics,
33
34 pub wasm: WasmMetrics,
36
37 pub errors: ErrorMetrics,
39
40 pub cache: CacheMetrics,
42
43 pub system: SystemMetrics,
45}
46
47impl PortalisMetrics {
48 pub fn new() -> Result<Self, prometheus::Error> {
50 let registry = Arc::new(Registry::new());
51
52 Ok(Self {
53 translation: TranslationMetrics::new(®istry)?,
54 agents: AgentMetrics::new(®istry)?,
55 pipeline: PipelineMetrics::new(®istry)?,
56 wasm: WasmMetrics::new(®istry)?,
57 errors: ErrorMetrics::new(®istry)?,
58 cache: CacheMetrics::new(®istry)?,
59 system: SystemMetrics::new(®istry)?,
60 registry: registry.clone(),
61 })
62 }
63
64 pub fn export(&self) -> Result<String, prometheus::Error> {
66 use prometheus::Encoder;
67 let encoder = prometheus::TextEncoder::new();
68 let metric_families = self.registry.gather();
69 let mut buffer = Vec::new();
70 encoder.encode(&metric_families, &mut buffer)?;
71 Ok(String::from_utf8(buffer).unwrap_or_default())
72 }
73}
74
75#[derive(Clone)]
77pub struct TranslationMetrics {
78 pub translations_total: IntCounterVec,
80
81 pub translations_success: IntCounterVec,
83
84 pub translations_failed: IntCounterVec,
86
87 pub translation_duration: HistogramVec,
89
90 pub translation_loc: HistogramVec,
92
93 pub translation_complexity: GaugeVec,
95
96 pub success_rate: GaugeVec,
98
99 pub translations_in_progress: IntGaugeVec,
101}
102
103impl TranslationMetrics {
104 pub fn new(registry: &Registry) -> Result<Self, prometheus::Error> {
105 let translations_total = IntCounterVec::new(
106 Opts::new(
107 "portalis_translations_total",
108 "Total number of translation attempts",
109 ),
110 &["source_language", "target_format"],
111 )?;
112 registry.register(Box::new(translations_total.clone()))?;
113
114 let translations_success = IntCounterVec::new(
115 Opts::new(
116 "portalis_translations_success_total",
117 "Total number of successful translations",
118 ),
119 &["source_language", "target_format"],
120 )?;
121 registry.register(Box::new(translations_success.clone()))?;
122
123 let translations_failed = IntCounterVec::new(
124 Opts::new(
125 "portalis_translations_failed_total",
126 "Total number of failed translations",
127 ),
128 &["source_language", "target_format", "error_category"],
129 )?;
130 registry.register(Box::new(translations_failed.clone()))?;
131
132 let translation_duration = HistogramVec::new(
133 HistogramOpts::new(
134 "portalis_translation_duration_seconds",
135 "Time taken to complete translation",
136 )
137 .buckets(vec![0.01, 0.05, 0.1, 0.5, 1.0, 5.0, 10.0, 30.0, 60.0, 120.0]),
138 &["source_language", "complexity_level"],
139 )?;
140 registry.register(Box::new(translation_duration.clone()))?;
141
142 let translation_loc = HistogramVec::new(
143 HistogramOpts::new(
144 "portalis_translation_lines_of_code",
145 "Number of lines of code translated",
146 )
147 .buckets(vec![10.0, 50.0, 100.0, 500.0, 1000.0, 5000.0, 10000.0, 50000.0]),
148 &["source_language"],
149 )?;
150 registry.register(Box::new(translation_loc.clone()))?;
151
152 let translation_complexity = GaugeVec::new(
153 Opts::new(
154 "portalis_translation_complexity_score",
155 "Cyclomatic complexity score of translation",
156 ),
157 &["translation_id", "source_language"],
158 )?;
159 registry.register(Box::new(translation_complexity.clone()))?;
160
161 let success_rate = GaugeVec::new(
162 Opts::new(
163 "portalis_translation_success_rate",
164 "Translation success rate percentage",
165 ),
166 &["source_language", "target_format"],
167 )?;
168 registry.register(Box::new(success_rate.clone()))?;
169
170 let translations_in_progress = IntGaugeVec::new(
171 Opts::new(
172 "portalis_translations_in_progress",
173 "Number of translations currently being processed",
174 ),
175 &["source_language"],
176 )?;
177 registry.register(Box::new(translations_in_progress.clone()))?;
178
179 Ok(Self {
180 translations_total,
181 translations_success,
182 translations_failed,
183 translation_duration,
184 translation_loc,
185 translation_complexity,
186 success_rate,
187 translations_in_progress,
188 })
189 }
190}
191
192#[derive(Clone)]
194pub struct AgentMetrics {
195 pub agent_executions: IntCounterVec,
197
198 pub agent_duration: HistogramVec,
200
201 pub agent_status: IntCounterVec,
203
204 pub agent_memory_bytes: GaugeVec,
206
207 pub agent_cpu_percent: GaugeVec,
209
210 pub agents_active: IntGaugeVec,
212}
213
214impl AgentMetrics {
215 pub fn new(registry: &Registry) -> Result<Self, prometheus::Error> {
216 let agent_executions = IntCounterVec::new(
217 Opts::new(
218 "portalis_agent_executions_total",
219 "Total number of agent executions",
220 ),
221 &["agent_name", "agent_type"],
222 )?;
223 registry.register(Box::new(agent_executions.clone()))?;
224
225 let agent_duration = HistogramVec::new(
226 HistogramOpts::new(
227 "portalis_agent_execution_duration_seconds",
228 "Agent execution duration in seconds",
229 )
230 .buckets(vec![0.001, 0.01, 0.1, 0.5, 1.0, 5.0, 10.0, 30.0, 60.0]),
231 &["agent_name", "agent_type"],
232 )?;
233 registry.register(Box::new(agent_duration.clone()))?;
234
235 let agent_status = IntCounterVec::new(
236 Opts::new(
237 "portalis_agent_status_total",
238 "Agent execution status counts",
239 ),
240 &["agent_name", "status"],
241 )?;
242 registry.register(Box::new(agent_status.clone()))?;
243
244 let agent_memory_bytes = GaugeVec::new(
245 Opts::new(
246 "portalis_agent_memory_bytes",
247 "Agent memory usage in bytes",
248 ),
249 &["agent_name"],
250 )?;
251 registry.register(Box::new(agent_memory_bytes.clone()))?;
252
253 let agent_cpu_percent = GaugeVec::new(
254 Opts::new(
255 "portalis_agent_cpu_percent",
256 "Agent CPU usage percentage",
257 ),
258 &["agent_name"],
259 )?;
260 registry.register(Box::new(agent_cpu_percent.clone()))?;
261
262 let agents_active = IntGaugeVec::new(
263 Opts::new(
264 "portalis_agents_active",
265 "Number of currently active agents",
266 ),
267 &["agent_type"],
268 )?;
269 registry.register(Box::new(agents_active.clone()))?;
270
271 Ok(Self {
272 agent_executions,
273 agent_duration,
274 agent_status,
275 agent_memory_bytes,
276 agent_cpu_percent,
277 agents_active,
278 })
279 }
280}
281
282#[derive(Clone)]
284pub struct PipelineMetrics {
285 pub phase_duration: HistogramVec,
287
288 pub phase_status: IntCounterVec,
290
291 pub pipeline_duration: HistogramVec,
293
294 pub pipelines_active: IntGauge,
296
297 pub pipeline_queue_depth: IntGaugeVec,
299}
300
301impl PipelineMetrics {
302 pub fn new(registry: &Registry) -> Result<Self, prometheus::Error> {
303 let phase_duration = HistogramVec::new(
304 HistogramOpts::new(
305 "portalis_pipeline_phase_duration_seconds",
306 "Duration of each pipeline phase",
307 )
308 .buckets(vec![0.01, 0.1, 0.5, 1.0, 5.0, 10.0, 30.0, 60.0]),
309 &["phase_name"],
310 )?;
311 registry.register(Box::new(phase_duration.clone()))?;
312
313 let phase_status = IntCounterVec::new(
314 Opts::new(
315 "portalis_pipeline_phase_status_total",
316 "Phase execution status counts",
317 ),
318 &["phase_name", "status"],
319 )?;
320 registry.register(Box::new(phase_status.clone()))?;
321
322 let pipeline_duration = HistogramVec::new(
323 HistogramOpts::new(
324 "portalis_pipeline_duration_seconds",
325 "End-to-end pipeline duration",
326 )
327 .buckets(vec![1.0, 5.0, 10.0, 30.0, 60.0, 120.0, 300.0]),
328 &["pipeline_type"],
329 )?;
330 registry.register(Box::new(pipeline_duration.clone()))?;
331
332 let pipelines_active = IntGauge::new(
333 "portalis_pipelines_active",
334 "Number of currently active pipelines",
335 )?;
336 registry.register(Box::new(pipelines_active.clone()))?;
337
338 let pipeline_queue_depth = IntGaugeVec::new(
339 Opts::new(
340 "portalis_pipeline_queue_depth",
341 "Number of pipelines waiting in queue",
342 ),
343 &["priority"],
344 )?;
345 registry.register(Box::new(pipeline_queue_depth.clone()))?;
346
347 Ok(Self {
348 phase_duration,
349 phase_status,
350 pipeline_duration,
351 pipelines_active,
352 pipeline_queue_depth,
353 })
354 }
355}
356
357#[derive(Clone)]
359pub struct WasmMetrics {
360 pub wasm_compile_duration: HistogramVec,
362
363 pub wasm_binary_size_bytes: HistogramVec,
365
366 pub wasm_optimization_level: GaugeVec,
368
369 pub wasm_execution_duration: HistogramVec,
371
372 pub wasm_memory_bytes: GaugeVec,
374
375 pub wasm_modules_total: IntCounter,
377}
378
379impl WasmMetrics {
380 pub fn new(registry: &Registry) -> Result<Self, prometheus::Error> {
381 let wasm_compile_duration = HistogramVec::new(
382 HistogramOpts::new(
383 "portalis_wasm_compile_duration_seconds",
384 "Time taken to compile WASM module",
385 )
386 .buckets(vec![0.01, 0.05, 0.1, 0.5, 1.0, 5.0, 10.0]),
387 &["optimization_level"],
388 )?;
389 registry.register(Box::new(wasm_compile_duration.clone()))?;
390
391 let wasm_binary_size_bytes = HistogramVec::new(
392 HistogramOpts::new(
393 "portalis_wasm_binary_size_bytes",
394 "Size of compiled WASM binary",
395 )
396 .buckets(vec![1024.0, 10240.0, 102400.0, 1024000.0, 10240000.0]),
397 &["optimization_level"],
398 )?;
399 registry.register(Box::new(wasm_binary_size_bytes.clone()))?;
400
401 let wasm_optimization_level = GaugeVec::new(
402 Opts::new(
403 "portalis_wasm_optimization_level",
404 "WASM optimization level (0-3)",
405 ),
406 &["module_id"],
407 )?;
408 registry.register(Box::new(wasm_optimization_level.clone()))?;
409
410 let wasm_execution_duration = HistogramVec::new(
411 HistogramOpts::new(
412 "portalis_wasm_execution_duration_seconds",
413 "WASM module execution time",
414 )
415 .buckets(vec![0.001, 0.01, 0.1, 0.5, 1.0, 5.0]),
416 &["module_name"],
417 )?;
418 registry.register(Box::new(wasm_execution_duration.clone()))?;
419
420 let wasm_memory_bytes = GaugeVec::new(
421 Opts::new(
422 "portalis_wasm_memory_bytes",
423 "WASM module memory usage",
424 ),
425 &["module_name"],
426 )?;
427 registry.register(Box::new(wasm_memory_bytes.clone()))?;
428
429 let wasm_modules_total = IntCounter::new(
430 "portalis_wasm_modules_total",
431 "Total number of WASM modules created",
432 )?;
433 registry.register(Box::new(wasm_modules_total.clone()))?;
434
435 Ok(Self {
436 wasm_compile_duration,
437 wasm_binary_size_bytes,
438 wasm_optimization_level,
439 wasm_execution_duration,
440 wasm_memory_bytes,
441 wasm_modules_total,
442 })
443 }
444}
445
446#[derive(Clone)]
448pub struct ErrorMetrics {
449 pub errors_total: IntCounterVec,
451
452 pub parse_errors: IntCounterVec,
454
455 pub translation_errors: IntCounterVec,
457
458 pub compilation_errors: IntCounterVec,
460
461 pub runtime_errors: IntCounterVec,
463
464 pub error_recoveries: IntCounterVec,
466}
467
468impl ErrorMetrics {
469 pub fn new(registry: &Registry) -> Result<Self, prometheus::Error> {
470 let errors_total = IntCounterVec::new(
471 Opts::new(
472 "portalis_errors_total",
473 "Total errors by category",
474 ),
475 &["category", "severity", "component"],
476 )?;
477 registry.register(Box::new(errors_total.clone()))?;
478
479 let parse_errors = IntCounterVec::new(
480 Opts::new(
481 "portalis_parse_errors_total",
482 "Parse errors encountered",
483 ),
484 &["error_type", "source_language"],
485 )?;
486 registry.register(Box::new(parse_errors.clone()))?;
487
488 let translation_errors = IntCounterVec::new(
489 Opts::new(
490 "portalis_translation_errors_total",
491 "Translation errors encountered",
492 ),
493 &["error_type", "phase"],
494 )?;
495 registry.register(Box::new(translation_errors.clone()))?;
496
497 let compilation_errors = IntCounterVec::new(
498 Opts::new(
499 "portalis_compilation_errors_total",
500 "Compilation errors encountered",
501 ),
502 &["error_type", "target_format"],
503 )?;
504 registry.register(Box::new(compilation_errors.clone()))?;
505
506 let runtime_errors = IntCounterVec::new(
507 Opts::new(
508 "portalis_runtime_errors_total",
509 "Runtime errors encountered",
510 ),
511 &["error_type", "component"],
512 )?;
513 registry.register(Box::new(runtime_errors.clone()))?;
514
515 let error_recoveries = IntCounterVec::new(
516 Opts::new(
517 "portalis_error_recoveries_total",
518 "Successful error recovery attempts",
519 ),
520 &["error_category", "recovery_method"],
521 )?;
522 registry.register(Box::new(error_recoveries.clone()))?;
523
524 Ok(Self {
525 errors_total,
526 parse_errors,
527 translation_errors,
528 compilation_errors,
529 runtime_errors,
530 error_recoveries,
531 })
532 }
533}
534
535#[derive(Clone)]
537pub struct CacheMetrics {
538 pub cache_hits: IntCounterVec,
540
541 pub cache_misses: IntCounterVec,
543
544 pub cache_evictions: IntCounterVec,
546
547 pub cache_size_bytes: GaugeVec,
549
550 pub cache_entries: GaugeVec,
552
553 pub cache_hit_rate: GaugeVec,
555}
556
557impl CacheMetrics {
558 pub fn new(registry: &Registry) -> Result<Self, prometheus::Error> {
559 let cache_hits = IntCounterVec::new(
560 Opts::new(
561 "portalis_cache_hits_total",
562 "Total cache hits",
563 ),
564 &["cache_name", "cache_type"],
565 )?;
566 registry.register(Box::new(cache_hits.clone()))?;
567
568 let cache_misses = IntCounterVec::new(
569 Opts::new(
570 "portalis_cache_misses_total",
571 "Total cache misses",
572 ),
573 &["cache_name", "cache_type"],
574 )?;
575 registry.register(Box::new(cache_misses.clone()))?;
576
577 let cache_evictions = IntCounterVec::new(
578 Opts::new(
579 "portalis_cache_evictions_total",
580 "Total cache evictions",
581 ),
582 &["cache_name", "reason"],
583 )?;
584 registry.register(Box::new(cache_evictions.clone()))?;
585
586 let cache_size_bytes = GaugeVec::new(
587 Opts::new(
588 "portalis_cache_size_bytes",
589 "Cache size in bytes",
590 ),
591 &["cache_name"],
592 )?;
593 registry.register(Box::new(cache_size_bytes.clone()))?;
594
595 let cache_entries = GaugeVec::new(
596 Opts::new(
597 "portalis_cache_entries",
598 "Number of entries in cache",
599 ),
600 &["cache_name"],
601 )?;
602 registry.register(Box::new(cache_entries.clone()))?;
603
604 let cache_hit_rate = GaugeVec::new(
605 Opts::new(
606 "portalis_cache_hit_rate",
607 "Cache hit rate percentage",
608 ),
609 &["cache_name"],
610 )?;
611 registry.register(Box::new(cache_hit_rate.clone()))?;
612
613 Ok(Self {
614 cache_hits,
615 cache_misses,
616 cache_evictions,
617 cache_size_bytes,
618 cache_entries,
619 cache_hit_rate,
620 })
621 }
622}
623
624#[derive(Clone)]
626pub struct SystemMetrics {
627 pub cpu_usage_percent: Gauge,
629
630 pub memory_usage_bytes: Gauge,
632
633 pub disk_usage_bytes: GaugeVec,
635
636 pub network_io_bytes: CounterVec,
638
639 pub process_count: IntGauge,
641
642 pub uptime_seconds: Counter,
644}
645
646impl SystemMetrics {
647 pub fn new(registry: &Registry) -> Result<Self, prometheus::Error> {
648 let cpu_usage_percent = Gauge::new(
649 "portalis_cpu_usage_percent",
650 "CPU usage percentage",
651 )?;
652 registry.register(Box::new(cpu_usage_percent.clone()))?;
653
654 let memory_usage_bytes = Gauge::new(
655 "portalis_memory_usage_bytes",
656 "Memory usage in bytes",
657 )?;
658 registry.register(Box::new(memory_usage_bytes.clone()))?;
659
660 let disk_usage_bytes = GaugeVec::new(
661 Opts::new(
662 "portalis_disk_usage_bytes",
663 "Disk usage in bytes",
664 ),
665 &["mount_point"],
666 )?;
667 registry.register(Box::new(disk_usage_bytes.clone()))?;
668
669 let network_io_bytes = CounterVec::new(
670 Opts::new(
671 "portalis_network_io_bytes_total",
672 "Network I/O in bytes",
673 ),
674 &["direction", "interface"],
675 )?;
676 registry.register(Box::new(network_io_bytes.clone()))?;
677
678 let process_count = IntGauge::new(
679 "portalis_process_count",
680 "Number of active processes",
681 )?;
682 registry.register(Box::new(process_count.clone()))?;
683
684 let uptime_seconds = Counter::new(
685 "portalis_uptime_seconds_total",
686 "System uptime in seconds",
687 )?;
688 registry.register(Box::new(uptime_seconds.clone()))?;
689
690 Ok(Self {
691 cpu_usage_percent,
692 memory_usage_bytes,
693 disk_usage_bytes,
694 network_io_bytes,
695 process_count,
696 uptime_seconds,
697 })
698 }
699}
700
701pub struct Timer {
703 start: Instant,
704}
705
706impl Timer {
707 pub fn new() -> Self {
708 Self {
709 start: Instant::now(),
710 }
711 }
712
713 pub fn observe_duration(&self, histogram: &Histogram) {
714 histogram.observe(self.start.elapsed().as_secs_f64());
715 }
716
717 pub fn observe_duration_with_labels(&self, histogram: &HistogramVec, labels: &[&str]) {
718 histogram
719 .with_label_values(labels)
720 .observe(self.start.elapsed().as_secs_f64());
721 }
722
723 pub fn elapsed_secs(&self) -> f64 {
724 self.start.elapsed().as_secs_f64()
725 }
726}
727
728#[cfg(test)]
729mod tests {
730 use super::*;
731
732 #[test]
733 fn test_metrics_creation() {
734 let metrics = PortalisMetrics::new();
735 assert!(metrics.is_ok());
736 }
737
738 #[test]
739 fn test_metrics_export() {
740 let metrics = PortalisMetrics::new().unwrap();
741 let export = metrics.export();
742 assert!(export.is_ok());
743 assert!(!export.unwrap().is_empty());
744 }
745
746 #[test]
747 fn test_translation_metrics() {
748 let registry = Registry::new();
749 let metrics = TranslationMetrics::new(®istry).unwrap();
750
751 metrics.translations_total
752 .with_label_values(&["python", "wasm"])
753 .inc();
754
755 metrics.translations_success
756 .with_label_values(&["python", "wasm"])
757 .inc();
758
759 let families = registry.gather();
760 assert!(!families.is_empty());
761 }
762
763 #[test]
764 fn test_timer() {
765 let timer = Timer::new();
766 std::thread::sleep(std::time::Duration::from_millis(10));
767 let elapsed = timer.elapsed_secs();
768 assert!(elapsed >= 0.01);
769 }
770}