Skip to content

05_02_09 - Live Monitoring

📋 Descripción General

Sistema de monitoreo en tiempo real del grafo de dependencias. Detecta cambios en el catálogo, reconstruye el grafo automáticamente y notifica a clientes conectados (CLI, web browsers, IDEs).

🎯 Objetivos

  1. File Watching con detección de cambios en catálogo JSON
  2. Hot Reload del grafo sin reiniciar aplicación
  3. Change Detection precisa (qué cambió exactamente)
  4. WebSocket Server para notificaciones en tiempo real
  5. Multiple Clients - CLI, web, IDE plugins

🏗️ Arquitectura

LiveMonitor
├── FileWatcher (std::filesystem polling)
├── GraphReloader (incremental rebuild)
├── ChangeDetector (diff analysis)
├── NotificationServer (WebSocket)
└── ClientManager (multiple subscribers)

🔄 Flujo de Trabajo

1. FileWatcher detecta cambio en catalog.json
2. GraphReloader reconstruye grafo
3. ChangeDetector compara old vs new
4. NotificationServer broadcast a clientes
5. Clientes reciben update JSON y refrescan UI

🔧 Uso Básico

Monitoreo Standalone

#include "live_monitor.hpp"

using namespace audio_lab::dependency_graph;

// Crear monitor
LiveMonitor monitor("path/to/catalog.json");

// Registrar callback para cambios
monitor.on_change([](const GraphChange& change) {
    std::cout << "Graph changed!\n";
    std::cout << "  Nodes added: " << change.nodes_added.size() << "\n";
    std::cout << "  Nodes removed: " << change.nodes_removed.size() << "\n";
    std::cout << "  Edges added: " << change.edges_added.size() << "\n";
    std::cout << "  Edges removed: " << change.edges_removed.size() << "\n";
});

// Iniciar monitoreo
monitor.start();

std::cout << "Monitoring catalog for changes. Press Ctrl+C to stop.\n";

// Keep alive
while (true) {
    std::this_thread::sleep_for(std::chrono::seconds(1));
}

Monitoreo con WebSocket Server

#include "live_monitor.hpp"

using namespace audio_lab::dependency_graph;

// Crear monitor con servidor web
LiveMonitor monitor("catalog.json");

// Configurar WebSocket server
monitor.enable_websocket_server(8080);  // Puerto 8080

// Iniciar
monitor.start();

std::cout << "WebSocket server listening on ws://localhost:8080\n";
std::cout << "Connect from browser or CLI client.\n";

// Keep alive
while (true) {
    std::this_thread::sleep_for(std::chrono::seconds(1));
}

Cliente WebSocket (JavaScript)

<!DOCTYPE html>
<html>
<head>
    <title>Dependency Graph Live Monitor</title>
</head>
<body>
    <h1>Dependency Graph - Live Monitor</h1>
    <div id="status">Connecting...</div>
    <div id="graph-stats"></div>
    <div id="recent-changes"></div>

    <script>
        const ws = new WebSocket('ws://localhost:8080');

        ws.onopen = () => {
            document.getElementById('status').textContent = '✅ Connected';
        };

        ws.onmessage = (event) => {
            const change = JSON.parse(event.data);

            console.log('Graph update received:', change);

            // Update stats
            document.getElementById('graph-stats').innerHTML = `
                <h2>Current Graph</h2>
                <ul>
                    <li>Nodes: ${change.current_node_count}</li>
                    <li>Edges: ${change.current_edge_count}</li>
                </ul>
            `;

            // Show recent changes
            const changesHtml = `
                <h2>Recent Changes</h2>
                <ul>
                    <li>Nodes added: ${change.nodes_added.length}</li>
                    <li>Nodes removed: ${change.nodes_removed.length}</li>
                    <li>Nodes modified: ${change.nodes_modified.length}</li>
                    <li>Edges added: ${change.edges_added.length}</li>
                    <li>Edges removed: ${change.edges_removed.length}</li>
                </ul>
            `;

            document.getElementById('recent-changes').innerHTML = changesHtml;
        };

        ws.onerror = (error) => {
            document.getElementById('status').textContent = '❌ Error';
            console.error('WebSocket error:', error);
        };

        ws.onclose = () => {
            document.getElementById('status').textContent = '⚠️ Disconnected';
        };
    </script>
