1 /* -*- mode:c; coding:utf-8 -*- */
2 
3 #include <nginx.h>
4 #include <ngx_config.h>
5 #include <ngx_core.h>
6 #include <ngx_http.h>
7 #include "ngx_http_json_status_module.h"
8 
9 static ngx_command_t
10 ngx_http_json_status_commands[] = {
11   {
12     ngx_string("status"),
13     NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
14     ngx_http_json_status,
15     0,
16     0,
17     NULL
18   },
19   ngx_null_command
20 };
21 
22 static ngx_http_module_t ngx_http_json_status_module_ctx = {
23   NULL,                              /* preconfiguration */
24   NULL,                              /* postconfiguration */
25 
26   ngx_http_json_status_create_main_conf, /* create main configuration */
27   ngx_http_json_status_init_main_conf,   /* init main configuration */
28 
29   NULL,                              /* create server configuration */
30   NULL,                              /* merge server configuration */
31 
32   NULL,                              /* create location configuration */
33   NULL                               /* merge location configuration */
34 };
35 
36 ngx_module_t ngx_http_json_status_module = {
37   NGX_MODULE_V1,
38   &ngx_http_json_status_module_ctx, /* module context */
39   ngx_http_json_status_commands,    /* module directives */
40   NGX_HTTP_MODULE,                      /* module type */
41   NULL,                                 /* init master */
42   NULL,                                 /* init module */
43   NULL,                                 /* init process */
44   NULL,                                 /* init thread */
45   NULL,                                 /* exit thread */
46   NULL,                                 /* exit process */
47   NULL,                                 /* exit master */
48   NGX_MODULE_V1_PADDING
49 };
50 
51 static void *
ngx_http_json_status_create_main_conf(ngx_conf_t * cf)52 ngx_http_json_status_create_main_conf(ngx_conf_t *cf)
53 {
54   ngx_http_json_status_main_conf_t *jsmcf;
55 
56   jsmcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_json_status_main_conf_t));
57   if (jsmcf == NULL) {
58     return NULL;
59   }
60 
61   return jsmcf;
62 }
63 
64 static char *
ngx_http_json_status_init_main_conf(ngx_conf_t * cf,void * conf)65 ngx_http_json_status_init_main_conf(ngx_conf_t *cf, void *conf)
66 {
67   ngx_http_json_status_main_conf_t *jsmcf;
68   ngx_http_upstream_main_conf_t    *umcf;
69   struct hostent                   *host;
70   ngx_uint_t                        i;
71 
72   jsmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_json_status_module);
73   umcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_upstream_module);
74   if (jsmcf == NULL || umcf == NULL) {
75     return NGX_CONF_ERROR;
76   }
77 
78   if (gethostname(jsmcf->hostname, NGX_MAXHOSTNAMELEN) == -1) {
79     ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "gethostname() failed");
80     return NULL;
81   }
82   host = gethostbyname(jsmcf->hostname);
83   if (host == NULL) {
84     ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "gethostbyname() failed");
85     return NULL;
86   }
87   ngx_sprintf(jsmcf->addr, "%d.%d.%d.%d",
88 	      (BYTE)*(host->h_addr),
89 	      (BYTE)*(host->h_addr + 1),
90 	      (BYTE)*(host->h_addr + 2),
91 	      (BYTE)*(host->h_addr + 3));
92 
93   /* size calculation of upstreams */
94   size_t upstream_size = sizeof("\"upstreams\":{},");
95   for (i = 0; i < umcf->upstreams.nelts; i++) {
96     ngx_http_upstream_srv_conf_t *uscf   = ((ngx_http_upstream_srv_conf_t **)umcf->upstreams.elts)[i];
97     ngx_http_upstream_rr_peers_t *peers  = uscf->peer.data;
98     upstream_size += sizeof("\"\":[]")+sizeof(uscf->host)+ // upstream name
99       (sizeof("{\"server\":\"\",\"backup\":\"\",\"weight\":\"\",\"state\":\"\",\"active\":\"\",\"keepalive\":\"\",\"requests\":\"\",\"responses\":\"\",\"sent\":\"\",\"received\":\"\",\"fails\":\"\",\"unavail\":\"\",\"health_checks\":\"\",\"downtime\":\"\",\"downstart\":\"\"}")+
100        sizeof("{\"total\":\"\",\"1xx\":\"\",\"2xx\":\"\",\"3xx\":\"\",\"4xx\":\"\",\"5xx\":\"\"}")+ // responses
101        sizeof("{\"checks\":\"\",\"fails\":\"\",\"unhealthy\":\"\",\"last_passed\":\"\"}")+ // health_checks
102        //sizeof(ngx_uint_t)*10+ // responses + health_checks values
103        sizeof("N/A")*10+     // responses + health_checks values (Tentative)
104        sizeof(ngx_str_t)*1+  // server
105        sizeof(ngx_uint_t)*3+ // backup + weight + fails
106        sizeof("unhealthy")+  // satte
107        sizeof("N/A")*8       // etc(Tentative)
108        )*peers->number
109       ;
110   }
111 
112   /* sum total */
113   jsmcf->contents_size = sizeof("{}")+
114     sizeof("();")+ // callback for
115     /* server info */
116     sizeof("\"version\":\"\",")+sizeof(NGX_HTTP_JSON_STATUS_MODULE_VERSION)+
117     sizeof("\"nginx_version\":\"\",")+sizeof(NGINX_VERSION)+
118     sizeof("\"address\":\"\",")+sizeof(jsmcf->addr)+
119     sizeof("\"timestamp\":\"\",")+sizeof(time_t)+
120     /* connections */
121     sizeof("\"connections\":{},")+
122     sizeof("\"accepted\":\"\",")+NGX_ATOMIC_T_LEN+
123     sizeof("\"dropped\":\"\",")+NGX_ATOMIC_T_LEN+ // c_dropped = c_accepted.to_i - handled.to_i (see newrelic_nginx_agent)
124     sizeof("\"active\":\"\",")+NGX_ATOMIC_T_LEN+
125     sizeof("\"idle\":\"\",")+NGX_ATOMIC_T_LEN+
126     sizeof("\"counter\":\"\",")+NGX_ATOMIC_T_LEN+
127     /* requests */
128     sizeof("\"requests\":{},")+
129     sizeof("\"total\":\"\",")+NGX_ATOMIC_T_LEN+
130     sizeof("\"current\":\"\"")+NGX_ATOMIC_T_LEN+ // r_current = c_reading.to_i + c_writing.to_i (see newrelic_nginx_agent)
131     /* upstreams */
132     upstream_size+
133     /* terminate */
134     sizeof("\0")
135     ;
136 
137   return NGX_CONF_OK;
138 }
139 
140 static ngx_int_t
ngx_http_json_status_handler(ngx_http_request_t * r)141 ngx_http_json_status_handler(ngx_http_request_t *r)
142 {
143   ngx_http_upstream_main_conf_t    *umcf;
144   ngx_http_json_status_main_conf_t *jsmcf;
145   ngx_buf_t                        *b;
146   ngx_int_t                         rc;
147   ngx_chain_t                       out;
148   time_t                            now = time((time_t *)0);
149   ngx_atomic_int_t                  ap, hn, ac, rq, rd, wr, wa, acc;
150   ngx_uint_t                        i, j, k;
151   ngx_str_t                        *callback = ngx_http_get_arg_string(r, (u_char *)ARG_PARAMETER_CALLBACK);
152 
153   ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "#start. %s:%d", __FUNCTION__, __LINE__);
154 
155   umcf = ngx_http_get_module_main_conf(r, ngx_http_upstream_module);
156   jsmcf = ngx_http_get_module_main_conf(r, ngx_http_json_status_module); /*ngx_http_request_t, module(ngx_module_t)*/
157 
158   /* GET or HEAD only */
159   if (r->method != NGX_HTTP_GET && r->method != NGX_HTTP_HEAD) {
160     ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "method = %d: GET = %d,HEAD = %d", r->method, NGX_HTTP_GET, NGX_HTTP_HEAD);
161     return NGX_HTTP_NOT_ALLOWED;
162   }
163 
164   /* discard request body is not required */
165   rc = ngx_http_discard_request_body(r);
166   if (rc != NGX_OK) {
167     return rc;
168   }
169 
170   b = ngx_create_temp_buf(r->pool, jsmcf->contents_size + (sizeof(u_char)*callback->len));
171   if (b == NULL) {
172     return NGX_HTTP_INTERNAL_SERVER_ERROR;
173   }
174   out.buf = b;
175   out.next = NULL;
176 
177   /* You enable NGX_STAT_STUB and (src/event/ngx_event.h) */
178   acc = *ngx_connection_counter;
179   ap = *ngx_stat_accepted;
180   ac = *ngx_stat_active; // it is necessary to reset the value if the worker was aborted
181   hn = *ngx_stat_handled;
182   rq = *ngx_stat_requests;
183   rd = *ngx_stat_reading;
184   wr = *ngx_stat_writing;
185 #if nginx_version >= 1004001 /* From 1.4.1 (http://lxr.evanmiller.org/http/ident?i=ngx_stat_waiting) */
186   wa = *ngx_stat_waiting;
187 #else
188   wa = ac - (rd + wr);
189 #endif
190 
191   if (callback->len > 0) {
192     b->last = ngx_sprintf(b->last, "%V(", callback);
193   }
194 
195   b->last = ngx_sprintf(b->last, "{"); /* contents start */
196   b->last = ngx_sprintf(b->last, "\"version\":\"%s\",", NGX_HTTP_JSON_STATUS_MODULE_VERSION); /* module version */
197   b->last = ngx_sprintf(b->last, "\"nginx_version\":\"%s\",", NGINX_VERSION);
198   b->last = ngx_sprintf(b->last, "\"address\":\"%s\",", jsmcf->addr);
199   b->last = ngx_sprintf(b->last, "\"timestamp\":\"%l\",", now);
200   b->last = ngx_sprintf(b->last, "\"connections\":{\"accepted\":\"%uA\",\"dropped\":\"%uA\",\"active\":\"%uA\",\"idle\":\"%uA\",\"counter\":\"%uA\"},", ap, ap-hn, ac, wa, acc);
201   b->last = ngx_sprintf(b->last, "\"requests\":{\"total\":\"%uA\",\"current\":\"%uA\"},", rq, rd+wr);
202   b->last = ngx_sprintf(b->last, "\"upstreams\":{");
203 
204   for (i = 0; i < umcf->upstreams.nelts; i++) {
205     ngx_http_upstream_srv_conf_t *uscf   = ((ngx_http_upstream_srv_conf_t **)umcf->upstreams.elts)[i];
206     ngx_http_upstream_rr_peers_t *peers  = uscf->peer.data;
207     ngx_http_upstream_server_t   *server = uscf->servers->elts;
208 
209     if (i>0) {b->last = ngx_sprintf(b->last, ",", &peers->peer[j].name);}
210     // upstream directive
211     b->last = ngx_sprintf(b->last, "\"%V\":[", &uscf->host);
212 
213     ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "#upstream: %V", &uscf->host);
214 
215     for (j = 0; j < peers->number; j++) {
216 
217       // config information
218       ngx_uint_t config_backup = 0;
219       ngx_uint_t config_down = 0;
220       for (k = 0; k < uscf->servers->nelts; k++) {
221         if (ngx_strtcmp(&peers->peer[j].name, &server[k].addrs[SERVER_ADDRS_ZERO].name) == 0) {
222           config_down = server[k].down;
223           config_backup = server[k].backup;
224           break;
225         }
226       }
227 
228       ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "#config: down=%d, backup=%d(%s:%d)", config_down, config_backup, __FUNCTION__, __LINE__);
229 
230       if (j>0) {b->last = ngx_sprintf(b->last, ",", &peers->peer[j].name);}
231       b->last = ngx_sprintf(b->last, "{\"server\":\"%V\",\"backup\":\"%d\",\"weight\":\"%d\",\"state\":\"%s\",\"active\":\"%s\",\"keepalive\":\"%s\",\"requests\":\"%s\",\"responses\":%s,\"sent\":\"%s\",\"received\":\"%s\",\"fails\":\"%d\",\"unavail\":\"%s\",\"health_checks\":%s,\"downtime\":\"%s\",\"downstart\":\"%s\"}",
232 			    &peers->peer[j].name,
233 			    config_backup,
234 			    peers->peer[j].weight, //effective_weight, //current_weight,
235 			    (peers->peer[j].down == 1)?(u_char*)"down":(u_char*)"up", // Current state, which may be one of "up”, "down”, "unavail”, or "unhealthy"
236 			    (u_char *)"N/A", // active
237 			    (u_char *)"N/A", // keepalive
238 			    (u_char *)"N/A", // requests
239 			    (u_char *)"{\"total\":\"N/A\",\"1xx\":\"N/A\",\"2xx\":\"N/A\",\"3xx\":\"N/A\",\"4xx\":\"N/A\",\"5xx\":\"N/A\"}", // responses
240 			    (u_char *)"N/A", // sent
241 			    (u_char *)"N/A", // received
242 			    peers->peer[j].fails,
243 			    (u_char *)"N/A", // unavail
244 			    (u_char *)"{\"checks\":\"N/A\",\"fails\":\"N/A\",\"unhealthy\":\"N/A\",\"last_passed\":\"N/A\"}", // health_checks
245 			    (u_char *)"N/A", // downtime
246 			    (u_char *)"N/A"  // downstart
247 			    );
248     }
249 
250     b->last = ngx_sprintf(b->last, "]", &uscf->host);
251   }
252 
253   b->last = ngx_sprintf(b->last, "}");
254   b->last = ngx_sprintf(b->last, "}"); /* contents end */
255   if (callback->len > 0) {
256     b->last = ngx_sprintf(b->last, ");");
257   }
258 
259   b->memory = 1;
260   b->flush = 1;
261   b->last_buf = 1;
262   b->last_in_chain = 1;
263 
264   ngx_str_set(&r->headers_out.content_type, "application/json; charset=utf-8");
265   r->headers_out.status = NGX_HTTP_OK;
266   r->headers_out.content_length_n = b->last - b->pos;
267   rc = ngx_http_send_header(r);
268   if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
269     return rc;
270   }
271 
272   return ngx_http_output_filter(r, &out);
273 }
274 
275 static char *
ngx_http_json_status(ngx_conf_t * cf,ngx_command_t * cmd,void * conf)276 ngx_http_json_status(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
277 {
278   ngx_http_core_loc_conf_t  *clcf;
279 
280   clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
281   clcf->handler = ngx_http_json_status_handler;
282 
283   return NGX_CONF_OK;
284 }
285 
286 /* ********** misc ********** */
287 static int
ngx_strtcmp(ngx_str_t * s1,ngx_str_t * s2)288 ngx_strtcmp(ngx_str_t *s1, ngx_str_t *s2) {
289   if (s1->len == 0 || s2->len == 0 || s1->data == NULL || s2->data == NULL) {
290     return 128;
291   }
292   if (s1->len == s2->len) {
293     return ngx_strncmp(s1->data, s2->data, s1->len);
294   }
295   return ngx_strcmp(s1->data, s2->data);
296 }
297 
298 static ngx_str_t *
ngx_http_get_arg_string(ngx_http_request_t * r,u_char * name)299 ngx_http_get_arg_string(ngx_http_request_t *r, u_char *name) {
300   ngx_http_variable_value_t *vv;
301   ngx_str_t  key = {ngx_strlen(name), (u_char *)name};
302   u_char    *data;
303   ngx_str_t *str;
304 
305   str = ngx_pcalloc(r->pool, sizeof(ngx_str_t));
306 
307   vv = ngx_http_get_variable(r, &key, ngx_hash_key(key.data, key.len));
308   if (vv == NULL || vv->not_found) {
309     str->len = 0;
310     str->data = (u_char *)"";
311     return str;
312   }
313 
314   data = ngx_pcalloc(r->pool, (vv->len + 1) * sizeof(u_char));
315   ngx_cpystrn(data, vv->data, vv->len + 1);
316 
317   str->data = data;
318   str->len = vv->len;
319 
320   ngx_str_null(&key);
321   vv->data = NULL;
322 
323   return str;
324 }
325