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¶
- File Watching con detección de cambios en catálogo JSON
- Hot Reload del grafo sin reiniciar aplicación
- Change Detection precisa (qué cambió exactamente)
- WebSocket Server para notificaciones en tiempo real
- 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:
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:
3. 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