</body>
</html>

CLI Client (C++)

#include "websocket_client.hpp"

using namespace audio_lab::dependency_graph;

// Conectar al servidor
WebSocketClient client("ws://localhost:8080");

client.on_message([](const std::string& json) {
    // Parsear cambio
    auto change = parse_graph_change(json);

    // Mostrar en terminal
    std::cout << "\n🔄 Graph Update Received\n";
    std::cout << "━━━━━━━━━━━━━━━━━━━━━━━━\n";

    if (!change.nodes_added.empty()) {
        std::cout << "✅ Nodes Added (" << change.nodes_added.size() << "):\n";
        for (const auto& node : change.nodes_added) {
            std::cout << "  + " << node.label << " (" << node.id << ")\n";
        }
    }

    if (!change.nodes_removed.empty()) {
        std::cout << "❌ Nodes Removed (" << change.nodes_removed.size() << "):\n";
        for (const auto& id : change.nodes_removed) {
            std::cout << "  - " << id << "\n";
        }
    }

    if (!change.nodes_modified.empty()) {
        std::cout << "📝 Nodes Modified (" << change.nodes_modified.size() << "):\n";
        for (const auto& mod : change.nodes_modified) {
            std::cout << "  ~ " << mod.id << "\n";
            for (const auto& prop : mod.changed_properties) {
                std::cout << "      " << prop.first << ": ";
                std::cout << prop.second.old_value << " → " << prop.second.new_value << "\n";
            }
        }
    }

    std::cout << "\n";
});

client.connect();

// Keep alive
while (true) {
    std::this_thread::sleep_for(std::chrono::seconds(1));
}

📊 Tipos de Cambios Detectados

1. Cambios en Nodos

struct NodeChange {
    UUID id;
    ChangeType type;  // ADDED, REMOVED, MODIFIED

    // Para MODIFIED
    std::unordered_map<std::string, PropertyChange> changed_properties;
};

struct PropertyChange {
    std::string old_value;
    std::string new_value;

    bool is_significant() const {
        // CPU/Latency > 20% = significativo
        // Cambio de status/level = significativo
        // Cambio de versión = moderado
    }
};

Propiedades Monitoreadas: - label - Nombre del módulo - level - Nivel jerárquico - category - Categoría funcional - status - Estado de desarrollo - cpu_cycles - Rendimiento CPU - latency_samples - Latencia - version - Versión semver - author - Creador

2. Cambios en Aristas

struct EdgeChange {
    UUID source;
    UUID target;
    ChangeType type;  // ADDED, REMOVED, MODIFIED

    // Para MODIFIED
    DependencyType old_type;
    DependencyType new_type;
};

3. Cambios Estructurales

struct StructuralChange {
    bool topology_changed{false};       // Nuevas/eliminadas conexiones
    bool hierarchy_violated{false};     // Violación de jerarquía introducida
    bool new_cycles_introduced{false};  // Nuevos ciclos detectados

    std::vector<Cycle> new_cycles;
    std::vector<std::string> hierarchy_violations;
};

🔍 Detección de Cambios

Algoritmo de Diff

GraphChange detect_changes(const Graph& old_graph, const Graph& new_graph) {
    GraphChange change;

    // 1. Detectar nodos añadidos/eliminados
    std::unordered_set<UUID> old_ids, new_ids;
    for (auto& [id, _] : old_graph.get_nodes()) old_ids.insert(id);
    for (auto& [id, _] : new_graph.get_nodes()) new_ids.insert(id);

    for (const auto& id : new_ids) {
        if (!old_ids.count(id)) {
            change.nodes_added.push_back(new_graph.get_node(id));
        }
    }

    for (const auto& id : old_ids) {
        if (!new_ids.count(id)) {
            change.nodes_removed.push_back(id);
        }
    }

    // 2. Detectar modificaciones en nodos comunes
    for (const auto& id : old_ids) {
        if (new_ids.count(id)) {
            auto old_node = old_graph.get_node(id);
            auto new_node = new_graph.get_node(id);

            auto node_change = detect_node_changes(old_node, new_node);
            if (node_change.has_changes()) {
                change.nodes_modified.push_back(node_change);
            }
        }
    }

    // 3. Detectar cambios en aristas
    // ... similar para edges

    // 4. Detectar cambios estructurales
    change.structural = detect_structural_changes(old_graph, new_graph);

    return change;
}

