├── README.md ├── build.sh ├── config ├── demo_ngx_stats.html ├── lib ├── admin.h └── init_shm.h └── ngx_http_stats.c /README.md: -------------------------------------------------------------------------------- 1 | # ngx_stats 2 | 3 | [Demo](https://bnchdan.github.io/ngx_stats/demo_ngx_stats.html) 4 | 5 | ![image](https://user-images.githubusercontent.com/30780133/208644100-b586a7e4-47bc-4c89-87e3-f98124267789.png) 6 | 7 | 8 | ### Installation 9 | 10 | 1. Add load_module ngx_http_stats_module.so; to /etc/nginx/nginx.conf 11 | 2. Restart /etc/init.d/nginx restart 12 | 3. In browser go to [(ip/hostname)/ngx_stats](https://bnchdan.github.io/ngx_stats/demo_ngx_stats.html) 13 | ### Build from source 14 | 15 | 1. Download nginx corresponding to your current version (Check with `nginx -v`) 16 | ```bash 17 | wget https://nginx.org/download/nginx-1.16.1.tar.gz 18 | tar -xzf nginx-1.16.1.tar.gz 19 | export NGINX_PATH=$(pwd)/nginx-1.16.1/ 20 | ``` 21 | 2. Compile the module 22 | ```bash 23 | git clone https://github.com/bnchdan/ngx_stats 24 | cd ngx_stats 25 | ./build.sh 26 | ``` 27 | 3. The dynamic module can be found at `${NGINX_PATH}/objs/ngx_http_stats_module.so` 28 | 29 | ### Simple configuration 30 | ``` 31 | load_module {your_ngx_path}/objs/ngx_http_stats_module.so; 32 | ... 33 | http { 34 | ... 35 | server{ 36 | ... 37 | ngx_stats on; 38 | ngx_stats_admin_ip "127.0.0.1"; 39 | ... 40 | } 41 | ... 42 | } 43 | ... 44 | 45 | ``` 46 | 47 | 48 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -z ${NGINX_PATH+x} ]; then 4 | echo "Please set the NGINX_PATH variable"; 5 | exit 6 | fi 7 | 8 | MODULE_PATH=$(pwd)/ 9 | CONFIG_ARGS=$(nginx -V 2>&1 | tail -n 1 | cut -c 21- | sed 's/--add-dynamic-module=.*//g') 10 | CONFIG_ARGS="${CONFIG_ARGS} --add-dynamic-module=${MODULE_PATH}" 11 | echo $CONFIG_ARGS 12 | 13 | ( 14 | cd ${NGINX_PATH} || exit 15 | bash -c "./configure ${CONFIG_ARGS}" 16 | make modules -j "$(nproc)" 17 | ) || exit 18 | 19 | echo "Load the dynamic module ${NGINX_PATH}/objs/ngx_http_stats_module.so and restart nginx to install" 20 | 21 | -------------------------------------------------------------------------------- /config: -------------------------------------------------------------------------------- 1 | 2 | 3 | EXECUTE_SRCS="\ 4 | $ngx_addon_dir/ngx_http_stats.c\ 5 | " 6 | 7 | 8 | if test -n "$ngx_module_link"; then 9 | ngx_module_type=HTTP 10 | ngx_module_name=ngx_http_stats_module 11 | ngx_module_srcs="$ngx_addon_dir/ngx_http_stats.c" 12 | . auto/module 13 | else 14 | HTTP_MODULES="$HTTP_MODULES ngx_http_stats_module" 15 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS $EXECUTE_SRCS" 16 | NGX_ADDON_DEPS="$NGX_ADDON_DEPS $EXECUTE_DEPS" 17 | ngx_module_incs="$ngx_waf_incs" 18 | fi 19 | 20 | -------------------------------------------------------------------------------- /demo_ngx_stats.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | ngx-stats 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 31 | 32 | 33 | 57 |
58 |
59 |
60 |
61 | 62 |
63 |
64 | 65 | 66 | 158 | 159 | 160 | -------------------------------------------------------------------------------- /lib/admin.h: -------------------------------------------------------------------------------- 1 | #define ADMIN_PAGE "/ngx-stats" 2 | #define ADMIN_PAGE_LEN 9 3 | #define ADMIN_HTML " ngx-stats
" 4 | #define get_route_p_s "/ngx-stats?r_p_s=1" 5 | 6 | int is_admin(ngx_http_request_t* r, char* admin_ip){ 7 | 8 | if ( (r->uri.len - 1 )< ADMIN_PAGE_LEN ) 9 | return 0; 10 | 11 | if ( strncmp ( admin_ip, (char *)r->connection->addr_text.data, r->connection->addr_text.len ) != 0) 12 | return 0; 13 | 14 | if (strncmp ((char*)r->uri.data, get_route_p_s, 18) == 0) 15 | return 2; 16 | 17 | if (r->uri.len - 2 == ADMIN_PAGE_LEN && r->uri.data[ADMIN_PAGE_LEN+1] != '/' ) 18 | return 0; 19 | 20 | if ( (r->uri.len - 2) > ADMIN_PAGE_LEN && r->uri.data[ADMIN_PAGE_LEN+1] != '?') 21 | return 0; 22 | 23 | if (strncmp ((char*)r->uri.data, ADMIN_PAGE, ADMIN_PAGE_LEN + 1) != 0) 24 | return 0; 25 | 26 | return 1; 27 | } 28 | 29 | -------------------------------------------------------------------------------- /lib/init_shm.h: -------------------------------------------------------------------------------- 1 | typedef struct { 2 | unsigned int time; 3 | long int count; 4 | } req; 5 | 6 | typedef struct{ 7 | req r_per_s[9]; 8 | } ngx_stats_shm_count_t; 9 | 10 | ngx_slab_pool_t *shpool; 11 | 12 | static ngx_int_t ngx_stats_init_shm_zone(ngx_shm_zone_t *shm_zone, void *data); 13 | void init_r_p_s(req *data, int size); 14 | 15 | static ngx_int_t ngx_stats_init_shm_zone(ngx_shm_zone_t *shm_zone, void *data){ 16 | ngx_stats_shm_count_t *shm_data; 17 | if(data){ 18 | shm_zone->data = data; 19 | return NGX_OK; 20 | } 21 | shpool = (ngx_slab_pool_t *)shm_zone->shm.addr; 22 | shm_data = ngx_slab_alloc(shpool, sizeof *shm_data ); 23 | init_r_p_s(shm_data->r_per_s , 9); 24 | shm_zone->data = shm_data; 25 | 26 | return NGX_OK; 27 | } 28 | 29 | void init_r_p_s(req *data, int size){ 30 | for (int i=0; i<= size; i++){ 31 | data[i].count = 0; 32 | data[i].time = 0 ; 33 | } 34 | } 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /ngx_http_stats.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "lib/admin.h" 8 | #include "lib/init_shm.h" 9 | 10 | typedef struct { 11 | ngx_shm_zone_t *shm_zone; 12 | ngx_flag_t enabled; 13 | ngx_str_t IP; 14 | } ngx_http_stats_loc_conf_t; 15 | 16 | 17 | static ngx_int_t ngx_http_stats(ngx_conf_t *cf); 18 | static void *ngx_http_stats_create_loc_conf(ngx_conf_t *cf); 19 | static char *ngx_http_stats_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child); 20 | static ngx_int_t ngx_http_stats_handler(ngx_http_request_t *r); 21 | 22 | static ngx_int_t server_html_stats(ngx_http_request_t *r); 23 | static ngx_int_t server_html_stats_r_p_s(ngx_http_request_t *r); 24 | 25 | 26 | 27 | 28 | static ngx_command_t ngx_http_stats_commands[] = { 29 | { 30 | ngx_string("ngx_stats"), 31 | NGX_HTTP_LOC_CONF | NGX_HTTP_LIF_CONF | NGX_HTTP_SIF_CONF | NGX_HTTP_SRV_CONF | NGX_CONF_FLAG, 32 | ngx_conf_set_flag_slot, 33 | NGX_HTTP_LOC_CONF_OFFSET, 34 | offsetof(ngx_http_stats_loc_conf_t, enabled), 35 | NULL 36 | }, 37 | { 38 | ngx_string("ngx_stats_admin_ip"), 39 | NGX_HTTP_LOC_CONF | NGX_HTTP_SRV_CONF | NGX_CONF_TAKE1, 40 | ngx_conf_set_str_slot, 41 | NGX_HTTP_LOC_CONF_OFFSET, 42 | offsetof(ngx_http_stats_loc_conf_t, IP), 43 | NULL 44 | }, 45 | ngx_null_command 46 | }; 47 | 48 | 49 | static ngx_http_module_t ngx_http_stats_module_ctx = { 50 | NULL, 51 | ngx_http_stats, 52 | NULL, 53 | NULL, 54 | NULL, 55 | NULL, 56 | ngx_http_stats_create_loc_conf, 57 | ngx_http_stats_merge_loc_conf 58 | }; 59 | 60 | ngx_module_t ngx_http_stats_module = { 61 | NGX_MODULE_V1, 62 | &ngx_http_stats_module_ctx, 63 | ngx_http_stats_commands, 64 | NGX_HTTP_MODULE, 65 | NULL, 66 | NULL, 67 | NULL, 68 | NULL, 69 | NULL, 70 | NULL, 71 | NULL, 72 | NGX_MODULE_V1_PADDING 73 | }; 74 | 75 | 76 | static void *ngx_http_stats_create_loc_conf(ngx_conf_t *cf) { 77 | ngx_http_stats_loc_conf_t *conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_stats_loc_conf_t)); 78 | if (conf == NULL) { 79 | return NGX_CONF_ERROR; 80 | } 81 | conf->IP = (ngx_str_t) {0, NULL}; 82 | conf->enabled = NGX_CONF_UNSET; 83 | 84 | return conf; 85 | } 86 | 87 | 88 | static char *ngx_http_stats_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) { 89 | ngx_http_stats_loc_conf_t *prev = parent; 90 | ngx_http_stats_loc_conf_t *conf = child; 91 | 92 | ngx_conf_merge_value(conf->enabled, prev->enabled, 0) 93 | 94 | ngx_conf_merge_str_value(conf->IP, prev->IP, NULL) 95 | ngx_shm_zone_t *shm_zone; 96 | ngx_str_t *shm_name; 97 | shm_name = ngx_palloc(cf->pool, sizeof *shm_name); 98 | shm_name->len = sizeof("shared_memory") - 1; 99 | shm_name->data = (unsigned char *) "shared_memory"; 100 | shm_zone = ngx_shared_memory_add(cf, shm_name, 8*ngx_pagesize, &ngx_http_stats_module); 101 | if(shm_zone == NULL){ 102 | return NGX_CONF_ERROR; 103 | } 104 | 105 | shm_zone->init = ngx_stats_init_shm_zone; 106 | conf->shm_zone = shm_zone; 107 | 108 | ngx_conf_merge_ptr_value(conf->shm_zone, prev->shm_zone, NULL); 109 | 110 | return NGX_CONF_OK; 111 | } 112 | 113 | 114 | static ngx_int_t ngx_http_stats(ngx_conf_t *cf) { 115 | ngx_http_handler_pt *h; 116 | ngx_http_core_main_conf_t *main_conf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); 117 | 118 | h = ngx_array_push(&main_conf->phases[NGX_HTTP_PRECONTENT_PHASE].handlers); 119 | if (h == NULL) { 120 | ngx_log_error(NGX_LOG_ERR, cf->log, 0, "null"); 121 | return NGX_ERROR; 122 | } 123 | 124 | *h = ngx_http_stats_handler; 125 | 126 | return NGX_OK; 127 | } 128 | 129 | 130 | static ngx_int_t ngx_http_stats_handler(ngx_http_request_t *r) { 131 | 132 | ngx_http_stats_loc_conf_t *conf = ngx_http_get_module_loc_conf(r, ngx_http_stats_module); 133 | 134 | if (!conf->enabled) { 135 | return NGX_DECLINED; 136 | } 137 | 138 | unsigned char key_s; 139 | unsigned int mTime; 140 | ngx_http_stats_loc_conf_t *lccf; 141 | ngx_shm_zone_t *shm_zone; 142 | 143 | lccf = ngx_http_get_module_loc_conf(r, ngx_http_stats_module); 144 | 145 | if(lccf->shm_zone == NULL){ 146 | return NGX_DECLINED; 147 | } 148 | shm_zone = lccf->shm_zone; 149 | mTime = time(NULL); 150 | key_s = mTime % 10; 151 | 152 | ngx_shmtx_lock(&shpool->mutex); 153 | //count req/s 154 | if ((mTime - 9 )> ((ngx_stats_shm_count_t *)shm_zone->data)->r_per_s[key_s].time ){ 155 | ((ngx_stats_shm_count_t *)shm_zone->data)->r_per_s[key_s].time = mTime; 156 | ((ngx_stats_shm_count_t *)shm_zone->data)->r_per_s[key_s].count = 0; 157 | } 158 | ((ngx_stats_shm_count_t *)shm_zone->data)->r_per_s[key_s].count ++; 159 | ngx_shmtx_unlock(&shpool->mutex); 160 | 161 | //check if is admin 162 | int isAdm = is_admin(r, (char *)conf->IP.data); 163 | switch ( isAdm ) { 164 | case 1: 165 | return server_html_stats(r); 166 | 167 | case 2: 168 | return server_html_stats_r_p_s( r); 169 | } 170 | 171 | return NGX_DECLINED; 172 | 173 | } 174 | 175 | 176 | 177 | static ngx_int_t server_html_stats(ngx_http_request_t *r) { 178 | ngx_http_stats_loc_conf_t *lccf; 179 | ngx_shm_zone_t *shm_zone; 180 | unsigned char key; 181 | unsigned int mTime; 182 | ngx_buf_t* b; 183 | ngx_chain_t out; 184 | char html[8192] =" "; 185 | 186 | lccf = ngx_http_get_module_loc_conf(r, ngx_http_stats_module); 187 | if(lccf->shm_zone == NULL){ 188 | return NGX_DECLINED; 189 | } 190 | 191 | shm_zone = lccf->shm_zone; 192 | mTime = time(NULL); 193 | key = (mTime - 9 ) % 10; 194 | 195 | sprintf(html,ADMIN_HTML, 196 | ((ngx_stats_shm_count_t *)shm_zone->data)->r_per_s[ key].time, 197 | ((ngx_stats_shm_count_t *)shm_zone->data)->r_per_s[ (key+1)%10].time, 198 | ((ngx_stats_shm_count_t *)shm_zone->data)->r_per_s[ (key+2)%10].time, 199 | ((ngx_stats_shm_count_t *)shm_zone->data)->r_per_s[ (key+3)%10].time, 200 | ((ngx_stats_shm_count_t *)shm_zone->data)->r_per_s[ (key+4)%10].time, 201 | ((ngx_stats_shm_count_t *)shm_zone->data)->r_per_s[ (key+5)%10].time, 202 | ((ngx_stats_shm_count_t *)shm_zone->data)->r_per_s[ (key+6)%10].time, 203 | ((ngx_stats_shm_count_t *)shm_zone->data)->r_per_s[ (key+7)%10].time, 204 | ((ngx_stats_shm_count_t *)shm_zone->data)->r_per_s[ (key+8)%10].time, 205 | ((ngx_stats_shm_count_t *)shm_zone->data)->r_per_s[ (key+9)%10].time, 206 | 207 | ((ngx_stats_shm_count_t *)shm_zone->data)->r_per_s[ key].count, 208 | ((ngx_stats_shm_count_t *)shm_zone->data)->r_per_s[ (key+1)%10].count, 209 | ((ngx_stats_shm_count_t *)shm_zone->data)->r_per_s[ (key+2)%10].count, 210 | ((ngx_stats_shm_count_t *)shm_zone->data)->r_per_s[ (key+3)%10].count, 211 | ((ngx_stats_shm_count_t *)shm_zone->data)->r_per_s[ (key+4)%10].count, 212 | ((ngx_stats_shm_count_t *)shm_zone->data)->r_per_s[ (key+5)%10].count, 213 | ((ngx_stats_shm_count_t *)shm_zone->data)->r_per_s[ (key+6)%10].count, 214 | ((ngx_stats_shm_count_t *)shm_zone->data)->r_per_s[ (key+7)%10].count, 215 | ((ngx_stats_shm_count_t *)shm_zone->data)->r_per_s[ (key+8)%10].count, 216 | ((ngx_stats_shm_count_t *)shm_zone->data)->r_per_s[ (key+9)%10].count 217 | ); 218 | 219 | b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t)); 220 | out.buf = b; 221 | out.next = NULL; 222 | ngx_str_t count_str = ngx_string(html); 223 | 224 | b->pos = count_str.data; 225 | b->last = count_str.data + count_str.len; 226 | b->memory = 1; 227 | b->last_buf = 1; 228 | r->headers_out.content_type.len = sizeof("text/html") - 1; 229 | r->headers_out.content_type.data = (u_char *) "text/html"; 230 | r->headers_out.status = NGX_HTTP_OK; 231 | r->headers_out.content_length_n = count_str.len; 232 | 233 | ngx_http_send_header(r); 234 | ngx_http_output_filter(r, &out); 235 | ngx_http_finalize_request(r, 0); 236 | 237 | return NGX_DONE; 238 | } 239 | 240 | 241 | static ngx_int_t server_html_stats_r_p_s(ngx_http_request_t *r) { 242 | char json[64], *json_response; 243 | unsigned char key; 244 | unsigned int mTime; 245 | ngx_http_stats_loc_conf_t *lccf; 246 | ngx_shm_zone_t *shm_zone; 247 | ngx_buf_t* b; 248 | ngx_chain_t out; 249 | 250 | lccf = ngx_http_get_module_loc_conf(r, ngx_http_stats_module); 251 | 252 | if(lccf->shm_zone == NULL){ 253 | return NGX_DECLINED; 254 | } 255 | shm_zone = lccf->shm_zone; 256 | mTime = time(NULL); 257 | 258 | key = (mTime - 1 )% 10 ; // -1 second 259 | //create JSON 260 | sprintf( json, "{\"req\":%ld, \"time\":%u}\n", 261 | (long int)(((ngx_stats_shm_count_t *)shm_zone->data)->r_per_s[key].count), 262 | ((ngx_stats_shm_count_t *)shm_zone->data)->r_per_s[key].time 263 | ); 264 | json_response = malloc(strlen(json)+1); 265 | strncpy(json_response, json, strlen(json)); 266 | b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t)); 267 | out.buf = b; 268 | out.next = NULL; 269 | 270 | b->pos = (u_char *)json_response; 271 | b->last = (u_char *)json_response + strlen(json_response); 272 | b->memory = 1; 273 | b->last_buf = 1; 274 | 275 | r->headers_out.content_type.len = sizeof("application/json") - 1; 276 | r->headers_out.content_type.data = (u_char *) "application/json"; 277 | r->headers_out.status = NGX_HTTP_OK; 278 | r->headers_out.content_length_n = strlen(json_response); 279 | 280 | ngx_http_send_header(r); 281 | ngx_http_output_filter(r, &out); 282 | ngx_http_finalize_request(r, 0); 283 | 284 | free(json_response); 285 | return NGX_DONE; 286 | } 287 | 288 | 289 | 290 | --------------------------------------------------------------------------------