# Simplified Monitoring Implementation Plan ## Kind 34567 Event Kind Distribution Reporting **Date:** 2025-10-16 **Status:** Implementation Ready --- ## Overview Simplified real-time monitoring system that: - Reports event kind distribution (which includes total event count) - Uses kind 34567 addressable events with `d=event_kinds` - Controlled by two config variables - Enabled on-demand when admin logs in - Uses simple throttling to prevent performance impact --- ## Configuration Variables ### Database Config Table Add two new configuration keys: ```sql INSERT INTO config (key, value, data_type, description, category) VALUES ('kind_34567_reporting_enabled', 'false', 'boolean', 'Enable/disable kind 34567 event kind distribution reporting', 'monitoring'), ('kind_34567_reporting_throttling_sec', '5', 'integer', 'Minimum seconds between kind 34567 reports (throttling)', 'monitoring'); ``` ### Configuration Access ```c // In src/monitoring.c or src/api.c int is_monitoring_enabled(void) { return get_config_bool("kind_34567_reporting_enabled", 0); } int get_monitoring_throttle_seconds(void) { return get_config_int("kind_34567_reporting_throttling_sec", 5); } ``` --- ## Event Structure ### Kind 34567 Event Format ```json { "id": "", "pubkey": "", "created_at": 1697123456, "kind": 34567, "content": "{\"data_type\":\"event_kinds\",\"timestamp\":1697123456,\"data\":{\"total_events\":125000,\"distribution\":[{\"kind\":1,\"count\":45000,\"percentage\":36.0},{\"kind\":3,\"count\":12500,\"percentage\":10.0}]}}", "tags": [ ["d", "event_kinds"], ["relay", ""] ], "sig": "" } ``` ### Content JSON Structure ```json { "data_type": "event_kinds", "timestamp": 1697123456, "data": { "total_events": 125000, "distribution": [ { "kind": 1, "count": 45000, "percentage": 36.0 }, { "kind": 3, "count": 12500, "percentage": 10.0 } ] }, "metadata": { "query_time_ms": 18 } } ``` --- ## Implementation ### File Structure ``` src/ monitoring.h # New file - monitoring system header monitoring.c # New file - monitoring implementation main.c # Modified - add trigger hook config.c # Modified - add config keys (or use migration) ``` ### 1. Header File: `src/monitoring.h` ```c #ifndef MONITORING_H #define MONITORING_H #include #include // Initialize monitoring system int init_monitoring_system(void); // Cleanup monitoring system void cleanup_monitoring_system(void); // Called when an event is stored (from main.c) void monitoring_on_event_stored(void); // Enable/disable monitoring (called from admin API) int set_monitoring_enabled(int enabled); // Get monitoring status int is_monitoring_enabled(void); // Get throttle interval int get_monitoring_throttle_seconds(void); #endif /* MONITORING_H */ ``` ### 2. Implementation: `src/monitoring.c` ```c #include "monitoring.h" #include "config.h" #include "debug.h" #include "../nostr_core_lib/nostr_core/nostr_core.h" #include #include #include // External references extern sqlite3* g_db; extern int broadcast_event_to_subscriptions(cJSON* event); extern int store_event(cJSON* event); extern const char* get_config_value(const char* key); extern int get_config_bool(const char* key, int default_value); extern int get_config_int(const char* key, int default_value); extern char* get_relay_private_key(void); // Throttling state static time_t last_report_time = 0; // Initialize monitoring system int init_monitoring_system(void) { DEBUG_LOG("Monitoring system initialized"); last_report_time = 0; return 0; } // Cleanup monitoring system void cleanup_monitoring_system(void) { DEBUG_LOG("Monitoring system cleaned up"); } // Check if monitoring is enabled int is_monitoring_enabled(void) { return get_config_bool("kind_34567_reporting_enabled", 0); } // Get throttle interval int get_monitoring_throttle_seconds(void) { return get_config_int("kind_34567_reporting_throttling_sec", 5); } // Enable/disable monitoring int set_monitoring_enabled(int enabled) { // Update config table const char* value = enabled ? "true" : "false"; // This would call update_config_in_table() or similar // For now, assume we have a function to update config extern int update_config_in_table(const char* key, const char* value); return update_config_in_table("kind_34567_reporting_enabled", value); } // Query event kind distribution from database static char* query_event_kind_distribution(void) { if (!g_db) { DEBUG_ERROR("Database not available for monitoring query"); return NULL; } struct timespec start_time; clock_gettime(CLOCK_MONOTONIC, &start_time); // Query total events sqlite3_stmt* stmt; int total_events = 0; if (sqlite3_prepare_v2(g_db, "SELECT COUNT(*) FROM events", -1, &stmt, NULL) == SQLITE_OK) { if (sqlite3_step(stmt) == SQLITE_ROW) { total_events = sqlite3_column_int(stmt, 0); } sqlite3_finalize(stmt); } // Query kind distribution cJSON* response = cJSON_CreateObject(); cJSON_AddStringToObject(response, "data_type", "event_kinds"); cJSON_AddNumberToObject(response, "timestamp", (double)time(NULL)); cJSON* data = cJSON_CreateObject(); cJSON_AddNumberToObject(data, "total_events", total_events); cJSON* distribution = cJSON_CreateArray(); const char* sql = "SELECT kind, COUNT(*) as count, " "ROUND(COUNT(*) * 100.0 / (SELECT COUNT(*) FROM events), 2) as percentage " "FROM events GROUP BY kind ORDER BY count DESC"; if (sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL) == SQLITE_OK) { while (sqlite3_step(stmt) == SQLITE_ROW) { cJSON* kind_obj = cJSON_CreateObject(); cJSON_AddNumberToObject(kind_obj, "kind", sqlite3_column_int(stmt, 0)); cJSON_AddNumberToObject(kind_obj, "count", sqlite3_column_int64(stmt, 1)); cJSON_AddNumberToObject(kind_obj, "percentage", sqlite3_column_double(stmt, 2)); cJSON_AddItemToArray(distribution, kind_obj); } sqlite3_finalize(stmt); } cJSON_AddItemToObject(data, "distribution", distribution); cJSON_AddItemToObject(response, "data", data); // Calculate query time struct timespec end_time; clock_gettime(CLOCK_MONOTONIC, &end_time); double query_time_ms = (end_time.tv_sec - start_time.tv_sec) * 1000.0 + (end_time.tv_nsec - start_time.tv_nsec) / 1000000.0; cJSON* metadata = cJSON_CreateObject(); cJSON_AddNumberToObject(metadata, "query_time_ms", query_time_ms); cJSON_AddItemToObject(response, "metadata", metadata); char* json_string = cJSON_Print(response); cJSON_Delete(response); return json_string; } // Generate and broadcast kind 34567 event static int generate_monitoring_event(const char* json_content) { if (!json_content) return -1; // Get relay keys const char* relay_pubkey = get_config_value("relay_pubkey"); char* relay_privkey_hex = get_relay_private_key(); if (!relay_pubkey || !relay_privkey_hex) { if (relay_privkey_hex) free(relay_privkey_hex); DEBUG_ERROR("Could not get relay keys for monitoring event"); return -1; } // Convert relay private key to bytes unsigned char relay_privkey[32]; if (nostr_hex_to_bytes(relay_privkey_hex, relay_privkey, sizeof(relay_privkey)) != 0) { free(relay_privkey_hex); DEBUG_ERROR("Failed to convert relay private key"); return -1; } free(relay_privkey_hex); // Create tags array cJSON* tags = cJSON_CreateArray(); // d tag for addressable event cJSON* d_tag = cJSON_CreateArray(); cJSON_AddItemToArray(d_tag, cJSON_CreateString("d")); cJSON_AddItemToArray(d_tag, cJSON_CreateString("event_kinds")); cJSON_AddItemToArray(tags, d_tag); // relay tag cJSON* relay_tag = cJSON_CreateArray(); cJSON_AddItemToArray(relay_tag, cJSON_CreateString("relay")); cJSON_AddItemToArray(relay_tag, cJSON_CreateString(relay_pubkey)); cJSON_AddItemToArray(tags, relay_tag); // Create and sign event cJSON* event = nostr_create_and_sign_event( 34567, // kind json_content, // content tags, // tags relay_privkey, // private key time(NULL) // timestamp ); if (!event) { DEBUG_ERROR("Failed to create and sign monitoring event"); return -1; } // Broadcast to subscriptions broadcast_event_to_subscriptions(event); // Store in database int result = store_event(event); cJSON_Delete(event); return result; } // Called when an event is stored void monitoring_on_event_stored(void) { // Check if monitoring is enabled if (!is_monitoring_enabled()) { return; } // Check throttling time_t now = time(NULL); int throttle_seconds = get_monitoring_throttle_seconds(); if (now - last_report_time < throttle_seconds) { return; // Too soon, skip this update } // Query event kind distribution char* json_content = query_event_kind_distribution(); if (!json_content) { DEBUG_ERROR("Failed to query event kind distribution"); return; } // Generate and broadcast monitoring event int result = generate_monitoring_event(json_content); free(json_content); if (result == 0) { last_report_time = now; DEBUG_LOG("Generated kind 34567 monitoring event"); } else { DEBUG_ERROR("Failed to generate monitoring event"); } } ``` ### 3. Integration: Modify `src/main.c` Add monitoring hook to event storage: ```c // At top of file #include "monitoring.h" // In main() function, after init_database() if (init_monitoring_system() != 0) { DEBUG_WARN("Failed to initialize monitoring system"); // Continue anyway - monitoring is optional } // In store_event() function, after successful storage int store_event(cJSON* event) { // ... existing code ... if (rc != SQLITE_DONE) { // ... error handling ... } free(tags_json); // Trigger monitoring update monitoring_on_event_stored(); return 0; } // In cleanup section of main() cleanup_monitoring_system(); ``` ### 4. Admin API: Enable/Disable Monitoring Add admin command to enable monitoring (in `src/dm_admin.c` or `src/api.c`): ```c // Handle admin command to enable monitoring if (strcmp(command, "enable_monitoring") == 0) { set_monitoring_enabled(1); send_nip17_response(sender_pubkey, "✅ Kind 34567 monitoring enabled", error_msg, sizeof(error_msg)); return 0; } // Handle admin command to disable monitoring if (strcmp(command, "disable_monitoring") == 0) { set_monitoring_enabled(0); send_nip17_response(sender_pubkey, "🔴 Kind 34567 monitoring disabled", error_msg, sizeof(error_msg)); return 0; } // Handle admin command to set throttle interval if (strncmp(command, "set_monitoring_throttle ", 24) == 0) { int seconds = atoi(command + 24); if (seconds >= 1 && seconds <= 3600) { char value[16]; snprintf(value, sizeof(value), "%d", seconds); update_config_in_table("kind_34567_reporting_throttling_sec", value); char response[128]; snprintf(response, sizeof(response), "✅ Monitoring throttle set to %d seconds", seconds); send_nip17_response(sender_pubkey, response, error_msg, sizeof(error_msg)); } return 0; } ``` --- ## Frontend Integration ### Admin Dashboard Subscription ```javascript // When admin logs in to dashboard async function enableMonitoring() { // Send admin command to enable monitoring await sendAdminCommand(['enable_monitoring']); // Subscribe to kind 34567 events const subscription = { kinds: [34567], authors: [relayPubkey], "#d": ["event_kinds"] }; relay.subscribe([subscription], { onevent: (event) => { handleMonitoringEvent(event); } }); } // Handle incoming monitoring events function handleMonitoringEvent(event) { const content = JSON.parse(event.content); if (content.data_type === 'event_kinds') { updateEventKindsChart(content.data); updateTotalEventsDisplay(content.data.total_events); } } // When admin logs out or closes dashboard async function disableMonitoring() { await sendAdminCommand(['disable_monitoring']); } ``` ### Display Event Kind Distribution ```javascript function updateEventKindsChart(data) { const { total_events, distribution } = data; // Update total events display document.getElementById('total-events').textContent = total_events.toLocaleString(); // Update chart/table with distribution const tableBody = document.getElementById('kind-distribution-table'); tableBody.innerHTML = ''; distribution.forEach(item => { const row = document.createElement('tr'); row.innerHTML = ` Kind ${item.kind} ${item.count.toLocaleString()} ${item.percentage}% `; tableBody.appendChild(row); }); } ``` --- ## Configuration Migration ### Add to Schema or Migration Script ```sql -- Add monitoring configuration INSERT INTO config (key, value, data_type, description, category) VALUES ('kind_34567_reporting_enabled', 'false', 'boolean', 'Enable/disable kind 34567 event kind distribution reporting', 'monitoring'), ('kind_34567_reporting_throttling_sec', '5', 'integer', 'Minimum seconds between kind 34567 reports (throttling)', 'monitoring'); ``` Or add to existing config initialization in `src/config.c`. --- ## Testing ### 1. Enable Monitoring ```bash # Via admin command (NIP-17 DM) echo '["enable_monitoring"]' | nak event --kind 14 --content - ws://localhost:8888 ``` ### 2. Subscribe to Monitoring Events ```bash # Subscribe to kind 34567 events nak req --kinds 34567 --authors ws://localhost:8888 ``` ### 3. Generate Events ```bash # Send some test events to trigger monitoring for i in {1..10}; do nak event -c "Test event $i" ws://localhost:8888 sleep 1 done ``` ### 4. Verify Monitoring Events You should see kind 34567 events every 5 seconds (or configured throttle interval) with event kind distribution. --- ## Performance Impact ### With 3 events/second (relay.damus.io scale) **Query execution**: - Frequency: Every 5 seconds (throttled) - Query time: ~700ms (for 1M events) - Overhead: 700ms / 5000ms = 14% (acceptable) **Per-event overhead**: - Check if enabled: < 0.01ms - Check throttle: < 0.01ms - Total: < 0.02ms per event (negligible) **Overall impact**: < 1% on event processing, 14% on query thread (separate from event processing) --- ## Future Enhancements Once this is working, easy to add: 1. **More data types**: Add `d=connections`, `d=subscriptions`, etc. 2. **Materialized counters**: Optimize queries for very large databases 3. **Historical data**: Store monitoring events for trending 4. **Alerts**: Trigger on thresholds (e.g., > 90% capacity) --- ## Summary This simplified plan provides: ✅ **Single data type**: Event kind distribution (includes total events) ✅ **Two config variables**: Enable/disable and throttle control ✅ **On-demand activation**: Enabled when admin logs in ✅ **Simple throttling**: Prevents performance impact ✅ **Clean implementation**: ~200 lines of code ✅ **Easy to extend**: Add more data types later **Estimated implementation time**: 4-6 hours **Files to create/modify**: - Create: `src/monitoring.h` (~30 lines) - Create: `src/monitoring.c` (~200 lines) - Modify: `src/main.c` (~10 lines) - Modify: `src/config.c` or migration (~5 lines) - Modify: `src/dm_admin.c` or `src/api.c` (~30 lines) - Create: `api/monitoring.js` (frontend, ~100 lines) **Total new code**: ~375 lines