Niveles de Severidad

enum class ChangeSeverity {
    TRIVIAL,      // Cambio de comentario, autor, etc.
    MODERATE,     // Cambio de versión, metadata
    SIGNIFICANT,  // CPU/Latency > 20%, cambio de category
    CRITICAL      // Cambio de level, status, nuevos ciclos
};

📡 Protocolo WebSocket

Mensajes del Servidor → Cliente

1. Graph Update:

{
    "type": "graph_update",
    "timestamp": "2025-10-10T14:30:00Z",
    "change": {
        "nodes_added": [
            {
                "id": "new_module",
                "label": "New Module",
                "level": "L1_ATOM",
                "category": "FILTER",
                "cpu_cycles": 50
            }
        ],
        "nodes_removed": ["old_module"],
        "nodes_modified": [
            {
                "id": "existing_module",
                "changes": {
                    "cpu_cycles": {
                        "old": 100,
                        "new": 120
                    }
                }
            }
        ],
        "edges_added": [
            {"source": "module_a", "target": "module_b"}
        ],
        "edges_removed": [],
        "structural": {
            "topology_changed": true,
            "new_cycles_introduced": false
        }
    },
    "current_stats": {
        "node_count": 42,
        "edge_count": 87,
        "density": 0.05
    }
}

2. Error:

{
    "type": "error",
    "message": "Failed to parse catalog.json",
    "details": "JSON syntax error at line 42"
}

3. Heartbeat:

{
    "type": "heartbeat",
    "timestamp": "2025-10-10T14:30:00Z"
}

Mensajes del Cliente → Servidor

1. Subscribe:

{
    "type": "subscribe",
    "client_id": "web_client_123",
    "filters": {
        "severity": ["SIGNIFICANT", "CRITICAL"],
        "node_levels": ["L2_CELL", "L3_ENGINE"]
    }
}

2. Request Current State:

{
    "type": "request_state"
}

3. Ping:

{
    "type": "ping"
}

🎨 Casos de Uso

1. Hot Reload en Desarrollo

// Durante desarrollo, el catálogo cambia frecuentemente
LiveMonitor monitor("catalog.json");

monitor.on_change([&app](const GraphChange& change) {
    // Reconstruir visualización sin reiniciar app
    app.update_visualization(change);

    // Mostrar notificación
    app.show_notification("Graph updated: " +
        std::to_string(change.nodes_added.size()) + " nodes added");
});

monitor.start();

2. CI/CD Integration

// En pipeline CI/CD, verificar que cambios no rompan nada
LiveMonitor monitor("catalog.json");

bool has_critical_issues = false;

monitor.on_change([&](const GraphChange& change) {
    if (change.structural.new_cycles_introduced) {
        std::cerr << "❌ New cycles detected!\n";
        has_critical_issues = true;
    }

    if (change.structural.hierarchy_violated) {
        std::cerr << "❌ Hierarchy violations detected!\n";
        has_critical_issues = true;
    }

    // Salir del monitor
    monitor.stop();
});

// Triggear cambio (modificar catalog.json en el pipeline)
modify_catalog();

// Esperar detección
std::this_thread::sleep_for(std::chrono::seconds(2));

return has_critical_issues ? 1 : 0;

3. Dashboard en Vivo

<!-- Dashboard web que actualiza en tiempo real -->
<div id="dashboard">
    <div id="stats"></div>
    <div id="recent-changes"></div>
    <div id="graph-viz"></div>
</div>

<script>
    const ws = new WebSocket('ws://localhost:8080');

    ws.onmessage = (event) => {
        const update = JSON.parse(event.data);

        // Actualizar estadísticas
        updateStats(update.current_stats);

        // Agregar a log de cambios
        appendToChangeLog(update.change);

        // Animar cambios en visualización
        animateGraphChanges(update.change);
    };
</script>

4. IDE Plugin (VS Code Extension)

// VS Code extension que muestra notificaciones
import * as vscode from 'vscode';
import WebSocket from 'ws';

const ws = new WebSocket('ws://localhost:8080');

