#include "monitor.h" #include "logging.h" #include #include #include #include #include system_monitor_t monitor; void history_deque_init(history_deque_t *dq, int capacity) { dq->points = calloc(capacity, sizeof(history_point_t)); dq->capacity = capacity; dq->head = 0; dq->count = 0; } void history_deque_push(history_deque_t *dq, double time, double value) { if (!dq || !dq->points) return; dq->points[dq->head] = (history_point_t){ .time = time, .value = value }; dq->head = (dq->head + 1) % dq->capacity; if (dq->count < dq->capacity) dq->count++; } void network_history_deque_init(network_history_deque_t *dq, int capacity) { dq->points = calloc(capacity, sizeof(network_history_point_t)); dq->capacity = capacity; dq->head = 0; dq->count = 0; } void network_history_deque_push(network_history_deque_t *dq, double time, double rx, double tx) { if (!dq || !dq->points) return; dq->points[dq->head] = (network_history_point_t){ .time = time, .rx_kbps = rx, .tx_kbps = tx }; dq->head = (dq->head + 1) % dq->capacity; if (dq->count < dq->capacity) dq->count++; } void disk_history_deque_init(disk_history_deque_t *dq, int capacity) { dq->points = calloc(capacity, sizeof(disk_history_point_t)); dq->capacity = capacity; dq->head = 0; dq->count = 0; } void disk_history_deque_push(disk_history_deque_t *dq, double time, double read_mbps, double write_mbps) { if (!dq || !dq->points) return; dq->points[dq->head] = (disk_history_point_t){ .time = time, .read_mbps = read_mbps, .write_mbps = write_mbps }; dq->head = (dq->head + 1) % dq->capacity; if (dq->count < dq->capacity) dq->count++; } void request_time_deque_init(request_time_deque_t *dq, int capacity) { dq->times = calloc(capacity, sizeof(double)); dq->capacity = capacity; dq->head = 0; dq->count = 0; } void request_time_deque_push(request_time_deque_t *dq, double time_ms) { if (!dq || !dq->times) return; dq->times[dq->head] = time_ms; dq->head = (dq->head + 1) % dq->capacity; if (dq->count < dq->capacity) dq->count++; } static void init_db(void) { if (!monitor.db) return; char *err_msg = 0; const char *sql_create_table = "CREATE TABLE IF NOT EXISTS vhost_stats (" " id INTEGER PRIMARY KEY AUTOINCREMENT," " vhost TEXT NOT NULL," " timestamp REAL NOT NULL," " http_requests INTEGER DEFAULT 0," " websocket_requests INTEGER DEFAULT 0," " total_requests INTEGER DEFAULT 0," " bytes_sent INTEGER DEFAULT 0," " bytes_recv INTEGER DEFAULT 0," " avg_request_time_ms REAL DEFAULT 0," " UNIQUE(vhost, timestamp)" ");"; const char *sql_create_index = "CREATE INDEX IF NOT EXISTS idx_vhost_timestamp ON vhost_stats(vhost, timestamp);"; if (sqlite3_exec(monitor.db, sql_create_table, 0, 0, &err_msg) != SQLITE_OK || sqlite3_exec(monitor.db, sql_create_index, 0, 0, &err_msg) != SQLITE_OK) { fprintf(stderr, "SQL error: %s\n", err_msg); sqlite3_free(err_msg); } } static void load_stats_from_db(void) { if (!monitor.db) return; sqlite3_stmt *res; const char *sql = "SELECT vhost, http_requests, websocket_requests, total_requests, " "bytes_sent, bytes_recv, avg_request_time_ms " "FROM vhost_stats v1 WHERE timestamp = (" " SELECT MAX(timestamp) FROM vhost_stats v2 WHERE v2.vhost = v1.vhost" ")"; if (sqlite3_prepare_v2(monitor.db, sql, -1, &res, 0) != SQLITE_OK) { fprintf(stderr, "Failed to execute statement: %s\n", sqlite3_errmsg(monitor.db)); return; } int vhost_count = 0; while (sqlite3_step(res) == SQLITE_ROW) { vhost_stats_t *stats = monitor_get_or_create_vhost_stats((const char*)sqlite3_column_text(res, 0)); if (stats) { stats->http_requests = sqlite3_column_int64(res, 1); stats->websocket_requests = sqlite3_column_int64(res, 2); stats->total_requests = sqlite3_column_int64(res, 3); stats->bytes_sent = sqlite3_column_int64(res, 4); stats->bytes_recv = sqlite3_column_int64(res, 5); stats->avg_request_time_ms = sqlite3_column_double(res, 6); vhost_count++; } } sqlite3_finalize(res); log_info("Loaded statistics for %d vhosts from database", vhost_count); } void monitor_init(const char *db_file) { memset(&monitor, 0, sizeof(system_monitor_t)); monitor.start_time = time(NULL); history_deque_init(&monitor.cpu_history, HISTORY_SECONDS); history_deque_init(&monitor.memory_history, HISTORY_SECONDS); network_history_deque_init(&monitor.network_history, HISTORY_SECONDS); disk_history_deque_init(&monitor.disk_history, HISTORY_SECONDS); history_deque_init(&monitor.throughput_history, HISTORY_SECONDS); history_deque_init(&monitor.load1_history, HISTORY_SECONDS); history_deque_init(&monitor.load5_history, HISTORY_SECONDS); history_deque_init(&monitor.load15_history, HISTORY_SECONDS); if (sqlite3_open(db_file, &monitor.db) != SQLITE_OK) { fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(monitor.db)); if (monitor.db) { sqlite3_close(monitor.db); monitor.db = NULL; } } else { init_db(); load_stats_from_db(); } monitor_update(); } void monitor_cleanup(void) { if (monitor.db) { sqlite3_close(monitor.db); monitor.db = NULL; } vhost_stats_t *current = monitor.vhost_stats_head; while (current) { vhost_stats_t *next = current->next; if (current->throughput_history.points) free(current->throughput_history.points); if (current->request_times.times) free(current->request_times.times); free(current); current = next; } monitor.vhost_stats_head = NULL; if (monitor.cpu_history.points) free(monitor.cpu_history.points); if (monitor.memory_history.points) free(monitor.memory_history.points); if (monitor.network_history.points) free(monitor.network_history.points); if (monitor.disk_history.points) free(monitor.disk_history.points); if (monitor.throughput_history.points) free(monitor.throughput_history.points); if (monitor.load1_history.points) free(monitor.load1_history.points); if (monitor.load5_history.points) free(monitor.load5_history.points); if (monitor.load15_history.points) free(monitor.load15_history.points); } static void save_stats_to_db(void) { if (!monitor.db) return; sqlite3_stmt *stmt; const char *sql = "INSERT OR REPLACE INTO vhost_stats " "(vhost, timestamp, http_requests, websocket_requests, total_requests, " "bytes_sent, bytes_recv, avg_request_time_ms) " "VALUES (?, ?, ?, ?, ?, ?, ?, ?);"; if (sqlite3_prepare_v2(monitor.db, sql, -1, &stmt, NULL) != SQLITE_OK) return; double current_time = (double)time(NULL); for (vhost_stats_t *s = monitor.vhost_stats_head; s != NULL; s = s->next) { if (s->request_times.count > 0) { double total_time = 0; for(int i = 0; i < s->request_times.count; i++) { total_time += s->request_times.times[i]; } s->avg_request_time_ms = total_time / s->request_times.count; } sqlite3_bind_text(stmt, 1, s->vhost_name, -1, SQLITE_STATIC); sqlite3_bind_double(stmt, 2, current_time); sqlite3_bind_int64(stmt, 3, s->http_requests); sqlite3_bind_int64(stmt, 4, s->websocket_requests); sqlite3_bind_int64(stmt, 5, s->total_requests); sqlite3_bind_int64(stmt, 6, s->bytes_sent); sqlite3_bind_int64(stmt, 7, s->bytes_recv); sqlite3_bind_double(stmt, 8, s->avg_request_time_ms); sqlite3_step(stmt); sqlite3_reset(stmt); } sqlite3_finalize(stmt); } static double get_cpu_usage(void) { static long long prev_user = 0, prev_nice = 0, prev_system = 0, prev_idle = 0; long long user, nice, system, idle, iowait, irq, softirq; FILE *f = fopen("/proc/stat", "r"); if (!f) return 0.0; if (fscanf(f, "cpu %lld %lld %lld %lld %lld %lld %lld", &user, &nice, &system, &idle, &iowait, &irq, &softirq) != 7) { fclose(f); return 0.0; } fclose(f); long long prev_total = prev_user + prev_nice + prev_system + prev_idle; long long total = user + nice + system + idle; long long totald = total - prev_total; long long idled = idle - prev_idle; prev_user = user; prev_nice = nice; prev_system = system; prev_idle = idle; return totald == 0 ? 0.0 : (double)(totald - idled) * 100.0 / totald; } static void get_memory_usage(double *used_gb) { struct sysinfo info; if (sysinfo(&info) != 0) { *used_gb = 0; return; } *used_gb = (double)(info.totalram - info.freeram - info.bufferram) * info.mem_unit / (1024.0 * 1024.0 * 1024.0); } static void get_network_stats(long long *bytes_sent, long long *bytes_recv) { FILE *f = fopen("/proc/net/dev", "r"); if (!f) { *bytes_sent = 0; *bytes_recv = 0; return; } char line[256]; if (!fgets(line, sizeof(line), f) || !fgets(line, sizeof(line), f)) { fclose(f); *bytes_sent = 0; *bytes_recv = 0; return; } long long total_recv = 0, total_sent = 0; while (fgets(line, sizeof(line), f)) { char iface[32]; long long r, t; if (sscanf(line, "%31[^:]: %lld %*d %*d %*d %*d %*d %*d %*d %lld", iface, &r, &t) == 3) { char *trimmed = iface; while (*trimmed == ' ') trimmed++; if (strcmp(trimmed, "lo") != 0) { total_recv += r; total_sent += t; } } } fclose(f); *bytes_sent = total_sent; *bytes_recv = total_recv; } static void get_disk_stats(long long *sectors_read, long long *sectors_written) { FILE *f = fopen("/proc/diskstats", "r"); if (!f) { *sectors_read = 0; *sectors_written = 0; return; } char line[2048]; long long total_read = 0, total_written = 0; while (fgets(line, sizeof(line), f)) { char device[64]; long long sectors_r = 0, sectors_w = 0; int nfields = 0; char major[16], minor[16], dev[64]; char rc[32], rm[32], sr[32], rtm[32], rtm2[32], wc[32], wm[32], sw[32]; nfields = sscanf(line, "%15s %15s %63s %31s %31s %31s %31s %31s %31s %31s %31s %31s", major, minor, dev, rc, rm, sr, rtm, rtm2, wc, wm, sw, sw); if (nfields >= 11) { strncpy(device, dev, sizeof(device)-1); device[sizeof(device)-1] = '\0'; sectors_r = atoll(sr); sectors_w = atoll(sw); if (strncmp(device, "loop", 4) != 0 && strncmp(device, "ram", 3) != 0) { int len = strlen(device); if ((strncmp(device, "sd", 2) == 0 && len == 3) || (strncmp(device, "nvme", 4) == 0 && strstr(device, "n1p") == NULL) || (strncmp(device, "vd", 2) == 0 && len == 3) || (strncmp(device, "hd", 2) == 0 && len == 3)) { total_read += sectors_r; total_written += sectors_w; } } } } fclose(f); *sectors_read = total_read; *sectors_written = total_written; } static void get_load_averages(double *load1, double *load5, double *load15) { FILE *f = fopen("/proc/loadavg", "r"); if (!f) { *load1 = *load5 = *load15 = 0.0; return; } if (fscanf(f, "%lf %lf %lf", load1, load5, load15) != 3) { *load1 = *load5 = *load15 = 0.0; } fclose(f); } void monitor_update(void) { double current_time = time(NULL); history_deque_push(&monitor.cpu_history, current_time, get_cpu_usage()); double mem_used_gb; get_memory_usage(&mem_used_gb); history_deque_push(&monitor.memory_history, current_time, mem_used_gb); long long net_sent, net_recv; get_network_stats(&net_sent, &net_recv); double time_delta = current_time - monitor.last_net_update_time; if (time_delta > 0 && monitor.last_net_update_time > 0) { double rx = (net_recv - monitor.last_net_recv) / time_delta / 1024.0; double tx = (net_sent - monitor.last_net_sent) / time_delta / 1024.0; network_history_deque_push(&monitor.network_history, current_time, fmax(0, rx), fmax(0, tx)); history_deque_push(&monitor.throughput_history, current_time, fmax(0, rx + tx)); } monitor.last_net_sent = net_sent; monitor.last_net_recv = net_recv; monitor.last_net_update_time = current_time; long long disk_read, disk_write; get_disk_stats(&disk_read, &disk_write); double disk_time_delta = current_time - monitor.last_disk_update_time; if (disk_time_delta > 0 && monitor.last_disk_update_time > 0) { double read_mbps = (disk_read - monitor.last_disk_read) * 512.0 / disk_time_delta / (1024.0 * 1024.0); double write_mbps = (disk_write - monitor.last_disk_write) * 512.0 / disk_time_delta / (1024.0 * 1024.0); disk_history_deque_push(&monitor.disk_history, current_time, fmax(0, read_mbps), fmax(0, write_mbps)); } monitor.last_disk_read = disk_read; monitor.last_disk_write = disk_write; monitor.last_disk_update_time = current_time; double load1, load5, load15; get_load_averages(&load1, &load5, &load15); history_deque_push(&monitor.load1_history, current_time, load1); history_deque_push(&monitor.load5_history, current_time, load5); history_deque_push(&monitor.load15_history, current_time, load15); for (vhost_stats_t *s = monitor.vhost_stats_head; s != NULL; s = s->next) { double vhost_delta = current_time - s->last_update; if (vhost_delta >= 1.0) { double kbps = 0; if (s->last_update > 0) { long long bytes_diff = (s->bytes_sent - s->last_bytes_sent) + (s->bytes_recv - s->last_bytes_recv); kbps = bytes_diff / vhost_delta / 1024.0; } history_deque_push(&s->throughput_history, current_time, fmax(0, kbps)); s->last_bytes_sent = s->bytes_sent; s->last_bytes_recv = s->bytes_recv; s->last_update = current_time; } } static time_t last_db_save = 0; if (current_time - last_db_save >= 10) { save_stats_to_db(); last_db_save = current_time; } } vhost_stats_t* monitor_get_or_create_vhost_stats(const char *vhost_name) { if (!vhost_name || strlen(vhost_name) == 0) return NULL; for (vhost_stats_t *curr = monitor.vhost_stats_head; curr; curr = curr->next) { if (strcmp(curr->vhost_name, vhost_name) == 0) return curr; } vhost_stats_t *new_stats = calloc(1, sizeof(vhost_stats_t)); if (!new_stats) return NULL; strncpy(new_stats->vhost_name, vhost_name, sizeof(new_stats->vhost_name) - 1); new_stats->last_update = time(NULL); history_deque_init(&new_stats->throughput_history, 60); request_time_deque_init(&new_stats->request_times, 100); new_stats->next = monitor.vhost_stats_head; monitor.vhost_stats_head = new_stats; return new_stats; } void monitor_record_request_start(vhost_stats_t *stats, int is_websocket) { if (!stats) return; if (is_websocket) { __sync_fetch_and_add(&stats->websocket_requests, 1); } else { __sync_fetch_and_add(&stats->http_requests, 1); } __sync_fetch_and_add(&stats->total_requests, 1); } void monitor_record_request_end(vhost_stats_t *stats, double start_time) { if (!stats || start_time <= 0) return; struct timespec end_time; clock_gettime(CLOCK_MONOTONIC, &end_time); double duration_ms = ((end_time.tv_sec + end_time.tv_nsec / 1e9) - start_time) * 1000.0; if (duration_ms >= 0 && duration_ms < 60000) { request_time_deque_push(&stats->request_times, duration_ms); } } void monitor_record_bytes(vhost_stats_t *stats, long long sent, long long recv) { if (!stats) return; __sync_fetch_and_add(&stats->bytes_sent, sent); __sync_fetch_and_add(&stats->bytes_recv, recv); }