Skip to content

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

  1. NodeMetricsCalculator - Métricas por nodo
  2. GraphMetricsCalculator - Métricas del grafo completo
  3. 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:

betweenness(v) = Σ (σₛₜ(v) / σₛₜ)
Donde σₛₜ = número de shortest paths de s a t, σₛₜ(v) = cuántos pasan por v

Uso:

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:

closeness(v) = (n-1) / Σ distance(v, u)

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:

C(v) = edges_between_neighbors / possible_edges

Uso:

std::cout << "Clustering: " << metrics.clustering_coeff << "\n";

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).

std::cout << "Total CPU Impact: "
          << metrics.total_cpu_impact << " cycles\n";

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.

std::cout << "Total Latency: "
          << metrics.total_latency_impact << " samples\n";

Ejemplo:

reverb_engine:
  - Direct latency: 256 samples
  - Filter chain: 3 samples
  - Total: 259 samples

Dependency Depth

Definición: Profundidad máxima del árbol de dependencias.

std::cout << "Dependency Depth: " << metrics.dependency_depth << "\n";

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:

density = actual_edges / (n * (n-1))

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

std::cout << "Total CPU: " << metrics.total_cpu_budget << " cycles\n";
Suma de CPU de todos los módulos.

Max Latency Path

std::cout << "Max Latency Path: " << metrics.max_latency_path << " samples\n";
Peor caso de latencia en el sistema.

Average Reusability

std::cout << "Avg Reusability: " << metrics.avg_reusability << "\n";
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