ws.on('message', (data: string) => {
    const update = JSON.parse(data);

    if (update.change.structural.new_cycles_introduced) {
        vscode.window.showErrorMessage(
            '🔴 Dependency cycle detected in catalog!'
        );
    }

    if (update.change.nodes_added.length > 0) {
        vscode.window.showInformationMessage(
            `✅ ${update.change.nodes_added.length} new modules added`
        );
    }
});

🔒 Consideraciones de Performance

File Watching

// Polling interval configurable
monitor.set_poll_interval(std::chrono::milliseconds(500));  // Default: 500ms

// Evitar thrashing con debouncing
monitor.set_debounce_delay(std::chrono::milliseconds(200));  // Esperar 200ms

// Ejemplo: si catalog.json cambia 3 veces en 100ms,
// solo se procesará una vez después de 200ms de inactividad

Incremental Rebuild

// Reconstrucción completa vs incremental
if (change.is_minor()) {
    // Solo actualizar nodos modificados
    graph.update_nodes(change.nodes_modified);
} else {
    // Rebuild completo necesario
    graph = builder.rebuild_from_catalog();
}

Client Management

// Limitar clientes concurrentes
monitor.set_max_clients(100);

// Timeout para clientes inactivos
monitor.set_client_timeout(std::chrono::seconds(60));

// Broadcast throttling (evitar saturar red)
monitor.set_broadcast_throttle(std::chrono::milliseconds(100));

📈 Métricas Monitoreadas

struct MonitorStats {
    size_t total_changes_detected{0};
    size_t files_watched{0};
    size_t active_clients{0};

    std::chrono::milliseconds avg_reload_time{0};
    std::chrono::milliseconds avg_diff_time{0};
    std::chrono::milliseconds avg_broadcast_time{0};

    size_t total_nodes_added{0};
    size_t total_nodes_removed{0};
    size_t total_edges_added{0};
    size_t total_edges_removed{0};
};

🛠️ Configuración

struct LiveMonitorConfig {
    std::filesystem::path catalog_path;

    // File watching
    std::chrono::milliseconds poll_interval{500};
    std::chrono::milliseconds debounce_delay{200};

    // WebSocket server
    bool enable_websocket{true};
    uint16_t websocket_port{8080};
    size_t max_clients{100};
    std::chrono::seconds client_timeout{60};

    // Change detection
    bool detect_trivial_changes{false};  // Ignorar cambios triviales
    ChangeSeverity min_severity{ChangeSeverity::MODERATE};

    // Notifications
    bool enable_console_output{true};
    bool enable_file_logging{false};
    std::filesystem::path log_file{"monitor.log"};
};

🔗 Integración con Otros Módulos

Con Visualization (TAREA 2)

monitor.on_change([&renderer](const GraphChange& change) {
    // Regenerar visualización DOT
    DotExporter exporter;
    std::string dot = exporter.export_graph(change.new_graph);

    // Guardar
    std::ofstream("live_graph.dot") << dot;

    // Renderizar
    system("dot -Tpng live_graph.dot -o live_graph.png");
});

Con Metrics (TAREA 5)

monitor.on_change([&](const GraphChange& change) {
    // Recalcular métricas
    MetricsEngine metrics(change.new_graph);
    metrics.compute_all();

    // Detectar regresiones
    if (metrics.get_graph_metrics().density > 0.1) {
        std::cerr << "⚠️  Graph density increased above threshold!\n";
    }
});

Con Export (TAREA 8)

monitor.on_change([](const GraphChange& change) {
    // Auto-export a múltiples formatos
    auto batch = ExportTemplates::full_export();
    batch.set_output_directory("exports/latest/");
    batch.export_all(change.new_graph, "live_graph");
});

🧪 Testing

TEST_CASE("LiveMonitor detects file changes") {
    // Crear catalog temporal
    std::filesystem::path temp_catalog = "test_catalog.json";

    // Crear monitor
    LiveMonitor monitor(temp_catalog);

    bool change_detected = false;
    monitor.on_change([&](const GraphChange&) {
        change_detected = true;
    });

    monitor.start();

    // Modificar archivo
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    modify_file(temp_catalog);

    // Esperar detección
    std::this_thread::sleep_for(std::chrono::milliseconds(1000));

    REQUIRE(change_detected);
}

Parte de: 05_02_DEPENDENCY_GRAPH - Dependency Graph Visualizer Requiere: graph.hpp, graph_builder.hpp, WebSocket library Exporta: live_monitor.hpp