use std::collections::HashMap;
use std::path::PathBuf;
use crate::deploy::pod::PodPartition;
use crate::graph::{CodeGraph, NodeData};
use crate::model::{FileExtraction, FileId};
#[derive(Debug, Clone, serde::Serialize)]
pub struct FileMetrics {
pub ast_node_count: usize,
pub symbol_count: usize,
pub import_count: usize,
pub reference_count: usize,
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct PodMetrics {
pub total_ast_nodes: usize,
pub file_count: usize,
pub symbol_count: usize,
}
pub fn compute_file_metrics<F>(extractions: &[F]) -> HashMap<PathBuf, FileMetrics>
where
F: std::borrow::Borrow<FileExtraction>,
{
let mut metrics = HashMap::new();
for file in extractions {
let file = file.borrow();
metrics.insert(
file.path.clone(),
FileMetrics {
ast_node_count: file.ast_node_count,
symbol_count: file.symbols.len(),
import_count: file.imports.len(),
reference_count: file.references.len(),
},
);
}
metrics
}
pub fn compute_pod_metrics(
partition: &PodPartition,
file_metrics: &HashMap<PathBuf, FileMetrics>,
graph: &CodeGraph,
) -> Vec<PodMetrics> {
let mut fid_to_path: HashMap<FileId, PathBuf> = HashMap::new();
for (&fid, &idx) in &graph.file_to_index {
if let Some(NodeData::File(f)) = graph.graph().node_weight(idx) {
fid_to_path.insert(fid, f.path.clone());
}
}
let mut pod_metrics = Vec::with_capacity(partition.pods.len());
for pod in &partition.pods {
let mut total_ast_nodes = 0usize;
let file_count = pod.files.len();
let mut symbol_count = 0usize;
for &fid in &pod.files {
match fid_to_path
.get(&fid)
.and_then(|path| file_metrics.get(path))
{
Some(metrics) => {
total_ast_nodes = total_ast_nodes.saturating_add(metrics.ast_node_count);
symbol_count = symbol_count.saturating_add(metrics.symbol_count);
}
None => {
tracing::warn!(pod = pod.id, file = fid.to_raw(), "pod file has no metrics")
}
}
}
pod_metrics.push(PodMetrics {
total_ast_nodes,
file_count,
symbol_count,
});
}
pod_metrics
}
#[cfg(test)]
mod tests {
use super::*;
use crate::deploy::pod::{Pod, PodPartition};
use crate::graph::node::{FileNode, NodeData};
use crate::language::LangId;
use crate::model::ids::{FileId, SnapshotId};
use std::collections::HashMap;
#[test]
fn pod_metrics_total_saturates() {
let mut graph = CodeGraph::new(SnapshotId::new(1).unwrap());
let a = FileId::new(1).unwrap();
let b = FileId::new(2).unwrap();
for (fid, path) in [(a, "a.py"), (b, "b.py")] {
let idx = graph.add_node(NodeData::File(FileNode::new(
fid,
PathBuf::from(path),
LangId::Python,
SnapshotId::new(1).unwrap(),
)));
graph.file_to_index.insert(fid, idx);
}
let partition = PodPartition {
pods: vec![Pod {
id: 0,
files: vec![a, b],
language: LangId::Python,
}],
inter_pod_edges: Vec::new(),
file_languages: HashMap::from([(a, LangId::Python), (b, LangId::Python)]),
};
let file_metrics = HashMap::from([
(
PathBuf::from("a.py"),
FileMetrics {
ast_node_count: usize::MAX,
symbol_count: 0,
import_count: 0,
reference_count: 0,
},
),
(
PathBuf::from("b.py"),
FileMetrics {
ast_node_count: usize::MAX,
symbol_count: 0,
import_count: 0,
reference_count: 0,
},
),
]);
let metrics = compute_pod_metrics(&partition, &file_metrics, &graph);
assert_eq!(metrics.len(), 1);
assert_eq!(
metrics[0].total_ast_nodes,
usize::MAX,
"the aggregate must saturate, not overflow"
);
}
}