Skip to main content

portalis_core/
metrics.rs

1//! Central Metrics Registry for Portalis
2//! Week 33 - Phase 4: Monitoring and Observability
3//!
4//! This module provides a comprehensive metrics system for tracking:
5//! - Translation success/failure rates
6//! - Per-agent execution time
7//! - Pipeline phase duration
8//! - WASM performance metrics
9//! - Error categorization
10//! - Cache hit rates
11
12use 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/// Central metrics registry for the Portalis platform
20#[derive(Clone)]
21pub struct PortalisMetrics {
22    /// Prometheus registry
23    pub registry: Arc<Registry>,
24
25    /// Translation metrics
26    pub translation: TranslationMetrics,
27
28    /// Agent execution metrics
29    pub agents: AgentMetrics,
30
31    /// Pipeline metrics
32    pub pipeline: PipelineMetrics,
33
34    /// WASM metrics
35    pub wasm: WasmMetrics,
36
37    /// Error metrics
38    pub errors: ErrorMetrics,
39
40    /// Cache metrics
41    pub cache: CacheMetrics,
42
43    /// System metrics
44    pub system: SystemMetrics,
45}
46
47impl PortalisMetrics {
48    /// Create a new metrics registry with all metric families
49    pub fn new() -> Result<Self, prometheus::Error> {
50        let registry = Arc::new(Registry::new());
51
52        Ok(Self {
53            translation: TranslationMetrics::new(&registry)?,
54            agents: AgentMetrics::new(&registry)?,
55            pipeline: PipelineMetrics::new(&registry)?,
56            wasm: WasmMetrics::new(&registry)?,
57            errors: ErrorMetrics::new(&registry)?,
58            cache: CacheMetrics::new(&registry)?,
59            system: SystemMetrics::new(&registry)?,
60            registry: registry.clone(),
61        })
62    }
63
64    /// Export metrics in Prometheus text format
65    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/// Translation-specific metrics
76#[derive(Clone)]
77pub struct TranslationMetrics {
78    /// Total translations attempted
79    pub translations_total: IntCounterVec,
80
81    /// Successful translations
82    pub translations_success: IntCounterVec,
83
84    /// Failed translations
85    pub translations_failed: IntCounterVec,
86
87    /// Translation duration histogram
88    pub translation_duration: HistogramVec,
89
90    /// Lines of code translated
91    pub translation_loc: HistogramVec,
92
93    /// Translation complexity score
94    pub translation_complexity: GaugeVec,
95
96    /// Success rate (computed metric)
97    pub success_rate: GaugeVec,
98
99    /// Current translations in progress
100    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/// Per-agent execution metrics
193#[derive(Clone)]
194pub struct AgentMetrics {
195    /// Agent execution count
196    pub agent_executions: IntCounterVec,
197
198    /// Agent execution duration
199    pub agent_duration: HistogramVec,
200
201    /// Agent success/failure
202    pub agent_status: IntCounterVec,
203
204    /// Agent resource usage
205    pub agent_memory_bytes: GaugeVec,
206
207    /// Agent CPU usage
208    pub agent_cpu_percent: GaugeVec,
209
210    /// Active agents
211    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/// Pipeline phase metrics
283#[derive(Clone)]
284pub struct PipelineMetrics {
285    /// Phase duration
286    pub phase_duration: HistogramVec,
287
288    /// Phase success/failure
289    pub phase_status: IntCounterVec,
290
291    /// Pipeline end-to-end duration
292    pub pipeline_duration: HistogramVec,
293
294    /// Active pipelines
295    pub pipelines_active: IntGauge,
296
297    /// Pipeline queue depth
298    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/// WASM-specific performance metrics
358#[derive(Clone)]
359pub struct WasmMetrics {
360    /// WASM compilation time
361    pub wasm_compile_duration: HistogramVec,
362
363    /// WASM binary size
364    pub wasm_binary_size_bytes: HistogramVec,
365
366    /// WASM optimization level
367    pub wasm_optimization_level: GaugeVec,
368
369    /// WASM execution time
370    pub wasm_execution_duration: HistogramVec,
371
372    /// WASM memory usage
373    pub wasm_memory_bytes: GaugeVec,
374
375    /// WASM module count
376    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/// Error categorization and tracking metrics
447#[derive(Clone)]
448pub struct ErrorMetrics {
449    /// Total errors by category
450    pub errors_total: IntCounterVec,
451
452    /// Parse errors
453    pub parse_errors: IntCounterVec,
454
455    /// Translation errors
456    pub translation_errors: IntCounterVec,
457
458    /// Compilation errors
459    pub compilation_errors: IntCounterVec,
460
461    /// Runtime errors
462    pub runtime_errors: IntCounterVec,
463
464    /// Error recovery attempts
465    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/// Cache performance metrics
536#[derive(Clone)]
537pub struct CacheMetrics {
538    /// Cache hits
539    pub cache_hits: IntCounterVec,
540
541    /// Cache misses
542    pub cache_misses: IntCounterVec,
543
544    /// Cache evictions
545    pub cache_evictions: IntCounterVec,
546
547    /// Cache size
548    pub cache_size_bytes: GaugeVec,
549
550    /// Cache entry count
551    pub cache_entries: GaugeVec,
552
553    /// Cache hit rate (computed)
554    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/// System-level metrics
625#[derive(Clone)]
626pub struct SystemMetrics {
627    /// CPU usage
628    pub cpu_usage_percent: Gauge,
629
630    /// Memory usage
631    pub memory_usage_bytes: Gauge,
632
633    /// Disk usage
634    pub disk_usage_bytes: GaugeVec,
635
636    /// Network I/O
637    pub network_io_bytes: CounterVec,
638
639    /// Process count
640    pub process_count: IntGauge,
641
642    /// Uptime
643    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
701/// Helper for timing operations
702pub 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(&registry).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}