use opentelemetry::{trace::{Tracer, Span, Status}, global, KeyValue};
use std::collections::HashMap;
use std::time::Duration;
pub struct AnalyzerService {
tracer: Box<dyn Tracer>,
cache: HashMap<String, AnalysisResult>,
}
#[derive(Clone, Debug)]
pub struct AnalysisResult {
pub total_files: usize,
pub total_lines: usize,
pub languages: Vec<String>,
}
impl AnalyzerService {
pub fn new() -> Self {
Self {
tracer: global::tracer("analyzer-service"),
cache: HashMap::new(),
}
}
pub fn analyze_repository(&mut self, repo_path: &str) -> Result<AnalysisResult, String> {
let mut span = self.tracer.start("analyze_repository");
span.set_attribute(KeyValue::new("repo.path", repo_path.to_string()));
if let Some(cached) = self.check_cache(repo_path) {
span.set_attribute(KeyValue::new("cache.hit", true));
return Ok(cached);
}
let files = match self.scan_directory(repo_path) {
Ok(files) => files,
Err(e) => {
span.record_error(&e);
span.set_status(Status::error(format!("Failed to scan directory: {}", e)));
return Err(format!("Scan failed: {}", e));
}
};
span.set_attribute(KeyValue::new("files.count", files.len() as i64));
let mut total_lines = 0;
let mut languages = Vec::new();
for file in &files {
let file_result = self.analyze_file(file)?;
total_lines += file_result.lines;
if !languages.contains(&file_result.language) {
languages.push(file_result.language);
}
}
let result = AnalysisResult {
total_files: files.len(),
total_lines,
languages,
};
self.cache.insert(repo_path.to_string(), result.clone());
span.set_attribute(KeyValue::new("analysis.total_lines", total_lines as i64));
span.set_attribute(KeyValue::new("analysis.languages", languages.len() as i64));
Ok(result)
}
fn check_cache(&self, repo_path: &str) -> Option<AnalysisResult> {
let span = self.tracer.start("check_cache");
std::thread::sleep(Duration::from_millis(2));
self.cache.get(repo_path).cloned()
}
fn scan_directory(&self, path: &str) -> Result<Vec<String>, std::io::Error> {
let mut span = self.tracer.start("scan_directory");
span.set_attribute(KeyValue::new("path", path.to_string()));
std::thread::sleep(Duration::from_millis(15));
Ok(vec![
format!("{}/main.rs", path),
format!("{}/lib.rs", path),
format!("{}/config.toml", path),
])
}
fn analyze_file(&self, file_path: &str) -> Result<FileAnalysis, String> {
let mut span = self.tracer.start("analyze_file");
span.set_attribute(KeyValue::new("file.path", file_path.to_string()));
std::thread::sleep(Duration::from_millis(5));
let language = if file_path.ends_with(".rs") {
"rust"
} else if file_path.ends_with(".toml") {
"toml"
} else {
"unknown"
};
let lines = 100;
span.set_attribute(KeyValue::new("file.language", language));
span.set_attribute(KeyValue::new("file.lines", lines));
Ok(FileAnalysis {
language: language.to_string(),
lines,
})
}
pub fn generate_report(&self, results: &[AnalysisResult]) -> String {
let span = self.tracer.start("generate_report");
let total_repos = results.len();
let total_files: usize = results.iter().map(|r| r.total_files).sum();
let total_lines: usize = results.iter().map(|r| r.total_lines).sum();
std::thread::sleep(Duration::from_millis(10));
format!(
"Analysis Report:\n- Repositories: {}\n- Total Files: {}\n- Total Lines: {}",
total_repos, total_files, total_lines
)
}
}
struct FileAnalysis {
language: String,
lines: usize,
}