initial module
This commit is contained in:
304
src/ngx_http_monitoring.h
Normal file
304
src/ngx_http_monitoring.h
Normal file
@@ -0,0 +1,304 @@
|
||||
#ifndef _NGX_HTTP_MONITORING_H_INCLUDED_
|
||||
#define _NGX_HTTP_MONITORING_H_INCLUDED_
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include <ngx_http.h>
|
||||
|
||||
#include <sys/statvfs.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define NGX_HTTP_MONITORING_VERSION 1
|
||||
#define NGX_HTTP_MONITORING_MODULE_VERSION "1.0.0"
|
||||
|
||||
#define NGX_HTTP_MONITORING_DEFAULT_SHM_SIZE (8 * 1024 * 1024)
|
||||
#define NGX_HTTP_MONITORING_MIN_INTERVAL 100
|
||||
#define NGX_HTTP_MONITORING_HISTORY_MAX 3600
|
||||
#define NGX_HTTP_MONITORING_TOP_URLS_MAX 256
|
||||
#define NGX_HTTP_MONITORING_TOP_UA_MAX 64
|
||||
#define NGX_HTTP_MONITORING_UPSTREAMS_MAX 128
|
||||
#define NGX_HTTP_MONITORING_WORKERS_MAX 128
|
||||
#define NGX_HTTP_MONITORING_IFACES_MAX 32
|
||||
#define NGX_HTTP_MONITORING_DISKS_MAX 32
|
||||
#define NGX_HTTP_MONITORING_FILESYSTEMS_MAX 32
|
||||
#define NGX_HTTP_MONITORING_LATENCY_BUCKETS 14
|
||||
#define NGX_HTTP_MONITORING_SIZE_BUCKETS 12
|
||||
#define NGX_HTTP_MONITORING_METHODS 8
|
||||
|
||||
#define NGX_HTTP_MONITORING_KEY_LEN 128
|
||||
#define NGX_HTTP_MONITORING_NAME_LEN 64
|
||||
|
||||
typedef enum {
|
||||
NGX_HTTP_MONITORING_EP_NONE = 0,
|
||||
NGX_HTTP_MONITORING_EP_DASHBOARD,
|
||||
NGX_HTTP_MONITORING_EP_API_FULL,
|
||||
NGX_HTTP_MONITORING_EP_API_SYSTEM,
|
||||
NGX_HTTP_MONITORING_EP_API_NGINX,
|
||||
NGX_HTTP_MONITORING_EP_API_NETWORK,
|
||||
NGX_HTTP_MONITORING_EP_API_DISK,
|
||||
NGX_HTTP_MONITORING_EP_API_PROCESSES,
|
||||
NGX_HTTP_MONITORING_EP_API_UPSTREAMS,
|
||||
NGX_HTTP_MONITORING_EP_API_CONNECTIONS,
|
||||
NGX_HTTP_MONITORING_EP_API_REQUESTS,
|
||||
NGX_HTTP_MONITORING_EP_LIVE,
|
||||
NGX_HTTP_MONITORING_EP_PROMETHEUS,
|
||||
NGX_HTTP_MONITORING_EP_HEALTH
|
||||
} ngx_http_monitoring_endpoint_e;
|
||||
|
||||
typedef enum {
|
||||
NGX_HTTP_MONITORING_METHOD_GET = 0,
|
||||
NGX_HTTP_MONITORING_METHOD_POST,
|
||||
NGX_HTTP_MONITORING_METHOD_PUT,
|
||||
NGX_HTTP_MONITORING_METHOD_DELETE,
|
||||
NGX_HTTP_MONITORING_METHOD_HEAD,
|
||||
NGX_HTTP_MONITORING_METHOD_OPTIONS,
|
||||
NGX_HTTP_MONITORING_METHOD_PATCH,
|
||||
NGX_HTTP_MONITORING_METHOD_OTHER
|
||||
} ngx_http_monitoring_method_e;
|
||||
|
||||
typedef struct {
|
||||
ngx_uint_t deny;
|
||||
ngx_cidr_t cidr;
|
||||
} ngx_http_monitoring_access_rule_t;
|
||||
|
||||
typedef struct {
|
||||
ngx_msec_t refresh_interval;
|
||||
ngx_msec_t history;
|
||||
ngx_msec_t resolution;
|
||||
size_t shm_size;
|
||||
ngx_uint_t max_top_urls;
|
||||
ngx_flag_t collect_system;
|
||||
ngx_flag_t collect_nginx;
|
||||
ngx_flag_t collect_network;
|
||||
ngx_flag_t access_log;
|
||||
|
||||
ngx_shm_zone_t *shm_zone;
|
||||
ngx_event_t collector_event;
|
||||
ngx_uint_t collector_active;
|
||||
void *sh;
|
||||
ngx_slab_pool_t *shpool;
|
||||
} ngx_http_monitoring_main_conf_t;
|
||||
|
||||
typedef struct {
|
||||
ngx_flag_t enabled;
|
||||
ngx_flag_t dashboard;
|
||||
ngx_flag_t api;
|
||||
ngx_flag_t sse;
|
||||
ngx_str_t api_token;
|
||||
ngx_str_t basic_auth;
|
||||
ngx_str_t cors;
|
||||
ngx_uint_t rate_limit;
|
||||
ngx_array_t *access_rules;
|
||||
} ngx_http_monitoring_loc_conf_t;
|
||||
|
||||
typedef struct {
|
||||
time_t timestamp;
|
||||
ngx_msec_t msec;
|
||||
double cpu_usage;
|
||||
double load1;
|
||||
double load5;
|
||||
double load15;
|
||||
double memory_used_pct;
|
||||
double swap_used_pct;
|
||||
double requests_per_sec;
|
||||
double responses_per_sec;
|
||||
double network_rx_per_sec;
|
||||
double network_tx_per_sec;
|
||||
double disk_read_per_sec;
|
||||
double disk_write_per_sec;
|
||||
double latency_p95;
|
||||
ngx_atomic_uint_t requests_total;
|
||||
ngx_atomic_uint_t status_4xx;
|
||||
ngx_atomic_uint_t status_5xx;
|
||||
} ngx_http_monitoring_history_sample_t;
|
||||
|
||||
typedef struct {
|
||||
ngx_atomic_t hits;
|
||||
ngx_atomic_t errors;
|
||||
ngx_atomic_t bytes;
|
||||
ngx_atomic_t latency_ms_total;
|
||||
ngx_atomic_t last_seen;
|
||||
uint32_t hash;
|
||||
u_char key[NGX_HTTP_MONITORING_KEY_LEN];
|
||||
} ngx_http_monitoring_top_entry_t;
|
||||
|
||||
typedef struct {
|
||||
ngx_atomic_t requests;
|
||||
ngx_atomic_t failures;
|
||||
ngx_atomic_t latency_ms_total;
|
||||
ngx_atomic_t status_4xx;
|
||||
ngx_atomic_t status_5xx;
|
||||
ngx_atomic_t last_seen;
|
||||
uint32_t hash;
|
||||
u_char peer[NGX_HTTP_MONITORING_KEY_LEN];
|
||||
} ngx_http_monitoring_upstream_entry_t;
|
||||
|
||||
typedef struct {
|
||||
ngx_atomic_t pid;
|
||||
ngx_atomic_t requests;
|
||||
ngx_atomic_t bytes;
|
||||
ngx_atomic_t last_seen;
|
||||
ngx_atomic_t active;
|
||||
ngx_atomic_t errors;
|
||||
ngx_atomic_t vm_size;
|
||||
ngx_atomic_t vm_rss;
|
||||
ngx_atomic_t voluntary_ctxt;
|
||||
ngx_atomic_t nonvoluntary_ctxt;
|
||||
} ngx_http_monitoring_worker_metric_t;
|
||||
|
||||
typedef struct {
|
||||
u_char name[NGX_HTTP_MONITORING_NAME_LEN];
|
||||
ngx_atomic_t rx_bytes;
|
||||
ngx_atomic_t tx_bytes;
|
||||
ngx_atomic_t rx_packets;
|
||||
ngx_atomic_t tx_packets;
|
||||
ngx_atomic_t rx_errors;
|
||||
ngx_atomic_t tx_errors;
|
||||
ngx_atomic_t flags;
|
||||
} ngx_http_monitoring_iface_metric_t;
|
||||
|
||||
typedef struct {
|
||||
u_char name[NGX_HTTP_MONITORING_NAME_LEN];
|
||||
ngx_atomic_t reads;
|
||||
ngx_atomic_t writes;
|
||||
ngx_atomic_t read_bytes;
|
||||
ngx_atomic_t write_bytes;
|
||||
ngx_atomic_t io_ms;
|
||||
} ngx_http_monitoring_disk_metric_t;
|
||||
|
||||
typedef struct {
|
||||
u_char path[NGX_HTTP_MONITORING_KEY_LEN];
|
||||
u_char type[NGX_HTTP_MONITORING_NAME_LEN];
|
||||
ngx_atomic_t total;
|
||||
ngx_atomic_t used;
|
||||
ngx_atomic_t free;
|
||||
ngx_atomic_t avail;
|
||||
ngx_atomic_t files;
|
||||
ngx_atomic_t files_free;
|
||||
} ngx_http_monitoring_fs_metric_t;
|
||||
|
||||
typedef struct {
|
||||
ngx_atomic_t usage_milli;
|
||||
ngx_atomic_t cores;
|
||||
ngx_atomic_t load1_milli;
|
||||
ngx_atomic_t load5_milli;
|
||||
ngx_atomic_t load15_milli;
|
||||
ngx_atomic_t mem_total;
|
||||
ngx_atomic_t mem_available;
|
||||
ngx_atomic_t mem_free;
|
||||
ngx_atomic_t swap_total;
|
||||
ngx_atomic_t swap_free;
|
||||
ngx_atomic_t uptime;
|
||||
ngx_atomic_t process_count;
|
||||
ngx_atomic_t tcp_established;
|
||||
ngx_atomic_t tcp_listen;
|
||||
ngx_atomic_t sockets_used;
|
||||
ngx_atomic_t sockets_tcp;
|
||||
ngx_atomic_t sockets_udp;
|
||||
} ngx_http_monitoring_system_metric_t;
|
||||
|
||||
typedef struct {
|
||||
ngx_atomic_t total;
|
||||
ngx_atomic_t accepted;
|
||||
ngx_atomic_t handled;
|
||||
ngx_atomic_t active;
|
||||
ngx_atomic_t reading;
|
||||
ngx_atomic_t writing;
|
||||
ngx_atomic_t waiting;
|
||||
ngx_atomic_t ssl_requests;
|
||||
ngx_atomic_t ssl_handshakes;
|
||||
ngx_atomic_t keepalive_requests;
|
||||
ngx_atomic_t sse_clients;
|
||||
ngx_atomic_t sse_events;
|
||||
ngx_atomic_t rate_limited;
|
||||
} ngx_http_monitoring_connection_metric_t;
|
||||
|
||||
typedef struct {
|
||||
ngx_atomic_t total;
|
||||
ngx_atomic_t responses;
|
||||
ngx_atomic_t bytes;
|
||||
ngx_atomic_t request_time_ms_total;
|
||||
ngx_atomic_t status_1xx;
|
||||
ngx_atomic_t status_2xx;
|
||||
ngx_atomic_t status_3xx;
|
||||
ngx_atomic_t status_4xx;
|
||||
ngx_atomic_t status_5xx;
|
||||
ngx_atomic_t method[NGX_HTTP_MONITORING_METHODS];
|
||||
ngx_atomic_t latency_bucket[NGX_HTTP_MONITORING_LATENCY_BUCKETS];
|
||||
ngx_atomic_t size_bucket[NGX_HTTP_MONITORING_SIZE_BUCKETS];
|
||||
} ngx_http_monitoring_request_metric_t;
|
||||
|
||||
typedef struct {
|
||||
ngx_atomic_t collector_lock;
|
||||
ngx_atomic_t rate_limit_window;
|
||||
ngx_atomic_t rate_limit_count;
|
||||
ngx_atomic_t history_write;
|
||||
ngx_atomic_t history_count;
|
||||
ngx_atomic_t history_capacity;
|
||||
ngx_atomic_t generation;
|
||||
ngx_atomic_t initialized;
|
||||
|
||||
ngx_http_monitoring_system_metric_t system;
|
||||
ngx_http_monitoring_connection_metric_t connections;
|
||||
ngx_http_monitoring_request_metric_t requests;
|
||||
|
||||
ngx_atomic_t iface_count;
|
||||
ngx_http_monitoring_iface_metric_t ifaces[NGX_HTTP_MONITORING_IFACES_MAX];
|
||||
|
||||
ngx_atomic_t disk_count;
|
||||
ngx_http_monitoring_disk_metric_t disks[NGX_HTTP_MONITORING_DISKS_MAX];
|
||||
|
||||
ngx_atomic_t fs_count;
|
||||
ngx_http_monitoring_fs_metric_t filesystems[NGX_HTTP_MONITORING_FILESYSTEMS_MAX];
|
||||
|
||||
ngx_http_monitoring_worker_metric_t workers[NGX_HTTP_MONITORING_WORKERS_MAX];
|
||||
ngx_http_monitoring_top_entry_t urls[NGX_HTTP_MONITORING_TOP_URLS_MAX];
|
||||
ngx_http_monitoring_top_entry_t user_agents[NGX_HTTP_MONITORING_TOP_UA_MAX];
|
||||
ngx_http_monitoring_upstream_entry_t upstreams[NGX_HTTP_MONITORING_UPSTREAMS_MAX];
|
||||
|
||||
ngx_http_monitoring_history_sample_t history[NGX_HTTP_MONITORING_HISTORY_MAX];
|
||||
|
||||
ngx_atomic_t prev_req_total;
|
||||
ngx_atomic_t prev_resp_total;
|
||||
ngx_atomic_t prev_net_rx;
|
||||
ngx_atomic_t prev_net_tx;
|
||||
ngx_atomic_t prev_disk_read;
|
||||
ngx_atomic_t prev_disk_write;
|
||||
ngx_atomic_t prev_collect_msec;
|
||||
ngx_atomic_t prev_history_msec;
|
||||
ngx_atomic_t ewma_rps_milli;
|
||||
ngx_atomic_t ewma_resp_milli;
|
||||
|
||||
uint64_t prev_cpu_total;
|
||||
uint64_t prev_cpu_idle;
|
||||
} ngx_http_monitoring_shctx_t;
|
||||
|
||||
extern ngx_module_t ngx_http_monitoring_module;
|
||||
|
||||
ngx_int_t ngx_http_monitoring_init_shm(ngx_shm_zone_t *shm_zone, void *data);
|
||||
ngx_int_t ngx_http_monitoring_init_collector(ngx_cycle_t *cycle);
|
||||
void ngx_http_monitoring_collect(ngx_event_t *ev);
|
||||
void ngx_http_monitoring_account_request(ngx_http_request_t *r);
|
||||
void ngx_http_monitoring_account_upstream(ngx_http_request_t *r, ngx_msec_t latency);
|
||||
|
||||
ngx_http_monitoring_endpoint_e ngx_http_monitoring_match_endpoint(ngx_http_request_t *r);
|
||||
ngx_int_t ngx_http_monitoring_send_json(ngx_http_request_t *r,
|
||||
ngx_http_monitoring_endpoint_e endpoint);
|
||||
ngx_int_t ngx_http_monitoring_send_dashboard(ngx_http_request_t *r);
|
||||
ngx_int_t ngx_http_monitoring_send_sse(ngx_http_request_t *r);
|
||||
ngx_int_t ngx_http_monitoring_send_prometheus(ngx_http_request_t *r);
|
||||
ngx_int_t ngx_http_monitoring_send_health(ngx_http_request_t *r);
|
||||
|
||||
ngx_http_monitoring_shctx_t *ngx_http_monitoring_get_shctx(ngx_http_request_t *r);
|
||||
ngx_http_monitoring_main_conf_t *ngx_http_monitoring_get_main_conf(ngx_http_request_t *r);
|
||||
ngx_uint_t ngx_http_monitoring_method_index(ngx_uint_t method);
|
||||
ngx_uint_t ngx_http_monitoring_latency_bucket(ngx_msec_t ms);
|
||||
ngx_uint_t ngx_http_monitoring_size_bucket(off_t bytes);
|
||||
double ngx_http_monitoring_percentile(ngx_http_monitoring_shctx_t *sh,
|
||||
double percentile);
|
||||
uint32_t ngx_http_monitoring_hash(ngx_str_t *value);
|
||||
void ngx_http_monitoring_update_top(ngx_slab_pool_t *shpool,
|
||||
ngx_http_monitoring_top_entry_t *entries, ngx_uint_t capacity,
|
||||
ngx_str_t *key, ngx_msec_t latency, off_t bytes, ngx_uint_t is_error);
|
||||
|
||||
#endif /* _NGX_HTTP_MONITORING_H_INCLUDED_ */
|
||||
841
src/ngx_http_monitoring_collectors.c
Normal file
841
src/ngx_http_monitoring_collectors.c
Normal file
@@ -0,0 +1,841 @@
|
||||
#include "ngx_http_monitoring.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <ifaddrs.h>
|
||||
#include <mntent.h>
|
||||
#include <net/if.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#ifndef O_CLOEXEC
|
||||
#define O_CLOEXEC 0
|
||||
#endif
|
||||
|
||||
static ssize_t ngx_http_monitoring_read_file(const char *path, u_char *buf,
|
||||
size_t size);
|
||||
static void ngx_http_monitoring_collect_cpu(ngx_http_monitoring_shctx_t *sh);
|
||||
static void ngx_http_monitoring_collect_load(ngx_http_monitoring_shctx_t *sh);
|
||||
static void ngx_http_monitoring_collect_meminfo(ngx_http_monitoring_shctx_t *sh);
|
||||
static void ngx_http_monitoring_collect_uptime(ngx_http_monitoring_shctx_t *sh);
|
||||
static void ngx_http_monitoring_collect_processes(ngx_http_monitoring_shctx_t *sh);
|
||||
static void ngx_http_monitoring_collect_self_status(
|
||||
ngx_http_monitoring_shctx_t *sh);
|
||||
static void ngx_http_monitoring_collect_tcp(ngx_http_monitoring_shctx_t *sh);
|
||||
static void ngx_http_monitoring_collect_sockstat(ngx_http_monitoring_shctx_t *sh);
|
||||
static void ngx_http_monitoring_collect_network(ngx_http_monitoring_shctx_t *sh);
|
||||
static void ngx_http_monitoring_collect_diskstats(ngx_http_monitoring_shctx_t *sh);
|
||||
static void ngx_http_monitoring_collect_filesystems(ngx_http_monitoring_shctx_t *sh);
|
||||
static void ngx_http_monitoring_collect_nginx(ngx_http_monitoring_shctx_t *sh);
|
||||
static void ngx_http_monitoring_collect_history(
|
||||
ngx_http_monitoring_main_conf_t *mmcf, ngx_http_monitoring_shctx_t *sh);
|
||||
static ngx_atomic_uint_t ngx_http_monitoring_sum_network(
|
||||
ngx_http_monitoring_shctx_t *sh, ngx_uint_t tx);
|
||||
static ngx_atomic_uint_t ngx_http_monitoring_sum_disk(
|
||||
ngx_http_monitoring_shctx_t *sh, ngx_uint_t write);
|
||||
static ngx_atomic_uint_t ngx_http_monitoring_delta(ngx_atomic_uint_t now,
|
||||
ngx_atomic_uint_t prev);
|
||||
static ngx_uint_t ngx_http_monitoring_skip_fs(const char *type);
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_http_monitoring_init_collector(ngx_cycle_t *cycle)
|
||||
{
|
||||
ngx_http_monitoring_main_conf_t *mmcf;
|
||||
ngx_http_monitoring_shctx_t *sh;
|
||||
ngx_uint_t slot;
|
||||
|
||||
if (ngx_process != NGX_PROCESS_WORKER) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
mmcf = ngx_http_cycle_get_module_main_conf(cycle,
|
||||
ngx_http_monitoring_module);
|
||||
if (mmcf == NULL || mmcf->sh == NULL) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
sh = (ngx_http_monitoring_shctx_t *) mmcf->sh;
|
||||
|
||||
if (ngx_process_slot >= 0) {
|
||||
slot = ((ngx_uint_t) ngx_process_slot)
|
||||
% NGX_HTTP_MONITORING_WORKERS_MAX;
|
||||
sh->workers[slot].pid = ngx_pid;
|
||||
sh->workers[slot].active = 1;
|
||||
sh->workers[slot].last_seen = ngx_time();
|
||||
}
|
||||
|
||||
ngx_memzero(&mmcf->collector_event, sizeof(ngx_event_t));
|
||||
mmcf->collector_event.handler = ngx_http_monitoring_collect;
|
||||
mmcf->collector_event.log = cycle->log;
|
||||
mmcf->collector_event.data = cycle;
|
||||
mmcf->collector_active = 1;
|
||||
|
||||
ngx_add_timer(&mmcf->collector_event,
|
||||
50 + ((ngx_process_slot >= 0 ? ngx_process_slot : 0) % 16)
|
||||
* 25);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ngx_http_monitoring_collect(ngx_event_t *ev)
|
||||
{
|
||||
ngx_cycle_t *cycle;
|
||||
ngx_http_monitoring_main_conf_t *mmcf;
|
||||
ngx_http_monitoring_shctx_t *sh;
|
||||
ngx_uint_t slot;
|
||||
|
||||
cycle = ev->data;
|
||||
mmcf = ngx_http_cycle_get_module_main_conf(cycle,
|
||||
ngx_http_monitoring_module);
|
||||
if (mmcf == NULL || mmcf->sh == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
sh = (ngx_http_monitoring_shctx_t *) mmcf->sh;
|
||||
|
||||
if (ngx_process_slot >= 0) {
|
||||
slot = ((ngx_uint_t) ngx_process_slot)
|
||||
% NGX_HTTP_MONITORING_WORKERS_MAX;
|
||||
sh->workers[slot].pid = ngx_pid;
|
||||
sh->workers[slot].active = 1;
|
||||
sh->workers[slot].last_seen = ngx_time();
|
||||
}
|
||||
|
||||
ngx_http_monitoring_collect_self_status(sh);
|
||||
|
||||
if (ngx_atomic_cmp_set(&sh->collector_lock, 0, (ngx_atomic_uint_t) ngx_pid))
|
||||
{
|
||||
if (mmcf->collect_nginx) {
|
||||
ngx_http_monitoring_collect_nginx(sh);
|
||||
}
|
||||
|
||||
if (mmcf->collect_system) {
|
||||
ngx_http_monitoring_collect_cpu(sh);
|
||||
ngx_http_monitoring_collect_load(sh);
|
||||
ngx_http_monitoring_collect_meminfo(sh);
|
||||
ngx_http_monitoring_collect_uptime(sh);
|
||||
ngx_http_monitoring_collect_processes(sh);
|
||||
ngx_http_monitoring_collect_tcp(sh);
|
||||
ngx_http_monitoring_collect_sockstat(sh);
|
||||
ngx_http_monitoring_collect_diskstats(sh);
|
||||
ngx_http_monitoring_collect_filesystems(sh);
|
||||
}
|
||||
|
||||
if (mmcf->collect_network) {
|
||||
ngx_http_monitoring_collect_network(sh);
|
||||
}
|
||||
|
||||
ngx_http_monitoring_collect_history(mmcf, sh);
|
||||
|
||||
sh->generation++;
|
||||
sh->collector_lock = 0;
|
||||
}
|
||||
|
||||
if (!ngx_exiting && mmcf->collector_active) {
|
||||
ngx_add_timer(ev, mmcf->refresh_interval);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static ssize_t
|
||||
ngx_http_monitoring_read_file(const char *path, u_char *buf, size_t size)
|
||||
{
|
||||
int fd;
|
||||
ssize_t n;
|
||||
|
||||
if (size == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
fd = open(path, O_RDONLY | O_CLOEXEC);
|
||||
if (fd == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
n = read(fd, buf, size - 1);
|
||||
close(fd);
|
||||
|
||||
if (n < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
buf[n] = '\0';
|
||||
return n;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_http_monitoring_collect_cpu(ngx_http_monitoring_shctx_t *sh)
|
||||
{
|
||||
u_char buf[32768];
|
||||
char *line, *next;
|
||||
unsigned long long user, nice, system, idle, iowait, irq, softirq, steal;
|
||||
uint64_t total, idle_all, delta_total, delta_idle;
|
||||
ngx_uint_t cores;
|
||||
|
||||
if (ngx_http_monitoring_read_file("/proc/stat", buf, sizeof(buf)) <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
user = nice = system = idle = iowait = irq = softirq = steal = 0;
|
||||
|
||||
if (sscanf((char *) buf, "cpu %llu %llu %llu %llu %llu %llu %llu %llu",
|
||||
&user, &nice, &system, &idle, &iowait, &irq, &softirq, &steal)
|
||||
< 4)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
total = user + nice + system + idle + iowait + irq + softirq + steal;
|
||||
idle_all = idle + iowait;
|
||||
|
||||
if (sh->prev_cpu_total > 0 && total > sh->prev_cpu_total) {
|
||||
delta_total = total - sh->prev_cpu_total;
|
||||
delta_idle = idle_all - sh->prev_cpu_idle;
|
||||
if (delta_total > 0) {
|
||||
sh->system.usage_milli =
|
||||
(ngx_atomic_uint_t) (((delta_total - delta_idle) * 100000)
|
||||
/ delta_total);
|
||||
}
|
||||
}
|
||||
|
||||
sh->prev_cpu_total = total;
|
||||
sh->prev_cpu_idle = idle_all;
|
||||
|
||||
cores = 0;
|
||||
line = (char *) buf;
|
||||
while (line != NULL && *line != '\0') {
|
||||
next = strchr(line, '\n');
|
||||
if (next != NULL) {
|
||||
*next++ = '\0';
|
||||
}
|
||||
|
||||
if (line[0] == 'c' && line[1] == 'p' && line[2] == 'u'
|
||||
&& isdigit((unsigned char) line[3]))
|
||||
{
|
||||
cores++;
|
||||
}
|
||||
|
||||
line = next;
|
||||
}
|
||||
|
||||
if (cores > 0) {
|
||||
sh->system.cores = cores;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_http_monitoring_collect_load(ngx_http_monitoring_shctx_t *sh)
|
||||
{
|
||||
u_char buf[256];
|
||||
double l1, l5, l15;
|
||||
|
||||
if (ngx_http_monitoring_read_file("/proc/loadavg", buf, sizeof(buf)) <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (sscanf((char *) buf, "%lf %lf %lf", &l1, &l5, &l15) != 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
sh->system.load1_milli = (ngx_atomic_uint_t) (l1 * 1000.0);
|
||||
sh->system.load5_milli = (ngx_atomic_uint_t) (l5 * 1000.0);
|
||||
sh->system.load15_milli = (ngx_atomic_uint_t) (l15 * 1000.0);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_http_monitoring_collect_meminfo(ngx_http_monitoring_shctx_t *sh)
|
||||
{
|
||||
u_char buf[8192];
|
||||
char *line, *next;
|
||||
unsigned long long value;
|
||||
|
||||
if (ngx_http_monitoring_read_file("/proc/meminfo", buf, sizeof(buf)) <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
line = (char *) buf;
|
||||
while (line != NULL && *line != '\0') {
|
||||
next = strchr(line, '\n');
|
||||
if (next != NULL) {
|
||||
*next++ = '\0';
|
||||
}
|
||||
|
||||
if (sscanf(line, "MemTotal: %llu kB", &value) == 1) {
|
||||
sh->system.mem_total = (ngx_atomic_uint_t) (value * 1024);
|
||||
} else if (sscanf(line, "MemAvailable: %llu kB", &value) == 1) {
|
||||
sh->system.mem_available = (ngx_atomic_uint_t) (value * 1024);
|
||||
} else if (sscanf(line, "MemFree: %llu kB", &value) == 1) {
|
||||
sh->system.mem_free = (ngx_atomic_uint_t) (value * 1024);
|
||||
} else if (sscanf(line, "SwapTotal: %llu kB", &value) == 1) {
|
||||
sh->system.swap_total = (ngx_atomic_uint_t) (value * 1024);
|
||||
} else if (sscanf(line, "SwapFree: %llu kB", &value) == 1) {
|
||||
sh->system.swap_free = (ngx_atomic_uint_t) (value * 1024);
|
||||
}
|
||||
|
||||
line = next;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_http_monitoring_collect_uptime(ngx_http_monitoring_shctx_t *sh)
|
||||
{
|
||||
u_char buf[256];
|
||||
double uptime;
|
||||
|
||||
if (ngx_http_monitoring_read_file("/proc/uptime", buf, sizeof(buf)) <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (sscanf((char *) buf, "%lf", &uptime) == 1) {
|
||||
sh->system.uptime = (ngx_atomic_uint_t) uptime;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_http_monitoring_collect_processes(ngx_http_monitoring_shctx_t *sh)
|
||||
{
|
||||
DIR *dir;
|
||||
struct dirent *de;
|
||||
ngx_uint_t count;
|
||||
|
||||
dir = opendir("/proc");
|
||||
if (dir == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
count = 0;
|
||||
while ((de = readdir(dir)) != NULL) {
|
||||
if (isdigit((unsigned char) de->d_name[0])) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
sh->system.process_count = count;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_http_monitoring_collect_self_status(ngx_http_monitoring_shctx_t *sh)
|
||||
{
|
||||
u_char buf[8192];
|
||||
char *line, *next;
|
||||
unsigned long long value;
|
||||
ngx_uint_t slot;
|
||||
ngx_http_monitoring_worker_metric_t *w;
|
||||
|
||||
if (ngx_process_slot < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ngx_http_monitoring_read_file("/proc/self/status", buf, sizeof(buf))
|
||||
<= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
slot = ((ngx_uint_t) ngx_process_slot) % NGX_HTTP_MONITORING_WORKERS_MAX;
|
||||
w = &sh->workers[slot];
|
||||
|
||||
line = (char *) buf;
|
||||
while (line != NULL && *line != '\0') {
|
||||
next = strchr(line, '\n');
|
||||
if (next != NULL) {
|
||||
*next++ = '\0';
|
||||
}
|
||||
|
||||
if (sscanf(line, "VmSize: %llu kB", &value) == 1) {
|
||||
w->vm_size = (ngx_atomic_uint_t) (value * 1024);
|
||||
} else if (sscanf(line, "VmRSS: %llu kB", &value) == 1) {
|
||||
w->vm_rss = (ngx_atomic_uint_t) (value * 1024);
|
||||
} else if (sscanf(line, "voluntary_ctxt_switches: %llu", &value)
|
||||
== 1)
|
||||
{
|
||||
w->voluntary_ctxt = (ngx_atomic_uint_t) value;
|
||||
} else if (sscanf(line, "nonvoluntary_ctxt_switches: %llu", &value)
|
||||
== 1)
|
||||
{
|
||||
w->nonvoluntary_ctxt = (ngx_atomic_uint_t) value;
|
||||
}
|
||||
|
||||
line = next;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_http_monitoring_collect_tcp(ngx_http_monitoring_shctx_t *sh)
|
||||
{
|
||||
FILE *fp;
|
||||
char line[512];
|
||||
unsigned state;
|
||||
ngx_uint_t established, listen;
|
||||
const char *paths[2] = { "/proc/net/tcp", "/proc/net/tcp6" };
|
||||
ngx_uint_t i;
|
||||
|
||||
established = 0;
|
||||
listen = 0;
|
||||
|
||||
for (i = 0; i < 2; i++) {
|
||||
fp = fopen(paths[i], "r");
|
||||
if (fp == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
(void) fgets(line, sizeof(line), fp);
|
||||
|
||||
while (fgets(line, sizeof(line), fp) != NULL) {
|
||||
state = 0;
|
||||
if (sscanf(line, " %*u: %*64s %*64s %x", &state) == 1) {
|
||||
if (state == 0x01) {
|
||||
established++;
|
||||
} else if (state == 0x0a) {
|
||||
listen++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
sh->system.tcp_established = established;
|
||||
sh->system.tcp_listen = listen;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_http_monitoring_collect_sockstat(ngx_http_monitoring_shctx_t *sh)
|
||||
{
|
||||
u_char buf[4096];
|
||||
char *line, *next;
|
||||
unsigned used, tcp, udp;
|
||||
|
||||
if (ngx_http_monitoring_read_file("/proc/net/sockstat", buf, sizeof(buf))
|
||||
<= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
line = (char *) buf;
|
||||
while (line != NULL && *line != '\0') {
|
||||
next = strchr(line, '\n');
|
||||
if (next != NULL) {
|
||||
*next++ = '\0';
|
||||
}
|
||||
|
||||
if (sscanf(line, "sockets: used %u", &used) == 1) {
|
||||
sh->system.sockets_used = used;
|
||||
} else if (sscanf(line, "TCP: inuse %u", &tcp) == 1) {
|
||||
sh->system.sockets_tcp = tcp;
|
||||
} else if (sscanf(line, "UDP: inuse %u", &udp) == 1) {
|
||||
sh->system.sockets_udp = udp;
|
||||
}
|
||||
|
||||
line = next;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_http_monitoring_collect_network(ngx_http_monitoring_shctx_t *sh)
|
||||
{
|
||||
u_char buf[32768];
|
||||
char *line, *next, *colon, name[NGX_HTTP_MONITORING_NAME_LEN];
|
||||
unsigned long long rx_bytes, rx_packets, rx_errs;
|
||||
unsigned long long tx_bytes, tx_packets, tx_errs;
|
||||
ngx_uint_t count, i;
|
||||
struct ifaddrs *ifaddr, *ifa;
|
||||
|
||||
if (ngx_http_monitoring_read_file("/proc/net/dev", buf, sizeof(buf)) <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
count = 0;
|
||||
line = (char *) buf;
|
||||
|
||||
while (line != NULL && *line != '\0') {
|
||||
next = strchr(line, '\n');
|
||||
if (next != NULL) {
|
||||
*next++ = '\0';
|
||||
}
|
||||
|
||||
colon = strchr(line, ':');
|
||||
if (colon != NULL && count < NGX_HTTP_MONITORING_IFACES_MAX) {
|
||||
*colon = '\0';
|
||||
while (*line == ' ') {
|
||||
line++;
|
||||
}
|
||||
|
||||
ngx_memzero(name, sizeof(name));
|
||||
if (sscanf(line, "%63s", name) != 1) {
|
||||
line = next;
|
||||
continue;
|
||||
}
|
||||
|
||||
rx_bytes = rx_packets = rx_errs = 0;
|
||||
tx_bytes = tx_packets = tx_errs = 0;
|
||||
if (sscanf(colon + 1,
|
||||
"%llu %llu %llu %*u %*u %*u %*u %*u "
|
||||
"%llu %llu %llu",
|
||||
&rx_bytes, &rx_packets, &rx_errs,
|
||||
&tx_bytes, &tx_packets, &tx_errs) == 6)
|
||||
{
|
||||
ngx_cpystrn(sh->ifaces[count].name, (u_char *) name,
|
||||
NGX_HTTP_MONITORING_NAME_LEN);
|
||||
sh->ifaces[count].rx_bytes = (ngx_atomic_uint_t) rx_bytes;
|
||||
sh->ifaces[count].tx_bytes = (ngx_atomic_uint_t) tx_bytes;
|
||||
sh->ifaces[count].rx_packets = (ngx_atomic_uint_t) rx_packets;
|
||||
sh->ifaces[count].tx_packets = (ngx_atomic_uint_t) tx_packets;
|
||||
sh->ifaces[count].rx_errors = (ngx_atomic_uint_t) rx_errs;
|
||||
sh->ifaces[count].tx_errors = (ngx_atomic_uint_t) tx_errs;
|
||||
sh->ifaces[count].flags = 0;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
line = next;
|
||||
}
|
||||
|
||||
if (getifaddrs(&ifaddr) == 0) {
|
||||
for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
|
||||
if (ifa->ifa_name == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
if (ngx_strncmp(sh->ifaces[i].name, ifa->ifa_name,
|
||||
NGX_HTTP_MONITORING_NAME_LEN) == 0)
|
||||
{
|
||||
sh->ifaces[i].flags = ifa->ifa_flags;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
freeifaddrs(ifaddr);
|
||||
}
|
||||
|
||||
sh->iface_count = count;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_http_monitoring_collect_diskstats(ngx_http_monitoring_shctx_t *sh)
|
||||
{
|
||||
u_char buf[65536];
|
||||
char *line, *next, name[NGX_HTTP_MONITORING_NAME_LEN];
|
||||
unsigned major, minor;
|
||||
unsigned long long reads, sectors_read, writes, sectors_written, io_ms;
|
||||
ngx_uint_t count;
|
||||
|
||||
if (ngx_http_monitoring_read_file("/proc/diskstats", buf, sizeof(buf))
|
||||
<= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
count = 0;
|
||||
line = (char *) buf;
|
||||
|
||||
while (line != NULL && *line != '\0'
|
||||
&& count < NGX_HTTP_MONITORING_DISKS_MAX)
|
||||
{
|
||||
next = strchr(line, '\n');
|
||||
if (next != NULL) {
|
||||
*next++ = '\0';
|
||||
}
|
||||
|
||||
ngx_memzero(name, sizeof(name));
|
||||
reads = sectors_read = writes = sectors_written = io_ms = 0;
|
||||
|
||||
if (sscanf(line,
|
||||
" %u %u %63s %llu %*u %llu %*u %llu %*u %llu %*u %*u %llu",
|
||||
&major, &minor, name, &reads, §ors_read,
|
||||
&writes, §ors_written, &io_ms) == 8)
|
||||
{
|
||||
(void) major;
|
||||
(void) minor;
|
||||
|
||||
if (ngx_strncmp(name, "loop", 4) != 0
|
||||
&& ngx_strncmp(name, "ram", 3) != 0)
|
||||
{
|
||||
ngx_cpystrn(sh->disks[count].name, (u_char *) name,
|
||||
NGX_HTTP_MONITORING_NAME_LEN);
|
||||
sh->disks[count].reads = (ngx_atomic_uint_t) reads;
|
||||
sh->disks[count].writes = (ngx_atomic_uint_t) writes;
|
||||
sh->disks[count].read_bytes =
|
||||
(ngx_atomic_uint_t) (sectors_read * 512);
|
||||
sh->disks[count].write_bytes =
|
||||
(ngx_atomic_uint_t) (sectors_written * 512);
|
||||
sh->disks[count].io_ms = (ngx_atomic_uint_t) io_ms;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
line = next;
|
||||
}
|
||||
|
||||
sh->disk_count = count;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_http_monitoring_collect_filesystems(ngx_http_monitoring_shctx_t *sh)
|
||||
{
|
||||
FILE *fp;
|
||||
struct mntent *mnt;
|
||||
struct statvfs st;
|
||||
ngx_uint_t count;
|
||||
uint64_t total, free_bytes, avail, used;
|
||||
|
||||
fp = setmntent("/proc/mounts", "r");
|
||||
if (fp == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
count = 0;
|
||||
while ((mnt = getmntent(fp)) != NULL
|
||||
&& count < NGX_HTTP_MONITORING_FILESYSTEMS_MAX)
|
||||
{
|
||||
if (ngx_http_monitoring_skip_fs(mnt->mnt_type)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (statvfs(mnt->mnt_dir, &st) != 0 || st.f_blocks == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
total = (uint64_t) st.f_blocks * st.f_frsize;
|
||||
free_bytes = (uint64_t) st.f_bfree * st.f_frsize;
|
||||
avail = (uint64_t) st.f_bavail * st.f_frsize;
|
||||
used = total - free_bytes;
|
||||
|
||||
ngx_cpystrn(sh->filesystems[count].path, (u_char *) mnt->mnt_dir,
|
||||
NGX_HTTP_MONITORING_KEY_LEN);
|
||||
ngx_cpystrn(sh->filesystems[count].type, (u_char *) mnt->mnt_type,
|
||||
NGX_HTTP_MONITORING_NAME_LEN);
|
||||
sh->filesystems[count].total = (ngx_atomic_uint_t) total;
|
||||
sh->filesystems[count].used = (ngx_atomic_uint_t) used;
|
||||
sh->filesystems[count].free = (ngx_atomic_uint_t) free_bytes;
|
||||
sh->filesystems[count].avail = (ngx_atomic_uint_t) avail;
|
||||
sh->filesystems[count].files = (ngx_atomic_uint_t) st.f_files;
|
||||
sh->filesystems[count].files_free = (ngx_atomic_uint_t) st.f_ffree;
|
||||
|
||||
count++;
|
||||
}
|
||||
|
||||
endmntent(fp);
|
||||
sh->fs_count = count;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_http_monitoring_collect_nginx(ngx_http_monitoring_shctx_t *sh)
|
||||
{
|
||||
#if (NGX_STAT_STUB)
|
||||
sh->connections.accepted = *ngx_stat_accepted;
|
||||
sh->connections.handled = *ngx_stat_handled;
|
||||
sh->connections.total = *ngx_stat_active;
|
||||
sh->connections.active = *ngx_stat_active;
|
||||
sh->connections.reading = *ngx_stat_reading;
|
||||
sh->connections.writing = *ngx_stat_writing;
|
||||
sh->connections.waiting = *ngx_stat_waiting;
|
||||
#else
|
||||
(void) sh;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_http_monitoring_collect_history(ngx_http_monitoring_main_conf_t *mmcf,
|
||||
ngx_http_monitoring_shctx_t *sh)
|
||||
{
|
||||
ngx_time_t *tp;
|
||||
ngx_msec_t now_msec;
|
||||
ngx_atomic_uint_t req, resp, net_rx, net_tx;
|
||||
ngx_atomic_uint_t disk_read, disk_write, prev_msec;
|
||||
ngx_atomic_uint_t dt, rps_milli, resp_milli;
|
||||
ngx_atomic_uint_t net_rx_rate, net_tx_rate;
|
||||
ngx_atomic_uint_t disk_read_rate, disk_write_rate;
|
||||
ngx_uint_t pos, capacity;
|
||||
ngx_http_monitoring_history_sample_t *sample;
|
||||
double mem_pct, swap_pct;
|
||||
|
||||
tp = ngx_timeofday();
|
||||
now_msec = (ngx_msec_t) (tp->sec * 1000 + tp->msec);
|
||||
|
||||
if (sh->prev_history_msec != 0
|
||||
&& now_msec - sh->prev_history_msec < mmcf->resolution)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
req = sh->requests.total;
|
||||
resp = sh->requests.responses;
|
||||
net_rx = ngx_http_monitoring_sum_network(sh, 0);
|
||||
net_tx = ngx_http_monitoring_sum_network(sh, 1);
|
||||
disk_read = ngx_http_monitoring_sum_disk(sh, 0);
|
||||
disk_write = ngx_http_monitoring_sum_disk(sh, 1);
|
||||
|
||||
prev_msec = sh->prev_collect_msec;
|
||||
dt = (prev_msec == 0 || now_msec <= prev_msec)
|
||||
? mmcf->refresh_interval
|
||||
: now_msec - prev_msec;
|
||||
if (dt == 0) {
|
||||
dt = 1;
|
||||
}
|
||||
|
||||
rps_milli = (ngx_http_monitoring_delta(req, sh->prev_req_total)
|
||||
* 1000000) / dt;
|
||||
resp_milli = (ngx_http_monitoring_delta(resp, sh->prev_resp_total)
|
||||
* 1000000) / dt;
|
||||
net_rx_rate = (ngx_http_monitoring_delta(net_rx, sh->prev_net_rx)
|
||||
* 1000) / dt;
|
||||
net_tx_rate = (ngx_http_monitoring_delta(net_tx, sh->prev_net_tx)
|
||||
* 1000) / dt;
|
||||
disk_read_rate = (ngx_http_monitoring_delta(disk_read,
|
||||
sh->prev_disk_read) * 1000) / dt;
|
||||
disk_write_rate = (ngx_http_monitoring_delta(disk_write,
|
||||
sh->prev_disk_write) * 1000) / dt;
|
||||
|
||||
if (sh->ewma_rps_milli == 0) {
|
||||
sh->ewma_rps_milli = rps_milli;
|
||||
sh->ewma_resp_milli = resp_milli;
|
||||
} else {
|
||||
sh->ewma_rps_milli = (sh->ewma_rps_milli * 7 + rps_milli * 3) / 10;
|
||||
sh->ewma_resp_milli = (sh->ewma_resp_milli * 7
|
||||
+ resp_milli * 3) / 10;
|
||||
}
|
||||
|
||||
capacity = sh->history_capacity;
|
||||
if (capacity == 0 || capacity > NGX_HTTP_MONITORING_HISTORY_MAX) {
|
||||
capacity = NGX_HTTP_MONITORING_HISTORY_MAX;
|
||||
}
|
||||
|
||||
pos = ngx_atomic_fetch_add(&sh->history_write, 1) % capacity;
|
||||
sample = &sh->history[pos];
|
||||
ngx_memzero(sample, sizeof(ngx_http_monitoring_history_sample_t));
|
||||
|
||||
mem_pct = 0;
|
||||
if (sh->system.mem_total > 0) {
|
||||
mem_pct = ((double) (sh->system.mem_total
|
||||
- sh->system.mem_available) * 100.0)
|
||||
/ (double) sh->system.mem_total;
|
||||
}
|
||||
|
||||
swap_pct = 0;
|
||||
if (sh->system.swap_total > 0) {
|
||||
swap_pct = ((double) (sh->system.swap_total - sh->system.swap_free)
|
||||
* 100.0) / (double) sh->system.swap_total;
|
||||
}
|
||||
|
||||
sample->timestamp = tp->sec;
|
||||
sample->msec = tp->msec;
|
||||
sample->cpu_usage = (double) sh->system.usage_milli / 1000.0;
|
||||
sample->load1 = (double) sh->system.load1_milli / 1000.0;
|
||||
sample->load5 = (double) sh->system.load5_milli / 1000.0;
|
||||
sample->load15 = (double) sh->system.load15_milli / 1000.0;
|
||||
sample->memory_used_pct = mem_pct;
|
||||
sample->swap_used_pct = swap_pct;
|
||||
sample->requests_per_sec = (double) sh->ewma_rps_milli / 1000.0;
|
||||
sample->responses_per_sec = (double) sh->ewma_resp_milli / 1000.0;
|
||||
sample->network_rx_per_sec = (double) net_rx_rate;
|
||||
sample->network_tx_per_sec = (double) net_tx_rate;
|
||||
sample->disk_read_per_sec = (double) disk_read_rate;
|
||||
sample->disk_write_per_sec = (double) disk_write_rate;
|
||||
sample->latency_p95 = ngx_http_monitoring_percentile(sh, 95.0);
|
||||
sample->requests_total = req;
|
||||
sample->status_4xx = sh->requests.status_4xx;
|
||||
sample->status_5xx = sh->requests.status_5xx;
|
||||
|
||||
if (sh->history_count < capacity) {
|
||||
ngx_atomic_fetch_add(&sh->history_count, 1);
|
||||
}
|
||||
|
||||
sh->prev_req_total = req;
|
||||
sh->prev_resp_total = resp;
|
||||
sh->prev_net_rx = net_rx;
|
||||
sh->prev_net_tx = net_tx;
|
||||
sh->prev_disk_read = disk_read;
|
||||
sh->prev_disk_write = disk_write;
|
||||
sh->prev_collect_msec = now_msec;
|
||||
sh->prev_history_msec = now_msec;
|
||||
}
|
||||
|
||||
|
||||
static ngx_atomic_uint_t
|
||||
ngx_http_monitoring_sum_network(ngx_http_monitoring_shctx_t *sh, ngx_uint_t tx)
|
||||
{
|
||||
ngx_uint_t i, count;
|
||||
ngx_atomic_uint_t total;
|
||||
|
||||
total = 0;
|
||||
count = ngx_min((ngx_uint_t) sh->iface_count,
|
||||
NGX_HTTP_MONITORING_IFACES_MAX);
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
total += tx ? sh->ifaces[i].tx_bytes : sh->ifaces[i].rx_bytes;
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
|
||||
static ngx_atomic_uint_t
|
||||
ngx_http_monitoring_sum_disk(ngx_http_monitoring_shctx_t *sh, ngx_uint_t write)
|
||||
{
|
||||
ngx_uint_t i, count;
|
||||
ngx_atomic_uint_t total;
|
||||
|
||||
total = 0;
|
||||
count = ngx_min((ngx_uint_t) sh->disk_count,
|
||||
NGX_HTTP_MONITORING_DISKS_MAX);
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
total += write ? sh->disks[i].write_bytes : sh->disks[i].read_bytes;
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
|
||||
static ngx_atomic_uint_t
|
||||
ngx_http_monitoring_delta(ngx_atomic_uint_t now, ngx_atomic_uint_t prev)
|
||||
{
|
||||
return now >= prev ? now - prev : 0;
|
||||
}
|
||||
|
||||
|
||||
static ngx_uint_t
|
||||
ngx_http_monitoring_skip_fs(const char *type)
|
||||
{
|
||||
static const char *skip[] = {
|
||||
"proc", "sysfs", "devtmpfs", "devpts", "securityfs", "cgroup",
|
||||
"cgroup2", "pstore", "autofs", "mqueue", "hugetlbfs", "debugfs",
|
||||
"tracefs", "configfs", "fusectl", "binfmt_misc", "rpc_pipefs",
|
||||
"overlay", "nsfs", NULL
|
||||
};
|
||||
|
||||
ngx_uint_t i;
|
||||
|
||||
for (i = 0; skip[i] != NULL; i++) {
|
||||
if (strcmp(type, skip[i]) == 0) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
114
src/ngx_http_monitoring_dashboard.c
Normal file
114
src/ngx_http_monitoring_dashboard.c
Normal file
@@ -0,0 +1,114 @@
|
||||
#include "ngx_http_monitoring.h"
|
||||
|
||||
static const u_char ngx_http_monitoring_dashboard_html[] =
|
||||
"<!doctype html><html lang=\"en\"><head><meta charset=\"utf-8\">"
|
||||
"<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">"
|
||||
"<title>Nginx Monitor</title><style>"
|
||||
":root{color-scheme:dark;--bg:#0b0f14;--panel:#111821;--panel2:#151f2b;--line:#263344;--text:#e6edf3;--muted:#8fa1b3;--green:#45d483;--red:#ff5f6d;--yellow:#f4c95d;--blue:#5aa9ff;--cyan:#45d4d0}"
|
||||
"*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--text);font:14px/1.45 system-ui,-apple-system,Segoe UI,sans-serif}"
|
||||
"header{height:58px;display:flex;align-items:center;justify-content:space-between;padding:0 20px;border-bottom:1px solid var(--line);background:#0f151d;position:sticky;top:0;z-index:5}"
|
||||
".brand{font-size:17px;font-weight:700}.sub{color:var(--muted);font-size:12px}.status{display:flex;align-items:center;gap:8px;color:var(--muted)}.dot{width:9px;height:9px;border-radius:50%;background:var(--red);box-shadow:0 0 0 3px rgba(255,95,109,.12)}.dot.ok{background:var(--green);box-shadow:0 0 0 3px rgba(69,212,131,.13)}"
|
||||
".layout{display:grid;grid-template-columns:190px 1fr;min-height:calc(100vh - 58px)}nav{border-right:1px solid var(--line);padding:14px;background:#0d131a;position:sticky;top:58px;height:calc(100vh - 58px)}"
|
||||
"button.tab{width:100%;height:36px;border:0;background:transparent;color:var(--muted);text-align:left;padding:0 12px;border-radius:7px;cursor:pointer;font-weight:600}button.tab.active,button.tab:hover{background:var(--panel2);color:var(--text)}"
|
||||
"main{padding:18px;max-width:1500px;width:100%;margin:0 auto}.grid{display:grid;gap:12px}.cards{grid-template-columns:repeat(6,minmax(120px,1fr))}.card,.panel{background:var(--panel);border:1px solid var(--line);border-radius:8px}.card{padding:12px;min-height:86px}.label{color:var(--muted);font-size:12px}.value{font-size:24px;font-weight:750;margin-top:8px;letter-spacing:0}.small{font-size:12px;color:var(--muted)}"
|
||||
".charts{grid-template-columns:repeat(2,minmax(280px,1fr));margin-top:12px}.panel{padding:14px;min-width:0}.panel h2{font-size:14px;margin:0 0 10px}.chart{width:100%;height:210px;display:block}.wide{grid-column:1/-1}"
|
||||
".toolbar{display:flex;gap:8px;align-items:center;margin:0 0 10px}input,select{height:32px;background:#0d131a;border:1px solid var(--line);border-radius:7px;color:var(--text);padding:0 10px;min-width:0}input{width:260px;max-width:100%}"
|
||||
"table{width:100%;border-collapse:collapse;font-size:13px}th,td{text-align:left;padding:8px 9px;border-bottom:1px solid var(--line);white-space:nowrap}th{color:var(--muted);font-weight:650;cursor:pointer}td.truncate{max-width:420px;overflow:hidden;text-overflow:ellipsis}.page{display:none}.page.active{display:block}.split{grid-template-columns:repeat(2,minmax(280px,1fr))}details{border-top:1px solid var(--line);padding-top:10px;margin-top:10px}summary{cursor:pointer;color:var(--muted)}"
|
||||
"@media (max-width:980px){.layout{grid-template-columns:1fr}nav{position:static;height:auto;border-right:0;border-bottom:1px solid var(--line);display:grid;grid-template-columns:repeat(3,1fr);gap:6px}.cards{grid-template-columns:repeat(2,minmax(120px,1fr))}.charts,.split{grid-template-columns:1fr}header{padding:0 12px}main{padding:12px}td.truncate{max-width:220px}}"
|
||||
"</style></head><body><header><div><div class=\"brand\">Nginx Monitor</div><div class=\"sub\" id=\"stamp\">waiting</div></div><div class=\"status\"><span class=\"dot\" id=\"dot\"></span><span id=\"conn\">offline</span></div></header>"
|
||||
"<div class=\"layout\"><nav id=\"nav\"></nav><main>"
|
||||
"<section class=\"page active\" data-page=\"Overview\"><div class=\"grid cards\" id=\"cards\"></div><div class=\"grid charts\"><div class=\"panel\"><h2>CPU</h2><canvas class=\"chart\" id=\"cpuChart\"></canvas></div><div class=\"panel\"><h2>Requests</h2><canvas class=\"chart\" id=\"rpsChart\"></canvas></div><div class=\"panel\"><h2>Network</h2><canvas class=\"chart\" id=\"netChart\"></canvas></div><div class=\"panel\"><h2>Latency</h2><canvas class=\"chart\" id=\"latChart\"></canvas></div></div></section>"
|
||||
"<section class=\"page\" data-page=\"CPU\"><div class=\"grid split\"><div class=\"panel\"><h2>CPU Load</h2><canvas class=\"chart\" id=\"cpuChart2\"></canvas></div><div class=\"panel\"><h2>Load Average</h2><table id=\"loadTable\"></table></div></div></section>"
|
||||
"<section class=\"page\" data-page=\"Memory\"><div class=\"grid split\"><div class=\"panel\"><h2>Memory</h2><canvas class=\"chart\" id=\"memChart\"></canvas></div><div class=\"panel\"><h2>Memory Stats</h2><table id=\"memTable\"></table></div></div></section>"
|
||||
"<section class=\"page\" data-page=\"Network\"><div class=\"panel\"><div class=\"toolbar\"><input id=\"ifFilter\" placeholder=\"Filter\"><select id=\"ifSort\"><option value=\"rx_bytes\">RX</option><option value=\"tx_bytes\">TX</option><option value=\"name\">Name</option></select></div><table id=\"ifTable\"></table></div></section>"
|
||||
"<section class=\"page\" data-page=\"Disk\"><div class=\"grid split\"><div class=\"panel\"><h2>Devices</h2><table id=\"diskTable\"></table></div><div class=\"panel\"><h2>Filesystems</h2><table id=\"fsTable\"></table></div></div></section>"
|
||||
"<section class=\"page\" data-page=\"Requests\"><div class=\"panel\"><div class=\"toolbar\"><input id=\"urlFilter\" placeholder=\"Filter\"><select id=\"urlSort\"><option value=\"hits\">Hits</option><option value=\"errors\">Errors</option><option value=\"avg_latency\">Latency</option></select></div><table id=\"urlTable\"></table><details><summary>User agents</summary><table id=\"uaTable\"></table></details></div></section>"
|
||||
"<section class=\"page\" data-page=\"Upstreams\"><div class=\"panel\"><table id=\"upTable\"></table></div></section>"
|
||||
"<section class=\"page\" data-page=\"Workers\"><div class=\"panel\"><table id=\"workerTable\"></table></div></section>"
|
||||
"<section class=\"page\" data-page=\"Errors\"><div class=\"grid split\"><div class=\"panel\"><h2>Status</h2><table id=\"errTable\"></table></div><div class=\"panel\"><h2>Error Rate</h2><canvas class=\"chart\" id=\"errChart\"></canvas></div></div></section>"
|
||||
"</main></div><script>"
|
||||
"const pages=['Overview','CPU','Memory','Network','Disk','Requests','Upstreams','Workers','Errors'];const S={data:null,hist:[],sort:{}};"
|
||||
"const nav=document.getElementById('nav');pages.forEach(p=>{const b=document.createElement('button');b.className='tab'+(p==='Overview'?' active':'');b.textContent=p;b.onclick=()=>show(p);nav.appendChild(b)});"
|
||||
"function show(p){document.querySelectorAll('.tab').forEach(b=>b.classList.toggle('active',b.textContent===p));document.querySelectorAll('.page').forEach(s=>s.classList.toggle('active',s.dataset.page===p));drawAll()}"
|
||||
"function fmt(n,u=''){if(n==null)return '0'+u;if(Math.abs(n)>=1e12)return(n/1e12).toFixed(2)+'T'+u;if(Math.abs(n)>=1e9)return(n/1e9).toFixed(2)+'G'+u;if(Math.abs(n)>=1e6)return(n/1e6).toFixed(2)+'M'+u;if(Math.abs(n)>=1e3)return(n/1e3).toFixed(2)+'K'+u;return(Number(n).toFixed(Number(n)%1?2:0))+u}"
|
||||
"function bytes(n){return fmt(n,'B')}function pct(n){return(Number(n)||0).toFixed(1)+'%'}function ms(n){return(Number(n)||0).toFixed(1)+'ms'}"
|
||||
"function q(path){const u=new URL(path,location.origin);const t=new URLSearchParams(location.search).get('token');if(t)u.searchParams.set('token',t);return u.pathname+u.search}"
|
||||
"async function poll(){try{const r=await fetch(q('/monitor/api'),{cache:'no-store'});if(!r.ok)throw Error(r.status);apply(await r.json(),true)}catch(e){setConn(false)}}"
|
||||
"function start(){if(window.EventSource){const es=new EventSource(q('/monitor/live'));es.addEventListener('metrics',e=>apply(JSON.parse(e.data),true));es.onopen=()=>setConn(true);es.onerror=()=>setConn(false)}poll();setInterval(poll,5000)}"
|
||||
"function setConn(ok){document.getElementById('dot').classList.toggle('ok',ok);document.getElementById('conn').textContent=ok?'live':'offline'}"
|
||||
"function merge(a,b){for(const k in b){if(b[k]&&typeof b[k]==='object'&&!Array.isArray(b[k]))a[k]=merge(a[k]||{},b[k]);else a[k]=b[k]}return a}"
|
||||
"function apply(d,ok){S.data=S.data&&!d.history?merge(S.data,d):d;if(d.history)S.hist=d.history;document.getElementById('stamp').textContent=new Date((d.timestamp||0)*1000).toLocaleString();setConn(ok);render()}"
|
||||
"function card(k,v,s=''){return `<div class=\"card\"><div class=\"label\">${k}</div><div class=\"value\">${v}</div><div class=\"small\">${s}</div></div>`}"
|
||||
"function render(){const d=S.data;if(!d)return;const sys=d.system||{},req=d.requests||{},ng=d.nginx||{},net=d.network||{},disk=d.disk||{};const mem=sys.memory||{},cpu=sys.cpu||{},lat=req.latency||{},st=req.status||{};"
|
||||
"document.getElementById('cards').innerHTML=[card('CPU',pct(cpu.usage),`${cpu.cores||0} cores`),card('Memory',pct(mem.used_pct),bytes(mem.used||0)+' used'),card('RPS',fmt(req.requests_per_sec||ng.requests?.requests_per_sec||0),fmt(req.total||0)+' total'),card('Latency p95',ms(lat.p95),`p99 ${ms(lat.p99)}`),card('Network',bytes(net.rx_bytes||0)+' / '+bytes(net.tx_bytes||0),'rx / tx'),card('Errors',fmt((st['4xx']||0)+(st['5xx']||0)),`${fmt(st['5xx']||0)} 5xx`)].join('');"
|
||||
"table('loadTable',[['Load 1m',cpu.load?.[0]||0],['Load 5m',cpu.load?.[1]||0],['Load 15m',cpu.load?.[2]||0],['Uptime',fmt(sys.uptime||0)+'s']]);"
|
||||
"table('memTable',[['Total',bytes(mem.total||0)],['Available',bytes(mem.available||0)],['Free',bytes(mem.free||0)],['Swap',pct(sys.swap?.used_pct||0)]]);"
|
||||
"rows('ifTable',net.interfaces||[],['name','rx_bytes','tx_bytes','rx_packets','tx_packets','up'],'ifFilter','ifSort');rows('diskTable',disk.devices||[],['name','reads','writes','read_bytes','write_bytes','io_ms']);rows('fsTable',disk.filesystems||[],['path','type','total','used','avail']);rows('urlTable',req.top_urls||[],['url','hits','errors','bytes','avg_latency'],'urlFilter','urlSort');rows('uaTable',req.user_agents||[],['user_agent','hits','errors','avg_latency']);rows('upTable',d.upstreams||[],['peer','requests','failures','status_4xx','status_5xx','avg_latency']);rows('workerTable',ng.workers||d.processes?.workers||[],['slot','pid','active','requests','bytes','errors','vm_rss','last_seen']);table('errTable',[['1xx',st['1xx']||0],['2xx',st['2xx']||0],['3xx',st['3xx']||0],['4xx',st['4xx']||0],['5xx',st['5xx']||0],['Error rate',((req.error_rate||0)*100).toFixed(3)+'%']]);drawAll()}"
|
||||
"function table(id,pairs){document.getElementById(id).innerHTML='<tbody>'+pairs.map(r=>`<tr><th>${r[0]}</th><td>${r[1]}</td></tr>`).join('')+'</tbody>'}"
|
||||
"function rows(id,data,cols,filterId,sortId){let a=[...data];const f=filterId&&document.getElementById(filterId)?.value.toLowerCase();if(f)a=a.filter(x=>JSON.stringify(x).toLowerCase().includes(f));const s=sortId&&document.getElementById(sortId)?.value;if(s)a.sort((x,y)=>typeof x[s]==='string'?String(x[s]).localeCompare(String(y[s])):(Number(y[s]||0)-Number(x[s]||0)));document.getElementById(id).innerHTML='<thead><tr>'+cols.map(c=>`<th>${c}</th>`).join('')+'</tr></thead><tbody>'+a.map(x=>'<tr>'+cols.map(c=>`<td class=\"${String(x[c]).length>36?'truncate':''}\">${val(c,x[c])}</td>`).join('')+'</tr>').join('')+'</tbody>'}"
|
||||
"function val(k,v){if(k.includes('bytes')||['total','used','avail','free'].includes(k))return bytes(v||0);if(k.includes('latency'))return ms(v||0);return v==null?'':v}"
|
||||
"['ifFilter','ifSort','urlFilter','urlSort'].forEach(id=>setTimeout(()=>{const e=document.getElementById(id);if(e)e.oninput=render},0));"
|
||||
"function drawAll(){line('cpuChart',S.hist.map(x=>x.cpu),'#45d483',100);line('cpuChart2',S.hist.map(x=>x.cpu),'#45d483',100);line('rpsChart',S.hist.map(x=>x.rps),'#5aa9ff');line('netChart',S.hist.map(x=>(x.network_rx_per_sec+x.network_tx_per_sec)/1024),'#45d4d0');line('latChart',S.hist.map(x=>x.latency_p95),'#f4c95d');line('memChart',S.hist.map(x=>x.memory),'#ff5f6d',100);line('errChart',S.hist.map(x=>(x.status_4xx||0)+(x.status_5xx||0)),'#ff5f6d')}"
|
||||
"function line(id,arr,color,max){const c=document.getElementById(id);if(!c)return;const r=c.getBoundingClientRect(),d=window.devicePixelRatio||1;c.width=r.width*d;c.height=r.height*d;const x=c.getContext('2d');x.scale(d,d);x.clearRect(0,0,r.width,r.height);x.strokeStyle='#263344';x.lineWidth=1;for(let i=0;i<4;i++){let y=10+i*(r.height-20)/3;x.beginPath();x.moveTo(0,y);x.lineTo(r.width,y);x.stroke()}if(!arr.length)return;const m=max||Math.max(...arr,1);x.strokeStyle=color;x.lineWidth=2;x.beginPath();arr.forEach((v,i)=>{let px=i*Math.max(1,r.width/(arr.length-1||1)),py=r.height-8-(Math.min(v,m)/m)*(r.height-18);i?x.lineTo(px,py):x.moveTo(px,py)});x.stroke()}"
|
||||
"start();</script></body></html>";
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_http_monitoring_send_dashboard(ngx_http_request_t *r)
|
||||
{
|
||||
ngx_buf_t *b;
|
||||
ngx_chain_t out;
|
||||
ngx_table_elt_t *h;
|
||||
|
||||
ngx_http_discard_request_body(r);
|
||||
|
||||
h = ngx_list_push(&r->headers_out.headers);
|
||||
if (h != NULL) {
|
||||
h->hash = 1;
|
||||
ngx_str_set(&h->key, "Cache-Control");
|
||||
ngx_str_set(&h->value, "no-store");
|
||||
}
|
||||
|
||||
h = ngx_list_push(&r->headers_out.headers);
|
||||
if (h != NULL) {
|
||||
h->hash = 1;
|
||||
ngx_str_set(&h->key, "X-Frame-Options");
|
||||
ngx_str_set(&h->value, "DENY");
|
||||
}
|
||||
|
||||
h = ngx_list_push(&r->headers_out.headers);
|
||||
if (h != NULL) {
|
||||
h->hash = 1;
|
||||
ngx_str_set(&h->key, "Content-Security-Policy");
|
||||
ngx_str_set(&h->value,
|
||||
"default-src 'self'; script-src 'unsafe-inline' 'self'; "
|
||||
"style-src 'unsafe-inline' 'self'; connect-src 'self'");
|
||||
}
|
||||
|
||||
r->headers_out.status = NGX_HTTP_OK;
|
||||
r->headers_out.content_length_n =
|
||||
sizeof(ngx_http_monitoring_dashboard_html) - 1;
|
||||
ngx_str_set(&r->headers_out.content_type, "text/html");
|
||||
|
||||
if (r->method == NGX_HTTP_HEAD) {
|
||||
r->header_only = 1;
|
||||
return ngx_http_send_header(r);
|
||||
}
|
||||
|
||||
b = ngx_calloc_buf(r->pool);
|
||||
if (b == NULL) {
|
||||
return NGX_HTTP_INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
|
||||
b->pos = (u_char *) ngx_http_monitoring_dashboard_html;
|
||||
b->last = (u_char *) ngx_http_monitoring_dashboard_html
|
||||
+ sizeof(ngx_http_monitoring_dashboard_html) - 1;
|
||||
b->memory = 1;
|
||||
b->last_buf = 1;
|
||||
|
||||
out.buf = b;
|
||||
out.next = NULL;
|
||||
|
||||
ngx_http_send_header(r);
|
||||
return ngx_http_output_filter(r, &out);
|
||||
}
|
||||
975
src/ngx_http_monitoring_json.c
Normal file
975
src/ngx_http_monitoring_json.c
Normal file
@@ -0,0 +1,975 @@
|
||||
#include "ngx_http_monitoring.h"
|
||||
|
||||
#include <net/if.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
typedef struct {
|
||||
u_char *pos;
|
||||
u_char *last;
|
||||
ngx_int_t failed;
|
||||
} ngx_http_monitoring_json_writer_t;
|
||||
|
||||
typedef struct {
|
||||
u_char key[NGX_HTTP_MONITORING_KEY_LEN];
|
||||
ngx_atomic_uint_t hits;
|
||||
ngx_atomic_uint_t errors;
|
||||
ngx_atomic_uint_t bytes;
|
||||
ngx_atomic_uint_t latency_ms_total;
|
||||
ngx_atomic_uint_t last_seen;
|
||||
} ngx_http_monitoring_top_view_t;
|
||||
|
||||
static void ngx_http_monitoring_json_append(
|
||||
ngx_http_monitoring_json_writer_t *jw, const char *s);
|
||||
static void ngx_http_monitoring_json_printf(
|
||||
ngx_http_monitoring_json_writer_t *jw, const char *fmt, ...);
|
||||
static void ngx_http_monitoring_json_string(
|
||||
ngx_http_monitoring_json_writer_t *jw, const u_char *data, size_t len);
|
||||
static void ngx_http_monitoring_json_header(
|
||||
ngx_http_monitoring_json_writer_t *jw, ngx_http_request_t *r,
|
||||
const char *scope);
|
||||
static void ngx_http_monitoring_json_system(
|
||||
ngx_http_monitoring_json_writer_t *jw, ngx_http_monitoring_shctx_t *sh);
|
||||
static void ngx_http_monitoring_json_network(
|
||||
ngx_http_monitoring_json_writer_t *jw, ngx_http_monitoring_shctx_t *sh);
|
||||
static void ngx_http_monitoring_json_disk(
|
||||
ngx_http_monitoring_json_writer_t *jw, ngx_http_monitoring_shctx_t *sh);
|
||||
static void ngx_http_monitoring_json_processes(
|
||||
ngx_http_monitoring_json_writer_t *jw, ngx_http_monitoring_shctx_t *sh);
|
||||
static void ngx_http_monitoring_json_nginx(
|
||||
ngx_http_monitoring_json_writer_t *jw, ngx_http_monitoring_shctx_t *sh);
|
||||
static void ngx_http_monitoring_json_connections(
|
||||
ngx_http_monitoring_json_writer_t *jw, ngx_http_monitoring_shctx_t *sh);
|
||||
static void ngx_http_monitoring_json_requests(
|
||||
ngx_http_monitoring_json_writer_t *jw, ngx_http_request_t *r,
|
||||
ngx_http_monitoring_shctx_t *sh);
|
||||
static void ngx_http_monitoring_json_upstreams(
|
||||
ngx_http_monitoring_json_writer_t *jw, ngx_http_monitoring_shctx_t *sh);
|
||||
static void ngx_http_monitoring_json_history(
|
||||
ngx_http_monitoring_json_writer_t *jw, ngx_http_monitoring_shctx_t *sh);
|
||||
static void ngx_http_monitoring_json_workers(
|
||||
ngx_http_monitoring_json_writer_t *jw, ngx_http_monitoring_shctx_t *sh);
|
||||
static void ngx_http_monitoring_json_top_entries(
|
||||
ngx_http_monitoring_json_writer_t *jw, ngx_http_request_t *r,
|
||||
ngx_http_monitoring_top_entry_t *entries, ngx_uint_t capacity,
|
||||
const char *key_name);
|
||||
static ngx_int_t ngx_http_monitoring_send_buffer(ngx_http_request_t *r,
|
||||
ngx_buf_t *b, ngx_str_t *type);
|
||||
static void ngx_http_monitoring_add_no_store(ngx_http_request_t *r);
|
||||
static void ngx_http_monitoring_sort_top(ngx_http_monitoring_top_view_t *v,
|
||||
ngx_uint_t n);
|
||||
|
||||
static ngx_uint_t ngx_http_monitoring_latency_bounds[
|
||||
NGX_HTTP_MONITORING_LATENCY_BUCKETS - 1
|
||||
] = { 1, 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000, 30000 };
|
||||
|
||||
static off_t ngx_http_monitoring_size_bounds[
|
||||
NGX_HTTP_MONITORING_SIZE_BUCKETS - 1
|
||||
] = { 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536,
|
||||
262144, 1048576, 10485760 };
|
||||
|
||||
static const char *ngx_http_monitoring_methods[NGX_HTTP_MONITORING_METHODS] = {
|
||||
"GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS", "PATCH", "OTHER"
|
||||
};
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_http_monitoring_send_json(ngx_http_request_t *r,
|
||||
ngx_http_monitoring_endpoint_e endpoint)
|
||||
{
|
||||
ngx_buf_t *b;
|
||||
ngx_http_monitoring_json_writer_t jw;
|
||||
ngx_http_monitoring_shctx_t *sh;
|
||||
ngx_str_t type = ngx_string("application/json");
|
||||
const char *scope;
|
||||
|
||||
sh = ngx_http_monitoring_get_shctx(r);
|
||||
if (sh == NULL) {
|
||||
return NGX_HTTP_SERVICE_UNAVAILABLE;
|
||||
}
|
||||
|
||||
b = ngx_create_temp_buf(r->pool, 512 * 1024);
|
||||
if (b == NULL) {
|
||||
return NGX_HTTP_INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
|
||||
jw.pos = b->pos;
|
||||
jw.last = b->end;
|
||||
jw.failed = 0;
|
||||
|
||||
switch (endpoint) {
|
||||
case NGX_HTTP_MONITORING_EP_API_SYSTEM:
|
||||
scope = "system";
|
||||
break;
|
||||
case NGX_HTTP_MONITORING_EP_API_NGINX:
|
||||
scope = "nginx";
|
||||
break;
|
||||
case NGX_HTTP_MONITORING_EP_API_NETWORK:
|
||||
scope = "network";
|
||||
break;
|
||||
case NGX_HTTP_MONITORING_EP_API_DISK:
|
||||
scope = "disk";
|
||||
break;
|
||||
case NGX_HTTP_MONITORING_EP_API_PROCESSES:
|
||||
scope = "processes";
|
||||
break;
|
||||
case NGX_HTTP_MONITORING_EP_API_UPSTREAMS:
|
||||
scope = "upstreams";
|
||||
break;
|
||||
case NGX_HTTP_MONITORING_EP_API_CONNECTIONS:
|
||||
scope = "connections";
|
||||
break;
|
||||
case NGX_HTTP_MONITORING_EP_API_REQUESTS:
|
||||
scope = "requests";
|
||||
break;
|
||||
default:
|
||||
scope = "full";
|
||||
break;
|
||||
}
|
||||
|
||||
ngx_http_monitoring_json_header(&jw, r, scope);
|
||||
|
||||
if (endpoint == NGX_HTTP_MONITORING_EP_API_FULL
|
||||
|| endpoint == NGX_HTTP_MONITORING_EP_API_SYSTEM)
|
||||
{
|
||||
ngx_http_monitoring_json_append(&jw, ",\"system\":");
|
||||
ngx_http_monitoring_json_system(&jw, sh);
|
||||
}
|
||||
|
||||
if (endpoint == NGX_HTTP_MONITORING_EP_API_FULL
|
||||
|| endpoint == NGX_HTTP_MONITORING_EP_API_NGINX)
|
||||
{
|
||||
ngx_http_monitoring_json_append(&jw, ",\"nginx\":");
|
||||
ngx_http_monitoring_json_nginx(&jw, sh);
|
||||
}
|
||||
|
||||
if (endpoint == NGX_HTTP_MONITORING_EP_API_FULL
|
||||
|| endpoint == NGX_HTTP_MONITORING_EP_API_NETWORK)
|
||||
{
|
||||
ngx_http_monitoring_json_append(&jw, ",\"network\":");
|
||||
ngx_http_monitoring_json_network(&jw, sh);
|
||||
}
|
||||
|
||||
if (endpoint == NGX_HTTP_MONITORING_EP_API_FULL
|
||||
|| endpoint == NGX_HTTP_MONITORING_EP_API_DISK)
|
||||
{
|
||||
ngx_http_monitoring_json_append(&jw, ",\"disk\":");
|
||||
ngx_http_monitoring_json_disk(&jw, sh);
|
||||
}
|
||||
|
||||
if (endpoint == NGX_HTTP_MONITORING_EP_API_FULL
|
||||
|| endpoint == NGX_HTTP_MONITORING_EP_API_PROCESSES)
|
||||
{
|
||||
ngx_http_monitoring_json_append(&jw, ",\"processes\":");
|
||||
ngx_http_monitoring_json_processes(&jw, sh);
|
||||
}
|
||||
|
||||
if (endpoint == NGX_HTTP_MONITORING_EP_API_FULL
|
||||
|| endpoint == NGX_HTTP_MONITORING_EP_API_UPSTREAMS)
|
||||
{
|
||||
ngx_http_monitoring_json_append(&jw, ",\"upstreams\":");
|
||||
ngx_http_monitoring_json_upstreams(&jw, sh);
|
||||
}
|
||||
|
||||
if (endpoint == NGX_HTTP_MONITORING_EP_API_FULL
|
||||
|| endpoint == NGX_HTTP_MONITORING_EP_API_CONNECTIONS)
|
||||
{
|
||||
ngx_http_monitoring_json_append(&jw, ",\"connections\":");
|
||||
ngx_http_monitoring_json_connections(&jw, sh);
|
||||
}
|
||||
|
||||
if (endpoint == NGX_HTTP_MONITORING_EP_API_FULL
|
||||
|| endpoint == NGX_HTTP_MONITORING_EP_API_REQUESTS)
|
||||
{
|
||||
ngx_http_monitoring_json_append(&jw, ",\"requests\":");
|
||||
ngx_http_monitoring_json_requests(&jw, r, sh);
|
||||
}
|
||||
|
||||
if (endpoint == NGX_HTTP_MONITORING_EP_API_FULL) {
|
||||
ngx_http_monitoring_json_append(&jw, ",\"history\":");
|
||||
ngx_http_monitoring_json_history(&jw, sh);
|
||||
}
|
||||
|
||||
ngx_http_monitoring_json_append(&jw, "}\n");
|
||||
|
||||
if (jw.failed) {
|
||||
return NGX_HTTP_INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
|
||||
b->last = jw.pos;
|
||||
b->last_buf = 1;
|
||||
|
||||
return ngx_http_monitoring_send_buffer(r, b, &type);
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_http_monitoring_send_prometheus(ngx_http_request_t *r)
|
||||
{
|
||||
ngx_buf_t *b;
|
||||
ngx_http_monitoring_json_writer_t jw;
|
||||
ngx_http_monitoring_shctx_t *sh;
|
||||
ngx_str_t type = ngx_string("text/plain");
|
||||
|
||||
sh = ngx_http_monitoring_get_shctx(r);
|
||||
if (sh == NULL) {
|
||||
return NGX_HTTP_SERVICE_UNAVAILABLE;
|
||||
}
|
||||
|
||||
b = ngx_create_temp_buf(r->pool, 128 * 1024);
|
||||
if (b == NULL) {
|
||||
return NGX_HTTP_INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
|
||||
jw.pos = b->pos;
|
||||
jw.last = b->end;
|
||||
jw.failed = 0;
|
||||
|
||||
ngx_http_monitoring_json_append(&jw,
|
||||
"# HELP nginx_monitor_requests_total Total HTTP requests observed by "
|
||||
"ngx_http_monitoring_module\n"
|
||||
"# TYPE nginx_monitor_requests_total counter\n");
|
||||
ngx_http_monitoring_json_printf(&jw, "nginx_monitor_requests_total %uA\n",
|
||||
sh->requests.total);
|
||||
|
||||
ngx_http_monitoring_json_append(&jw,
|
||||
"# HELP nginx_monitor_active_connections Active nginx connections\n"
|
||||
"# TYPE nginx_monitor_active_connections gauge\n");
|
||||
ngx_http_monitoring_json_printf(&jw,
|
||||
"nginx_monitor_active_connections %uA\n", sh->connections.active);
|
||||
|
||||
ngx_http_monitoring_json_append(&jw,
|
||||
"# HELP nginx_monitor_cpu_usage_ratio CPU usage ratio\n"
|
||||
"# TYPE nginx_monitor_cpu_usage_ratio gauge\n");
|
||||
ngx_http_monitoring_json_printf(&jw,
|
||||
"nginx_monitor_cpu_usage_ratio %.3f\n",
|
||||
(double) sh->system.usage_milli / 100000.0);
|
||||
|
||||
ngx_http_monitoring_json_append(&jw,
|
||||
"# HELP nginx_monitor_memory_used_ratio Memory usage ratio\n"
|
||||
"# TYPE nginx_monitor_memory_used_ratio gauge\n");
|
||||
if (sh->system.mem_total > 0) {
|
||||
ngx_http_monitoring_json_printf(&jw,
|
||||
"nginx_monitor_memory_used_ratio %.3f\n",
|
||||
(double) (sh->system.mem_total - sh->system.mem_available)
|
||||
/ (double) sh->system.mem_total);
|
||||
} else {
|
||||
ngx_http_monitoring_json_append(&jw,
|
||||
"nginx_monitor_memory_used_ratio 0\n");
|
||||
}
|
||||
|
||||
ngx_http_monitoring_json_append(&jw,
|
||||
"# HELP nginx_monitor_latency_p95_ms Estimated p95 request latency\n"
|
||||
"# TYPE nginx_monitor_latency_p95_ms gauge\n");
|
||||
ngx_http_monitoring_json_printf(&jw,
|
||||
"nginx_monitor_latency_p95_ms %.3f\n",
|
||||
ngx_http_monitoring_percentile(sh, 95.0));
|
||||
|
||||
if (jw.failed) {
|
||||
return NGX_HTTP_INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
|
||||
b->last = jw.pos;
|
||||
b->last_buf = 1;
|
||||
|
||||
return ngx_http_monitoring_send_buffer(r, b, &type);
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_http_monitoring_send_health(ngx_http_request_t *r)
|
||||
{
|
||||
ngx_buf_t *b;
|
||||
ngx_http_monitoring_json_writer_t jw;
|
||||
ngx_http_monitoring_shctx_t *sh;
|
||||
ngx_str_t type = ngx_string("application/json");
|
||||
|
||||
sh = ngx_http_monitoring_get_shctx(r);
|
||||
if (sh == NULL) {
|
||||
return NGX_HTTP_SERVICE_UNAVAILABLE;
|
||||
}
|
||||
|
||||
b = ngx_create_temp_buf(r->pool, 4096);
|
||||
if (b == NULL) {
|
||||
return NGX_HTTP_INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
|
||||
jw.pos = b->pos;
|
||||
jw.last = b->end;
|
||||
jw.failed = 0;
|
||||
|
||||
ngx_http_monitoring_json_header(&jw, r, "health");
|
||||
ngx_http_monitoring_json_printf(&jw,
|
||||
",\"status\":\"ok\",\"generation\":%uA,\"sse_clients\":%uA}\n",
|
||||
sh->generation, sh->connections.sse_clients);
|
||||
|
||||
if (jw.failed) {
|
||||
return NGX_HTTP_INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
|
||||
b->last = jw.pos;
|
||||
b->last_buf = 1;
|
||||
|
||||
return ngx_http_monitoring_send_buffer(r, b, &type);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_http_monitoring_json_append(ngx_http_monitoring_json_writer_t *jw,
|
||||
const char *s)
|
||||
{
|
||||
size_t len;
|
||||
|
||||
if (jw->failed) {
|
||||
return;
|
||||
}
|
||||
|
||||
len = ngx_strlen(s);
|
||||
if ((size_t) (jw->last - jw->pos) < len) {
|
||||
jw->failed = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
jw->pos = ngx_cpymem(jw->pos, s, len);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_http_monitoring_json_printf(ngx_http_monitoring_json_writer_t *jw,
|
||||
const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
u_char *p;
|
||||
|
||||
if (jw->failed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (jw->last - jw->pos <= 1) {
|
||||
jw->failed = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
va_start(args, fmt);
|
||||
p = ngx_vslprintf(jw->pos, jw->last, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
if (p >= jw->last) {
|
||||
jw->failed = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
jw->pos = p;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_http_monitoring_json_string(ngx_http_monitoring_json_writer_t *jw,
|
||||
const u_char *data, size_t len)
|
||||
{
|
||||
size_t i;
|
||||
u_char c;
|
||||
|
||||
ngx_http_monitoring_json_append(jw, "\"");
|
||||
|
||||
for (i = 0; i < len && !jw->failed; i++) {
|
||||
c = data[i];
|
||||
|
||||
if (c == '"' || c == '\\') {
|
||||
if (jw->last - jw->pos < 2) {
|
||||
jw->failed = 1;
|
||||
return;
|
||||
}
|
||||
*jw->pos++ = '\\';
|
||||
*jw->pos++ = c;
|
||||
} else if (c == '\n') {
|
||||
ngx_http_monitoring_json_append(jw, "\\n");
|
||||
} else if (c == '\r') {
|
||||
ngx_http_monitoring_json_append(jw, "\\r");
|
||||
} else if (c == '\t') {
|
||||
ngx_http_monitoring_json_append(jw, "\\t");
|
||||
} else if (c < 0x20) {
|
||||
ngx_http_monitoring_json_printf(jw, "\\u%04x", c);
|
||||
} else {
|
||||
if (jw->last - jw->pos < 1) {
|
||||
jw->failed = 1;
|
||||
return;
|
||||
}
|
||||
*jw->pos++ = c;
|
||||
}
|
||||
}
|
||||
|
||||
ngx_http_monitoring_json_append(jw, "\"");
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_http_monitoring_json_header(ngx_http_monitoring_json_writer_t *jw,
|
||||
ngx_http_request_t *r, const char *scope)
|
||||
{
|
||||
ngx_time_t *tp;
|
||||
|
||||
tp = ngx_timeofday();
|
||||
|
||||
ngx_http_monitoring_json_printf(jw,
|
||||
"{\"version\":%d,\"module\":\"%s\",\"timestamp\":%T,"
|
||||
"\"msec\":%M,\"scope\":\"%s\",\"pid\":%P",
|
||||
NGX_HTTP_MONITORING_VERSION,
|
||||
NGX_HTTP_MONITORING_MODULE_VERSION,
|
||||
tp->sec, tp->msec, scope, ngx_pid);
|
||||
|
||||
(void) r;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_http_monitoring_json_system(ngx_http_monitoring_json_writer_t *jw,
|
||||
ngx_http_monitoring_shctx_t *sh)
|
||||
{
|
||||
double mem_used, mem_pct, swap_pct;
|
||||
|
||||
mem_used = 0;
|
||||
mem_pct = 0;
|
||||
if (sh->system.mem_total > 0) {
|
||||
mem_used = (double) (sh->system.mem_total - sh->system.mem_available);
|
||||
mem_pct = mem_used * 100.0 / (double) sh->system.mem_total;
|
||||
}
|
||||
|
||||
swap_pct = 0;
|
||||
if (sh->system.swap_total > 0) {
|
||||
swap_pct = (double) (sh->system.swap_total - sh->system.swap_free)
|
||||
* 100.0 / (double) sh->system.swap_total;
|
||||
}
|
||||
|
||||
ngx_http_monitoring_json_printf(jw,
|
||||
"{\"cpu\":{\"usage\":%.3f,\"cores\":%uA,\"load\":[%.3f,%.3f,%.3f]},"
|
||||
"\"memory\":{\"total\":%uA,\"available\":%uA,\"free\":%uA,"
|
||||
"\"used\":%.0f,\"used_pct\":%.3f},"
|
||||
"\"swap\":{\"total\":%uA,\"free\":%uA,\"used_pct\":%.3f},"
|
||||
"\"uptime\":%uA}",
|
||||
(double) sh->system.usage_milli / 1000.0,
|
||||
sh->system.cores,
|
||||
(double) sh->system.load1_milli / 1000.0,
|
||||
(double) sh->system.load5_milli / 1000.0,
|
||||
(double) sh->system.load15_milli / 1000.0,
|
||||
sh->system.mem_total, sh->system.mem_available, sh->system.mem_free,
|
||||
mem_used, mem_pct,
|
||||
sh->system.swap_total, sh->system.swap_free, swap_pct,
|
||||
sh->system.uptime);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_http_monitoring_json_network(ngx_http_monitoring_json_writer_t *jw,
|
||||
ngx_http_monitoring_shctx_t *sh)
|
||||
{
|
||||
ngx_uint_t i, count;
|
||||
ngx_atomic_uint_t rx, tx, rx_packets, tx_packets, rx_errors, tx_errors;
|
||||
|
||||
rx = tx = rx_packets = tx_packets = rx_errors = tx_errors = 0;
|
||||
count = ngx_min((ngx_uint_t) sh->iface_count,
|
||||
NGX_HTTP_MONITORING_IFACES_MAX);
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
rx += sh->ifaces[i].rx_bytes;
|
||||
tx += sh->ifaces[i].tx_bytes;
|
||||
rx_packets += sh->ifaces[i].rx_packets;
|
||||
tx_packets += sh->ifaces[i].tx_packets;
|
||||
rx_errors += sh->ifaces[i].rx_errors;
|
||||
tx_errors += sh->ifaces[i].tx_errors;
|
||||
}
|
||||
|
||||
ngx_http_monitoring_json_printf(jw,
|
||||
"{\"rx_bytes\":%uA,\"tx_bytes\":%uA,\"rx_packets\":%uA,"
|
||||
"\"tx_packets\":%uA,\"rx_errors\":%uA,\"tx_errors\":%uA,"
|
||||
"\"interfaces\":[",
|
||||
rx, tx, rx_packets, tx_packets, rx_errors, tx_errors);
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
if (i) {
|
||||
ngx_http_monitoring_json_append(jw, ",");
|
||||
}
|
||||
|
||||
ngx_http_monitoring_json_append(jw, "{\"name\":");
|
||||
ngx_http_monitoring_json_string(jw, sh->ifaces[i].name,
|
||||
ngx_strlen(sh->ifaces[i].name));
|
||||
ngx_http_monitoring_json_printf(jw,
|
||||
",\"rx_bytes\":%uA,\"tx_bytes\":%uA,\"rx_packets\":%uA,"
|
||||
"\"tx_packets\":%uA,\"rx_errors\":%uA,\"tx_errors\":%uA,"
|
||||
"\"up\":%s}",
|
||||
sh->ifaces[i].rx_bytes, sh->ifaces[i].tx_bytes,
|
||||
sh->ifaces[i].rx_packets, sh->ifaces[i].tx_packets,
|
||||
sh->ifaces[i].rx_errors, sh->ifaces[i].tx_errors,
|
||||
(sh->ifaces[i].flags & IFF_UP) ? "true" : "false");
|
||||
}
|
||||
|
||||
ngx_http_monitoring_json_append(jw, "]}");
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_http_monitoring_json_disk(ngx_http_monitoring_json_writer_t *jw,
|
||||
ngx_http_monitoring_shctx_t *sh)
|
||||
{
|
||||
ngx_uint_t i, count;
|
||||
ngx_atomic_uint_t reads, writes, read_bytes, write_bytes;
|
||||
|
||||
reads = writes = read_bytes = write_bytes = 0;
|
||||
count = ngx_min((ngx_uint_t) sh->disk_count,
|
||||
NGX_HTTP_MONITORING_DISKS_MAX);
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
reads += sh->disks[i].reads;
|
||||
writes += sh->disks[i].writes;
|
||||
read_bytes += sh->disks[i].read_bytes;
|
||||
write_bytes += sh->disks[i].write_bytes;
|
||||
}
|
||||
|
||||
ngx_http_monitoring_json_printf(jw,
|
||||
"{\"reads\":%uA,\"writes\":%uA,\"read_bytes\":%uA,"
|
||||
"\"write_bytes\":%uA,\"devices\":[",
|
||||
reads, writes, read_bytes, write_bytes);
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
if (i) {
|
||||
ngx_http_monitoring_json_append(jw, ",");
|
||||
}
|
||||
|
||||
ngx_http_monitoring_json_append(jw, "{\"name\":");
|
||||
ngx_http_monitoring_json_string(jw, sh->disks[i].name,
|
||||
ngx_strlen(sh->disks[i].name));
|
||||
ngx_http_monitoring_json_printf(jw,
|
||||
",\"reads\":%uA,\"writes\":%uA,\"read_bytes\":%uA,"
|
||||
"\"write_bytes\":%uA,\"io_ms\":%uA}",
|
||||
sh->disks[i].reads, sh->disks[i].writes,
|
||||
sh->disks[i].read_bytes, sh->disks[i].write_bytes,
|
||||
sh->disks[i].io_ms);
|
||||
}
|
||||
|
||||
ngx_http_monitoring_json_append(jw, "],\"filesystems\":[");
|
||||
|
||||
count = ngx_min((ngx_uint_t) sh->fs_count,
|
||||
NGX_HTTP_MONITORING_FILESYSTEMS_MAX);
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
if (i) {
|
||||
ngx_http_monitoring_json_append(jw, ",");
|
||||
}
|
||||
|
||||
ngx_http_monitoring_json_append(jw, "{\"path\":");
|
||||
ngx_http_monitoring_json_string(jw, sh->filesystems[i].path,
|
||||
ngx_strlen(sh->filesystems[i].path));
|
||||
ngx_http_monitoring_json_append(jw, ",\"type\":");
|
||||
ngx_http_monitoring_json_string(jw, sh->filesystems[i].type,
|
||||
ngx_strlen(sh->filesystems[i].type));
|
||||
ngx_http_monitoring_json_printf(jw,
|
||||
",\"total\":%uA,\"used\":%uA,\"free\":%uA,\"avail\":%uA,"
|
||||
"\"files\":%uA,\"files_free\":%uA}",
|
||||
sh->filesystems[i].total, sh->filesystems[i].used,
|
||||
sh->filesystems[i].free, sh->filesystems[i].avail,
|
||||
sh->filesystems[i].files, sh->filesystems[i].files_free);
|
||||
}
|
||||
|
||||
ngx_http_monitoring_json_append(jw, "]}");
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_http_monitoring_json_processes(ngx_http_monitoring_json_writer_t *jw,
|
||||
ngx_http_monitoring_shctx_t *sh)
|
||||
{
|
||||
ngx_http_monitoring_json_printf(jw,
|
||||
"{\"process_count\":%uA,\"tcp\":{\"established\":%uA,"
|
||||
"\"listen\":%uA},\"sockets\":{\"used\":%uA,\"tcp\":%uA,"
|
||||
"\"udp\":%uA},\"workers\":",
|
||||
sh->system.process_count, sh->system.tcp_established,
|
||||
sh->system.tcp_listen, sh->system.sockets_used,
|
||||
sh->system.sockets_tcp, sh->system.sockets_udp);
|
||||
|
||||
ngx_http_monitoring_json_workers(jw, sh);
|
||||
ngx_http_monitoring_json_append(jw, "}");
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_http_monitoring_json_nginx(ngx_http_monitoring_json_writer_t *jw,
|
||||
ngx_http_monitoring_shctx_t *sh)
|
||||
{
|
||||
ngx_http_monitoring_json_printf(jw,
|
||||
"{\"connections\":{\"accepted\":%uA,\"handled\":%uA,\"active\":%uA,"
|
||||
"\"reading\":%uA,\"writing\":%uA,\"waiting\":%uA},"
|
||||
"\"requests\":{\"total\":%uA,\"responses\":%uA,"
|
||||
"\"requests_per_sec\":%.3f,\"responses_per_sec\":%.3f},"
|
||||
"\"ssl\":{\"requests\":%uA,\"handshakes\":%uA},"
|
||||
"\"keepalive\":{\"requests\":%uA},\"workers\":",
|
||||
sh->connections.accepted, sh->connections.handled,
|
||||
sh->connections.active, sh->connections.reading,
|
||||
sh->connections.writing, sh->connections.waiting,
|
||||
sh->requests.total, sh->requests.responses,
|
||||
(double) sh->ewma_rps_milli / 1000.0,
|
||||
(double) sh->ewma_resp_milli / 1000.0,
|
||||
sh->connections.ssl_requests, sh->connections.ssl_handshakes,
|
||||
sh->connections.keepalive_requests);
|
||||
|
||||
ngx_http_monitoring_json_workers(jw, sh);
|
||||
ngx_http_monitoring_json_append(jw, "}");
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_http_monitoring_json_connections(ngx_http_monitoring_json_writer_t *jw,
|
||||
ngx_http_monitoring_shctx_t *sh)
|
||||
{
|
||||
ngx_http_monitoring_json_printf(jw,
|
||||
"{\"accepted\":%uA,\"handled\":%uA,\"active\":%uA,"
|
||||
"\"reading\":%uA,\"writing\":%uA,\"waiting\":%uA,"
|
||||
"\"ssl_requests\":%uA,\"ssl_handshakes\":%uA,"
|
||||
"\"keepalive_requests\":%uA,\"sse_clients\":%uA,"
|
||||
"\"sse_events\":%uA,\"rate_limited\":%uA}",
|
||||
sh->connections.accepted, sh->connections.handled,
|
||||
sh->connections.active, sh->connections.reading,
|
||||
sh->connections.writing, sh->connections.waiting,
|
||||
sh->connections.ssl_requests, sh->connections.ssl_handshakes,
|
||||
sh->connections.keepalive_requests, sh->connections.sse_clients,
|
||||
sh->connections.sse_events, sh->connections.rate_limited);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_http_monitoring_json_requests(ngx_http_monitoring_json_writer_t *jw,
|
||||
ngx_http_request_t *r, ngx_http_monitoring_shctx_t *sh)
|
||||
{
|
||||
ngx_uint_t i;
|
||||
double avg_latency;
|
||||
|
||||
avg_latency = 0;
|
||||
if (sh->requests.total > 0) {
|
||||
avg_latency = (double) sh->requests.request_time_ms_total
|
||||
/ (double) sh->requests.total;
|
||||
}
|
||||
|
||||
ngx_http_monitoring_json_printf(jw,
|
||||
"{\"total\":%uA,\"responses\":%uA,\"bytes\":%uA,"
|
||||
"\"requests_per_sec\":%.3f,\"responses_per_sec\":%.3f,"
|
||||
"\"error_rate\":%.6f,"
|
||||
"\"latency\":{\"avg\":%.3f,\"p50\":%.3f,\"p90\":%.3f,"
|
||||
"\"p95\":%.3f,\"p99\":%.3f},"
|
||||
"\"status\":{\"1xx\":%uA,\"2xx\":%uA,\"3xx\":%uA,"
|
||||
"\"4xx\":%uA,\"5xx\":%uA},\"methods\":{",
|
||||
sh->requests.total, sh->requests.responses, sh->requests.bytes,
|
||||
(double) sh->ewma_rps_milli / 1000.0,
|
||||
(double) sh->ewma_resp_milli / 1000.0,
|
||||
sh->requests.total > 0
|
||||
? (double) (sh->requests.status_4xx + sh->requests.status_5xx)
|
||||
/ (double) sh->requests.total
|
||||
: 0.0,
|
||||
avg_latency,
|
||||
ngx_http_monitoring_percentile(sh, 50.0),
|
||||
ngx_http_monitoring_percentile(sh, 90.0),
|
||||
ngx_http_monitoring_percentile(sh, 95.0),
|
||||
ngx_http_monitoring_percentile(sh, 99.0),
|
||||
sh->requests.status_1xx, sh->requests.status_2xx,
|
||||
sh->requests.status_3xx, sh->requests.status_4xx,
|
||||
sh->requests.status_5xx);
|
||||
|
||||
for (i = 0; i < NGX_HTTP_MONITORING_METHODS; i++) {
|
||||
if (i) {
|
||||
ngx_http_monitoring_json_append(jw, ",");
|
||||
}
|
||||
ngx_http_monitoring_json_printf(jw, "\"%s\":%uA",
|
||||
ngx_http_monitoring_methods[i],
|
||||
sh->requests.method[i]);
|
||||
}
|
||||
|
||||
ngx_http_monitoring_json_append(jw, "},\"latency_histogram\":[");
|
||||
|
||||
for (i = 0; i < NGX_HTTP_MONITORING_LATENCY_BUCKETS; i++) {
|
||||
if (i) {
|
||||
ngx_http_monitoring_json_append(jw, ",");
|
||||
}
|
||||
if (i < NGX_HTTP_MONITORING_LATENCY_BUCKETS - 1) {
|
||||
ngx_http_monitoring_json_printf(jw,
|
||||
"{\"le\":%ui,\"count\":%uA}",
|
||||
ngx_http_monitoring_latency_bounds[i],
|
||||
sh->requests.latency_bucket[i]);
|
||||
} else {
|
||||
ngx_http_monitoring_json_printf(jw,
|
||||
"{\"le\":\"+Inf\",\"count\":%uA}",
|
||||
sh->requests.latency_bucket[i]);
|
||||
}
|
||||
}
|
||||
|
||||
ngx_http_monitoring_json_append(jw, "],\"size_histogram\":[");
|
||||
|
||||
for (i = 0; i < NGX_HTTP_MONITORING_SIZE_BUCKETS; i++) {
|
||||
if (i) {
|
||||
ngx_http_monitoring_json_append(jw, ",");
|
||||
}
|
||||
if (i < NGX_HTTP_MONITORING_SIZE_BUCKETS - 1) {
|
||||
ngx_http_monitoring_json_printf(jw,
|
||||
"{\"le\":%O,\"count\":%uA}",
|
||||
ngx_http_monitoring_size_bounds[i],
|
||||
sh->requests.size_bucket[i]);
|
||||
} else {
|
||||
ngx_http_monitoring_json_printf(jw,
|
||||
"{\"le\":\"+Inf\",\"count\":%uA}",
|
||||
sh->requests.size_bucket[i]);
|
||||
}
|
||||
}
|
||||
|
||||
ngx_http_monitoring_json_append(jw, "],\"top_urls\":");
|
||||
ngx_http_monitoring_json_top_entries(jw, r, sh->urls,
|
||||
NGX_HTTP_MONITORING_TOP_URLS_MAX, "url");
|
||||
|
||||
ngx_http_monitoring_json_append(jw, ",\"user_agents\":");
|
||||
ngx_http_monitoring_json_top_entries(jw, r, sh->user_agents,
|
||||
NGX_HTTP_MONITORING_TOP_UA_MAX, "user_agent");
|
||||
|
||||
ngx_http_monitoring_json_append(jw, "}");
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_http_monitoring_json_upstreams(ngx_http_monitoring_json_writer_t *jw,
|
||||
ngx_http_monitoring_shctx_t *sh)
|
||||
{
|
||||
ngx_uint_t i, count, emitted;
|
||||
ngx_http_monitoring_upstream_entry_t *u;
|
||||
double avg;
|
||||
|
||||
count = NGX_HTTP_MONITORING_UPSTREAMS_MAX;
|
||||
emitted = 0;
|
||||
|
||||
ngx_http_monitoring_json_append(jw, "[");
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
u = &sh->upstreams[i];
|
||||
if (u->requests == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (emitted++) {
|
||||
ngx_http_monitoring_json_append(jw, ",");
|
||||
}
|
||||
|
||||
avg = u->requests > 0
|
||||
? (double) u->latency_ms_total / (double) u->requests
|
||||
: 0.0;
|
||||
|
||||
ngx_http_monitoring_json_append(jw, "{\"peer\":");
|
||||
ngx_http_monitoring_json_string(jw, u->peer, ngx_strlen(u->peer));
|
||||
ngx_http_monitoring_json_printf(jw,
|
||||
",\"requests\":%uA,\"failures\":%uA,\"status_4xx\":%uA,"
|
||||
"\"status_5xx\":%uA,\"avg_latency\":%.3f,\"last_seen\":%uA}",
|
||||
u->requests, u->failures, u->status_4xx, u->status_5xx,
|
||||
avg, u->last_seen);
|
||||
}
|
||||
|
||||
ngx_http_monitoring_json_append(jw, "]");
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_http_monitoring_json_history(ngx_http_monitoring_json_writer_t *jw,
|
||||
ngx_http_monitoring_shctx_t *sh)
|
||||
{
|
||||
ngx_uint_t i, idx, count, capacity, write;
|
||||
ngx_http_monitoring_history_sample_t *s;
|
||||
|
||||
count = ngx_min((ngx_uint_t) sh->history_count,
|
||||
NGX_HTTP_MONITORING_HISTORY_MAX);
|
||||
capacity = ngx_min((ngx_uint_t) sh->history_capacity,
|
||||
NGX_HTTP_MONITORING_HISTORY_MAX);
|
||||
|
||||
if (capacity == 0) {
|
||||
ngx_http_monitoring_json_append(jw, "[]");
|
||||
return;
|
||||
}
|
||||
|
||||
write = (ngx_uint_t) sh->history_write;
|
||||
|
||||
ngx_http_monitoring_json_append(jw, "[");
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
idx = (write + capacity - count + i) % capacity;
|
||||
s = &sh->history[idx];
|
||||
|
||||
if (i) {
|
||||
ngx_http_monitoring_json_append(jw, ",");
|
||||
}
|
||||
|
||||
ngx_http_monitoring_json_printf(jw,
|
||||
"{\"timestamp\":%T,\"cpu\":%.3f,\"memory\":%.3f,"
|
||||
"\"swap\":%.3f,\"rps\":%.3f,\"responses_per_sec\":%.3f,"
|
||||
"\"network_rx_per_sec\":%.3f,\"network_tx_per_sec\":%.3f,"
|
||||
"\"disk_read_per_sec\":%.3f,\"disk_write_per_sec\":%.3f,"
|
||||
"\"latency_p95\":%.3f,\"requests_total\":%uA,"
|
||||
"\"status_4xx\":%uA,\"status_5xx\":%uA}",
|
||||
s->timestamp, s->cpu_usage, s->memory_used_pct,
|
||||
s->swap_used_pct, s->requests_per_sec, s->responses_per_sec,
|
||||
s->network_rx_per_sec, s->network_tx_per_sec,
|
||||
s->disk_read_per_sec, s->disk_write_per_sec,
|
||||
s->latency_p95, s->requests_total, s->status_4xx,
|
||||
s->status_5xx);
|
||||
}
|
||||
|
||||
ngx_http_monitoring_json_append(jw, "]");
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_http_monitoring_json_workers(ngx_http_monitoring_json_writer_t *jw,
|
||||
ngx_http_monitoring_shctx_t *sh)
|
||||
{
|
||||
ngx_uint_t i, emitted;
|
||||
|
||||
emitted = 0;
|
||||
ngx_http_monitoring_json_append(jw, "[");
|
||||
|
||||
for (i = 0; i < NGX_HTTP_MONITORING_WORKERS_MAX; i++) {
|
||||
if (sh->workers[i].pid == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (emitted++) {
|
||||
ngx_http_monitoring_json_append(jw, ",");
|
||||
}
|
||||
|
||||
ngx_http_monitoring_json_printf(jw,
|
||||
"{\"slot\":%ui,\"pid\":%uA,\"active\":%s,\"requests\":%uA,"
|
||||
"\"bytes\":%uA,\"errors\":%uA,\"vm_size\":%uA,"
|
||||
"\"vm_rss\":%uA,\"voluntary_ctxt\":%uA,"
|
||||
"\"nonvoluntary_ctxt\":%uA,\"last_seen\":%uA}",
|
||||
i, sh->workers[i].pid,
|
||||
sh->workers[i].active ? "true" : "false",
|
||||
sh->workers[i].requests, sh->workers[i].bytes,
|
||||
sh->workers[i].errors, sh->workers[i].vm_size,
|
||||
sh->workers[i].vm_rss, sh->workers[i].voluntary_ctxt,
|
||||
sh->workers[i].nonvoluntary_ctxt, sh->workers[i].last_seen);
|
||||
}
|
||||
|
||||
ngx_http_monitoring_json_append(jw, "]");
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_http_monitoring_json_top_entries(ngx_http_monitoring_json_writer_t *jw,
|
||||
ngx_http_request_t *r, ngx_http_monitoring_top_entry_t *entries,
|
||||
ngx_uint_t capacity, const char *key_name)
|
||||
{
|
||||
ngx_uint_t i, n;
|
||||
ngx_http_monitoring_top_view_t *view;
|
||||
double avg;
|
||||
|
||||
view = ngx_pcalloc(r->pool, capacity * sizeof(ngx_http_monitoring_top_view_t));
|
||||
if (view == NULL) {
|
||||
jw->failed = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
n = 0;
|
||||
for (i = 0; i < capacity; i++) {
|
||||
if (entries[i].hits == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ngx_cpystrn(view[n].key, entries[i].key, NGX_HTTP_MONITORING_KEY_LEN);
|
||||
view[n].hits = entries[i].hits;
|
||||
view[n].errors = entries[i].errors;
|
||||
view[n].bytes = entries[i].bytes;
|
||||
view[n].latency_ms_total = entries[i].latency_ms_total;
|
||||
view[n].last_seen = entries[i].last_seen;
|
||||
n++;
|
||||
}
|
||||
|
||||
ngx_http_monitoring_sort_top(view, n);
|
||||
|
||||
ngx_http_monitoring_json_append(jw, "[");
|
||||
for (i = 0; i < n; i++) {
|
||||
if (i) {
|
||||
ngx_http_monitoring_json_append(jw, ",");
|
||||
}
|
||||
|
||||
avg = view[i].hits > 0
|
||||
? (double) view[i].latency_ms_total / (double) view[i].hits
|
||||
: 0.0;
|
||||
|
||||
ngx_http_monitoring_json_append(jw, "{\"");
|
||||
ngx_http_monitoring_json_append(jw, key_name);
|
||||
ngx_http_monitoring_json_append(jw, "\":");
|
||||
ngx_http_monitoring_json_string(jw, view[i].key,
|
||||
ngx_strlen(view[i].key));
|
||||
ngx_http_monitoring_json_printf(jw,
|
||||
",\"hits\":%uA,\"errors\":%uA,\"bytes\":%uA,"
|
||||
"\"avg_latency\":%.3f,\"last_seen\":%uA}",
|
||||
view[i].hits, view[i].errors, view[i].bytes, avg,
|
||||
view[i].last_seen);
|
||||
}
|
||||
ngx_http_monitoring_json_append(jw, "]");
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_http_monitoring_send_buffer(ngx_http_request_t *r, ngx_buf_t *b,
|
||||
ngx_str_t *type)
|
||||
{
|
||||
ngx_chain_t out;
|
||||
|
||||
ngx_http_discard_request_body(r);
|
||||
ngx_http_monitoring_add_no_store(r);
|
||||
|
||||
r->headers_out.status = NGX_HTTP_OK;
|
||||
r->headers_out.content_length_n = b->last - b->pos;
|
||||
r->headers_out.content_type = *type;
|
||||
|
||||
if (r->method == NGX_HTTP_HEAD) {
|
||||
r->header_only = 1;
|
||||
return ngx_http_send_header(r);
|
||||
}
|
||||
|
||||
out.buf = b;
|
||||
out.next = NULL;
|
||||
|
||||
ngx_http_send_header(r);
|
||||
return ngx_http_output_filter(r, &out);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_http_monitoring_add_no_store(ngx_http_request_t *r)
|
||||
{
|
||||
ngx_table_elt_t *h;
|
||||
|
||||
h = ngx_list_push(&r->headers_out.headers);
|
||||
if (h != NULL) {
|
||||
h->hash = 1;
|
||||
ngx_str_set(&h->key, "Cache-Control");
|
||||
ngx_str_set(&h->value, "no-store");
|
||||
}
|
||||
|
||||
h = ngx_list_push(&r->headers_out.headers);
|
||||
if (h != NULL) {
|
||||
h->hash = 1;
|
||||
ngx_str_set(&h->key, "X-Content-Type-Options");
|
||||
ngx_str_set(&h->value, "nosniff");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_http_monitoring_sort_top(ngx_http_monitoring_top_view_t *v, ngx_uint_t n)
|
||||
{
|
||||
ngx_uint_t i, j;
|
||||
ngx_http_monitoring_top_view_t tmp;
|
||||
|
||||
for (i = 1; i < n; i++) {
|
||||
tmp = v[i];
|
||||
j = i;
|
||||
|
||||
while (j > 0 && v[j - 1].hits < tmp.hits) {
|
||||
v[j] = v[j - 1];
|
||||
j--;
|
||||
}
|
||||
|
||||
v[j] = tmp;
|
||||
}
|
||||
}
|
||||
985
src/ngx_http_monitoring_module.c
Normal file
985
src/ngx_http_monitoring_module.c
Normal file
@@ -0,0 +1,985 @@
|
||||
#include "ngx_http_monitoring.h"
|
||||
|
||||
static void *ngx_http_monitoring_create_main_conf(ngx_conf_t *cf);
|
||||
static char *ngx_http_monitoring_init_main_conf(ngx_conf_t *cf, void *conf);
|
||||
static void *ngx_http_monitoring_create_loc_conf(ngx_conf_t *cf);
|
||||
static char *ngx_http_monitoring_merge_loc_conf(ngx_conf_t *cf, void *parent,
|
||||
void *child);
|
||||
static ngx_int_t ngx_http_monitoring_init(ngx_conf_t *cf);
|
||||
static ngx_int_t ngx_http_monitoring_init_process(ngx_cycle_t *cycle);
|
||||
static void ngx_http_monitoring_exit_process(ngx_cycle_t *cycle);
|
||||
|
||||
static char *ngx_http_monitoring_access_rule(ngx_conf_t *cf, ngx_command_t *cmd,
|
||||
void *conf);
|
||||
|
||||
static ngx_int_t ngx_http_monitoring_content_handler(ngx_http_request_t *r);
|
||||
static ngx_int_t ngx_http_monitoring_log_handler(ngx_http_request_t *r);
|
||||
static ngx_int_t ngx_http_monitoring_authorize(ngx_http_request_t *r,
|
||||
ngx_http_monitoring_loc_conf_t *mlcf,
|
||||
ngx_http_monitoring_endpoint_e endpoint);
|
||||
static ngx_int_t ngx_http_monitoring_preflight(ngx_http_request_t *r,
|
||||
ngx_http_monitoring_loc_conf_t *mlcf);
|
||||
static void ngx_http_monitoring_add_cors(ngx_http_request_t *r,
|
||||
ngx_http_monitoring_loc_conf_t *mlcf);
|
||||
static ngx_int_t ngx_http_monitoring_plain_response(ngx_http_request_t *r,
|
||||
ngx_uint_t status, ngx_str_t *body);
|
||||
static ngx_int_t ngx_http_monitoring_match_access(ngx_http_request_t *r,
|
||||
ngx_http_monitoring_loc_conf_t *mlcf);
|
||||
static ngx_int_t ngx_http_monitoring_check_basic_auth(ngx_http_request_t *r,
|
||||
ngx_http_monitoring_loc_conf_t *mlcf);
|
||||
static ngx_int_t ngx_http_monitoring_check_api_token(ngx_http_request_t *r,
|
||||
ngx_http_monitoring_loc_conf_t *mlcf);
|
||||
static ngx_int_t ngx_http_monitoring_check_rate_limit(ngx_http_request_t *r,
|
||||
ngx_http_monitoring_loc_conf_t *mlcf);
|
||||
static ngx_table_elt_t *ngx_http_monitoring_find_header(ngx_http_request_t *r,
|
||||
ngx_str_t *name);
|
||||
static ngx_int_t ngx_http_monitoring_consttime_eq(ngx_str_t *a, ngx_str_t *b);
|
||||
|
||||
|
||||
static ngx_command_t ngx_http_monitoring_commands[] = {
|
||||
|
||||
{ ngx_string("monitor"),
|
||||
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
|
||||
ngx_conf_set_flag_slot,
|
||||
NGX_HTTP_LOC_CONF_OFFSET,
|
||||
offsetof(ngx_http_monitoring_loc_conf_t, enabled),
|
||||
NULL },
|
||||
|
||||
{ ngx_string("monitor_dashboard"),
|
||||
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
|
||||
ngx_conf_set_flag_slot,
|
||||
NGX_HTTP_LOC_CONF_OFFSET,
|
||||
offsetof(ngx_http_monitoring_loc_conf_t, dashboard),
|
||||
NULL },
|
||||
|
||||
{ ngx_string("monitor_api"),
|
||||
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
|
||||
ngx_conf_set_flag_slot,
|
||||
NGX_HTTP_LOC_CONF_OFFSET,
|
||||
offsetof(ngx_http_monitoring_loc_conf_t, api),
|
||||
NULL },
|
||||
|
||||
{ ngx_string("monitor_sse"),
|
||||
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
|
||||
ngx_conf_set_flag_slot,
|
||||
NGX_HTTP_LOC_CONF_OFFSET,
|
||||
offsetof(ngx_http_monitoring_loc_conf_t, sse),
|
||||
NULL },
|
||||
|
||||
{ ngx_string("monitor_refresh_interval"),
|
||||
NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1,
|
||||
ngx_conf_set_msec_slot,
|
||||
NGX_HTTP_MAIN_CONF_OFFSET,
|
||||
offsetof(ngx_http_monitoring_main_conf_t, refresh_interval),
|
||||
NULL },
|
||||
|
||||
{ ngx_string("monitor_history"),
|
||||
NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1,
|
||||
ngx_conf_set_msec_slot,
|
||||
NGX_HTTP_MAIN_CONF_OFFSET,
|
||||
offsetof(ngx_http_monitoring_main_conf_t, history),
|
||||
NULL },
|
||||
|
||||
{ ngx_string("monitor_resolution"),
|
||||
NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1,
|
||||
ngx_conf_set_msec_slot,
|
||||
NGX_HTTP_MAIN_CONF_OFFSET,
|
||||
offsetof(ngx_http_monitoring_main_conf_t, resolution),
|
||||
NULL },
|
||||
|
||||
{ ngx_string("monitor_shm_size"),
|
||||
NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1,
|
||||
ngx_conf_set_size_slot,
|
||||
NGX_HTTP_MAIN_CONF_OFFSET,
|
||||
offsetof(ngx_http_monitoring_main_conf_t, shm_size),
|
||||
NULL },
|
||||
|
||||
{ ngx_string("monitor_collect_system"),
|
||||
NGX_HTTP_MAIN_CONF|NGX_CONF_FLAG,
|
||||
ngx_conf_set_flag_slot,
|
||||
NGX_HTTP_MAIN_CONF_OFFSET,
|
||||
offsetof(ngx_http_monitoring_main_conf_t, collect_system),
|
||||
NULL },
|
||||
|
||||
{ ngx_string("monitor_collect_nginx"),
|
||||
NGX_HTTP_MAIN_CONF|NGX_CONF_FLAG,
|
||||
ngx_conf_set_flag_slot,
|
||||
NGX_HTTP_MAIN_CONF_OFFSET,
|
||||
offsetof(ngx_http_monitoring_main_conf_t, collect_nginx),
|
||||
NULL },
|
||||
|
||||
{ ngx_string("monitor_collect_network"),
|
||||
NGX_HTTP_MAIN_CONF|NGX_CONF_FLAG,
|
||||
ngx_conf_set_flag_slot,
|
||||
NGX_HTTP_MAIN_CONF_OFFSET,
|
||||
offsetof(ngx_http_monitoring_main_conf_t, collect_network),
|
||||
NULL },
|
||||
|
||||
{ ngx_string("monitor_access_log"),
|
||||
NGX_HTTP_MAIN_CONF|NGX_CONF_FLAG,
|
||||
ngx_conf_set_flag_slot,
|
||||
NGX_HTTP_MAIN_CONF_OFFSET,
|
||||
offsetof(ngx_http_monitoring_main_conf_t, access_log),
|
||||
NULL },
|
||||
|
||||
{ ngx_string("monitor_max_top_urls"),
|
||||
NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1,
|
||||
ngx_conf_set_num_slot,
|
||||
NGX_HTTP_MAIN_CONF_OFFSET,
|
||||
offsetof(ngx_http_monitoring_main_conf_t, max_top_urls),
|
||||
NULL },
|
||||
|
||||
{ ngx_string("monitor_api_token"),
|
||||
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
|
||||
ngx_conf_set_str_slot,
|
||||
NGX_HTTP_LOC_CONF_OFFSET,
|
||||
offsetof(ngx_http_monitoring_loc_conf_t, api_token),
|
||||
NULL },
|
||||
|
||||
{ ngx_string("monitor_basic_auth"),
|
||||
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
|
||||
ngx_conf_set_str_slot,
|
||||
NGX_HTTP_LOC_CONF_OFFSET,
|
||||
offsetof(ngx_http_monitoring_loc_conf_t, basic_auth),
|
||||
NULL },
|
||||
|
||||
{ ngx_string("monitor_cors"),
|
||||
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
|
||||
ngx_conf_set_str_slot,
|
||||
NGX_HTTP_LOC_CONF_OFFSET,
|
||||
offsetof(ngx_http_monitoring_loc_conf_t, cors),
|
||||
NULL },
|
||||
|
||||
{ ngx_string("monitor_rate_limit"),
|
||||
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
|
||||
ngx_conf_set_num_slot,
|
||||
NGX_HTTP_LOC_CONF_OFFSET,
|
||||
offsetof(ngx_http_monitoring_loc_conf_t, rate_limit),
|
||||
NULL },
|
||||
|
||||
{ ngx_string("monitor_allow"),
|
||||
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
|
||||
ngx_http_monitoring_access_rule,
|
||||
NGX_HTTP_LOC_CONF_OFFSET,
|
||||
0,
|
||||
NULL },
|
||||
|
||||
{ ngx_string("monitor_deny"),
|
||||
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
|
||||
ngx_http_monitoring_access_rule,
|
||||
NGX_HTTP_LOC_CONF_OFFSET,
|
||||
0,
|
||||
NULL },
|
||||
|
||||
ngx_null_command
|
||||
};
|
||||
|
||||
|
||||
static ngx_http_module_t ngx_http_monitoring_module_ctx = {
|
||||
NULL, /* preconfiguration */
|
||||
ngx_http_monitoring_init, /* postconfiguration */
|
||||
|
||||
ngx_http_monitoring_create_main_conf, /* create main configuration */
|
||||
ngx_http_monitoring_init_main_conf, /* init main configuration */
|
||||
|
||||
NULL, /* create server configuration */
|
||||
NULL, /* merge server configuration */
|
||||
|
||||
ngx_http_monitoring_create_loc_conf, /* create location configuration */
|
||||
ngx_http_monitoring_merge_loc_conf /* merge location configuration */
|
||||
};
|
||||
|
||||
|
||||
ngx_module_t ngx_http_monitoring_module = {
|
||||
NGX_MODULE_V1,
|
||||
&ngx_http_monitoring_module_ctx,
|
||||
ngx_http_monitoring_commands,
|
||||
NGX_HTTP_MODULE,
|
||||
NULL, /* init master */
|
||||
NULL, /* init module */
|
||||
ngx_http_monitoring_init_process, /* init process */
|
||||
NULL, /* init thread */
|
||||
NULL, /* exit thread */
|
||||
ngx_http_monitoring_exit_process, /* exit process */
|
||||
NULL, /* exit master */
|
||||
NGX_MODULE_V1_PADDING
|
||||
};
|
||||
|
||||
|
||||
static void *
|
||||
ngx_http_monitoring_create_main_conf(ngx_conf_t *cf)
|
||||
{
|
||||
ngx_http_monitoring_main_conf_t *mmcf;
|
||||
|
||||
mmcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_monitoring_main_conf_t));
|
||||
if (mmcf == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
mmcf->refresh_interval = NGX_CONF_UNSET_MSEC;
|
||||
mmcf->history = NGX_CONF_UNSET_MSEC;
|
||||
mmcf->resolution = NGX_CONF_UNSET_MSEC;
|
||||
mmcf->shm_size = NGX_CONF_UNSET_SIZE;
|
||||
mmcf->max_top_urls = NGX_CONF_UNSET_UINT;
|
||||
mmcf->collect_system = NGX_CONF_UNSET;
|
||||
mmcf->collect_nginx = NGX_CONF_UNSET;
|
||||
mmcf->collect_network = NGX_CONF_UNSET;
|
||||
mmcf->access_log = NGX_CONF_UNSET;
|
||||
|
||||
return mmcf;
|
||||
}
|
||||
|
||||
|
||||
static char *
|
||||
ngx_http_monitoring_init_main_conf(ngx_conf_t *cf, void *conf)
|
||||
{
|
||||
ngx_http_monitoring_main_conf_t *mmcf = conf;
|
||||
|
||||
ngx_conf_init_msec_value(mmcf->refresh_interval, 1000);
|
||||
ngx_conf_init_msec_value(mmcf->history, 300000);
|
||||
ngx_conf_init_msec_value(mmcf->resolution, 1000);
|
||||
ngx_conf_init_size_value(mmcf->shm_size,
|
||||
NGX_HTTP_MONITORING_DEFAULT_SHM_SIZE);
|
||||
ngx_conf_init_uint_value(mmcf->max_top_urls, 100);
|
||||
ngx_conf_init_value(mmcf->collect_system, 1);
|
||||
ngx_conf_init_value(mmcf->collect_nginx, 1);
|
||||
ngx_conf_init_value(mmcf->collect_network, 1);
|
||||
ngx_conf_init_value(mmcf->access_log, 1);
|
||||
|
||||
if (mmcf->refresh_interval < NGX_HTTP_MONITORING_MIN_INTERVAL) {
|
||||
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
|
||||
"monitor_refresh_interval must be at least 100ms");
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
if (mmcf->resolution < NGX_HTTP_MONITORING_MIN_INTERVAL) {
|
||||
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
|
||||
"monitor_resolution must be at least 100ms");
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
if (mmcf->max_top_urls == 0
|
||||
|| mmcf->max_top_urls > NGX_HTTP_MONITORING_TOP_URLS_MAX)
|
||||
{
|
||||
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
|
||||
"monitor_max_top_urls must be between 1 and %d",
|
||||
NGX_HTTP_MONITORING_TOP_URLS_MAX);
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
|
||||
|
||||
static void *
|
||||
ngx_http_monitoring_create_loc_conf(ngx_conf_t *cf)
|
||||
{
|
||||
ngx_http_monitoring_loc_conf_t *mlcf;
|
||||
|
||||
mlcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_monitoring_loc_conf_t));
|
||||
if (mlcf == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
mlcf->enabled = NGX_CONF_UNSET;
|
||||
mlcf->dashboard = NGX_CONF_UNSET;
|
||||
mlcf->api = NGX_CONF_UNSET;
|
||||
mlcf->sse = NGX_CONF_UNSET;
|
||||
mlcf->rate_limit = NGX_CONF_UNSET_UINT;
|
||||
|
||||
return mlcf;
|
||||
}
|
||||
|
||||
|
||||
static char *
|
||||
ngx_http_monitoring_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
|
||||
{
|
||||
ngx_http_monitoring_loc_conf_t *prev = parent;
|
||||
ngx_http_monitoring_loc_conf_t *conf = child;
|
||||
|
||||
ngx_conf_merge_value(conf->enabled, prev->enabled, 0);
|
||||
ngx_conf_merge_value(conf->dashboard, prev->dashboard, 1);
|
||||
ngx_conf_merge_value(conf->api, prev->api, 1);
|
||||
ngx_conf_merge_value(conf->sse, prev->sse, 1);
|
||||
ngx_conf_merge_uint_value(conf->rate_limit, prev->rate_limit, 60);
|
||||
ngx_conf_merge_str_value(conf->api_token, prev->api_token, "");
|
||||
ngx_conf_merge_str_value(conf->basic_auth, prev->basic_auth, "");
|
||||
ngx_conf_merge_str_value(conf->cors, prev->cors, "");
|
||||
|
||||
if (conf->access_rules == NULL) {
|
||||
conf->access_rules = prev->access_rules;
|
||||
}
|
||||
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
|
||||
|
||||
static char *
|
||||
ngx_http_monitoring_access_rule(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
||||
{
|
||||
ngx_http_monitoring_loc_conf_t *mlcf = conf;
|
||||
ngx_http_monitoring_access_rule_t *rule;
|
||||
ngx_str_t *value;
|
||||
ngx_int_t rc;
|
||||
|
||||
if (mlcf->access_rules == NULL) {
|
||||
mlcf->access_rules = ngx_array_create(cf->pool, 4,
|
||||
sizeof(ngx_http_monitoring_access_rule_t));
|
||||
if (mlcf->access_rules == NULL) {
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
rule = ngx_array_push(mlcf->access_rules);
|
||||
if (rule == NULL) {
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
value = cf->args->elts;
|
||||
ngx_memzero(rule, sizeof(ngx_http_monitoring_access_rule_t));
|
||||
rule->deny = (cmd->name.len == sizeof("monitor_deny") - 1);
|
||||
|
||||
if (value[1].len == 3 && ngx_strncmp(value[1].data, "all", 3) == 0) {
|
||||
rule->cidr.family = AF_UNSPEC;
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
|
||||
rc = ngx_ptocidr(&value[1], &rule->cidr);
|
||||
if (rc == NGX_ERROR) {
|
||||
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
|
||||
"invalid CIDR \"%V\"", &value[1]);
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
if (rc == NGX_DONE) {
|
||||
ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
|
||||
"low address bits of \"%V\" are meaningless",
|
||||
&value[1]);
|
||||
}
|
||||
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_http_monitoring_init(ngx_conf_t *cf)
|
||||
{
|
||||
ngx_http_handler_pt *h;
|
||||
ngx_http_core_main_conf_t *cmcf;
|
||||
ngx_http_monitoring_main_conf_t *mmcf;
|
||||
ngx_str_t name = ngx_string("ngx_http_monitoring");
|
||||
|
||||
mmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_monitoring_module);
|
||||
|
||||
mmcf->shm_zone = ngx_shared_memory_add(cf, &name, mmcf->shm_size,
|
||||
&ngx_http_monitoring_module);
|
||||
if (mmcf->shm_zone == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
mmcf->shm_zone->init = ngx_http_monitoring_init_shm;
|
||||
mmcf->shm_zone->data = mmcf;
|
||||
|
||||
cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
|
||||
|
||||
h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
|
||||
if (h == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
*h = ngx_http_monitoring_content_handler;
|
||||
|
||||
h = ngx_array_push(&cmcf->phases[NGX_HTTP_LOG_PHASE].handlers);
|
||||
if (h == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
*h = ngx_http_monitoring_log_handler;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_http_monitoring_init_process(ngx_cycle_t *cycle)
|
||||
{
|
||||
return ngx_http_monitoring_init_collector(cycle);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_http_monitoring_exit_process(ngx_cycle_t *cycle)
|
||||
{
|
||||
ngx_http_monitoring_main_conf_t *mmcf;
|
||||
ngx_http_monitoring_shctx_t *sh;
|
||||
ngx_uint_t slot;
|
||||
|
||||
mmcf = ngx_http_cycle_get_module_main_conf(cycle,
|
||||
ngx_http_monitoring_module);
|
||||
if (mmcf == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
mmcf->collector_active = 0;
|
||||
if (mmcf->collector_event.timer_set) {
|
||||
ngx_del_timer(&mmcf->collector_event);
|
||||
}
|
||||
|
||||
sh = (ngx_http_monitoring_shctx_t *) mmcf->sh;
|
||||
if (sh == NULL || ngx_process_slot < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
slot = ((ngx_uint_t) ngx_process_slot) % NGX_HTTP_MONITORING_WORKERS_MAX;
|
||||
sh->workers[slot].active = 0;
|
||||
sh->workers[slot].last_seen = ngx_time();
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_http_monitoring_content_handler(ngx_http_request_t *r)
|
||||
{
|
||||
ngx_int_t rc;
|
||||
ngx_http_monitoring_endpoint_e endpoint;
|
||||
ngx_http_monitoring_loc_conf_t *mlcf;
|
||||
|
||||
endpoint = ngx_http_monitoring_match_endpoint(r);
|
||||
if (endpoint == NGX_HTTP_MONITORING_EP_NONE) {
|
||||
return NGX_DECLINED;
|
||||
}
|
||||
|
||||
mlcf = ngx_http_get_module_loc_conf(r, ngx_http_monitoring_module);
|
||||
if (mlcf == NULL || !mlcf->enabled) {
|
||||
return NGX_DECLINED;
|
||||
}
|
||||
|
||||
if (r->method == NGX_HTTP_OPTIONS) {
|
||||
return ngx_http_monitoring_preflight(r, mlcf);
|
||||
}
|
||||
|
||||
if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
|
||||
return NGX_HTTP_NOT_ALLOWED;
|
||||
}
|
||||
|
||||
if (endpoint == NGX_HTTP_MONITORING_EP_DASHBOARD && !mlcf->dashboard) {
|
||||
return NGX_HTTP_NOT_FOUND;
|
||||
}
|
||||
|
||||
if (endpoint >= NGX_HTTP_MONITORING_EP_API_FULL
|
||||
&& endpoint <= NGX_HTTP_MONITORING_EP_API_REQUESTS
|
||||
&& !mlcf->api)
|
||||
{
|
||||
return NGX_HTTP_NOT_FOUND;
|
||||
}
|
||||
|
||||
if (endpoint == NGX_HTTP_MONITORING_EP_LIVE && !mlcf->sse) {
|
||||
return NGX_HTTP_NOT_FOUND;
|
||||
}
|
||||
|
||||
rc = ngx_http_monitoring_authorize(r, mlcf, endpoint);
|
||||
if (rc != NGX_OK) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
ngx_http_monitoring_add_cors(r, mlcf);
|
||||
|
||||
switch (endpoint) {
|
||||
case NGX_HTTP_MONITORING_EP_DASHBOARD:
|
||||
return ngx_http_monitoring_send_dashboard(r);
|
||||
case NGX_HTTP_MONITORING_EP_LIVE:
|
||||
return ngx_http_monitoring_send_sse(r);
|
||||
case NGX_HTTP_MONITORING_EP_PROMETHEUS:
|
||||
return ngx_http_monitoring_send_prometheus(r);
|
||||
case NGX_HTTP_MONITORING_EP_HEALTH:
|
||||
return ngx_http_monitoring_send_health(r);
|
||||
default:
|
||||
return ngx_http_monitoring_send_json(r, endpoint);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_http_monitoring_log_handler(ngx_http_request_t *r)
|
||||
{
|
||||
ngx_http_monitoring_main_conf_t *mmcf;
|
||||
|
||||
mmcf = ngx_http_get_module_main_conf(r, ngx_http_monitoring_module);
|
||||
if (mmcf == NULL || !mmcf->access_log || mmcf->sh == NULL) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (ngx_http_monitoring_match_endpoint(r) != NGX_HTTP_MONITORING_EP_NONE) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
ngx_http_monitoring_account_request(r);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_http_monitoring_endpoint_e
|
||||
ngx_http_monitoring_match_endpoint(ngx_http_request_t *r)
|
||||
{
|
||||
ngx_str_t *u = &r->uri;
|
||||
|
||||
if (u->len == sizeof("/monitor") - 1
|
||||
&& ngx_strncmp(u->data, "/monitor", u->len) == 0)
|
||||
{
|
||||
return NGX_HTTP_MONITORING_EP_DASHBOARD;
|
||||
}
|
||||
|
||||
if (u->len == sizeof("/monitor/") - 1
|
||||
&& ngx_strncmp(u->data, "/monitor/", u->len) == 0)
|
||||
{
|
||||
return NGX_HTTP_MONITORING_EP_DASHBOARD;
|
||||
}
|
||||
|
||||
if (u->len == sizeof("/monitor/live") - 1
|
||||
&& ngx_strncmp(u->data, "/monitor/live", u->len) == 0)
|
||||
{
|
||||
return NGX_HTTP_MONITORING_EP_LIVE;
|
||||
}
|
||||
|
||||
if (u->len == sizeof("/monitor/metrics") - 1
|
||||
&& ngx_strncmp(u->data, "/monitor/metrics", u->len) == 0)
|
||||
{
|
||||
return NGX_HTTP_MONITORING_EP_PROMETHEUS;
|
||||
}
|
||||
|
||||
if (u->len == sizeof("/monitor/health") - 1
|
||||
&& ngx_strncmp(u->data, "/monitor/health", u->len) == 0)
|
||||
{
|
||||
return NGX_HTTP_MONITORING_EP_HEALTH;
|
||||
}
|
||||
|
||||
if (u->len == sizeof("/monitor/api") - 1
|
||||
&& ngx_strncmp(u->data, "/monitor/api", u->len) == 0)
|
||||
{
|
||||
return NGX_HTTP_MONITORING_EP_API_FULL;
|
||||
}
|
||||
|
||||
if (u->len == sizeof("/monitor/api/system") - 1
|
||||
&& ngx_strncmp(u->data, "/monitor/api/system", u->len) == 0)
|
||||
{
|
||||
return NGX_HTTP_MONITORING_EP_API_SYSTEM;
|
||||
}
|
||||
|
||||
if (u->len == sizeof("/monitor/api/nginx") - 1
|
||||
&& ngx_strncmp(u->data, "/monitor/api/nginx", u->len) == 0)
|
||||
{
|
||||
return NGX_HTTP_MONITORING_EP_API_NGINX;
|
||||
}
|
||||
|
||||
if (u->len == sizeof("/monitor/api/network") - 1
|
||||
&& ngx_strncmp(u->data, "/monitor/api/network", u->len) == 0)
|
||||
{
|
||||
return NGX_HTTP_MONITORING_EP_API_NETWORK;
|
||||
}
|
||||
|
||||
if (u->len == sizeof("/monitor/api/disk") - 1
|
||||
&& ngx_strncmp(u->data, "/monitor/api/disk", u->len) == 0)
|
||||
{
|
||||
return NGX_HTTP_MONITORING_EP_API_DISK;
|
||||
}
|
||||
|
||||
if (u->len == sizeof("/monitor/api/processes") - 1
|
||||
&& ngx_strncmp(u->data, "/monitor/api/processes", u->len) == 0)
|
||||
{
|
||||
return NGX_HTTP_MONITORING_EP_API_PROCESSES;
|
||||
}
|
||||
|
||||
if (u->len == sizeof("/monitor/api/upstreams") - 1
|
||||
&& ngx_strncmp(u->data, "/monitor/api/upstreams", u->len) == 0)
|
||||
{
|
||||
return NGX_HTTP_MONITORING_EP_API_UPSTREAMS;
|
||||
}
|
||||
|
||||
if (u->len == sizeof("/monitor/api/connections") - 1
|
||||
&& ngx_strncmp(u->data, "/monitor/api/connections", u->len) == 0)
|
||||
{
|
||||
return NGX_HTTP_MONITORING_EP_API_CONNECTIONS;
|
||||
}
|
||||
|
||||
if (u->len == sizeof("/monitor/api/requests") - 1
|
||||
&& ngx_strncmp(u->data, "/monitor/api/requests", u->len) == 0)
|
||||
{
|
||||
return NGX_HTTP_MONITORING_EP_API_REQUESTS;
|
||||
}
|
||||
|
||||
return NGX_HTTP_MONITORING_EP_NONE;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_http_monitoring_authorize(ngx_http_request_t *r,
|
||||
ngx_http_monitoring_loc_conf_t *mlcf,
|
||||
ngx_http_monitoring_endpoint_e endpoint)
|
||||
{
|
||||
ngx_str_t forbidden = ngx_string("forbidden\n");
|
||||
ngx_str_t unauthorized = ngx_string("unauthorized\n");
|
||||
ngx_str_t limited = ngx_string("rate limited\n");
|
||||
|
||||
if (ngx_http_monitoring_match_access(r, mlcf) != NGX_OK) {
|
||||
return ngx_http_monitoring_plain_response(r, NGX_HTTP_FORBIDDEN,
|
||||
&forbidden);
|
||||
}
|
||||
|
||||
if (ngx_http_monitoring_check_basic_auth(r, mlcf) != NGX_OK) {
|
||||
r->headers_out.www_authenticate =
|
||||
ngx_list_push(&r->headers_out.headers);
|
||||
if (r->headers_out.www_authenticate == NULL) {
|
||||
return NGX_HTTP_INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
|
||||
r->headers_out.www_authenticate->hash = 1;
|
||||
ngx_str_set(&r->headers_out.www_authenticate->key,
|
||||
"WWW-Authenticate");
|
||||
ngx_str_set(&r->headers_out.www_authenticate->value,
|
||||
"Basic realm=\"nginx monitoring\"");
|
||||
|
||||
return ngx_http_monitoring_plain_response(r, NGX_HTTP_UNAUTHORIZED,
|
||||
&unauthorized);
|
||||
}
|
||||
|
||||
if (endpoint != NGX_HTTP_MONITORING_EP_DASHBOARD
|
||||
&& ngx_http_monitoring_check_api_token(r, mlcf) != NGX_OK)
|
||||
{
|
||||
return ngx_http_monitoring_plain_response(r, NGX_HTTP_UNAUTHORIZED,
|
||||
&unauthorized);
|
||||
}
|
||||
|
||||
if (ngx_http_monitoring_check_rate_limit(r, mlcf) != NGX_OK) {
|
||||
return ngx_http_monitoring_plain_response(r, 429, &limited);
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_http_monitoring_preflight(ngx_http_request_t *r,
|
||||
ngx_http_monitoring_loc_conf_t *mlcf)
|
||||
{
|
||||
ngx_http_monitoring_add_cors(r, mlcf);
|
||||
|
||||
r->headers_out.status = NGX_HTTP_NO_CONTENT;
|
||||
r->headers_out.content_length_n = 0;
|
||||
r->header_only = 1;
|
||||
|
||||
return ngx_http_send_header(r);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_http_monitoring_add_cors(ngx_http_request_t *r,
|
||||
ngx_http_monitoring_loc_conf_t *mlcf)
|
||||
{
|
||||
ngx_table_elt_t *h;
|
||||
|
||||
if (mlcf->cors.len == 0
|
||||
|| (mlcf->cors.len == 3
|
||||
&& ngx_strncmp(mlcf->cors.data, "off", 3) == 0))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
h = ngx_list_push(&r->headers_out.headers);
|
||||
if (h == NULL) {
|
||||
return;
|
||||
}
|
||||
h->hash = 1;
|
||||
ngx_str_set(&h->key, "Access-Control-Allow-Origin");
|
||||
h->value = mlcf->cors;
|
||||
|
||||
h = ngx_list_push(&r->headers_out.headers);
|
||||
if (h == NULL) {
|
||||
return;
|
||||
}
|
||||
h->hash = 1;
|
||||
ngx_str_set(&h->key, "Access-Control-Allow-Methods");
|
||||
ngx_str_set(&h->value, "GET, HEAD, OPTIONS");
|
||||
|
||||
h = ngx_list_push(&r->headers_out.headers);
|
||||
if (h == NULL) {
|
||||
return;
|
||||
}
|
||||
h->hash = 1;
|
||||
ngx_str_set(&h->key, "Access-Control-Allow-Headers");
|
||||
ngx_str_set(&h->value, "Authorization, X-Monitor-Token, Content-Type");
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_http_monitoring_plain_response(ngx_http_request_t *r, ngx_uint_t status,
|
||||
ngx_str_t *body)
|
||||
{
|
||||
ngx_buf_t *b;
|
||||
ngx_chain_t out;
|
||||
|
||||
ngx_http_discard_request_body(r);
|
||||
|
||||
r->headers_out.status = status;
|
||||
r->headers_out.content_length_n = body->len;
|
||||
ngx_str_set(&r->headers_out.content_type, "text/plain");
|
||||
|
||||
if (r->method == NGX_HTTP_HEAD) {
|
||||
r->header_only = 1;
|
||||
return ngx_http_send_header(r);
|
||||
}
|
||||
|
||||
b = ngx_calloc_buf(r->pool);
|
||||
if (b == NULL) {
|
||||
return NGX_HTTP_INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
|
||||
b->pos = body->data;
|
||||
b->last = body->data + body->len;
|
||||
b->memory = 1;
|
||||
b->last_buf = 1;
|
||||
|
||||
out.buf = b;
|
||||
out.next = NULL;
|
||||
|
||||
ngx_http_send_header(r);
|
||||
return ngx_http_output_filter(r, &out);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_http_monitoring_match_access(ngx_http_request_t *r,
|
||||
ngx_http_monitoring_loc_conf_t *mlcf)
|
||||
{
|
||||
ngx_uint_t i, j;
|
||||
ngx_http_monitoring_access_rule_t *rule;
|
||||
struct sockaddr *sa;
|
||||
struct sockaddr_in *sin;
|
||||
#if (NGX_HAVE_INET6)
|
||||
struct sockaddr_in6 *sin6;
|
||||
#endif
|
||||
u_char *addr;
|
||||
u_char *mask;
|
||||
u_char *cidr;
|
||||
|
||||
if (mlcf->access_rules == NULL || mlcf->access_rules->nelts == 0) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
rule = mlcf->access_rules->elts;
|
||||
sa = r->connection->sockaddr;
|
||||
|
||||
for (i = 0; i < mlcf->access_rules->nelts; i++) {
|
||||
if (rule[i].cidr.family == AF_UNSPEC) {
|
||||
return rule[i].deny ? NGX_DECLINED : NGX_OK;
|
||||
}
|
||||
|
||||
if (sa->sa_family != (ngx_int_t) rule[i].cidr.family) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sa->sa_family == AF_INET) {
|
||||
sin = (struct sockaddr_in *) sa;
|
||||
if ((sin->sin_addr.s_addr & rule[i].cidr.u.in.mask)
|
||||
== rule[i].cidr.u.in.addr)
|
||||
{
|
||||
return rule[i].deny ? NGX_DECLINED : NGX_OK;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
#if (NGX_HAVE_INET6)
|
||||
if (sa->sa_family == AF_INET6) {
|
||||
sin6 = (struct sockaddr_in6 *) sa;
|
||||
addr = sin6->sin6_addr.s6_addr;
|
||||
mask = rule[i].cidr.u.in6.mask.s6_addr;
|
||||
cidr = rule[i].cidr.u.in6.addr.s6_addr;
|
||||
|
||||
for (j = 0; j < 16; j++) {
|
||||
if ((addr[j] & mask[j]) != cidr[j]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (j == 16) {
|
||||
return rule[i].deny ? NGX_DECLINED : NGX_OK;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_http_monitoring_check_basic_auth(ngx_http_request_t *r,
|
||||
ngx_http_monitoring_loc_conf_t *mlcf)
|
||||
{
|
||||
ngx_table_elt_t *authorization;
|
||||
ngx_str_t encoded, decoded, expected;
|
||||
|
||||
if (mlcf->basic_auth.len == 0
|
||||
|| (mlcf->basic_auth.len == 3
|
||||
&& ngx_strncmp(mlcf->basic_auth.data, "off", 3) == 0))
|
||||
{
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
authorization = r->headers_in.authorization;
|
||||
if (authorization == NULL || authorization->value.len <= 6
|
||||
|| ngx_strncasecmp(authorization->value.data,
|
||||
(u_char *) "Basic ", 6) != 0)
|
||||
{
|
||||
return NGX_DECLINED;
|
||||
}
|
||||
|
||||
encoded.data = authorization->value.data + 6;
|
||||
encoded.len = authorization->value.len - 6;
|
||||
decoded.len = ngx_base64_decoded_length(encoded.len);
|
||||
decoded.data = ngx_pnalloc(r->pool, decoded.len);
|
||||
if (decoded.data == NULL) {
|
||||
return NGX_DECLINED;
|
||||
}
|
||||
|
||||
if (ngx_decode_base64(&decoded, &encoded) != NGX_OK) {
|
||||
return NGX_DECLINED;
|
||||
}
|
||||
|
||||
expected = mlcf->basic_auth;
|
||||
return ngx_http_monitoring_consttime_eq(&decoded, &expected);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_http_monitoring_check_api_token(ngx_http_request_t *r,
|
||||
ngx_http_monitoring_loc_conf_t *mlcf)
|
||||
{
|
||||
ngx_table_elt_t *h;
|
||||
ngx_str_t header = ngx_string("X-Monitor-Token");
|
||||
ngx_str_t token, bearer, query;
|
||||
|
||||
if (mlcf->api_token.len == 0) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
h = ngx_http_monitoring_find_header(r, &header);
|
||||
if (h != NULL) {
|
||||
token = h->value;
|
||||
if (ngx_http_monitoring_consttime_eq(&token, &mlcf->api_token)
|
||||
== NGX_OK)
|
||||
{
|
||||
return NGX_OK;
|
||||
}
|
||||
}
|
||||
|
||||
h = r->headers_in.authorization;
|
||||
if (h != NULL && h->value.len > 7
|
||||
&& ngx_strncasecmp(h->value.data, (u_char *) "Bearer ", 7) == 0)
|
||||
{
|
||||
bearer.data = h->value.data + 7;
|
||||
bearer.len = h->value.len - 7;
|
||||
|
||||
if (ngx_http_monitoring_consttime_eq(&bearer, &mlcf->api_token)
|
||||
== NGX_OK)
|
||||
{
|
||||
return NGX_OK;
|
||||
}
|
||||
}
|
||||
|
||||
if (ngx_http_arg(r, (u_char *) "token", 5, &query) == NGX_OK
|
||||
&& ngx_http_monitoring_consttime_eq(&query, &mlcf->api_token)
|
||||
== NGX_OK)
|
||||
{
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
return NGX_DECLINED;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_http_monitoring_check_rate_limit(ngx_http_request_t *r,
|
||||
ngx_http_monitoring_loc_conf_t *mlcf)
|
||||
{
|
||||
time_t now;
|
||||
ngx_atomic_uint_t count;
|
||||
ngx_http_monitoring_shctx_t *sh;
|
||||
ngx_http_monitoring_main_conf_t *mmcf;
|
||||
|
||||
if (mlcf->rate_limit == 0) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
sh = ngx_http_monitoring_get_shctx(r);
|
||||
mmcf = ngx_http_monitoring_get_main_conf(r);
|
||||
if (sh == NULL || mmcf == NULL || mmcf->shpool == NULL) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
now = ngx_time();
|
||||
|
||||
if (sh->rate_limit_window != (ngx_atomic_uint_t) now) {
|
||||
ngx_shmtx_lock(&mmcf->shpool->mutex);
|
||||
if (sh->rate_limit_window != (ngx_atomic_uint_t) now) {
|
||||
sh->rate_limit_window = (ngx_atomic_uint_t) now;
|
||||
sh->rate_limit_count = 0;
|
||||
}
|
||||
ngx_shmtx_unlock(&mmcf->shpool->mutex);
|
||||
}
|
||||
|
||||
count = ngx_atomic_fetch_add(&sh->rate_limit_count, 1) + 1;
|
||||
if (count > mlcf->rate_limit) {
|
||||
ngx_atomic_fetch_add(&sh->connections.rate_limited, 1);
|
||||
return NGX_DECLINED;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_table_elt_t *
|
||||
ngx_http_monitoring_find_header(ngx_http_request_t *r, ngx_str_t *name)
|
||||
{
|
||||
ngx_list_part_t *part;
|
||||
ngx_table_elt_t *h;
|
||||
ngx_uint_t i;
|
||||
|
||||
part = &r->headers_in.headers.part;
|
||||
h = part->elts;
|
||||
|
||||
for (i = 0; /* void */; i++) {
|
||||
if (i >= part->nelts) {
|
||||
if (part->next == NULL) {
|
||||
break;
|
||||
}
|
||||
part = part->next;
|
||||
h = part->elts;
|
||||
i = 0;
|
||||
}
|
||||
|
||||
if (h[i].key.len == name->len
|
||||
&& ngx_strncasecmp(h[i].key.data, name->data, name->len) == 0)
|
||||
{
|
||||
return &h[i];
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_http_monitoring_consttime_eq(ngx_str_t *a, ngx_str_t *b)
|
||||
{
|
||||
size_t i, max;
|
||||
ngx_uint_t diff;
|
||||
|
||||
max = (a->len > b->len) ? a->len : b->len;
|
||||
diff = (ngx_uint_t) (a->len ^ b->len);
|
||||
|
||||
for (i = 0; i < max; i++) {
|
||||
diff |= (ngx_uint_t)
|
||||
((i < a->len ? a->data[i] : 0)
|
||||
^ (i < b->len ? b->data[i] : 0));
|
||||
}
|
||||
|
||||
return diff == 0 ? NGX_OK : NGX_DECLINED;
|
||||
}
|
||||
558
src/ngx_http_monitoring_shm.c
Normal file
558
src/ngx_http_monitoring_shm.c
Normal file
@@ -0,0 +1,558 @@
|
||||
#include "ngx_http_monitoring.h"
|
||||
|
||||
static void ngx_http_monitoring_update_worker(ngx_http_monitoring_shctx_t *sh,
|
||||
ngx_msec_t latency, off_t bytes, ngx_uint_t is_error);
|
||||
static void ngx_http_monitoring_update_status(ngx_http_monitoring_shctx_t *sh,
|
||||
ngx_uint_t status);
|
||||
static void ngx_http_monitoring_update_upstream_slot(ngx_slab_pool_t *shpool,
|
||||
ngx_http_monitoring_upstream_entry_t *entries, ngx_uint_t capacity,
|
||||
ngx_str_t *peer, ngx_msec_t latency, ngx_uint_t status);
|
||||
static ngx_uint_t ngx_http_monitoring_worker_slot(void);
|
||||
|
||||
static ngx_uint_t ngx_http_monitoring_latency_bounds[
|
||||
NGX_HTTP_MONITORING_LATENCY_BUCKETS - 1
|
||||
] = { 1, 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000, 30000 };
|
||||
|
||||
static off_t ngx_http_monitoring_size_bounds[
|
||||
NGX_HTTP_MONITORING_SIZE_BUCKETS - 1
|
||||
] = { 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536,
|
||||
262144, 1048576, 10485760 };
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_http_monitoring_init_shm(ngx_shm_zone_t *shm_zone, void *data)
|
||||
{
|
||||
ngx_http_monitoring_main_conf_t *ommcf = data;
|
||||
ngx_http_monitoring_main_conf_t *mmcf;
|
||||
ngx_http_monitoring_shctx_t *sh;
|
||||
ngx_slab_pool_t *shpool;
|
||||
ngx_uint_t capacity;
|
||||
|
||||
mmcf = shm_zone->data;
|
||||
|
||||
if (ommcf) {
|
||||
mmcf->sh = ommcf->sh;
|
||||
mmcf->shpool = ommcf->shpool;
|
||||
sh = (ngx_http_monitoring_shctx_t *) mmcf->sh;
|
||||
capacity = mmcf->history / ngx_max(mmcf->resolution, 1);
|
||||
if (capacity == 0) {
|
||||
capacity = 1;
|
||||
}
|
||||
if (capacity > NGX_HTTP_MONITORING_HISTORY_MAX) {
|
||||
capacity = NGX_HTTP_MONITORING_HISTORY_MAX;
|
||||
}
|
||||
sh->history_capacity = capacity;
|
||||
if ((ngx_uint_t) sh->history_count > capacity) {
|
||||
sh->history_count = capacity;
|
||||
}
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
|
||||
|
||||
if (shm_zone->shm.exists) {
|
||||
sh = shpool->data;
|
||||
if (sh == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
mmcf->sh = sh;
|
||||
mmcf->shpool = shpool;
|
||||
capacity = mmcf->history / ngx_max(mmcf->resolution, 1);
|
||||
if (capacity == 0) {
|
||||
capacity = 1;
|
||||
}
|
||||
if (capacity > NGX_HTTP_MONITORING_HISTORY_MAX) {
|
||||
capacity = NGX_HTTP_MONITORING_HISTORY_MAX;
|
||||
}
|
||||
sh->history_capacity = capacity;
|
||||
if ((ngx_uint_t) sh->history_count > capacity) {
|
||||
sh->history_count = capacity;
|
||||
}
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
sh = ngx_slab_alloc(shpool, sizeof(ngx_http_monitoring_shctx_t));
|
||||
if (sh == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_memzero(sh, sizeof(ngx_http_monitoring_shctx_t));
|
||||
|
||||
capacity = mmcf->history / ngx_max(mmcf->resolution, 1);
|
||||
if (capacity == 0) {
|
||||
capacity = 1;
|
||||
}
|
||||
if (capacity > NGX_HTTP_MONITORING_HISTORY_MAX) {
|
||||
capacity = NGX_HTTP_MONITORING_HISTORY_MAX;
|
||||
}
|
||||
|
||||
sh->history_capacity = capacity;
|
||||
sh->initialized = 1;
|
||||
sh->generation = 1;
|
||||
shpool->data = sh;
|
||||
|
||||
mmcf->sh = sh;
|
||||
mmcf->shpool = shpool;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_http_monitoring_shctx_t *
|
||||
ngx_http_monitoring_get_shctx(ngx_http_request_t *r)
|
||||
{
|
||||
ngx_http_monitoring_main_conf_t *mmcf;
|
||||
|
||||
mmcf = ngx_http_get_module_main_conf(r, ngx_http_monitoring_module);
|
||||
if (mmcf == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return (ngx_http_monitoring_shctx_t *) mmcf->sh;
|
||||
}
|
||||
|
||||
|
||||
ngx_http_monitoring_main_conf_t *
|
||||
ngx_http_monitoring_get_main_conf(ngx_http_request_t *r)
|
||||
{
|
||||
return ngx_http_get_module_main_conf(r, ngx_http_monitoring_module);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ngx_http_monitoring_account_request(ngx_http_request_t *r)
|
||||
{
|
||||
ngx_time_t *tp;
|
||||
ngx_msec_int_t ms;
|
||||
ngx_msec_t latency;
|
||||
off_t bytes;
|
||||
ngx_uint_t status, method, lb, sb, is_error;
|
||||
ngx_http_monitoring_shctx_t *sh;
|
||||
ngx_http_monitoring_main_conf_t *mmcf;
|
||||
ngx_str_t uri, ua;
|
||||
|
||||
sh = ngx_http_monitoring_get_shctx(r);
|
||||
mmcf = ngx_http_monitoring_get_main_conf(r);
|
||||
if (sh == NULL || mmcf == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
tp = ngx_timeofday();
|
||||
ms = (ngx_msec_int_t) ((tp->sec - r->start_sec) * 1000
|
||||
+ (tp->msec - r->start_msec));
|
||||
latency = (ms > 0) ? (ngx_msec_t) ms : 0;
|
||||
|
||||
status = r->headers_out.status;
|
||||
if (status == 0) {
|
||||
status = 499;
|
||||
}
|
||||
|
||||
bytes = r->headers_out.content_length_n;
|
||||
if (bytes < 0) {
|
||||
bytes = 0;
|
||||
}
|
||||
|
||||
ngx_atomic_fetch_add(&sh->requests.total, 1);
|
||||
ngx_atomic_fetch_add(&sh->requests.responses, 1);
|
||||
ngx_atomic_fetch_add(&sh->requests.bytes, (ngx_atomic_int_t) bytes);
|
||||
ngx_atomic_fetch_add(&sh->requests.request_time_ms_total, latency);
|
||||
|
||||
method = ngx_http_monitoring_method_index(r->method);
|
||||
ngx_atomic_fetch_add(&sh->requests.method[method], 1);
|
||||
|
||||
lb = ngx_http_monitoring_latency_bucket(latency);
|
||||
ngx_atomic_fetch_add(&sh->requests.latency_bucket[lb], 1);
|
||||
|
||||
sb = ngx_http_monitoring_size_bucket(bytes);
|
||||
ngx_atomic_fetch_add(&sh->requests.size_bucket[sb], 1);
|
||||
|
||||
ngx_http_monitoring_update_status(sh, status);
|
||||
|
||||
#if (NGX_HTTP_SSL)
|
||||
if (r->connection->ssl) {
|
||||
ngx_atomic_fetch_add(&sh->connections.ssl_requests, 1);
|
||||
if (r->connection->requests <= 1) {
|
||||
ngx_atomic_fetch_add(&sh->connections.ssl_handshakes, 1);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (r->connection->requests > 1) {
|
||||
ngx_atomic_fetch_add(&sh->connections.keepalive_requests, 1);
|
||||
}
|
||||
|
||||
is_error = status >= 500;
|
||||
ngx_http_monitoring_update_worker(sh, latency, bytes, is_error);
|
||||
|
||||
uri = r->uri;
|
||||
ngx_http_monitoring_update_top(mmcf->shpool, sh->urls,
|
||||
ngx_min(mmcf->max_top_urls,
|
||||
NGX_HTTP_MONITORING_TOP_URLS_MAX),
|
||||
&uri, latency, bytes,
|
||||
status >= 400);
|
||||
|
||||
if (r->headers_in.user_agent) {
|
||||
ua = r->headers_in.user_agent->value;
|
||||
ngx_http_monitoring_update_top(mmcf->shpool, sh->user_agents,
|
||||
NGX_HTTP_MONITORING_TOP_UA_MAX,
|
||||
&ua, latency, bytes,
|
||||
status >= 400);
|
||||
}
|
||||
|
||||
ngx_http_monitoring_account_upstream(r, latency);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ngx_http_monitoring_account_upstream(ngx_http_request_t *r, ngx_msec_t latency)
|
||||
{
|
||||
ngx_uint_t i;
|
||||
ngx_http_upstream_state_t *state;
|
||||
ngx_http_monitoring_shctx_t *sh;
|
||||
ngx_http_monitoring_main_conf_t *mmcf;
|
||||
ngx_str_t peer;
|
||||
ngx_msec_t response_time;
|
||||
|
||||
if (r->upstream_states == NULL || r->upstream_states->nelts == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
sh = ngx_http_monitoring_get_shctx(r);
|
||||
mmcf = ngx_http_monitoring_get_main_conf(r);
|
||||
if (sh == NULL || mmcf == NULL || mmcf->shpool == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
state = r->upstream_states->elts;
|
||||
|
||||
for (i = 0; i < r->upstream_states->nelts; i++) {
|
||||
if (state[i].peer && state[i].peer->len) {
|
||||
peer = *state[i].peer;
|
||||
} else {
|
||||
ngx_str_set(&peer, "unknown");
|
||||
}
|
||||
|
||||
response_time = state[i].response_time;
|
||||
if (response_time == (ngx_msec_t) -1) {
|
||||
response_time = latency;
|
||||
}
|
||||
|
||||
ngx_http_monitoring_update_upstream_slot(mmcf->shpool, sh->upstreams,
|
||||
NGX_HTTP_MONITORING_UPSTREAMS_MAX, &peer, response_time,
|
||||
state[i].status);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_http_monitoring_update_worker(ngx_http_monitoring_shctx_t *sh,
|
||||
ngx_msec_t latency, off_t bytes, ngx_uint_t is_error)
|
||||
{
|
||||
ngx_uint_t slot;
|
||||
ngx_http_monitoring_worker_metric_t *w;
|
||||
|
||||
slot = ngx_http_monitoring_worker_slot();
|
||||
w = &sh->workers[slot];
|
||||
|
||||
w->pid = ngx_pid;
|
||||
w->last_seen = ngx_time();
|
||||
ngx_atomic_fetch_add(&w->requests, 1);
|
||||
ngx_atomic_fetch_add(&w->bytes, (ngx_atomic_int_t) bytes);
|
||||
|
||||
if (latency > 30000) {
|
||||
ngx_atomic_fetch_add(&w->errors, 1);
|
||||
}
|
||||
|
||||
if (is_error) {
|
||||
ngx_atomic_fetch_add(&w->errors, 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_http_monitoring_update_status(ngx_http_monitoring_shctx_t *sh,
|
||||
ngx_uint_t status)
|
||||
{
|
||||
if (status < 200) {
|
||||
ngx_atomic_fetch_add(&sh->requests.status_1xx, 1);
|
||||
} else if (status < 300) {
|
||||
ngx_atomic_fetch_add(&sh->requests.status_2xx, 1);
|
||||
} else if (status < 400) {
|
||||
ngx_atomic_fetch_add(&sh->requests.status_3xx, 1);
|
||||
} else if (status < 500) {
|
||||
ngx_atomic_fetch_add(&sh->requests.status_4xx, 1);
|
||||
} else {
|
||||
ngx_atomic_fetch_add(&sh->requests.status_5xx, 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_http_monitoring_update_upstream_slot(ngx_slab_pool_t *shpool,
|
||||
ngx_http_monitoring_upstream_entry_t *entries, ngx_uint_t capacity,
|
||||
ngx_str_t *peer, ngx_msec_t latency, ngx_uint_t status)
|
||||
{
|
||||
ngx_uint_t i, victim, empty, failure;
|
||||
ngx_atomic_uint_t min_hits, hits;
|
||||
uint32_t hash;
|
||||
size_t len;
|
||||
ngx_http_monitoring_upstream_entry_t *e;
|
||||
|
||||
if (peer->len == 0 || capacity == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
hash = ngx_http_monitoring_hash(peer);
|
||||
empty = capacity;
|
||||
victim = 0;
|
||||
min_hits = (ngx_atomic_uint_t) -1;
|
||||
|
||||
for (i = 0; i < capacity; i++) {
|
||||
e = &entries[i];
|
||||
|
||||
if (e->requests == 0 && empty == capacity) {
|
||||
empty = i;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (e->hash == hash
|
||||
&& ngx_strncmp(e->peer, peer->data,
|
||||
ngx_min(peer->len, NGX_HTTP_MONITORING_KEY_LEN - 1))
|
||||
== 0
|
||||
&& e->peer[ngx_min(peer->len, NGX_HTTP_MONITORING_KEY_LEN - 1)]
|
||||
== '\0')
|
||||
{
|
||||
failure = (status == 0 || status >= 500);
|
||||
ngx_atomic_fetch_add(&e->requests, 1);
|
||||
ngx_atomic_fetch_add(&e->latency_ms_total, latency);
|
||||
e->last_seen = ngx_time();
|
||||
if (failure) {
|
||||
ngx_atomic_fetch_add(&e->failures, 1);
|
||||
}
|
||||
if (status >= 400 && status < 500) {
|
||||
ngx_atomic_fetch_add(&e->status_4xx, 1);
|
||||
}
|
||||
if (status >= 500) {
|
||||
ngx_atomic_fetch_add(&e->status_5xx, 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
hits = e->requests;
|
||||
if (hits < min_hits) {
|
||||
min_hits = hits;
|
||||
victim = i;
|
||||
}
|
||||
}
|
||||
|
||||
ngx_shmtx_lock(&shpool->mutex);
|
||||
|
||||
i = (empty != capacity) ? empty : victim;
|
||||
e = &entries[i];
|
||||
ngx_memzero(e, sizeof(ngx_http_monitoring_upstream_entry_t));
|
||||
|
||||
len = ngx_min(peer->len, NGX_HTTP_MONITORING_KEY_LEN - 1);
|
||||
ngx_memcpy(e->peer, peer->data, len);
|
||||
e->peer[len] = '\0';
|
||||
e->hash = hash;
|
||||
e->last_seen = ngx_time();
|
||||
|
||||
ngx_shmtx_unlock(&shpool->mutex);
|
||||
|
||||
failure = (status == 0 || status >= 500);
|
||||
ngx_atomic_fetch_add(&e->requests, 1);
|
||||
ngx_atomic_fetch_add(&e->latency_ms_total, latency);
|
||||
if (failure) {
|
||||
ngx_atomic_fetch_add(&e->failures, 1);
|
||||
}
|
||||
if (status >= 400 && status < 500) {
|
||||
ngx_atomic_fetch_add(&e->status_4xx, 1);
|
||||
}
|
||||
if (status >= 500) {
|
||||
ngx_atomic_fetch_add(&e->status_5xx, 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ngx_http_monitoring_update_top(ngx_slab_pool_t *shpool,
|
||||
ngx_http_monitoring_top_entry_t *entries, ngx_uint_t capacity,
|
||||
ngx_str_t *key, ngx_msec_t latency, off_t bytes, ngx_uint_t is_error)
|
||||
{
|
||||
ngx_uint_t i, empty, victim;
|
||||
ngx_atomic_uint_t min_hits, hits;
|
||||
uint32_t hash;
|
||||
size_t len;
|
||||
ngx_http_monitoring_top_entry_t *e;
|
||||
|
||||
if (shpool == NULL || key->len == 0 || capacity == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
hash = ngx_http_monitoring_hash(key);
|
||||
empty = capacity;
|
||||
victim = 0;
|
||||
min_hits = (ngx_atomic_uint_t) -1;
|
||||
len = ngx_min(key->len, NGX_HTTP_MONITORING_KEY_LEN - 1);
|
||||
|
||||
for (i = 0; i < capacity; i++) {
|
||||
e = &entries[i];
|
||||
|
||||
if (e->hits == 0 && empty == capacity) {
|
||||
empty = i;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (e->hash == hash
|
||||
&& ngx_strncmp(e->key, key->data, len) == 0
|
||||
&& e->key[len] == '\0')
|
||||
{
|
||||
ngx_atomic_fetch_add(&e->hits, 1);
|
||||
ngx_atomic_fetch_add(&e->bytes, (ngx_atomic_int_t) bytes);
|
||||
ngx_atomic_fetch_add(&e->latency_ms_total, latency);
|
||||
e->last_seen = ngx_time();
|
||||
if (is_error) {
|
||||
ngx_atomic_fetch_add(&e->errors, 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
hits = e->hits;
|
||||
if (hits < min_hits) {
|
||||
min_hits = hits;
|
||||
victim = i;
|
||||
}
|
||||
}
|
||||
|
||||
ngx_shmtx_lock(&shpool->mutex);
|
||||
|
||||
i = (empty != capacity) ? empty : victim;
|
||||
e = &entries[i];
|
||||
ngx_memzero(e, sizeof(ngx_http_monitoring_top_entry_t));
|
||||
|
||||
ngx_memcpy(e->key, key->data, len);
|
||||
e->key[len] = '\0';
|
||||
e->hash = hash;
|
||||
e->last_seen = ngx_time();
|
||||
|
||||
ngx_shmtx_unlock(&shpool->mutex);
|
||||
|
||||
ngx_atomic_fetch_add(&e->hits, 1);
|
||||
ngx_atomic_fetch_add(&e->bytes, (ngx_atomic_int_t) bytes);
|
||||
ngx_atomic_fetch_add(&e->latency_ms_total, latency);
|
||||
if (is_error) {
|
||||
ngx_atomic_fetch_add(&e->errors, 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ngx_uint_t
|
||||
ngx_http_monitoring_method_index(ngx_uint_t method)
|
||||
{
|
||||
if (method & NGX_HTTP_GET) {
|
||||
return NGX_HTTP_MONITORING_METHOD_GET;
|
||||
}
|
||||
if (method & NGX_HTTP_POST) {
|
||||
return NGX_HTTP_MONITORING_METHOD_POST;
|
||||
}
|
||||
if (method & NGX_HTTP_PUT) {
|
||||
return NGX_HTTP_MONITORING_METHOD_PUT;
|
||||
}
|
||||
if (method & NGX_HTTP_DELETE) {
|
||||
return NGX_HTTP_MONITORING_METHOD_DELETE;
|
||||
}
|
||||
if (method & NGX_HTTP_HEAD) {
|
||||
return NGX_HTTP_MONITORING_METHOD_HEAD;
|
||||
}
|
||||
if (method & NGX_HTTP_OPTIONS) {
|
||||
return NGX_HTTP_MONITORING_METHOD_OPTIONS;
|
||||
}
|
||||
#ifdef NGX_HTTP_PATCH
|
||||
if (method & NGX_HTTP_PATCH) {
|
||||
return NGX_HTTP_MONITORING_METHOD_PATCH;
|
||||
}
|
||||
#endif
|
||||
return NGX_HTTP_MONITORING_METHOD_OTHER;
|
||||
}
|
||||
|
||||
|
||||
ngx_uint_t
|
||||
ngx_http_monitoring_latency_bucket(ngx_msec_t ms)
|
||||
{
|
||||
ngx_uint_t i;
|
||||
|
||||
for (i = 0; i < NGX_HTTP_MONITORING_LATENCY_BUCKETS - 1; i++) {
|
||||
if (ms <= ngx_http_monitoring_latency_bounds[i]) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return NGX_HTTP_MONITORING_LATENCY_BUCKETS - 1;
|
||||
}
|
||||
|
||||
|
||||
ngx_uint_t
|
||||
ngx_http_monitoring_size_bucket(off_t bytes)
|
||||
{
|
||||
ngx_uint_t i;
|
||||
|
||||
for (i = 0; i < NGX_HTTP_MONITORING_SIZE_BUCKETS - 1; i++) {
|
||||
if (bytes <= ngx_http_monitoring_size_bounds[i]) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return NGX_HTTP_MONITORING_SIZE_BUCKETS - 1;
|
||||
}
|
||||
|
||||
|
||||
double
|
||||
ngx_http_monitoring_percentile(ngx_http_monitoring_shctx_t *sh,
|
||||
double percentile)
|
||||
{
|
||||
ngx_uint_t i;
|
||||
ngx_atomic_uint_t total, count, target;
|
||||
|
||||
total = 0;
|
||||
for (i = 0; i < NGX_HTTP_MONITORING_LATENCY_BUCKETS; i++) {
|
||||
total += sh->requests.latency_bucket[i];
|
||||
}
|
||||
|
||||
if (total == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
target = (ngx_atomic_uint_t) ((double) total * percentile / 100.0);
|
||||
if (target == 0) {
|
||||
target = 1;
|
||||
}
|
||||
|
||||
count = 0;
|
||||
for (i = 0; i < NGX_HTTP_MONITORING_LATENCY_BUCKETS - 1; i++) {
|
||||
count += sh->requests.latency_bucket[i];
|
||||
if (count >= target) {
|
||||
return (double) ngx_http_monitoring_latency_bounds[i];
|
||||
}
|
||||
}
|
||||
|
||||
return 30000.0;
|
||||
}
|
||||
|
||||
|
||||
uint32_t
|
||||
ngx_http_monitoring_hash(ngx_str_t *value)
|
||||
{
|
||||
return ngx_crc32_short(value->data, value->len);
|
||||
}
|
||||
|
||||
|
||||
static ngx_uint_t
|
||||
ngx_http_monitoring_worker_slot(void)
|
||||
{
|
||||
if (ngx_process_slot < 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ((ngx_uint_t) ngx_process_slot) % NGX_HTTP_MONITORING_WORKERS_MAX;
|
||||
}
|
||||
296
src/ngx_http_monitoring_sse.c
Normal file
296
src/ngx_http_monitoring_sse.c
Normal file
@@ -0,0 +1,296 @@
|
||||
#include "ngx_http_monitoring.h"
|
||||
|
||||
typedef struct {
|
||||
ngx_http_request_t *r;
|
||||
ngx_event_t event;
|
||||
ngx_msec_t interval;
|
||||
ngx_uint_t closed;
|
||||
ngx_uint_t sequence;
|
||||
ngx_uint_t slot;
|
||||
u_char data[2][16384];
|
||||
ngx_buf_t bufs[2];
|
||||
ngx_chain_t chains[2];
|
||||
} ngx_http_monitoring_sse_client_t;
|
||||
|
||||
static void ngx_http_monitoring_sse_timer(ngx_event_t *ev);
|
||||
static void ngx_http_monitoring_sse_cleanup(void *data);
|
||||
static ngx_int_t ngx_http_monitoring_sse_write(
|
||||
ngx_http_monitoring_sse_client_t *client);
|
||||
static u_char *ngx_http_monitoring_sse_payload(u_char *p, u_char *last,
|
||||
ngx_http_monitoring_sse_client_t *client,
|
||||
ngx_http_monitoring_shctx_t *sh);
|
||||
static ngx_atomic_uint_t ngx_http_monitoring_sse_sum_net(
|
||||
ngx_http_monitoring_shctx_t *sh, ngx_uint_t tx);
|
||||
static ngx_atomic_uint_t ngx_http_monitoring_sse_sum_disk(
|
||||
ngx_http_monitoring_shctx_t *sh, ngx_uint_t write);
|
||||
static void ngx_http_monitoring_sse_add_header(ngx_http_request_t *r,
|
||||
const char *key, const char *value);
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_http_monitoring_send_sse(ngx_http_request_t *r)
|
||||
{
|
||||
ngx_int_t rc;
|
||||
ngx_pool_cleanup_t *cln;
|
||||
ngx_http_monitoring_sse_client_t *client;
|
||||
ngx_http_monitoring_main_conf_t *mmcf;
|
||||
ngx_http_monitoring_shctx_t *sh;
|
||||
|
||||
mmcf = ngx_http_monitoring_get_main_conf(r);
|
||||
sh = ngx_http_monitoring_get_shctx(r);
|
||||
if (mmcf == NULL || sh == NULL) {
|
||||
return NGX_HTTP_SERVICE_UNAVAILABLE;
|
||||
}
|
||||
|
||||
ngx_http_discard_request_body(r);
|
||||
|
||||
r->headers_out.status = NGX_HTTP_OK;
|
||||
r->headers_out.content_length_n = -1;
|
||||
ngx_str_set(&r->headers_out.content_type, "text/event-stream");
|
||||
ngx_http_monitoring_sse_add_header(r, "Cache-Control", "no-store");
|
||||
ngx_http_monitoring_sse_add_header(r, "X-Accel-Buffering", "no");
|
||||
ngx_http_monitoring_sse_add_header(r, "Connection", "keep-alive");
|
||||
|
||||
rc = ngx_http_send_header(r);
|
||||
if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
client = ngx_pcalloc(r->pool, sizeof(ngx_http_monitoring_sse_client_t));
|
||||
if (client == NULL) {
|
||||
return NGX_HTTP_INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
|
||||
client->r = r;
|
||||
client->interval = ngx_max(mmcf->refresh_interval, 1000);
|
||||
client->event.handler = ngx_http_monitoring_sse_timer;
|
||||
client->event.data = client;
|
||||
client->event.log = r->connection->log;
|
||||
|
||||
cln = ngx_pool_cleanup_add(r->pool, 0);
|
||||
if (cln == NULL) {
|
||||
return NGX_HTTP_INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
cln->handler = ngx_http_monitoring_sse_cleanup;
|
||||
cln->data = client;
|
||||
|
||||
ngx_atomic_fetch_add(&sh->connections.sse_clients, 1);
|
||||
|
||||
r->main->count++;
|
||||
|
||||
if (ngx_http_monitoring_sse_write(client) == NGX_ERROR) {
|
||||
ngx_http_finalize_request(r, NGX_ERROR);
|
||||
return NGX_DONE;
|
||||
}
|
||||
|
||||
ngx_add_timer(&client->event, client->interval);
|
||||
|
||||
return NGX_DONE;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_http_monitoring_sse_timer(ngx_event_t *ev)
|
||||
{
|
||||
ngx_http_monitoring_sse_client_t *client;
|
||||
ngx_http_request_t *r;
|
||||
|
||||
client = ev->data;
|
||||
r = client->r;
|
||||
|
||||
if (client->closed || r->connection->error) {
|
||||
ngx_http_finalize_request(r, NGX_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
if (r->connection->buffered) {
|
||||
ngx_add_timer(ev, client->interval);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ngx_http_monitoring_sse_write(client) == NGX_ERROR) {
|
||||
ngx_http_finalize_request(r, NGX_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
ngx_add_timer(ev, client->interval);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_http_monitoring_sse_cleanup(void *data)
|
||||
{
|
||||
ngx_http_monitoring_sse_client_t *client = data;
|
||||
ngx_http_monitoring_shctx_t *sh;
|
||||
|
||||
if (client->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
client->closed = 1;
|
||||
|
||||
if (client->event.timer_set) {
|
||||
ngx_del_timer(&client->event);
|
||||
}
|
||||
|
||||
sh = ngx_http_monitoring_get_shctx(client->r);
|
||||
if (sh != NULL && sh->connections.sse_clients > 0) {
|
||||
ngx_atomic_fetch_add(&sh->connections.sse_clients, -1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_http_monitoring_sse_write(ngx_http_monitoring_sse_client_t *client)
|
||||
{
|
||||
ngx_http_request_t *r;
|
||||
ngx_http_monitoring_shctx_t *sh;
|
||||
ngx_buf_t *b;
|
||||
ngx_chain_t *out;
|
||||
u_char *p, *last;
|
||||
|
||||
r = client->r;
|
||||
sh = ngx_http_monitoring_get_shctx(r);
|
||||
if (sh == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
client->slot ^= 1;
|
||||
p = client->data[client->slot];
|
||||
last = p + sizeof(client->data[client->slot]);
|
||||
|
||||
p = ngx_slprintf(p, last, "retry: 3000\n: heartbeat %ui\n",
|
||||
client->sequence);
|
||||
p = ngx_http_monitoring_sse_payload(p, last, client, sh);
|
||||
if (p == last) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
b = &client->bufs[client->slot];
|
||||
ngx_memzero(b, sizeof(ngx_buf_t));
|
||||
b->pos = client->data[client->slot];
|
||||
b->last = p;
|
||||
b->memory = 1;
|
||||
b->flush = 1;
|
||||
|
||||
out = &client->chains[client->slot];
|
||||
out->buf = b;
|
||||
out->next = NULL;
|
||||
|
||||
if (ngx_http_output_filter(r, out) == NGX_ERROR) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_atomic_fetch_add(&sh->connections.sse_events, 1);
|
||||
client->sequence++;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static u_char *
|
||||
ngx_http_monitoring_sse_payload(u_char *p, u_char *last,
|
||||
ngx_http_monitoring_sse_client_t *client,
|
||||
ngx_http_monitoring_shctx_t *sh)
|
||||
{
|
||||
ngx_time_t *tp;
|
||||
ngx_atomic_uint_t rx, tx, dr, dw;
|
||||
double mem_pct;
|
||||
|
||||
tp = ngx_timeofday();
|
||||
rx = ngx_http_monitoring_sse_sum_net(sh, 0);
|
||||
tx = ngx_http_monitoring_sse_sum_net(sh, 1);
|
||||
dr = ngx_http_monitoring_sse_sum_disk(sh, 0);
|
||||
dw = ngx_http_monitoring_sse_sum_disk(sh, 1);
|
||||
|
||||
mem_pct = 0;
|
||||
if (sh->system.mem_total > 0) {
|
||||
mem_pct = ((double) (sh->system.mem_total - sh->system.mem_available)
|
||||
* 100.0) / (double) sh->system.mem_total;
|
||||
}
|
||||
|
||||
return ngx_slprintf(p, last,
|
||||
"id: %ui\n"
|
||||
"event: metrics\n"
|
||||
"data: {\"version\":%d,\"timestamp\":%T,\"sequence\":%ui,"
|
||||
"\"system\":{\"cpu\":{\"usage\":%.3f,\"cores\":%uA},"
|
||||
"\"memory\":{\"used_pct\":%.3f,\"total\":%uA,"
|
||||
"\"available\":%uA}},"
|
||||
"\"requests\":{\"total\":%uA,\"requests_per_sec\":%.3f,"
|
||||
"\"responses_per_sec\":%.3f,\"latency\":{\"p95\":%.3f,"
|
||||
"\"p99\":%.3f},\"status\":{\"4xx\":%uA,\"5xx\":%uA}},"
|
||||
"\"connections\":{\"active\":%uA,\"reading\":%uA,"
|
||||
"\"writing\":%uA,\"waiting\":%uA,\"sse_clients\":%uA,"
|
||||
"\"keepalive_requests\":%uA},"
|
||||
"\"network\":{\"rx_bytes\":%uA,\"tx_bytes\":%uA},"
|
||||
"\"disk\":{\"read_bytes\":%uA,\"write_bytes\":%uA}}\n\n",
|
||||
client->sequence, NGX_HTTP_MONITORING_VERSION, tp->sec,
|
||||
client->sequence,
|
||||
(double) sh->system.usage_milli / 1000.0, sh->system.cores,
|
||||
mem_pct, sh->system.mem_total, sh->system.mem_available,
|
||||
sh->requests.total, (double) sh->ewma_rps_milli / 1000.0,
|
||||
(double) sh->ewma_resp_milli / 1000.0,
|
||||
ngx_http_monitoring_percentile(sh, 95.0),
|
||||
ngx_http_monitoring_percentile(sh, 99.0),
|
||||
sh->requests.status_4xx, sh->requests.status_5xx,
|
||||
sh->connections.active, sh->connections.reading,
|
||||
sh->connections.writing, sh->connections.waiting,
|
||||
sh->connections.sse_clients, sh->connections.keepalive_requests,
|
||||
rx, tx, dr, dw);
|
||||
}
|
||||
|
||||
|
||||
static ngx_atomic_uint_t
|
||||
ngx_http_monitoring_sse_sum_net(ngx_http_monitoring_shctx_t *sh, ngx_uint_t tx)
|
||||
{
|
||||
ngx_uint_t i, count;
|
||||
ngx_atomic_uint_t total;
|
||||
|
||||
total = 0;
|
||||
count = ngx_min((ngx_uint_t) sh->iface_count,
|
||||
NGX_HTTP_MONITORING_IFACES_MAX);
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
total += tx ? sh->ifaces[i].tx_bytes : sh->ifaces[i].rx_bytes;
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
|
||||
static ngx_atomic_uint_t
|
||||
ngx_http_monitoring_sse_sum_disk(ngx_http_monitoring_shctx_t *sh,
|
||||
ngx_uint_t write)
|
||||
{
|
||||
ngx_uint_t i, count;
|
||||
ngx_atomic_uint_t total;
|
||||
|
||||
total = 0;
|
||||
count = ngx_min((ngx_uint_t) sh->disk_count,
|
||||
NGX_HTTP_MONITORING_DISKS_MAX);
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
total += write ? sh->disks[i].write_bytes : sh->disks[i].read_bytes;
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_http_monitoring_sse_add_header(ngx_http_request_t *r, const char *key,
|
||||
const char *value)
|
||||
{
|
||||
ngx_table_elt_t *h;
|
||||
|
||||
h = ngx_list_push(&r->headers_out.headers);
|
||||
if (h == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
h->hash = 1;
|
||||
h->key.len = ngx_strlen(key);
|
||||
h->key.data = (u_char *) key;
|
||||
h->value.len = ngx_strlen(value);
|
||||
h->value.data = (u_char *) value;
|
||||
}
|
||||
Reference in New Issue
Block a user