05_02_04_metrics_calculator - Sistema de Métricas Cuantitativas¶
📊 Propósito¶
El Metrics Calculator computa estadísticas numéricas del grafo revelando características estructurales, puntos críticos y oportunidades de optimización. Transforma la topología del grafo en insights accionables.
🏗️ Arquitectura¶
Componentes Principales¶
05_02_04_metrics_calculator/
├── metrics_engine.hpp # Calculadores de métricas
├── test_metrics.cpp # Tests unitarios
└── README.md # Documentación
Clases Implementadas¶
- NodeMetricsCalculator - Métricas por nodo
- GraphMetricsCalculator - Métricas del grafo completo
- MetricAlerter - Sistema de alertas por thresholds
📈 Node Metrics (Métricas por Nodo)¶
1. Degree Metrics¶
In-Degree: Cuántos módulos dependen de este Out-Degree: De cuántos módulos depende este Total Degree: in + out
NodeMetricsCalculator calc(graph);
auto metrics = calc.calculate("svf_filter");
std::cout << "In-degree: " << metrics.in_degree << "\n";
std::cout << "Out-degree: " << metrics.out_degree << "\n";
std::cout << "Total degree: " << metrics.total_degree << "\n";
Interpretación: - Alto in-degree = Módulo muy reutilizado (bueno para L0/L1, malo para L3) - Alto out-degree = Muchas dependencias (complejo, acoplado) - Bajo total degree = Módulo aislado o simple
2. Centrality Metrics¶
Betweenness Centrality¶
Definición: Frecuencia con que un nodo aparece en shortest paths entre otros nodos.
Fórmula:
Donde σₛₜ = número de shortest paths de s a t, σₛₜ(v) = cuántos pasan por vUso:
auto metrics = calc.calculate("add_kernel");
std::cout << "Betweenness: " << metrics.betweenness << "\n";
Interpretación: - Alto (>0.3) = Nodo crítico, bottleneck arquitectónico - Medio (0.1-0.3) = Importante para conectividad - Bajo (<0.1) = Nodo periférico
Ejemplo:
add_kernel → betweenness = 0.45
→ Crítico: eliminar rompe muchos paths
→ Optimizar primero
legacy_module → betweenness = 0.02
→ Periférico: seguro de refactorizar
Closeness Centrality¶
Definición: Inversa de la distancia promedio a todos los demás nodos.
Fórmula:
Interpretación: - Alto = Módulo central, acceso rápido a todo el grafo - Bajo = Módulo alejado, en "periferia" del grafo
3. Clustering Coefficient¶
Definición: Qué tan conectados están los vecinos entre sí.
Fórmula:
Uso:
Interpretación: - Alto (>0.5) = Dense cluster (vecinos muy conectados) - Bajo (<0.2) = Hub (vecinos no conectados)
Ejemplo:
filter_hub → clustering = 0.1
→ Hub central con vecinos independientes
dsp_cluster → clustering = 0.8
→ Grupo denso de módulos interdependientes
4. DSP-Specific Metrics¶
Total CPU Impact¶
Definición: CPU de este nodo + todas sus dependencias (transitive).
Interpretación: - Costo real de usar este módulo - Incluye toda la cadena de dependencias
Ejemplo:
poly_synth:
- Direct CPU: 2000 cycles
- Dependencies: 5000 cycles
- Total Impact: 7000 cycles ← Real cost
Total Latency Impact¶
Definición: Máxima latencia acumulada en cadena de dependencias.
Ejemplo:
Dependency Depth¶
Definición: Profundidad máxima del árbol de dependencias.
Interpretación: - 0 = Leaf node (sin dependencias) - 1-2 = Simple - 3-4 = Moderado - 5+ = Muy complejo
Reusability Score¶
Definición: In-degree ponderado por nivel jerárquico.
Fórmula:
reusability = in_degree * level_weight
Weights:
L0_KERNEL → 4.0 (highly reusable)
L1_ATOM → 2.0
L2_CELL → 0.5
L3_ENGINE → 0.1 (shouldn't be dependency)
Interpretación: - Alto score = Bien reutilizado y en nivel apropiado - Bajo score = Poco usado o nivel inapropiado
Ejemplo:
add_kernel (L0):
- in_degree: 15
- weight: 4.0
- score: 60.0 ✅ Excellent
poly_synth (L3):
- in_degree: 3
- weight: 0.1
- score: 0.3 ⚠️ Engines shouldn't be dependencies!
🌐 Graph-Level Metrics¶
1. Density¶
Definición: Ratio de aristas existentes vs posibles.
Fórmula:
Uso:
GraphMetricsCalculator calc(graph);
auto metrics = calc.calculate();
std::cout << "Density: " << metrics.density << "\n";
Interpretación: - 0.0 = Grafo vacío - 0.01-0.05 = Típico DSP (sparse) - 0.10+ = Denso (muchas dependencias) - 1.0 = Completo (todos con todos)
2. Diameter¶
Definición: Máxima distancia entre cualquier par de nodos.
Interpretación: - Pequeño (2-3) = Grafo compacto - Medio (4-6) = Normal - Grande (7+) = Cadenas largas, builds lentos
3. Average Path Length¶
Definición: Promedio de distancia entre todos los pares.
Interpretación: - Indica "small-world" properties - Bajo = módulos bien conectados - Alto = arquitectura fragmentada
4. DSP-Specific Graph Metrics¶
Total CPU Budget¶
Suma de CPU de todos los módulos.Max Latency Path¶
Peor caso de latencia en el sistema.Average Reusability¶
Promedio de scores de reusabilidad.🚨 Sistema de Alertas¶
Thresholds Configurables¶
MetricAlerter::Thresholds thresholds;
// Node-level
thresholds.max_dependencies = 20;
thresholds.max_betweenness = 0.5;
thresholds.max_cpu_impact = 5000;
thresholds.max_dependency_depth = 5;
// Graph-level
thresholds.min_density = 0.005;
thresholds.max_density = 0.10;
thresholds.max_diameter = 10;
thresholds.min_avg_reusability = 1.0;
MetricAlerter alerter(thresholds);
Severity Levels¶
- ℹ️ INFO - Información, no crítico
- ⚠️ WARNING - Atención requerida
- ❌ ERROR - Problema serio
- 🔴 CRITICAL - Acción inmediata necesaria
Uso¶
// Check node
NodeMetricsCalculator node_calc(graph);
auto node_metrics = node_calc.calculate("svf_filter");
auto alerts = alerter.check_node(node_metrics);
for (const auto& alert : alerts) {
std::cout << alert.to_string() << "\n";
}
// Check graph
GraphMetricsCalculator graph_calc(graph);
auto graph_metrics = graph_calc.calculate();
auto graph_alerts = alerter.check_graph(graph_metrics);
Output Ejemplo¶
⚠️ WARNING: out_degree [svf_filter] = 25 (threshold: 20)
Too many dependencies - consider refactoring
ℹ️ INFO: betweenness [add_kernel] = 0.62 (threshold: 0.5)
High centrality - critical node for architecture
❌ ERROR: cpu_impact [poly_synth] = 7500 (threshold: 5000)
High CPU impact - performance bottleneck
⚠️ WARNING: density = 0.15 (threshold: 0.10)
Dense graph - too many dependencies
⚠️ WARNING: diameter = 12 (threshold: 10)
Long dependency chains - slow builds
📊 Casos de Uso Prácticos¶
Caso 1: Architecture Health Check¶
GraphMetricsCalculator calc(graph);
auto metrics = calc.calculate();
std::cout << "=== Architecture Health Report ===\n\n";
std::cout << metrics.to_string();
// Check thresholds
MetricAlerter alerter;
auto alerts = alerter.check_graph(metrics);
if (alerts.empty()) {
std::cout << "\n✅ All metrics within healthy ranges\n";
} else {
std::cout << "\n⚠️ Issues detected:\n";
for (const auto& alert : alerts) {
std::cout << alert.to_string() << "\n";
}
}
Caso 2: Module Optimization Priority¶
// Calculate metrics for all nodes
NodeMetricsCalculator calc(graph);
auto all_metrics = calc.calculate_all();
// Sort by CPU impact (descending)
std::vector<std::pair<UUID, uint32_t>> by_cpu;
for (const auto& [id, m] : all_metrics) {
by_cpu.emplace_back(id, m.total_cpu_impact);
}
std::sort(by_cpu.begin(), by_cpu.end(),
[](auto& a, auto& b) { return a.second > b.second; });
std::cout << "Top 10 modules by CPU impact:\n";
for (size_t i = 0; i < std::min(by_cpu.size(), size_t(10)); ++i) {
auto node = graph.get_node(by_cpu[i].first);
std::cout << (i+1) << ". " << node->label
<< " - " << by_cpu[i].second << " cycles\n";
}
Caso 3: Detect Architectural Smells¶
NodeMetricsCalculator calc(graph);
for (const auto& [id, node] : graph.get_nodes()) {
auto metrics = calc.calculate(id);
// Smell 1: L3 Engine used as dependency
if (node->level == HierarchyLevel::L3_ENGINE && metrics.in_degree > 0) {
std::cout << "🚨 SMELL: Engine used as dependency: "
<< node->label << "\n";
}
// Smell 2: God object (too many dependencies)
if (metrics.out_degree > 30) {
std::cout << "🚨 SMELL: God object: " << node->label
<< " (" << metrics.out_degree << " deps)\n";
}
// Smell 3: Unused module (no dependents)
if (metrics.in_degree == 0 && metrics.out_degree > 0) {
std::cout << "⚠️ SMELL: Unused module: " << node->label << "\n";
}
// Smell 4: Critical bottleneck
if (metrics.betweenness > 0.7) {
std::cout << "⚠️ SMELL: Critical bottleneck: " << node->label
<< " (betweenness: " << metrics.betweenness << ")\n";
}
}
Caso 4: Reusability Report¶
NodeMetricsCalculator calc(graph);
// Group by level
std::map<HierarchyLevel, std::vector<double>> reusability_by_level;
for (const auto& [id, node] : graph.get_nodes()) {
auto metrics = calc.calculate(id);
reusability_by_level[node->level].push_back(metrics.reusability_score);
}
std::cout << "=== Reusability Report ===\n\n";
for (auto level : {HierarchyLevel::L0_KERNEL, HierarchyLevel::L1_ATOM,
HierarchyLevel::L2_CELL, HierarchyLevel::L3_ENGINE}) {
auto& scores = reusability_by_level[level];
if (scores.empty()) continue;
double avg = std::accumulate(scores.begin(), scores.end(), 0.0) / scores.size();
std::cout << to_string(level) << ":\n";
std::cout << " Avg Reusability: " << avg << "\n";
std::cout << " Modules: " << scores.size() << "\n\n";
}
🧪 Testing¶
TEST_CASE("Node metrics calculation") {
auto graph = create_test_graph();
NodeMetricsCalculator calc(graph);
auto metrics = calc.calculate("central_node");
REQUIRE(metrics.in_degree > 0);
REQUIRE(metrics.betweenness >= 0.0);
REQUIRE(metrics.betweenness <= 1.0);
}
TEST_CASE("Graph metrics calculation") {
auto graph = create_test_graph();
GraphMetricsCalculator calc(graph);
auto metrics = calc.calculate();
REQUIRE(metrics.density >= 0.0);
REQUIRE(metrics.density <= 1.0);
REQUIRE(metrics.node_count == graph.get_nodes().size());
}
TEST_CASE("Alerting system") {
MetricAlerter::Thresholds thresholds;
thresholds.max_dependencies = 5;
MetricAlerter alerter(thresholds);
NodeMetrics metrics;
metrics.out_degree = 10;
auto alerts = alerter.check_node(metrics);
REQUIRE(!alerts.empty());
REQUIRE(alerts[0].severity == MetricAlert::Severity::WARNING);
}
📈 Complejidad¶
| Operación | Complejidad | Notas |
|---|---|---|
| Degree metrics | O(1) | Ya calculado en grafo |
| Betweenness | O(V³) | BFS desde todos los nodos |
| Closeness | O(V²) | BFS desde un nodo |
| Clustering | O(k²) | k = vecinos |
| CPU/Latency impact | O(V+E) | DFS transitive |
| Graph density | O(1) | Simple cálculo |
| Diameter | O(V²) | BFS desde todos |
🎯 Normal Ranges (DSP Architecture)¶
| Métrica | Rango Normal | Alerta si |
|---|---|---|
| Density | 0.01 - 0.05 | >0.10 (muy denso) |
| Avg Degree | 2 - 5 | >10 (muy acoplado) |
| Diameter | 3 - 8 | >10 (cadenas largas) |
| Betweenness | 0.0 - 0.3 | >0.5 (bottleneck) |
| Out-degree | 0 - 15 | >20 (demasiadas deps) |
| Dep Depth | 0 - 4 | >5 (muy complejo) |
| Reusability (L0) | 10+ | <5 (poco usado) |
| Reusability (L3) | <1 | >2 (mal nivel) |
🚀 Próximos Pasos¶
✅ Implementado: - Node metrics (degree, centrality, clustering) - Graph metrics (density, diameter, path length) - DSP-specific metrics (CPU, latency, reusability) - Threshold-based alerting - Multiple severity levels
⏳ Futuras Mejoras: - PageRank centrality - Community detection (Louvain algorithm) - Temporal metrics (evolución en el tiempo) - Predictive metrics (ML-based) - Visual dashboards (web UI)
Status: ✅ Metrics Calculator Completo Métricas implementadas: 15+ (node + graph + DSP) Alerting: 4 severity levels con thresholds configurables