|
#include "monitor.h"
|
|
#include "logging.h"
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/sysinfo.h>
|
|
#include <math.h>
|
|
#include <pthread.h>
|
|
|
|
system_monitor_t monitor;
|
|
static pthread_mutex_t vhost_stats_mutex = PTHREAD_MUTEX_INITIALIZER;
|
|
|
|
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';
|
|
char *endptr;
|
|
sectors_r = strtoll(sr, &endptr, 10);
|
|
if (endptr == sr) sectors_r = 0;
|
|
sectors_w = strtoll(sw, &endptr, 10);
|
|
if (endptr == sw) sectors_w = 0;
|
|
|
|
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;
|
|
|
|
pthread_mutex_lock(&vhost_stats_mutex);
|
|
|
|
for (vhost_stats_t *curr = monitor.vhost_stats_head; curr; curr = curr->next) {
|
|
if (strcmp(curr->vhost_name, vhost_name) == 0) {
|
|
pthread_mutex_unlock(&vhost_stats_mutex);
|
|
return curr;
|
|
}
|
|
}
|
|
|
|
vhost_stats_t *new_stats = calloc(1, sizeof(vhost_stats_t));
|
|
if (!new_stats) {
|
|
pthread_mutex_unlock(&vhost_stats_mutex);
|
|
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;
|
|
|
|
pthread_mutex_unlock(&vhost_stats_mutex);
|
|
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);
|
|
}
|