use std::collections::HashMap;
use std::path::Path;
use crate::core::telemetry::correlator::{CorrelationKey, CorrelationResult};
use crate::core::telemetry::types::FunctionMetrics;
use crate::utils::error::ContextCreatorError;
pub struct TelemetryEnricher {
correlation_result: CorrelationResult,
}
impl TelemetryEnricher {
pub fn new(correlation_result: CorrelationResult) -> Self {
Self { correlation_result }
}
pub fn generate_function_comment(&self, key: &CorrelationKey) -> Option<String> {
let metrics = self.correlation_result.function_metrics.get(key)?;
let p50 = metrics.percentile_immutable(50.0);
let p95 = metrics.percentile_immutable(95.0);
let p99 = metrics.percentile_immutable(99.0);
let mut comment = String::from("<!-- OpenTelemetry Metrics\n");
comment.push_str(&format!("Calls: {} (last 24h)\n", metrics.call_count));
if !metrics.latencies.is_empty() {
comment.push_str("Latency: ");
if let Some(p50) = p50 {
comment.push_str(&format!("p50={p50:.0}ms"));
}
if let Some(p95) = p95 {
comment.push_str(&format!(", p95={p95:.0}ms"));
}
if let Some(p99) = p99 {
comment.push_str(&format!(", p99={p99:.0}ms"));
}
comment.push('\n');
}
if metrics.error_count > 0 {
let error_rate = (metrics.error_count as f64 / metrics.call_count as f64) * 100.0;
comment.push_str(&format!("Error Rate: {error_rate:.1}%\n"));
if let Some(error_msg) = &metrics.common_error {
comment.push_str(&format!("Most Common Error: \"{error_msg}\"\n"));
}
}
comment.push_str("-->");
Some(comment)
}
pub fn enrich_file(
&self,
file_path: &Path,
content: &str,
) -> Result<String, ContextCreatorError> {
let mut enriched = String::new();
let file_metrics: Vec<(&CorrelationKey, &FunctionMetrics)> = self
.correlation_result
.function_metrics
.iter()
.filter(|(key, _)| key.file_path == file_path)
.collect();
if !file_metrics.is_empty() {
enriched.push_str("<!-- OpenTelemetry Metrics Summary\n");
enriched.push_str(&format!("File: {}\n", file_path.display()));
enriched.push_str(&format!("Functions with metrics: {}\n", file_metrics.len()));
enriched.push_str("-->\n\n");
}
enriched.push_str(content);
Ok(enriched)
}
pub fn generate_summary(&self) -> String {
let mut summary = String::from("# OpenTelemetry Metrics Summary\n\n");
summary.push_str(&format!(
"Total functions with metrics: {}\n",
self.correlation_result.function_metrics.len()
));
summary.push_str(&format!(
"Uncorrelated spans: {}\n\n",
self.correlation_result.uncorrelated_spans.len()
));
let mut by_file: HashMap<&Path, Vec<(&CorrelationKey, &FunctionMetrics)>> = HashMap::new();
for (key, metrics) in &self.correlation_result.function_metrics {
by_file
.entry(&key.file_path)
.or_default()
.push((key, metrics));
}
for (file_path, functions) in by_file {
summary.push_str(&format!("## {}\n", file_path.display()));
for (key, metrics) in functions {
summary.push_str(&format!("\n### Function: {}\n", key.function_name));
if let Some(line) = key.line_number {
summary.push_str(&format!("Line: {line}\n"));
}
summary.push_str(&format!("- Calls: {}\n", metrics.call_count));
if !metrics.latencies.is_empty() {
if let Some(p50) = metrics.percentile_immutable(50.0) {
summary.push_str(&format!("- Latency p50: {p50:.0}ms\n"));
}
if let Some(p95) = metrics.percentile_immutable(95.0) {
summary.push_str(&format!("- Latency p95: {p95:.0}ms\n"));
}
}
if metrics.error_count > 0 {
let error_rate =
(metrics.error_count as f64 / metrics.call_count as f64) * 100.0;
summary.push_str(&format!("- Error rate: {error_rate:.1}%\n"));
}
}
summary.push('\n');
}
summary
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
fn create_test_correlation_result() -> CorrelationResult {
let mut function_metrics = HashMap::new();
let key = CorrelationKey {
file_path: PathBuf::from("/project/src/api/handlers.rs"),
function_name: "process_payment".to_string(),
line_number: Some(42),
};
let mut metrics = FunctionMetrics::new();
metrics.call_count = 1234;
for latency in [45.0, 50.0, 120.0, 130.0, 250.0] {
metrics.add_latency(latency);
}
metrics.error_count = 4;
metrics.common_error = Some("Payment gateway timeout".to_string());
function_metrics.insert(key, metrics);
CorrelationResult {
function_metrics,
correlated_count: 1,
uncorrelated_spans: vec![],
}
}
#[test]
fn test_generate_function_comment() {
let result = create_test_correlation_result();
let enricher = TelemetryEnricher::new(result);
let key = CorrelationKey {
file_path: PathBuf::from("/project/src/api/handlers.rs"),
function_name: "process_payment".to_string(),
line_number: Some(42),
};
let comment = enricher.generate_function_comment(&key).unwrap();
println!("Generated comment:\n{comment}");
assert!(comment.contains("<!-- OpenTelemetry Metrics"));
assert!(comment.contains("Calls: 1234"));
assert!(comment.contains("p50=120ms")); assert!(comment.contains("p95=250ms")); assert!(comment.contains("Error Rate: 0.3%"));
assert!(comment.contains("Payment gateway timeout"));
assert!(comment.contains("-->"));
}
#[test]
fn test_generate_function_comment_no_errors() {
let mut result = create_test_correlation_result();
if let Some(metrics) = result.function_metrics.values_mut().next() {
metrics.error_count = 0;
metrics.common_error = None;
}
let enricher = TelemetryEnricher::new(result);
let key = CorrelationKey {
file_path: PathBuf::from("/project/src/api/handlers.rs"),
function_name: "process_payment".to_string(),
line_number: Some(42),
};
let comment = enricher.generate_function_comment(&key).unwrap();
assert!(!comment.contains("Error Rate"));
assert!(!comment.contains("Most Common Error"));
}
#[test]
fn test_enrich_file() {
let result = create_test_correlation_result();
let enricher = TelemetryEnricher::new(result);
let file_path = PathBuf::from("/project/src/api/handlers.rs");
let content = "async fn process_payment() -> Result<()> {\n Ok(())\n}";
let enriched = enricher.enrich_file(&file_path, content).unwrap();
assert!(enriched.contains("<!-- OpenTelemetry Metrics Summary"));
assert!(enriched.contains("Functions with metrics: 1"));
assert!(enriched.contains(content));
}
#[test]
fn test_generate_summary() {
let result = create_test_correlation_result();
let enricher = TelemetryEnricher::new(result);
let summary = enricher.generate_summary();
assert!(summary.contains("# OpenTelemetry Metrics Summary"));
assert!(summary.contains("Total functions with metrics: 1"));
assert!(summary.contains("## /project/src/api/handlers.rs"));
assert!(summary.contains("### Function: process_payment"));
assert!(summary.contains("Line: 42"));
assert!(summary.contains("Calls: 1234"));
assert!(summary.contains("Latency p50: 120ms")); assert!(summary.contains("Error rate: 0.3%"));
}
}