v0.7.26 - Tidy up api
This commit is contained in:
665
src/api.c
665
src/api.c
@@ -20,12 +20,537 @@
|
||||
#include "../nostr_core_lib/nostr_core/nostr_core.h"
|
||||
#include "../nostr_core_lib/nostr_core/nip017.h"
|
||||
#include "../nostr_core_lib/nostr_core/nip044.h"
|
||||
#include "subscriptions.h"
|
||||
|
||||
// External subscription manager (from main.c via subscriptions.c)
|
||||
extern subscription_manager_t g_subscription_manager;
|
||||
|
||||
// Global variables for config change system
|
||||
static pending_config_change_t* pending_changes_head = NULL;
|
||||
static int pending_changes_count = 0;
|
||||
#define CONFIG_CHANGE_TIMEOUT 300 // 5 minutes
|
||||
|
||||
// Forward declarations for database functions
|
||||
int store_event(cJSON* event);
|
||||
int broadcast_event_to_subscriptions(cJSON* event);
|
||||
|
||||
// Forward declarations for config functions
|
||||
char* get_relay_private_key(void);
|
||||
const char* get_config_value(const char* key);
|
||||
int get_config_bool(const char* key, int default_value);
|
||||
int update_config_in_table(const char* key, const char* value);
|
||||
|
||||
// Monitoring system state
|
||||
static time_t last_report_time = 0;
|
||||
|
||||
// Forward declaration for monitoring helper function
|
||||
int generate_monitoring_event_for_type(const char* d_tag_value, cJSON* (*query_func)(void));
|
||||
|
||||
// Monitoring system helper functions
|
||||
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);
|
||||
}
|
||||
|
||||
int set_monitoring_enabled(int enabled) {
|
||||
const char* value = enabled ? "1" : "0";
|
||||
if (update_config_in_table("kind_34567_reporting_enabled", value) == 0) {
|
||||
DEBUG_INFO("Monitoring enabled state changed");
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Query event kind distribution from database
|
||||
cJSON* query_event_kind_distribution(void) {
|
||||
extern sqlite3* g_db;
|
||||
if (!g_db) {
|
||||
DEBUG_ERROR("Database not available for monitoring query");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Query event kinds distribution with total count
|
||||
sqlite3_stmt* stmt;
|
||||
const char* sql = "SELECT kind, COUNT(*) as count FROM events GROUP BY kind ORDER BY count DESC";
|
||||
|
||||
if (sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL) != SQLITE_OK) {
|
||||
DEBUG_ERROR("Failed to prepare event kind distribution query");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cJSON* distribution = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(distribution, "data_type", "event_kinds");
|
||||
cJSON_AddNumberToObject(distribution, "timestamp", (double)time(NULL));
|
||||
|
||||
cJSON* kinds_array = cJSON_CreateArray();
|
||||
long long total_events = 0;
|
||||
|
||||
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
int kind = sqlite3_column_int(stmt, 0);
|
||||
long long count = sqlite3_column_int64(stmt, 1);
|
||||
total_events += count;
|
||||
|
||||
cJSON* kind_obj = cJSON_CreateObject();
|
||||
cJSON_AddNumberToObject(kind_obj, "kind", kind);
|
||||
cJSON_AddNumberToObject(kind_obj, "count", count);
|
||||
cJSON_AddItemToArray(kinds_array, kind_obj);
|
||||
}
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
cJSON_AddNumberToObject(distribution, "total_events", total_events);
|
||||
cJSON_AddItemToObject(distribution, "kinds", kinds_array);
|
||||
|
||||
return distribution;
|
||||
}
|
||||
|
||||
// Query time-based statistics from database
|
||||
cJSON* query_time_based_statistics(void) {
|
||||
extern sqlite3* g_db;
|
||||
if (!g_db) {
|
||||
DEBUG_ERROR("Database not available for time stats query");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
time_t now = time(NULL);
|
||||
cJSON* time_stats = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(time_stats, "data_type", "time_stats");
|
||||
cJSON_AddNumberToObject(time_stats, "timestamp", (double)now);
|
||||
|
||||
cJSON* periods_array = cJSON_CreateArray();
|
||||
|
||||
// Define time periods: 24h, 7d, 30d
|
||||
struct {
|
||||
const char* period;
|
||||
time_t seconds;
|
||||
const char* description;
|
||||
} periods[] = {
|
||||
{"last_24h", 86400, "Events in the last 24 hours"},
|
||||
{"last_7d", 604800, "Events in the last 7 days"},
|
||||
{"last_30d", 2592000, "Events in the last 30 days"},
|
||||
{NULL, 0, NULL}
|
||||
};
|
||||
|
||||
// Get total events count
|
||||
sqlite3_stmt* total_stmt;
|
||||
const char* total_sql = "SELECT COUNT(*) FROM events";
|
||||
long long total_events = 0;
|
||||
|
||||
if (sqlite3_prepare_v2(g_db, total_sql, -1, &total_stmt, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_step(total_stmt) == SQLITE_ROW) {
|
||||
total_events = sqlite3_column_int64(total_stmt, 0);
|
||||
}
|
||||
sqlite3_finalize(total_stmt);
|
||||
}
|
||||
|
||||
// Query each time period
|
||||
for (int i = 0; periods[i].period != NULL; i++) {
|
||||
sqlite3_stmt* stmt;
|
||||
const char* sql = "SELECT COUNT(*) FROM events WHERE created_at >= ?";
|
||||
|
||||
if (sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL) != SQLITE_OK) {
|
||||
DEBUG_ERROR("Failed to prepare time stats query");
|
||||
continue;
|
||||
}
|
||||
|
||||
time_t cutoff = now - periods[i].seconds;
|
||||
sqlite3_bind_int64(stmt, 1, cutoff);
|
||||
|
||||
long long count = 0;
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
count = sqlite3_column_int64(stmt, 0);
|
||||
}
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
cJSON* period_obj = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(period_obj, "period", periods[i].period);
|
||||
cJSON_AddNumberToObject(period_obj, "count", count);
|
||||
cJSON_AddStringToObject(period_obj, "description", periods[i].description);
|
||||
cJSON_AddItemToArray(periods_array, period_obj);
|
||||
}
|
||||
|
||||
cJSON_AddItemToObject(time_stats, "periods", periods_array);
|
||||
cJSON_AddNumberToObject(time_stats, "total_events", total_events);
|
||||
|
||||
return time_stats;
|
||||
}
|
||||
|
||||
// Query top pubkeys by event count from database
|
||||
cJSON* query_top_pubkeys(void) {
|
||||
extern sqlite3* g_db;
|
||||
if (!g_db) {
|
||||
DEBUG_ERROR("Database not available for top pubkeys query");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Query top 10 pubkeys by event count
|
||||
sqlite3_stmt* stmt;
|
||||
const char* sql = "SELECT pubkey, COUNT(*) as count FROM events GROUP BY pubkey ORDER BY count DESC LIMIT 10";
|
||||
|
||||
if (sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL) != SQLITE_OK) {
|
||||
DEBUG_ERROR("Failed to prepare top pubkeys query");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cJSON* top_pubkeys = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(top_pubkeys, "data_type", "top_pubkeys");
|
||||
cJSON_AddNumberToObject(top_pubkeys, "timestamp", (double)time(NULL));
|
||||
|
||||
cJSON* pubkeys_array = cJSON_CreateArray();
|
||||
|
||||
// Get total events count for percentage calculation
|
||||
sqlite3_stmt* total_stmt;
|
||||
const char* total_sql = "SELECT COUNT(*) FROM events";
|
||||
long long total_events = 0;
|
||||
|
||||
if (sqlite3_prepare_v2(g_db, total_sql, -1, &total_stmt, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_step(total_stmt) == SQLITE_ROW) {
|
||||
total_events = sqlite3_column_int64(total_stmt, 0);
|
||||
}
|
||||
sqlite3_finalize(total_stmt);
|
||||
}
|
||||
|
||||
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
const char* pubkey = (const char*)sqlite3_column_text(stmt, 0);
|
||||
long long count = sqlite3_column_int64(stmt, 1);
|
||||
|
||||
cJSON* pubkey_obj = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(pubkey_obj, "pubkey", pubkey ? pubkey : "");
|
||||
cJSON_AddNumberToObject(pubkey_obj, "event_count", count);
|
||||
// Percentage will be calculated by frontend using total_events
|
||||
cJSON_AddItemToArray(pubkeys_array, pubkey_obj);
|
||||
}
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
cJSON_AddItemToObject(top_pubkeys, "pubkeys", pubkeys_array);
|
||||
cJSON_AddNumberToObject(top_pubkeys, "total_events", total_events);
|
||||
|
||||
return top_pubkeys;
|
||||
}
|
||||
|
||||
// Query active subscriptions from in-memory manager (NO DATABASE QUERY)
|
||||
cJSON* query_active_subscriptions(void) {
|
||||
// Access the global subscription manager
|
||||
pthread_mutex_lock(&g_subscription_manager.subscriptions_lock);
|
||||
|
||||
int total_subs = g_subscription_manager.total_subscriptions;
|
||||
int max_subs = g_subscription_manager.max_total_subscriptions;
|
||||
int max_per_client = g_subscription_manager.max_subscriptions_per_client;
|
||||
|
||||
// Calculate per-client statistics by iterating through active subscriptions
|
||||
int client_count = 0;
|
||||
int most_subs_per_client = 0;
|
||||
|
||||
// Count subscriptions per WebSocket connection
|
||||
subscription_t* current = g_subscription_manager.active_subscriptions;
|
||||
struct lws* last_wsi = NULL;
|
||||
int current_client_subs = 0;
|
||||
|
||||
while (current) {
|
||||
if (current->wsi != last_wsi) {
|
||||
// New client
|
||||
if (last_wsi != NULL) {
|
||||
client_count++;
|
||||
if (current_client_subs > most_subs_per_client) {
|
||||
most_subs_per_client = current_client_subs;
|
||||
}
|
||||
}
|
||||
last_wsi = current->wsi;
|
||||
current_client_subs = 1;
|
||||
} else {
|
||||
current_client_subs++;
|
||||
}
|
||||
current = current->next;
|
||||
}
|
||||
|
||||
// Handle last client
|
||||
if (last_wsi != NULL) {
|
||||
client_count++;
|
||||
if (current_client_subs > most_subs_per_client) {
|
||||
most_subs_per_client = current_client_subs;
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&g_subscription_manager.subscriptions_lock);
|
||||
|
||||
// Calculate statistics
|
||||
double utilization_percentage = max_subs > 0 ? (total_subs * 100.0 / max_subs) : 0.0;
|
||||
double avg_subs_per_client = client_count > 0 ? (total_subs * 1.0 / client_count) : 0.0;
|
||||
|
||||
// Build JSON response matching the design spec
|
||||
cJSON* subscriptions = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(subscriptions, "data_type", "active_subscriptions");
|
||||
cJSON_AddNumberToObject(subscriptions, "timestamp", (double)time(NULL));
|
||||
|
||||
cJSON* data = cJSON_CreateObject();
|
||||
cJSON_AddNumberToObject(data, "total_subscriptions", total_subs);
|
||||
cJSON_AddNumberToObject(data, "max_subscriptions", max_subs);
|
||||
cJSON_AddNumberToObject(data, "utilization_percentage", utilization_percentage);
|
||||
cJSON_AddNumberToObject(data, "subscriptions_per_client_avg", avg_subs_per_client);
|
||||
cJSON_AddNumberToObject(data, "most_subscriptions_per_client", most_subs_per_client);
|
||||
cJSON_AddNumberToObject(data, "max_subscriptions_per_client", max_per_client);
|
||||
cJSON_AddNumberToObject(data, "active_clients", client_count);
|
||||
|
||||
cJSON_AddItemToObject(subscriptions, "data", data);
|
||||
|
||||
return subscriptions;
|
||||
}
|
||||
|
||||
// Query detailed subscription information from in-memory manager (ADMIN ONLY)
|
||||
cJSON* query_subscription_details(void) {
|
||||
// Access the global subscription manager
|
||||
pthread_mutex_lock(&g_subscription_manager.subscriptions_lock);
|
||||
|
||||
time_t current_time = time(NULL);
|
||||
cJSON* subscriptions_data = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(subscriptions_data, "data_type", "subscription_details");
|
||||
cJSON_AddNumberToObject(subscriptions_data, "timestamp", (double)current_time);
|
||||
|
||||
cJSON* data = cJSON_CreateObject();
|
||||
cJSON* subscriptions_array = cJSON_CreateArray();
|
||||
|
||||
// Iterate through all active subscriptions
|
||||
subscription_t* current = g_subscription_manager.active_subscriptions;
|
||||
while (current) {
|
||||
cJSON* sub_obj = cJSON_CreateObject();
|
||||
|
||||
// Basic subscription info
|
||||
cJSON_AddStringToObject(sub_obj, "id", current->id);
|
||||
cJSON_AddStringToObject(sub_obj, "client_ip", current->client_ip);
|
||||
cJSON_AddNumberToObject(sub_obj, "created_at", (double)current->created_at);
|
||||
cJSON_AddNumberToObject(sub_obj, "duration_seconds", (double)(current_time - current->created_at));
|
||||
cJSON_AddNumberToObject(sub_obj, "events_sent", current->events_sent);
|
||||
cJSON_AddBoolToObject(sub_obj, "active", current->active);
|
||||
|
||||
// Extract filter details
|
||||
cJSON* filters_array = cJSON_CreateArray();
|
||||
subscription_filter_t* filter = current->filters;
|
||||
|
||||
while (filter) {
|
||||
cJSON* filter_obj = cJSON_CreateObject();
|
||||
|
||||
// Add kinds array if present
|
||||
if (filter->kinds) {
|
||||
cJSON_AddItemToObject(filter_obj, "kinds", cJSON_Duplicate(filter->kinds, 1));
|
||||
}
|
||||
|
||||
// Add authors array if present
|
||||
if (filter->authors) {
|
||||
cJSON_AddItemToObject(filter_obj, "authors", cJSON_Duplicate(filter->authors, 1));
|
||||
}
|
||||
|
||||
// Add ids array if present
|
||||
if (filter->ids) {
|
||||
cJSON_AddItemToObject(filter_obj, "ids", cJSON_Duplicate(filter->ids, 1));
|
||||
}
|
||||
|
||||
// Add since/until timestamps if set
|
||||
if (filter->since > 0) {
|
||||
cJSON_AddNumberToObject(filter_obj, "since", (double)filter->since);
|
||||
}
|
||||
if (filter->until > 0) {
|
||||
cJSON_AddNumberToObject(filter_obj, "until", (double)filter->until);
|
||||
}
|
||||
|
||||
// Add limit if set
|
||||
if (filter->limit > 0) {
|
||||
cJSON_AddNumberToObject(filter_obj, "limit", filter->limit);
|
||||
}
|
||||
|
||||
// Add tag filters if present
|
||||
if (filter->tag_filters) {
|
||||
cJSON_AddItemToObject(filter_obj, "tag_filters", cJSON_Duplicate(filter->tag_filters, 1));
|
||||
}
|
||||
|
||||
cJSON_AddItemToArray(filters_array, filter_obj);
|
||||
filter = filter->next;
|
||||
}
|
||||
|
||||
cJSON_AddItemToObject(sub_obj, "filters", filters_array);
|
||||
cJSON_AddItemToArray(subscriptions_array, sub_obj);
|
||||
|
||||
current = current->next;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&g_subscription_manager.subscriptions_lock);
|
||||
|
||||
// Add subscriptions array and count to data
|
||||
cJSON_AddItemToObject(data, "subscriptions", subscriptions_array);
|
||||
cJSON_AddNumberToObject(data, "total_count", cJSON_GetArraySize(subscriptions_array));
|
||||
|
||||
cJSON_AddItemToObject(subscriptions_data, "data", data);
|
||||
|
||||
return subscriptions_data;
|
||||
}
|
||||
|
||||
// Generate and broadcast monitoring event
|
||||
int generate_monitoring_event(void) {
|
||||
// Generate event_kinds monitoring event
|
||||
if (generate_monitoring_event_for_type("event_kinds", query_event_kind_distribution) != 0) {
|
||||
DEBUG_ERROR("Failed to generate event_kinds monitoring event");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Generate time_stats monitoring event
|
||||
if (generate_monitoring_event_for_type("time_stats", query_time_based_statistics) != 0) {
|
||||
DEBUG_ERROR("Failed to generate time_stats monitoring event");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Generate top_pubkeys monitoring event
|
||||
if (generate_monitoring_event_for_type("top_pubkeys", query_top_pubkeys) != 0) {
|
||||
DEBUG_ERROR("Failed to generate top_pubkeys monitoring event");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Generate active_subscriptions monitoring event
|
||||
if (generate_monitoring_event_for_type("active_subscriptions", query_active_subscriptions) != 0) {
|
||||
DEBUG_ERROR("Failed to generate active_subscriptions monitoring event");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Generate subscription_details monitoring event (admin-only)
|
||||
if (generate_monitoring_event_for_type("subscription_details", query_subscription_details) != 0) {
|
||||
DEBUG_ERROR("Failed to generate subscription_details monitoring event");
|
||||
return -1;
|
||||
}
|
||||
|
||||
DEBUG_INFO("Generated and broadcast all monitoring events");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Helper function to generate monitoring event for a specific type
|
||||
int generate_monitoring_event_for_type(const char* d_tag_value, cJSON* (*query_func)(void)) {
|
||||
// Query the monitoring data
|
||||
cJSON* monitoring_data = query_func();
|
||||
if (!monitoring_data) {
|
||||
DEBUG_ERROR("Failed to query monitoring data for %s", d_tag_value);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Convert to JSON string for content
|
||||
char* content_json = cJSON_Print(monitoring_data);
|
||||
cJSON_Delete(monitoring_data);
|
||||
|
||||
if (!content_json) {
|
||||
DEBUG_ERROR("Failed to serialize monitoring data for %s", d_tag_value);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Get relay keys for signing
|
||||
const char* relay_pubkey = get_config_value("relay_pubkey");
|
||||
char* relay_privkey_hex = get_relay_private_key();
|
||||
if (!relay_pubkey || !relay_privkey_hex) {
|
||||
free(content_json);
|
||||
DEBUG_ERROR("Could not get relay keys for monitoring event (%s)", d_tag_value);
|
||||
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);
|
||||
free(content_json);
|
||||
DEBUG_ERROR("Failed to convert relay private key for monitoring event (%s)", d_tag_value);
|
||||
return -1;
|
||||
}
|
||||
free(relay_privkey_hex);
|
||||
|
||||
// Create monitoring event (kind 34567)
|
||||
cJSON* monitoring_event = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(monitoring_event, "id", ""); // Will be set by signing
|
||||
cJSON_AddStringToObject(monitoring_event, "pubkey", relay_pubkey);
|
||||
cJSON_AddNumberToObject(monitoring_event, "created_at", (double)time(NULL));
|
||||
cJSON_AddNumberToObject(monitoring_event, "kind", 34567);
|
||||
cJSON_AddStringToObject(monitoring_event, "content", content_json);
|
||||
|
||||
// Create tags array with d tag for identification
|
||||
cJSON* tags = cJSON_CreateArray();
|
||||
|
||||
// d tag for event identification
|
||||
cJSON* d_tag = cJSON_CreateArray();
|
||||
cJSON_AddItemToArray(d_tag, cJSON_CreateString("d"));
|
||||
cJSON_AddItemToArray(d_tag, cJSON_CreateString(d_tag_value));
|
||||
cJSON_AddItemToArray(tags, d_tag);
|
||||
|
||||
cJSON_AddItemToObject(monitoring_event, "tags", tags);
|
||||
|
||||
// Use the library function to create and sign the event
|
||||
cJSON* signed_event = nostr_create_and_sign_event(
|
||||
34567, // kind
|
||||
cJSON_GetStringValue(cJSON_GetObjectItem(monitoring_event, "content")), // content
|
||||
tags, // tags
|
||||
relay_privkey, // private key
|
||||
(time_t)cJSON_GetNumberValue(cJSON_GetObjectItem(monitoring_event, "created_at")) // timestamp
|
||||
);
|
||||
|
||||
if (!signed_event) {
|
||||
cJSON_Delete(monitoring_event);
|
||||
free(content_json);
|
||||
DEBUG_ERROR("Failed to create and sign monitoring event (%s)", d_tag_value);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Replace the unsigned event with the signed one
|
||||
cJSON_Delete(monitoring_event);
|
||||
monitoring_event = signed_event;
|
||||
|
||||
// Broadcast the event to active subscriptions
|
||||
broadcast_event_to_subscriptions(monitoring_event);
|
||||
|
||||
// Store in database
|
||||
int store_result = store_event(monitoring_event);
|
||||
|
||||
cJSON_Delete(monitoring_event);
|
||||
free(content_json);
|
||||
|
||||
if (store_result != 0) {
|
||||
DEBUG_ERROR("Failed to store monitoring event (%s)", d_tag_value);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Monitoring hook 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 report
|
||||
}
|
||||
|
||||
// Generate and broadcast monitoring event
|
||||
if (generate_monitoring_event() == 0) {
|
||||
last_report_time = now;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize monitoring system
|
||||
int init_monitoring_system(void) {
|
||||
last_report_time = 0;
|
||||
DEBUG_INFO("Monitoring system initialized");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Cleanup monitoring system
|
||||
void cleanup_monitoring_system(void) {
|
||||
// No cleanup needed for monitoring system
|
||||
DEBUG_INFO("Monitoring system cleaned up");
|
||||
}
|
||||
|
||||
// Forward declaration for known_configs (defined in config.c)
|
||||
typedef struct {
|
||||
const char* key;
|
||||
@@ -107,9 +632,10 @@ int broadcast_event_to_subscriptions(cJSON* event);
|
||||
char* get_relay_private_key(void);
|
||||
const char* get_config_value(const char* key);
|
||||
int get_config_bool(const char* key, int default_value);
|
||||
int update_config_in_table(const char* key, const char* value);
|
||||
|
||||
// Forward declarations for database functions
|
||||
int store_event(cJSON* event);
|
||||
// Forward declaration for monitoring helper function
|
||||
int generate_monitoring_event_for_type(const char* d_tag_value, cJSON* (*query_func)(void));
|
||||
|
||||
// Handle HTTP request for embedded files (assumes GET)
|
||||
int handle_embedded_file_request(struct lws* wsi, const char* requested_uri) {
|
||||
@@ -636,6 +1162,12 @@ char* generate_stats_json(void) {
|
||||
}
|
||||
cJSON_AddNumberToObject(response, "database_size_bytes", db_size);
|
||||
|
||||
// Get active subscriptions count from in-memory manager
|
||||
pthread_mutex_lock(&g_subscription_manager.subscriptions_lock);
|
||||
int active_subs = g_subscription_manager.total_subscriptions;
|
||||
pthread_mutex_unlock(&g_subscription_manager.subscriptions_lock);
|
||||
cJSON_AddNumberToObject(response, "active_subscriptions", active_subs);
|
||||
|
||||
// Query total events count
|
||||
sqlite3_stmt* stmt;
|
||||
if (sqlite3_prepare_v2(g_db, "SELECT COUNT(*) FROM events", -1, &stmt, NULL) == SQLITE_OK) {
|
||||
@@ -914,6 +1446,11 @@ char* generate_stats_text(void) {
|
||||
long long db_bytes = db_size ? (long long)cJSON_GetNumberValue(db_size) : 0;
|
||||
double db_mb = db_bytes / (1024.0 * 1024.0);
|
||||
|
||||
// Get active subscriptions count from in-memory manager
|
||||
pthread_mutex_lock(&g_subscription_manager.subscriptions_lock);
|
||||
int active_subs = g_subscription_manager.total_subscriptions;
|
||||
pthread_mutex_unlock(&g_subscription_manager.subscriptions_lock);
|
||||
|
||||
// Format timestamps
|
||||
char oldest_str[64] = "-";
|
||||
char newest_str[64] = "-";
|
||||
@@ -953,10 +1490,11 @@ char* generate_stats_text(void) {
|
||||
"Metric\tValue\tDescription\n"
|
||||
"Database Size\t%.2f MB (%lld bytes)\tCurrent database file size\n"
|
||||
"Total Events\t%lld\tTotal number of events stored\n"
|
||||
"Active Subscriptions\t%d\tCurrent active WebSocket subscriptions\n"
|
||||
"Oldest Event\t%s\tTimestamp of oldest event\n"
|
||||
"Newest Event\t%s\tTimestamp of newest event\n"
|
||||
"\n",
|
||||
db_mb, db_bytes, total, oldest_str, newest_str);
|
||||
db_mb, db_bytes, total, active_subs, oldest_str, newest_str);
|
||||
|
||||
// Event Kind Distribution section
|
||||
offset += snprintf(stats_text + offset, 16384 - offset,
|
||||
@@ -1682,3 +2220,124 @@ int process_config_change_request(const char* admin_pubkey, const char* message)
|
||||
free(change_id);
|
||||
return 1; // Confirmation sent
|
||||
}
|
||||
|
||||
// Handle monitoring system admin commands
|
||||
int handle_monitoring_command(cJSON* event, const char* command, char* error_message, size_t error_size, struct lws* wsi) {
|
||||
if (!event || !command || !error_message) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Get request event ID for response correlation
|
||||
cJSON* request_id_obj = cJSON_GetObjectItem(event, "id");
|
||||
if (!request_id_obj || !cJSON_IsString(request_id_obj)) {
|
||||
snprintf(error_message, error_size, "Missing request event ID");
|
||||
return -1;
|
||||
}
|
||||
const char* request_id = cJSON_GetStringValue(request_id_obj);
|
||||
|
||||
// Get sender pubkey for response
|
||||
cJSON* sender_pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
|
||||
if (!sender_pubkey_obj || !cJSON_IsString(sender_pubkey_obj)) {
|
||||
snprintf(error_message, error_size, "Missing sender pubkey");
|
||||
return -1;
|
||||
}
|
||||
const char* sender_pubkey = cJSON_GetStringValue(sender_pubkey_obj);
|
||||
|
||||
// Parse command
|
||||
char cmd[256];
|
||||
char arg[256];
|
||||
cmd[0] = '\0';
|
||||
arg[0] = '\0';
|
||||
|
||||
// Simple command parsing - split on space
|
||||
const char* space_pos = strchr(command, ' ');
|
||||
if (space_pos) {
|
||||
size_t cmd_len = space_pos - command;
|
||||
if (cmd_len < sizeof(cmd)) {
|
||||
memcpy(cmd, command, cmd_len);
|
||||
cmd[cmd_len] = '\0';
|
||||
strcpy(arg, space_pos + 1);
|
||||
}
|
||||
} else {
|
||||
strcpy(cmd, command);
|
||||
}
|
||||
|
||||
// Convert to lowercase for case-insensitive matching
|
||||
for (char* p = cmd; *p; p++) {
|
||||
if (*p >= 'A' && *p <= 'Z') *p = *p + 32;
|
||||
}
|
||||
|
||||
// Handle commands
|
||||
if (strcmp(cmd, "enable_monitoring") == 0) {
|
||||
if (set_monitoring_enabled(1) == 0) {
|
||||
char* response_content = "✅ Monitoring enabled\n\nReal-time monitoring events will now be generated.";
|
||||
return send_admin_response(sender_pubkey, response_content, request_id, error_message, error_size, wsi);
|
||||
} else {
|
||||
char* response_content = "❌ Failed to enable monitoring";
|
||||
return send_admin_response(sender_pubkey, response_content, request_id, error_message, error_size, wsi);
|
||||
}
|
||||
} else if (strcmp(cmd, "disable_monitoring") == 0) {
|
||||
if (set_monitoring_enabled(0) == 0) {
|
||||
char* response_content = "✅ Monitoring disabled\n\nReal-time monitoring events will no longer be generated.";
|
||||
return send_admin_response(sender_pubkey, response_content, request_id, error_message, error_size, wsi);
|
||||
} else {
|
||||
char* response_content = "❌ Failed to disable monitoring";
|
||||
return send_admin_response(sender_pubkey, response_content, request_id, error_message, error_size, wsi);
|
||||
}
|
||||
} else if (strcmp(cmd, "set_monitoring_throttle") == 0) {
|
||||
if (arg[0] == '\0') {
|
||||
char* response_content = "❌ Missing throttle value\n\nUsage: set_monitoring_throttle <seconds>";
|
||||
return send_admin_response(sender_pubkey, response_content, request_id, error_message, error_size, wsi);
|
||||
}
|
||||
|
||||
char* endptr;
|
||||
long throttle_seconds = strtol(arg, &endptr, 10);
|
||||
if (*endptr != '\0' || throttle_seconds < 1 || throttle_seconds > 3600) {
|
||||
char* response_content = "❌ Invalid throttle value\n\nThrottle must be between 1 and 3600 seconds.";
|
||||
return send_admin_response(sender_pubkey, response_content, request_id, error_message, error_size, wsi);
|
||||
}
|
||||
|
||||
char throttle_str[16];
|
||||
snprintf(throttle_str, sizeof(throttle_str), "%ld", throttle_seconds);
|
||||
|
||||
if (update_config_in_table("kind_34567_reporting_throttling_sec", throttle_str) == 0) {
|
||||
char response_content[256];
|
||||
snprintf(response_content, sizeof(response_content),
|
||||
"✅ Monitoring throttle updated\n\nMinimum interval between monitoring events: %ld seconds", throttle_seconds);
|
||||
return send_admin_response(sender_pubkey, response_content, request_id, error_message, error_size, wsi);
|
||||
} else {
|
||||
char* response_content = "❌ Failed to update monitoring throttle";
|
||||
return send_admin_response(sender_pubkey, response_content, request_id, error_message, error_size, wsi);
|
||||
}
|
||||
} else if (strcmp(cmd, "monitoring_status") == 0) {
|
||||
int enabled = is_monitoring_enabled();
|
||||
int throttle = get_monitoring_throttle_seconds();
|
||||
|
||||
char response_content[512];
|
||||
snprintf(response_content, sizeof(response_content),
|
||||
"📊 Monitoring Status\n"
|
||||
"━━━━━━━━━━━━━━━━━━━━\n"
|
||||
"\n"
|
||||
"Enabled: %s\n"
|
||||
"Throttle: %d seconds\n"
|
||||
"\n"
|
||||
"Commands:\n"
|
||||
"• enable_monitoring\n"
|
||||
"• disable_monitoring\n"
|
||||
"• set_monitoring_throttle <seconds>\n"
|
||||
"• monitoring_status",
|
||||
enabled ? "Yes" : "No", throttle);
|
||||
|
||||
return send_admin_response(sender_pubkey, response_content, request_id, error_message, error_size, wsi);
|
||||
} else {
|
||||
char response_content[256];
|
||||
snprintf(response_content, sizeof(response_content),
|
||||
"❌ Unknown monitoring command: %s\n\n"
|
||||
"Available commands:\n"
|
||||
"• enable_monitoring\n"
|
||||
"• disable_monitoring\n"
|
||||
"• set_monitoring_throttle <seconds>\n"
|
||||
"• monitoring_status", cmd);
|
||||
return send_admin_response(sender_pubkey, response_content, request_id, error_message, error_size, wsi);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user