1 /*
2  * Functions dedicated to statistics output and the stats socket
3  *
4  * Copyright 2000-2012 Willy Tarreau <w@1wt.eu>
5  * Copyright 2007-2009 Krzysztof Piotr Oledzki <ole@ans.pl>
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version
10  * 2 of the License, or (at your option) any later version.
11  *
12  */
13 
14 #include <ctype.h>
15 #include <errno.h>
16 #include <fcntl.h>
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <string.h>
20 #include <pwd.h>
21 #include <grp.h>
22 
23 #include <sys/socket.h>
24 #include <sys/stat.h>
25 #include <sys/types.h>
26 
27 #include <common/cfgparse.h>
28 #include <common/compat.h>
29 #include <common/config.h>
30 #include <common/debug.h>
31 #include <common/memory.h>
32 #include <common/mini-clist.h>
33 #include <common/standard.h>
34 #include <common/ticks.h>
35 #include <common/time.h>
36 #include <common/uri_auth.h>
37 #include <common/version.h>
38 #include <common/base64.h>
39 
40 #include <types/applet.h>
41 #include <types/cli.h>
42 #include <types/global.h>
43 #include <types/dns.h>
44 #include <types/stats.h>
45 
46 #include <proto/backend.h>
47 #include <proto/channel.h>
48 #include <proto/checks.h>
49 #include <proto/cli.h>
50 #include <proto/compression.h>
51 #include <proto/stats.h>
52 #include <proto/fd.h>
53 #include <proto/freq_ctr.h>
54 #include <proto/frontend.h>
55 #include <proto/log.h>
56 #include <proto/pattern.h>
57 #include <proto/pipe.h>
58 #include <proto/listener.h>
59 #include <proto/map.h>
60 #include <proto/proto_http.h>
61 #include <proto/proxy.h>
62 #include <proto/sample.h>
63 #include <proto/session.h>
64 #include <proto/stream.h>
65 #include <proto/server.h>
66 #include <proto/raw_sock.h>
67 #include <proto/stream_interface.h>
68 #include <proto/task.h>
69 
70 #ifdef USE_OPENSSL
71 #include <proto/ssl_sock.h>
72 #include <types/ssl_sock.h>
73 #endif
74 
75 
76 /* These are the field names for each INF_* field position. Please pay attention
77  * to always use the exact same name except that the strings for new names must
78  * be lower case or CamelCase while the enum entries must be upper case.
79  */
80 const char *info_field_names[INF_TOTAL_FIELDS] = {
81 	[INF_NAME]                           = "Name",
82 	[INF_VERSION]                        = "Version",
83 	[INF_RELEASE_DATE]                   = "Release_date",
84 	[INF_NBTHREAD]                       = "Nbthread",
85 	[INF_NBPROC]                         = "Nbproc",
86 	[INF_PROCESS_NUM]                    = "Process_num",
87 	[INF_PID]                            = "Pid",
88 	[INF_UPTIME]                         = "Uptime",
89 	[INF_UPTIME_SEC]                     = "Uptime_sec",
90 	[INF_MEMMAX_MB]                      = "Memmax_MB",
91 	[INF_POOL_ALLOC_MB]                  = "PoolAlloc_MB",
92 	[INF_POOL_USED_MB]                   = "PoolUsed_MB",
93 	[INF_POOL_FAILED]                    = "PoolFailed",
94 	[INF_ULIMIT_N]                       = "Ulimit-n",
95 	[INF_MAXSOCK]                        = "Maxsock",
96 	[INF_MAXCONN]                        = "Maxconn",
97 	[INF_HARD_MAXCONN]                   = "Hard_maxconn",
98 	[INF_CURR_CONN]                      = "CurrConns",
99 	[INF_CUM_CONN]                       = "CumConns",
100 	[INF_CUM_REQ]                        = "CumReq",
101 	[INF_MAX_SSL_CONNS]                  = "MaxSslConns",
102 	[INF_CURR_SSL_CONNS]                 = "CurrSslConns",
103 	[INF_CUM_SSL_CONNS]                  = "CumSslConns",
104 	[INF_MAXPIPES]                       = "Maxpipes",
105 	[INF_PIPES_USED]                     = "PipesUsed",
106 	[INF_PIPES_FREE]                     = "PipesFree",
107 	[INF_CONN_RATE]                      = "ConnRate",
108 	[INF_CONN_RATE_LIMIT]                = "ConnRateLimit",
109 	[INF_MAX_CONN_RATE]                  = "MaxConnRate",
110 	[INF_SESS_RATE]                      = "SessRate",
111 	[INF_SESS_RATE_LIMIT]                = "SessRateLimit",
112 	[INF_MAX_SESS_RATE]                  = "MaxSessRate",
113 	[INF_SSL_RATE]                       = "SslRate",
114 	[INF_SSL_RATE_LIMIT]                 = "SslRateLimit",
115 	[INF_MAX_SSL_RATE]                   = "MaxSslRate",
116 	[INF_SSL_FRONTEND_KEY_RATE]          = "SslFrontendKeyRate",
117 	[INF_SSL_FRONTEND_MAX_KEY_RATE]      = "SslFrontendMaxKeyRate",
118 	[INF_SSL_FRONTEND_SESSION_REUSE_PCT] = "SslFrontendSessionReuse_pct",
119 	[INF_SSL_BACKEND_KEY_RATE]           = "SslBackendKeyRate",
120 	[INF_SSL_BACKEND_MAX_KEY_RATE]       = "SslBackendMaxKeyRate",
121 	[INF_SSL_CACHE_LOOKUPS]              = "SslCacheLookups",
122 	[INF_SSL_CACHE_MISSES]               = "SslCacheMisses",
123 	[INF_COMPRESS_BPS_IN]                = "CompressBpsIn",
124 	[INF_COMPRESS_BPS_OUT]               = "CompressBpsOut",
125 	[INF_COMPRESS_BPS_RATE_LIM]          = "CompressBpsRateLim",
126 	[INF_ZLIB_MEM_USAGE]                 = "ZlibMemUsage",
127 	[INF_MAX_ZLIB_MEM_USAGE]             = "MaxZlibMemUsage",
128 	[INF_TASKS]                          = "Tasks",
129 	[INF_RUN_QUEUE]                      = "Run_queue",
130 	[INF_IDLE_PCT]                       = "Idle_pct",
131 	[INF_NODE]                           = "node",
132 	[INF_DESCRIPTION]                    = "description",
133 	[INF_STOPPING]                       = "Stopping",
134 	[INF_JOBS]                           = "Jobs",
135 	[INF_LISTENERS]                      = "Listeners",
136 };
137 
138 const char *stat_field_names[ST_F_TOTAL_FIELDS] = {
139 	[ST_F_PXNAME]         = "pxname",
140 	[ST_F_SVNAME]         = "svname",
141 	[ST_F_QCUR]           = "qcur",
142 	[ST_F_QMAX]           = "qmax",
143 	[ST_F_SCUR]           = "scur",
144 	[ST_F_SMAX]           = "smax",
145 	[ST_F_SLIM]           = "slim",
146 	[ST_F_STOT]           = "stot",
147 	[ST_F_BIN]            = "bin",
148 	[ST_F_BOUT]           = "bout",
149 	[ST_F_DREQ]           = "dreq",
150 	[ST_F_DRESP]          = "dresp",
151 	[ST_F_EREQ]           = "ereq",
152 	[ST_F_ECON]           = "econ",
153 	[ST_F_ERESP]          = "eresp",
154 	[ST_F_WRETR]          = "wretr",
155 	[ST_F_WREDIS]         = "wredis",
156 	[ST_F_STATUS]         = "status",
157 	[ST_F_WEIGHT]         = "weight",
158 	[ST_F_ACT]            = "act",
159 	[ST_F_BCK]            = "bck",
160 	[ST_F_CHKFAIL]        = "chkfail",
161 	[ST_F_CHKDOWN]        = "chkdown",
162 	[ST_F_LASTCHG]        = "lastchg",
163 	[ST_F_DOWNTIME]       = "downtime",
164 	[ST_F_QLIMIT]         = "qlimit",
165 	[ST_F_PID]            = "pid",
166 	[ST_F_IID]            = "iid",
167 	[ST_F_SID]            = "sid",
168 	[ST_F_THROTTLE]       = "throttle",
169 	[ST_F_LBTOT]          = "lbtot",
170 	[ST_F_TRACKED]        = "tracked",
171 	[ST_F_TYPE]           = "type",
172 	[ST_F_RATE]           = "rate",
173 	[ST_F_RATE_LIM]       = "rate_lim",
174 	[ST_F_RATE_MAX]       = "rate_max",
175 	[ST_F_CHECK_STATUS]   = "check_status",
176 	[ST_F_CHECK_CODE]     = "check_code",
177 	[ST_F_CHECK_DURATION] = "check_duration",
178 	[ST_F_HRSP_1XX]       = "hrsp_1xx",
179 	[ST_F_HRSP_2XX]       = "hrsp_2xx",
180 	[ST_F_HRSP_3XX]       = "hrsp_3xx",
181 	[ST_F_HRSP_4XX]       = "hrsp_4xx",
182 	[ST_F_HRSP_5XX]       = "hrsp_5xx",
183 	[ST_F_HRSP_OTHER]     = "hrsp_other",
184 	[ST_F_HANAFAIL]       = "hanafail",
185 	[ST_F_REQ_RATE]       = "req_rate",
186 	[ST_F_REQ_RATE_MAX]   = "req_rate_max",
187 	[ST_F_REQ_TOT]        = "req_tot",
188 	[ST_F_CLI_ABRT]       = "cli_abrt",
189 	[ST_F_SRV_ABRT]       = "srv_abrt",
190 	[ST_F_COMP_IN]        = "comp_in",
191 	[ST_F_COMP_OUT]       = "comp_out",
192 	[ST_F_COMP_BYP]       = "comp_byp",
193 	[ST_F_COMP_RSP]       = "comp_rsp",
194 	[ST_F_LASTSESS]       = "lastsess",
195 	[ST_F_LAST_CHK]       = "last_chk",
196 	[ST_F_LAST_AGT]       = "last_agt",
197 	[ST_F_QTIME]          = "qtime",
198 	[ST_F_CTIME]          = "ctime",
199 	[ST_F_RTIME]          = "rtime",
200 	[ST_F_TTIME]          = "ttime",
201 	[ST_F_AGENT_STATUS]   = "agent_status",
202 	[ST_F_AGENT_CODE]     = "agent_code",
203 	[ST_F_AGENT_DURATION] = "agent_duration",
204 	[ST_F_CHECK_DESC]     = "check_desc",
205 	[ST_F_AGENT_DESC]     = "agent_desc",
206 	[ST_F_CHECK_RISE]     = "check_rise",
207 	[ST_F_CHECK_FALL]     = "check_fall",
208 	[ST_F_CHECK_HEALTH]   = "check_health",
209 	[ST_F_AGENT_RISE]     = "agent_rise",
210 	[ST_F_AGENT_FALL]     = "agent_fall",
211 	[ST_F_AGENT_HEALTH]   = "agent_health",
212 	[ST_F_ADDR]           = "addr",
213 	[ST_F_COOKIE]         = "cookie",
214 	[ST_F_MODE]           = "mode",
215 	[ST_F_ALGO]           = "algo",
216 	[ST_F_CONN_RATE]      = "conn_rate",
217 	[ST_F_CONN_RATE_MAX]  = "conn_rate_max",
218 	[ST_F_CONN_TOT]       = "conn_tot",
219 	[ST_F_INTERCEPTED]    = "intercepted",
220 	[ST_F_DCON]           = "dcon",
221 	[ST_F_DSES]           = "dses",
222 };
223 
224 /* one line of info */
225 static THREAD_LOCAL struct field info[INF_TOTAL_FIELDS];
226 /* one line of stats */
227 static THREAD_LOCAL struct field stats[ST_F_TOTAL_FIELDS];
228 
229 
230 
231 /*
232  * http_stats_io_handler()
233  *     -> stats_dump_stat_to_buffer()     // same as above, but used for CSV or HTML
234  *        -> stats_dump_csv_header()      // emits the CSV headers (same as above)
235  *        -> stats_dump_json_header()     // emits the JSON headers (same as above)
236  *        -> stats_dump_html_head()       // emits the HTML headers
237  *        -> stats_dump_html_info()       // emits the equivalent of "show info" at the top
238  *        -> stats_dump_proxy_to_buffer() // same as above, valid for CSV and HTML
239  *           -> stats_dump_html_px_hdr()
240  *           -> stats_dump_fe_stats()
241  *           -> stats_dump_li_stats()
242  *           -> stats_dump_sv_stats()
243  *           -> stats_dump_be_stats()
244  *           -> stats_dump_html_px_end()
245  *        -> stats_dump_html_end()       // emits HTML trailer
246  *        -> stats_dump_json_end()       // emits JSON trailer
247  */
248 
249 
250 extern const char *stat_status_codes[];
251 
252 /* Dumps the stats CSV header to the trash buffer which. The caller is responsible
253  * for clearing it if needed.
254  * NOTE: Some tools happen to rely on the field position instead of its name,
255  *       so please only append new fields at the end, never in the middle.
256  */
stats_dump_csv_header()257 static void stats_dump_csv_header()
258 {
259 	int field;
260 
261 	chunk_appendf(&trash, "# ");
262 	for (field = 0; field < ST_F_TOTAL_FIELDS; field++)
263 		chunk_appendf(&trash, "%s,", stat_field_names[field]);
264 
265 	chunk_appendf(&trash, "\n");
266 }
267 
268 
269 /* Emits a stats field without any surrounding element and properly encoded to
270  * resist CSV output. Returns non-zero on success, 0 if the buffer is full.
271  */
stats_emit_raw_data_field(struct chunk * out,const struct field * f)272 int stats_emit_raw_data_field(struct chunk *out, const struct field *f)
273 {
274 	switch (field_format(f, 0)) {
275 	case FF_EMPTY: return 1;
276 	case FF_S32:   return chunk_appendf(out, "%d", f->u.s32);
277 	case FF_U32:   return chunk_appendf(out, "%u", f->u.u32);
278 	case FF_S64:   return chunk_appendf(out, "%lld", (long long)f->u.s64);
279 	case FF_U64:   return chunk_appendf(out, "%llu", (unsigned long long)f->u.u64);
280 	case FF_STR:   return csv_enc_append(field_str(f, 0), 1, out) != NULL;
281 	default:       return chunk_appendf(out, "[INCORRECT_FIELD_TYPE_%08x]", f->type);
282 	}
283 }
284 
285 /* Emits a stats field prefixed with its type. No CSV encoding is prepared, the
286  * output is supposed to be used on its own line. Returns non-zero on success, 0
287  * if the buffer is full.
288  */
stats_emit_typed_data_field(struct chunk * out,const struct field * f)289 int stats_emit_typed_data_field(struct chunk *out, const struct field *f)
290 {
291 	switch (field_format(f, 0)) {
292 	case FF_EMPTY: return 1;
293 	case FF_S32:   return chunk_appendf(out, "s32:%d", f->u.s32);
294 	case FF_U32:   return chunk_appendf(out, "u32:%u", f->u.u32);
295 	case FF_S64:   return chunk_appendf(out, "s64:%lld", (long long)f->u.s64);
296 	case FF_U64:   return chunk_appendf(out, "u64:%llu", (unsigned long long)f->u.u64);
297 	case FF_STR:   return chunk_appendf(out, "str:%s", field_str(f, 0));
298 	default:       return chunk_appendf(out, "%08x:?", f->type);
299 	}
300 }
301 
302 /* Limit JSON integer values to the range [-(2**53)+1, (2**53)-1] as per
303  * the recommendation for interoperable integers in section 6 of RFC 7159.
304  */
305 #define JSON_INT_MAX ((1ULL << 53) - 1)
306 #define JSON_INT_MIN (0 - JSON_INT_MAX)
307 
308 /* Emits a stats field value and its type in JSON.
309  * Returns non-zero on success, 0 on error.
310  */
stats_emit_json_data_field(struct chunk * out,const struct field * f)311 int stats_emit_json_data_field(struct chunk *out, const struct field *f)
312 {
313 	int old_len;
314 	char buf[20];
315 	const char *type, *value = buf, *quote = "";
316 
317 	switch (field_format(f, 0)) {
318 	case FF_EMPTY: return 1;
319 	case FF_S32:   type = "\"s32\"";
320 		       snprintf(buf, sizeof(buf), "%d", f->u.s32);
321 		       break;
322 	case FF_U32:   type = "\"u32\"";
323 		       snprintf(buf, sizeof(buf), "%u", f->u.u32);
324 		       break;
325 	case FF_S64:   type = "\"s64\"";
326 		       if (f->u.s64 < JSON_INT_MIN || f->u.s64 > JSON_INT_MAX)
327 			       return 0;
328 		       type = "\"s64\"";
329 		       snprintf(buf, sizeof(buf), "%lld", (long long)f->u.s64);
330 		       break;
331 	case FF_U64:   if (f->u.u64 > JSON_INT_MAX)
332 			       return 0;
333 		       type = "\"u64\"";
334 		       snprintf(buf, sizeof(buf), "%llu",
335 				(unsigned long long) f->u.u64);
336 		       break;
337 	case FF_STR:   type = "\"str\"";
338 		       value = field_str(f, 0);
339 		       quote = "\"";
340 		       break;
341 	default:       snprintf(buf, sizeof(buf), "%u", f->type);
342 		       type = buf;
343 		       value = "unknown";
344 		       quote = "\"";
345 		       break;
346 	}
347 
348 	old_len = out->len;
349 	chunk_appendf(out, ",\"value\":{\"type\":%s,\"value\":%s%s%s}",
350 		      type, quote, value, quote);
351 	return !(old_len == out->len);
352 }
353 
354 /* Emits an encoding of the field type on 3 characters followed by a delimiter.
355  * Returns non-zero on success, 0 if the buffer is full.
356  */
stats_emit_field_tags(struct chunk * out,const struct field * f,char delim)357 int stats_emit_field_tags(struct chunk *out, const struct field *f, char delim)
358 {
359 	char origin, nature, scope;
360 
361 	switch (field_origin(f, 0)) {
362 	case FO_METRIC:  origin = 'M'; break;
363 	case FO_STATUS:  origin = 'S'; break;
364 	case FO_KEY:     origin = 'K'; break;
365 	case FO_CONFIG:  origin = 'C'; break;
366 	case FO_PRODUCT: origin = 'P'; break;
367 	default:         origin = '?'; break;
368 	}
369 
370 	switch (field_nature(f, 0)) {
371 	case FN_GAUGE:    nature = 'G'; break;
372 	case FN_LIMIT:    nature = 'L'; break;
373 	case FN_MIN:      nature = 'm'; break;
374 	case FN_MAX:      nature = 'M'; break;
375 	case FN_RATE:     nature = 'R'; break;
376 	case FN_COUNTER:  nature = 'C'; break;
377 	case FN_DURATION: nature = 'D'; break;
378 	case FN_AGE:      nature = 'A'; break;
379 	case FN_TIME:     nature = 'T'; break;
380 	case FN_NAME:     nature = 'N'; break;
381 	case FN_OUTPUT:   nature = 'O'; break;
382 	case FN_AVG:      nature = 'a'; break;
383 	default:          nature = '?'; break;
384 	}
385 
386 	switch (field_scope(f, 0)) {
387 	case FS_PROCESS: scope = 'P'; break;
388 	case FS_SERVICE: scope = 'S'; break;
389 	case FS_SYSTEM:  scope = 's'; break;
390 	case FS_CLUSTER: scope = 'C'; break;
391 	default:         scope = '?'; break;
392 	}
393 
394 	return chunk_appendf(out, "%c%c%c%c", origin, nature, scope, delim);
395 }
396 
397 /* Emits an encoding of the field type as JSON.
398   * Returns non-zero on success, 0 if the buffer is full.
399   */
stats_emit_json_field_tags(struct chunk * out,const struct field * f)400 int stats_emit_json_field_tags(struct chunk *out, const struct field *f)
401 {
402 	const char *origin, *nature, *scope;
403 	int old_len;
404 
405 	switch (field_origin(f, 0)) {
406 	case FO_METRIC:  origin = "Metric";  break;
407 	case FO_STATUS:  origin = "Status";  break;
408 	case FO_KEY:     origin = "Key";     break;
409 	case FO_CONFIG:  origin = "Config";  break;
410 	case FO_PRODUCT: origin = "Product"; break;
411 	default:         origin = "Unknown"; break;
412 	}
413 
414 	switch (field_nature(f, 0)) {
415 	case FN_GAUGE:    nature = "Gauge";    break;
416 	case FN_LIMIT:    nature = "Limit";    break;
417 	case FN_MIN:      nature = "Min";      break;
418 	case FN_MAX:      nature = "Max";      break;
419 	case FN_RATE:     nature = "Rate";     break;
420 	case FN_COUNTER:  nature = "Counter";  break;
421 	case FN_DURATION: nature = "Duration"; break;
422 	case FN_AGE:      nature = "Age";      break;
423 	case FN_TIME:     nature = "Time";     break;
424 	case FN_NAME:     nature = "Name";     break;
425 	case FN_OUTPUT:   nature = "Output";   break;
426 	case FN_AVG:      nature = "Avg";      break;
427 	default:          nature = "Unknown";  break;
428 	}
429 
430 	switch (field_scope(f, 0)) {
431 	case FS_PROCESS: scope = "Process"; break;
432 	case FS_SERVICE: scope = "Service"; break;
433 	case FS_SYSTEM:  scope = "System";  break;
434 	case FS_CLUSTER: scope = "Cluster"; break;
435 	default:         scope = "Unknown"; break;
436 	}
437 
438 	old_len = out->len;
439 	chunk_appendf(out, "\"tags\":{"
440 			    "\"origin\":\"%s\","
441 			    "\"nature\":\"%s\","
442 			    "\"scope\":\"%s\""
443 			   "}", origin, nature, scope);
444 	return !(old_len == out->len);
445 }
446 
447 /* Dump all fields from <stats> into <out> using CSV format */
stats_dump_fields_csv(struct chunk * out,const struct field * stats)448 static int stats_dump_fields_csv(struct chunk *out, const struct field *stats)
449 {
450 	int field;
451 
452 	for (field = 0; field < ST_F_TOTAL_FIELDS; field++) {
453 		if (!stats_emit_raw_data_field(out, &stats[field]))
454 			return 0;
455 		if (!chunk_strcat(out, ","))
456 			return 0;
457 	}
458 	chunk_strcat(out, "\n");
459 	return 1;
460 }
461 
462 /* Dump all fields from <stats> into <out> using a typed "field:desc:type:value" format */
stats_dump_fields_typed(struct chunk * out,const struct field * stats)463 static int stats_dump_fields_typed(struct chunk *out, const struct field *stats)
464 {
465 	int field;
466 
467 	for (field = 0; field < ST_F_TOTAL_FIELDS; field++) {
468 		if (!stats[field].type)
469 			continue;
470 
471 		chunk_appendf(out, "%c.%u.%u.%d.%s.%u:",
472 		              stats[ST_F_TYPE].u.u32 == STATS_TYPE_FE ? 'F' :
473 		              stats[ST_F_TYPE].u.u32 == STATS_TYPE_BE ? 'B' :
474 		              stats[ST_F_TYPE].u.u32 == STATS_TYPE_SO ? 'L' :
475 		              stats[ST_F_TYPE].u.u32 == STATS_TYPE_SV ? 'S' :
476 		              '?',
477 		              stats[ST_F_IID].u.u32, stats[ST_F_SID].u.u32,
478 		              field, stat_field_names[field], stats[ST_F_PID].u.u32);
479 
480 		if (!stats_emit_field_tags(out, &stats[field], ':'))
481 			return 0;
482 		if (!stats_emit_typed_data_field(out, &stats[field]))
483 			return 0;
484 		if (!chunk_strcat(out, "\n"))
485 			return 0;
486 	}
487 	return 1;
488 }
489 
490 /* Dump all fields from <stats> into <out> using the "show info json" format */
stats_dump_json_info_fields(struct chunk * out,const struct field * info)491 static int stats_dump_json_info_fields(struct chunk *out,
492 				       const struct field *info)
493 {
494 	int field;
495 	int started = 0;
496 
497 	if (!chunk_strcat(out, "["))
498 		return 0;
499 
500 	for (field = 0; field < INF_TOTAL_FIELDS; field++) {
501 		int old_len;
502 
503 		if (!field_format(info, field))
504 			continue;
505 
506 		if (started && !chunk_strcat(out, ","))
507 			goto err;
508 		started = 1;
509 
510 		old_len = out->len;
511 		chunk_appendf(out,
512 			      "{\"field\":{\"pos\":%d,\"name\":\"%s\"},"
513 			      "\"processNum\":%u,",
514 			      field, info_field_names[field],
515 			      info[INF_PROCESS_NUM].u.u32);
516 		if (old_len == out->len)
517 			goto err;
518 
519 		if (!stats_emit_json_field_tags(out, &info[field]))
520 			goto err;
521 
522 		if (!stats_emit_json_data_field(out, &info[field]))
523 			goto err;
524 
525 		if (!chunk_strcat(out, "}"))
526 			goto err;
527 	}
528 
529 	if (!chunk_strcat(out, "]"))
530 		goto err;
531 	return 1;
532 
533 err:
534 	chunk_reset(out);
535 	chunk_appendf(out, "{\"errorStr\":\"output buffer too short\"}");
536 	return 0;
537 }
538 
539 /* Dump all fields from <stats> into <out> using a typed "field:desc:type:value" format */
stats_dump_fields_json(struct chunk * out,const struct field * stats,int first_stat)540 static int stats_dump_fields_json(struct chunk *out, const struct field *stats,
541 				  int first_stat)
542 {
543 	int field;
544 	int started = 0;
545 
546 	if (!first_stat && !chunk_strcat(out, ","))
547 		return 0;
548 	if (!chunk_strcat(out, "["))
549 		return 0;
550 
551 	for (field = 0; field < ST_F_TOTAL_FIELDS; field++) {
552 		const char *obj_type;
553 		int old_len;
554 
555 		if (!stats[field].type)
556 			continue;
557 
558 		if (started && !chunk_strcat(out, ","))
559 			goto err;
560 		started = 1;
561 
562 		switch (stats[ST_F_TYPE].u.u32) {
563 		case STATS_TYPE_FE: obj_type = "Frontend"; break;
564 		case STATS_TYPE_BE: obj_type = "Backend";  break;
565 		case STATS_TYPE_SO: obj_type = "Listener"; break;
566 		case STATS_TYPE_SV: obj_type = "Server";   break;
567 		default:            obj_type = "Unknown";  break;
568 		}
569 
570 		old_len = out->len;
571 		chunk_appendf(out,
572 			      "{"
573 				"\"objType\":\"%s\","
574 				"\"proxyId\":%d,"
575 				"\"id\":%d,"
576 				"\"field\":{\"pos\":%d,\"name\":\"%s\"},"
577 				"\"processNum\":%u,",
578 			       obj_type, stats[ST_F_IID].u.u32,
579 			       stats[ST_F_SID].u.u32, field,
580 			       stat_field_names[field], stats[ST_F_PID].u.u32);
581 		if (old_len == out->len)
582 			goto err;
583 
584 		if (!stats_emit_json_field_tags(out, &stats[field]))
585 			goto err;
586 
587 		if (!stats_emit_json_data_field(out, &stats[field]))
588 			goto err;
589 
590 		if (!chunk_strcat(out, "}"))
591 			goto err;
592 	}
593 
594 	if (!chunk_strcat(out, "]"))
595 		goto err;
596 
597 	return 1;
598 
599 err:
600 	chunk_reset(out);
601 	if (!first_stat)
602 	    chunk_strcat(out, ",");
603 	chunk_appendf(out, "{\"errorStr\":\"output buffer too short\"}");
604 	return 0;
605 }
606 
607 /* Dump all fields from <stats> into <out> using the HTML format. A column is
608  * reserved for the checkbox is ST_SHOWADMIN is set in <flags>. Some extra info
609  * are provided if ST_SHLGNDS is present in <flags>.
610  */
stats_dump_fields_html(struct chunk * out,const struct field * stats,unsigned int flags)611 static int stats_dump_fields_html(struct chunk *out, const struct field *stats, unsigned int flags)
612 {
613 	struct chunk src;
614 
615 	if (stats[ST_F_TYPE].u.u32 == STATS_TYPE_FE) {
616 		chunk_appendf(out,
617 		              /* name, queue */
618 		              "<tr class=\"frontend\">");
619 
620 		if (flags & ST_SHOWADMIN) {
621 			/* Column sub-heading for Enable or Disable server */
622 			chunk_appendf(out, "<td></td>");
623 		}
624 
625 		chunk_appendf(out,
626 		              "<td class=ac>"
627 		              "<a name=\"%s/Frontend\"></a>"
628 		              "<a class=lfsb href=\"#%s/Frontend\">Frontend</a></td>"
629 		              "<td colspan=3></td>"
630 		              "",
631 		              field_str(stats, ST_F_PXNAME), field_str(stats, ST_F_PXNAME));
632 
633 		chunk_appendf(out,
634 		              /* sessions rate : current */
635 		              "<td><u>%s<div class=tips><table class=det>"
636 		              "<tr><th>Current connection rate:</th><td>%s/s</td></tr>"
637 		              "<tr><th>Current session rate:</th><td>%s/s</td></tr>"
638 		              "",
639 		              U2H(stats[ST_F_RATE].u.u32),
640 		              U2H(stats[ST_F_CONN_RATE].u.u32),
641 		              U2H(stats[ST_F_RATE].u.u32));
642 
643 		if (strcmp(field_str(stats, ST_F_MODE), "http") == 0)
644 			chunk_appendf(out,
645 			              "<tr><th>Current request rate:</th><td>%s/s</td></tr>",
646 			              U2H(stats[ST_F_REQ_RATE].u.u32));
647 
648 		chunk_appendf(out,
649 		              "</table></div></u></td>"
650 		              /* sessions rate : max */
651 		              "<td><u>%s<div class=tips><table class=det>"
652 		              "<tr><th>Max connection rate:</th><td>%s/s</td></tr>"
653 		              "<tr><th>Max session rate:</th><td>%s/s</td></tr>"
654 		              "",
655 		              U2H(stats[ST_F_RATE_MAX].u.u32),
656 		              U2H(stats[ST_F_CONN_RATE_MAX].u.u32),
657 		              U2H(stats[ST_F_RATE_MAX].u.u32));
658 
659 		if (strcmp(field_str(stats, ST_F_MODE), "http") == 0)
660 			chunk_appendf(out,
661 			              "<tr><th>Max request rate:</th><td>%s/s</td></tr>",
662 			              U2H(stats[ST_F_REQ_RATE_MAX].u.u32));
663 
664 		chunk_appendf(out,
665 		              "</table></div></u></td>"
666 		              /* sessions rate : limit */
667 		              "<td>%s</td>",
668 		              LIM2A(stats[ST_F_RATE_LIM].u.u32, "-"));
669 
670 		chunk_appendf(out,
671 		              /* sessions: current, max, limit, total */
672 		              "<td>%s</td><td>%s</td><td>%s</td>"
673 		              "<td><u>%s<div class=tips><table class=det>"
674 		              "<tr><th>Cum. connections:</th><td>%s</td></tr>"
675 		              "<tr><th>Cum. sessions:</th><td>%s</td></tr>"
676 		              "",
677 		              U2H(stats[ST_F_SCUR].u.u32), U2H(stats[ST_F_SMAX].u.u32), U2H(stats[ST_F_SLIM].u.u32),
678 		              U2H(stats[ST_F_STOT].u.u64),
679 		              U2H(stats[ST_F_CONN_TOT].u.u64),
680 		              U2H(stats[ST_F_STOT].u.u64));
681 
682 		/* http response (via hover): 1xx, 2xx, 3xx, 4xx, 5xx, other */
683 		if (strcmp(field_str(stats, ST_F_MODE), "http") == 0) {
684 			chunk_appendf(out,
685 			              "<tr><th>Cum. HTTP requests:</th><td>%s</td></tr>"
686 			              "<tr><th>- HTTP 1xx responses:</th><td>%s</td></tr>"
687 			              "<tr><th>- HTTP 2xx responses:</th><td>%s</td></tr>"
688 			              "<tr><th>&nbsp;&nbsp;Compressed 2xx:</th><td>%s</td><td>(%d%%)</td></tr>"
689 			              "<tr><th>- HTTP 3xx responses:</th><td>%s</td></tr>"
690 			              "<tr><th>- HTTP 4xx responses:</th><td>%s</td></tr>"
691 			              "<tr><th>- HTTP 5xx responses:</th><td>%s</td></tr>"
692 			              "<tr><th>- other responses:</th><td>%s</td></tr>"
693 			              "<tr><th>Intercepted requests:</th><td>%s</td></tr>"
694 			              "",
695 			              U2H(stats[ST_F_REQ_TOT].u.u64),
696 			              U2H(stats[ST_F_HRSP_1XX].u.u64),
697 			              U2H(stats[ST_F_HRSP_2XX].u.u64),
698 			              U2H(stats[ST_F_COMP_RSP].u.u64),
699 			              stats[ST_F_HRSP_2XX].u.u64 ?
700 			              (int)(100 * stats[ST_F_COMP_RSP].u.u64 / stats[ST_F_HRSP_2XX].u.u64) : 0,
701 			              U2H(stats[ST_F_HRSP_3XX].u.u64),
702 			              U2H(stats[ST_F_HRSP_4XX].u.u64),
703 			              U2H(stats[ST_F_HRSP_5XX].u.u64),
704 			              U2H(stats[ST_F_HRSP_OTHER].u.u64),
705 			              U2H(stats[ST_F_INTERCEPTED].u.u64));
706 		}
707 
708 		chunk_appendf(out,
709 		              "</table></div></u></td>"
710 		              /* sessions: lbtot, lastsess */
711 		              "<td></td><td></td>"
712 		              /* bytes : in */
713 		              "<td>%s</td>"
714 		              "",
715 		              U2H(stats[ST_F_BIN].u.u64));
716 
717 		chunk_appendf(out,
718 			      /* bytes:out + compression stats (via hover): comp_in, comp_out, comp_byp */
719 		              "<td>%s%s<div class=tips><table class=det>"
720 			      "<tr><th>Response bytes in:</th><td>%s</td></tr>"
721 			      "<tr><th>Compression in:</th><td>%s</td></tr>"
722 			      "<tr><th>Compression out:</th><td>%s</td><td>(%d%%)</td></tr>"
723 			      "<tr><th>Compression bypass:</th><td>%s</td></tr>"
724 			      "<tr><th>Total bytes saved:</th><td>%s</td><td>(%d%%)</td></tr>"
725 			      "</table></div>%s</td>",
726 		              (stats[ST_F_COMP_IN].u.u64 || stats[ST_F_COMP_BYP].u.u64) ? "<u>":"",
727 		              U2H(stats[ST_F_BOUT].u.u64),
728 		              U2H(stats[ST_F_BOUT].u.u64),
729 		              U2H(stats[ST_F_COMP_IN].u.u64),
730 			      U2H(stats[ST_F_COMP_OUT].u.u64),
731 			      stats[ST_F_COMP_IN].u.u64 ? (int)(stats[ST_F_COMP_OUT].u.u64 * 100 / stats[ST_F_COMP_IN].u.u64) : 0,
732 			      U2H(stats[ST_F_COMP_BYP].u.u64),
733 			      U2H(stats[ST_F_COMP_IN].u.u64 - stats[ST_F_COMP_OUT].u.u64),
734 			      stats[ST_F_BOUT].u.u64 ? (int)((stats[ST_F_COMP_IN].u.u64 - stats[ST_F_COMP_OUT].u.u64) * 100 / stats[ST_F_BOUT].u.u64) : 0,
735 		              (stats[ST_F_COMP_IN].u.u64 || stats[ST_F_COMP_BYP].u.u64) ? "</u>":"");
736 
737 		chunk_appendf(out,
738 		              /* denied: req, resp */
739 		              "<td>%s</td><td>%s</td>"
740 		              /* errors : request, connect, response */
741 		              "<td>%s</td><td></td><td></td>"
742 		              /* warnings: retries, redispatches */
743 		              "<td></td><td></td>"
744 		              /* server status : reflect frontend status */
745 		              "<td class=ac>%s</td>"
746 		              /* rest of server: nothing */
747 		              "<td class=ac colspan=8></td></tr>"
748 		              "",
749 		              U2H(stats[ST_F_DREQ].u.u64), U2H(stats[ST_F_DRESP].u.u64),
750 		              U2H(stats[ST_F_EREQ].u.u64),
751 		              field_str(stats, ST_F_STATUS));
752 	}
753 	else if (stats[ST_F_TYPE].u.u32 == STATS_TYPE_SO) {
754 		chunk_appendf(out, "<tr class=socket>");
755 		if (flags & ST_SHOWADMIN) {
756 			/* Column sub-heading for Enable or Disable server */
757 			chunk_appendf(out, "<td></td>");
758 		}
759 
760 		chunk_appendf(out,
761 		              /* frontend name, listener name */
762 		              "<td class=ac><a name=\"%s/+%s\"></a>%s"
763 		              "<a class=lfsb href=\"#%s/+%s\">%s</a>"
764 		              "",
765 		              field_str(stats, ST_F_PXNAME), field_str(stats, ST_F_SVNAME),
766 		              (flags & ST_SHLGNDS)?"<u>":"",
767 		              field_str(stats, ST_F_PXNAME), field_str(stats, ST_F_SVNAME), field_str(stats, ST_F_SVNAME));
768 
769 		if (flags & ST_SHLGNDS) {
770 			chunk_appendf(out, "<div class=tips>");
771 
772 			if (isdigit(*field_str(stats, ST_F_ADDR)))
773 				chunk_appendf(out, "IPv4: %s, ", field_str(stats, ST_F_ADDR));
774 			else if (*field_str(stats, ST_F_ADDR) == '[')
775 				chunk_appendf(out, "IPv6: %s, ", field_str(stats, ST_F_ADDR));
776 			else if (*field_str(stats, ST_F_ADDR))
777 				chunk_appendf(out, "%s, ", field_str(stats, ST_F_ADDR));
778 
779 			/* id */
780 			chunk_appendf(out, "id: %d</div>", stats[ST_F_SID].u.u32);
781 		}
782 
783 		chunk_appendf(out,
784 			      /* queue */
785 		              "%s</td><td colspan=3></td>"
786 		              /* sessions rate: current, max, limit */
787 		              "<td colspan=3>&nbsp;</td>"
788 		              /* sessions: current, max, limit, total, lbtot, lastsess */
789 		              "<td>%s</td><td>%s</td><td>%s</td>"
790 		              "<td>%s</td><td>&nbsp;</td><td>&nbsp;</td>"
791 		              /* bytes: in, out */
792 		              "<td>%s</td><td>%s</td>"
793 		              "",
794 		              (flags & ST_SHLGNDS)?"</u>":"",
795 		              U2H(stats[ST_F_SCUR].u.u32), U2H(stats[ST_F_SMAX].u.u32), U2H(stats[ST_F_SLIM].u.u32),
796 		              U2H(stats[ST_F_STOT].u.u64), U2H(stats[ST_F_BIN].u.u64), U2H(stats[ST_F_BOUT].u.u64));
797 
798 		chunk_appendf(out,
799 		              /* denied: req, resp */
800 		              "<td>%s</td><td>%s</td>"
801 		              /* errors: request, connect, response */
802 		              "<td>%s</td><td></td><td></td>"
803 		              /* warnings: retries, redispatches */
804 		              "<td></td><td></td>"
805 		              /* server status: reflect listener status */
806 		              "<td class=ac>%s</td>"
807 		              /* rest of server: nothing */
808 		              "<td class=ac colspan=8></td></tr>"
809 		              "",
810 		              U2H(stats[ST_F_DREQ].u.u64), U2H(stats[ST_F_DRESP].u.u64),
811 		              U2H(stats[ST_F_EREQ].u.u64),
812 		              field_str(stats, ST_F_STATUS));
813 	}
814 	else if (stats[ST_F_TYPE].u.u32 == STATS_TYPE_SV) {
815 		const char *style;
816 
817 		/* determine the style to use depending on the server's state,
818 		 * its health and weight. There isn't a 1-to-1 mapping between
819 		 * state and styles for the cases where the server is (still)
820 		 * up. The reason is that we don't want to report nolb and
821 		 * drain with the same color.
822 		 */
823 
824 		if (strcmp(field_str(stats, ST_F_STATUS), "DOWN") == 0 ||
825 		    strcmp(field_str(stats, ST_F_STATUS), "DOWN (agent)") == 0) {
826 			style = "down";
827 		}
828 		else if (strcmp(field_str(stats, ST_F_STATUS), "DOWN ") == 0) {
829 			style = "going_up";
830 		}
831 		else if (strcmp(field_str(stats, ST_F_STATUS), "DRAIN") == 0) {
832 			style = "draining";
833 		}
834 		else if (strcmp(field_str(stats, ST_F_STATUS), "NOLB ") == 0) {
835 			style = "going_down";
836 		}
837 		else if (strcmp(field_str(stats, ST_F_STATUS), "NOLB") == 0) {
838 			style = "nolb";
839 		}
840 		else if (strcmp(field_str(stats, ST_F_STATUS), "no check") == 0) {
841 			style = "no_check";
842 		}
843 		else if (!stats[ST_F_CHKFAIL].type ||
844 			 stats[ST_F_CHECK_HEALTH].u.u32 == stats[ST_F_CHECK_RISE].u.u32 + stats[ST_F_CHECK_FALL].u.u32 - 1) {
845 			/* no check or max health = UP */
846 			if (stats[ST_F_WEIGHT].u.u32)
847 				style = "up";
848 			else
849 				style = "draining";
850 		}
851 		else {
852 			style = "going_down";
853 		}
854 
855 		if (strncmp(field_str(stats, ST_F_STATUS), "MAINT", 5) == 0)
856 			chunk_appendf(out, "<tr class=\"maintain\">");
857 		else
858 			chunk_appendf(out,
859 			              "<tr class=\"%s_%s\">",
860 			              (stats[ST_F_BCK].u.u32) ? "backup" : "active", style);
861 
862 
863 		if (flags & ST_SHOWADMIN)
864 			chunk_appendf(out,
865 			              "<td><input class='%s-checkbox' type=\"checkbox\" name=\"s\" value=\"%s\"></td>",
866 			              field_str(stats, ST_F_PXNAME),
867 			              field_str(stats, ST_F_SVNAME));
868 
869 		chunk_appendf(out,
870 		              "<td class=ac><a name=\"%s/%s\"></a>%s"
871 		              "<a class=lfsb href=\"#%s/%s\">%s</a>"
872 		              "",
873 		              field_str(stats, ST_F_PXNAME), field_str(stats, ST_F_SVNAME),
874 		              (flags & ST_SHLGNDS) ? "<u>" : "",
875 		              field_str(stats, ST_F_PXNAME), field_str(stats, ST_F_SVNAME), field_str(stats, ST_F_SVNAME));
876 
877 		if (flags & ST_SHLGNDS) {
878 			chunk_appendf(out, "<div class=tips>");
879 
880 			if (isdigit(*field_str(stats, ST_F_ADDR)))
881 				chunk_appendf(out, "IPv4: %s, ", field_str(stats, ST_F_ADDR));
882 			else if (*field_str(stats, ST_F_ADDR) == '[')
883 				chunk_appendf(out, "IPv6: %s, ", field_str(stats, ST_F_ADDR));
884 			else if (*field_str(stats, ST_F_ADDR))
885 				chunk_appendf(out, "%s, ", field_str(stats, ST_F_ADDR));
886 
887 			/* id */
888 			chunk_appendf(out, "id: %d", stats[ST_F_SID].u.u32);
889 
890 			/* cookie */
891 			if (stats[ST_F_COOKIE].type) {
892 				chunk_appendf(out, ", cookie: '");
893 				chunk_initstr(&src, field_str(stats, ST_F_COOKIE));
894 				chunk_htmlencode(out, &src);
895 				chunk_appendf(out, "'");
896 			}
897 
898 			chunk_appendf(out, "</div>");
899 		}
900 
901 		chunk_appendf(out,
902 		              /* queue : current, max, limit */
903 		              "%s</td><td>%s</td><td>%s</td><td>%s</td>"
904 		              /* sessions rate : current, max, limit */
905 		              "<td>%s</td><td>%s</td><td></td>"
906 		              "",
907 		              (flags & ST_SHLGNDS) ? "</u>" : "",
908 		              U2H(stats[ST_F_QCUR].u.u32), U2H(stats[ST_F_QMAX].u.u32), LIM2A(stats[ST_F_QLIMIT].u.u32, "-"),
909 		              U2H(stats[ST_F_RATE].u.u32), U2H(stats[ST_F_RATE_MAX].u.u32));
910 
911 		chunk_appendf(out,
912 		              /* sessions: current, max, limit, total */
913 		              "<td>%s</td><td>%s</td><td>%s</td>"
914 		              "<td><u>%s<div class=tips><table class=det>"
915 		              "<tr><th>Cum. sessions:</th><td>%s</td></tr>"
916 		              "",
917 		              U2H(stats[ST_F_SCUR].u.u32), U2H(stats[ST_F_SMAX].u.u32), LIM2A(stats[ST_F_SLIM].u.u32, "-"),
918 		              U2H(stats[ST_F_STOT].u.u64),
919 		              U2H(stats[ST_F_STOT].u.u64));
920 
921 		/* http response (via hover): 1xx, 2xx, 3xx, 4xx, 5xx, other */
922 		if (strcmp(field_str(stats, ST_F_MODE), "http") == 0) {
923 			unsigned long long tot;
924 
925 			tot  = stats[ST_F_HRSP_OTHER].u.u64;
926 			tot += stats[ST_F_HRSP_1XX].u.u64;
927 			tot += stats[ST_F_HRSP_2XX].u.u64;
928 			tot += stats[ST_F_HRSP_3XX].u.u64;
929 			tot += stats[ST_F_HRSP_4XX].u.u64;
930 			tot += stats[ST_F_HRSP_5XX].u.u64;
931 
932 			chunk_appendf(out,
933 			              "<tr><th>Cum. HTTP responses:</th><td>%s</td></tr>"
934 			              "<tr><th>- HTTP 1xx responses:</th><td>%s</td><td>(%d%%)</td></tr>"
935 			              "<tr><th>- HTTP 2xx responses:</th><td>%s</td><td>(%d%%)</td></tr>"
936 			              "<tr><th>- HTTP 3xx responses:</th><td>%s</td><td>(%d%%)</td></tr>"
937 			              "<tr><th>- HTTP 4xx responses:</th><td>%s</td><td>(%d%%)</td></tr>"
938 			              "<tr><th>- HTTP 5xx responses:</th><td>%s</td><td>(%d%%)</td></tr>"
939 			              "<tr><th>- other responses:</th><td>%s</td><td>(%d%%)</td></tr>"
940 			              "",
941 			              U2H(tot),
942 			              U2H(stats[ST_F_HRSP_1XX].u.u64), tot ? (int)(100 * stats[ST_F_HRSP_1XX].u.u64 / tot) : 0,
943 			              U2H(stats[ST_F_HRSP_2XX].u.u64), tot ? (int)(100 * stats[ST_F_HRSP_2XX].u.u64 / tot) : 0,
944 			              U2H(stats[ST_F_HRSP_3XX].u.u64), tot ? (int)(100 * stats[ST_F_HRSP_3XX].u.u64 / tot) : 0,
945 			              U2H(stats[ST_F_HRSP_4XX].u.u64), tot ? (int)(100 * stats[ST_F_HRSP_4XX].u.u64 / tot) : 0,
946 			              U2H(stats[ST_F_HRSP_5XX].u.u64), tot ? (int)(100 * stats[ST_F_HRSP_5XX].u.u64 / tot) : 0,
947 			              U2H(stats[ST_F_HRSP_OTHER].u.u64), tot ? (int)(100 * stats[ST_F_HRSP_OTHER].u.u64 / tot) : 0);
948 		}
949 
950 		chunk_appendf(out, "<tr><th colspan=3>Avg over last 1024 success. conn.</th></tr>");
951 		chunk_appendf(out, "<tr><th>- Queue time:</th><td>%s</td><td>ms</td></tr>",   U2H(stats[ST_F_QTIME].u.u32));
952 		chunk_appendf(out, "<tr><th>- Connect time:</th><td>%s</td><td>ms</td></tr>", U2H(stats[ST_F_CTIME].u.u32));
953 		if (strcmp(field_str(stats, ST_F_MODE), "http") == 0)
954 			chunk_appendf(out, "<tr><th>- Response time:</th><td>%s</td><td>ms</td></tr>", U2H(stats[ST_F_RTIME].u.u32));
955 		chunk_appendf(out, "<tr><th>- Total time:</th><td>%s</td><td>ms</td></tr>",   U2H(stats[ST_F_TTIME].u.u32));
956 
957 		chunk_appendf(out,
958 		              "</table></div></u></td>"
959 		              /* sessions: lbtot, last */
960 		              "<td>%s</td><td>%s</td>",
961 		              U2H(stats[ST_F_LBTOT].u.u64),
962 		              human_time(stats[ST_F_LASTSESS].u.s32, 1));
963 
964 		chunk_appendf(out,
965 		              /* bytes : in, out */
966 		              "<td>%s</td><td>%s</td>"
967 		              /* denied: req, resp */
968 		              "<td></td><td>%s</td>"
969 		              /* errors : request, connect */
970 		              "<td></td><td>%s</td>"
971 		              /* errors : response */
972 		              "<td><u>%s<div class=tips>Connection resets during transfers: %lld client, %lld server</div></u></td>"
973 		              /* warnings: retries, redispatches */
974 		              "<td>%lld</td><td>%lld</td>"
975 		              "",
976 		              U2H(stats[ST_F_BIN].u.u64), U2H(stats[ST_F_BOUT].u.u64),
977 		              U2H(stats[ST_F_DRESP].u.u64),
978 		              U2H(stats[ST_F_ECON].u.u64),
979 		              U2H(stats[ST_F_ERESP].u.u64),
980 		              (long long)stats[ST_F_CLI_ABRT].u.u64,
981 		              (long long)stats[ST_F_SRV_ABRT].u.u64,
982 		              (long long)stats[ST_F_WRETR].u.u64,
983 			      (long long)stats[ST_F_WREDIS].u.u64);
984 
985 		/* status, last change */
986 		chunk_appendf(out, "<td class=ac>");
987 
988 		/* FIXME!!!!
989 		 *   LASTCHG should contain the last change for *this* server and must be computed
990 		 * properly above, as was done below, ie: this server if maint, otherwise ref server
991 		 * if tracking. Note that ref is either local or remote depending on tracking.
992 		 */
993 
994 
995 		if (strncmp(field_str(stats, ST_F_STATUS), "MAINT", 5) == 0) {
996 			chunk_appendf(out, "%s MAINT", human_time(stats[ST_F_LASTCHG].u.u32, 1));
997 		}
998 		else if (strcmp(field_str(stats, ST_F_STATUS), "no check") == 0) {
999 			chunk_strcat(out, "<i>no check</i>");
1000 		}
1001 		else {
1002 			chunk_appendf(out, "%s %s", human_time(stats[ST_F_LASTCHG].u.u32, 1), field_str(stats, ST_F_STATUS));
1003 			if (strncmp(field_str(stats, ST_F_STATUS), "DOWN", 4) == 0) {
1004 				if (stats[ST_F_CHECK_HEALTH].u.u32)
1005 					chunk_strcat(out, " &uarr;");
1006 			}
1007 			else if (stats[ST_F_CHECK_HEALTH].u.u32 < stats[ST_F_CHECK_RISE].u.u32 + stats[ST_F_CHECK_FALL].u.u32 - 1)
1008 				chunk_strcat(out, " &darr;");
1009 		}
1010 
1011 		if (strncmp(field_str(stats, ST_F_STATUS), "DOWN", 4) == 0 &&
1012 		    stats[ST_F_AGENT_STATUS].type && !stats[ST_F_AGENT_HEALTH].u.u32) {
1013 			chunk_appendf(out,
1014 			              "</td><td class=ac><u> %s",
1015 			              field_str(stats, ST_F_AGENT_STATUS));
1016 
1017 			if (stats[ST_F_AGENT_CODE].type)
1018 				chunk_appendf(out, "/%d", stats[ST_F_AGENT_CODE].u.u32);
1019 
1020 			if (stats[ST_F_AGENT_DURATION].type)
1021 				chunk_appendf(out, " in %lums", (long)stats[ST_F_AGENT_DURATION].u.u64);
1022 
1023 			chunk_appendf(out, "<div class=tips>%s", field_str(stats, ST_F_AGENT_DESC));
1024 
1025 			if (*field_str(stats, ST_F_LAST_AGT)) {
1026 				chunk_appendf(out, ": ");
1027 				chunk_initstr(&src, field_str(stats, ST_F_LAST_AGT));
1028 				chunk_htmlencode(out, &src);
1029 			}
1030 			chunk_appendf(out, "</div></u>");
1031 		}
1032 		else if (stats[ST_F_CHECK_STATUS].type) {
1033 			chunk_appendf(out,
1034 			              "</td><td class=ac><u> %s",
1035 			              field_str(stats, ST_F_CHECK_STATUS));
1036 
1037 			if (stats[ST_F_CHECK_CODE].type)
1038 				chunk_appendf(out, "/%d", stats[ST_F_CHECK_CODE].u.u32);
1039 
1040 			if (stats[ST_F_CHECK_DURATION].type)
1041 				chunk_appendf(out, " in %lums", (long)stats[ST_F_CHECK_DURATION].u.u64);
1042 
1043 			chunk_appendf(out, "<div class=tips>%s", field_str(stats, ST_F_CHECK_DESC));
1044 
1045 			if (*field_str(stats, ST_F_LAST_CHK)) {
1046 				chunk_appendf(out, ": ");
1047 				chunk_initstr(&src, field_str(stats, ST_F_LAST_CHK));
1048 				chunk_htmlencode(out, &src);
1049 			}
1050 			chunk_appendf(out, "</div></u>");
1051 		}
1052 		else
1053 			chunk_appendf(out, "</td><td>");
1054 
1055 		chunk_appendf(out,
1056 		              /* weight */
1057 		              "</td><td class=ac>%d</td>"
1058 		              /* act, bck */
1059 		              "<td class=ac>%s</td><td class=ac>%s</td>"
1060 		              "",
1061 		              stats[ST_F_WEIGHT].u.u32,
1062 		              stats[ST_F_BCK].u.u32 ? "-" : "Y",
1063 		              stats[ST_F_BCK].u.u32 ? "Y" : "-");
1064 
1065 		/* check failures: unique, fatal, down time */
1066 		if (strcmp(field_str(stats, ST_F_STATUS), "MAINT (resolution)") == 0) {
1067 			chunk_appendf(out, "<td class=ac colspan=3>resolution</td>");
1068 		}
1069 		else if (stats[ST_F_CHKFAIL].type) {
1070 			chunk_appendf(out, "<td><u>%lld", (long long)stats[ST_F_CHKFAIL].u.u64);
1071 
1072 			if (stats[ST_F_HANAFAIL].type)
1073 				chunk_appendf(out, "/%lld", (long long)stats[ST_F_HANAFAIL].u.u64);
1074 
1075 			chunk_appendf(out,
1076 			              "<div class=tips>Failed Health Checks%s</div></u></td>"
1077 			              "<td>%lld</td><td>%s</td>"
1078 			              "",
1079 			              stats[ST_F_HANAFAIL].type ? "/Health Analyses" : "",
1080 			              (long long)stats[ST_F_CHKDOWN].u.u64, human_time(stats[ST_F_DOWNTIME].u.u32, 1));
1081 		}
1082 		else if (strcmp(field_str(stats, ST_F_STATUS), "MAINT") != 0 && field_format(stats, ST_F_TRACKED) == FF_STR) {
1083 			/* tracking a server (hence inherited maint would appear as "MAINT (via...)" */
1084 			chunk_appendf(out,
1085 			              "<td class=ac colspan=3><a class=lfsb href=\"#%s\">via %s</a></td>",
1086 			              field_str(stats, ST_F_TRACKED), field_str(stats, ST_F_TRACKED));
1087 		}
1088 		else
1089 			chunk_appendf(out, "<td colspan=3></td>");
1090 
1091 		/* throttle */
1092 		if (stats[ST_F_THROTTLE].type)
1093 			chunk_appendf(out, "<td class=ac>%d %%</td></tr>\n", stats[ST_F_THROTTLE].u.u32);
1094 		else
1095 			chunk_appendf(out, "<td class=ac>-</td></tr>\n");
1096 	}
1097 	else if (stats[ST_F_TYPE].u.u32 == STATS_TYPE_BE) {
1098 		chunk_appendf(out, "<tr class=\"backend\">");
1099 		if (flags & ST_SHOWADMIN) {
1100 			/* Column sub-heading for Enable or Disable server */
1101 			chunk_appendf(out, "<td></td>");
1102 		}
1103 		chunk_appendf(out,
1104 		              "<td class=ac>"
1105 		              /* name */
1106 		              "%s<a name=\"%s/Backend\"></a>"
1107 		              "<a class=lfsb href=\"#%s/Backend\">Backend</a>"
1108 		              "",
1109 		              (flags & ST_SHLGNDS)?"<u>":"",
1110 		              field_str(stats, ST_F_PXNAME), field_str(stats, ST_F_PXNAME));
1111 
1112 		if (flags & ST_SHLGNDS) {
1113 			/* balancing */
1114 			chunk_appendf(out, "<div class=tips>balancing: %s",
1115 			              field_str(stats, ST_F_ALGO));
1116 
1117 			/* cookie */
1118 			if (stats[ST_F_COOKIE].type) {
1119 				chunk_appendf(out, ", cookie: '");
1120 				chunk_initstr(&src, field_str(stats, ST_F_COOKIE));
1121 				chunk_htmlencode(out, &src);
1122 				chunk_appendf(out, "'");
1123 			}
1124 			chunk_appendf(out, "</div>");
1125 		}
1126 
1127 		chunk_appendf(out,
1128 		              "%s</td>"
1129 		              /* queue : current, max */
1130 		              "<td>%s</td><td>%s</td><td></td>"
1131 		              /* sessions rate : current, max, limit */
1132 		              "<td>%s</td><td>%s</td><td></td>"
1133 		              "",
1134 		              (flags & ST_SHLGNDS)?"</u>":"",
1135 		              U2H(stats[ST_F_QCUR].u.u32), U2H(stats[ST_F_QMAX].u.u32),
1136 		              U2H(stats[ST_F_RATE].u.u32), U2H(stats[ST_F_RATE_MAX].u.u32));
1137 
1138 		chunk_appendf(out,
1139 		              /* sessions: current, max, limit, total */
1140 		              "<td>%s</td><td>%s</td><td>%s</td>"
1141 		              "<td><u>%s<div class=tips><table class=det>"
1142 		              "<tr><th>Cum. sessions:</th><td>%s</td></tr>"
1143 		              "",
1144 		              U2H(stats[ST_F_SCUR].u.u32), U2H(stats[ST_F_SMAX].u.u32), U2H(stats[ST_F_SLIM].u.u32),
1145 		              U2H(stats[ST_F_STOT].u.u64),
1146 		              U2H(stats[ST_F_STOT].u.u64));
1147 
1148 		/* http response (via hover): 1xx, 2xx, 3xx, 4xx, 5xx, other */
1149 		if (strcmp(field_str(stats, ST_F_MODE), "http") == 0) {
1150 			chunk_appendf(out,
1151 			              "<tr><th>Cum. HTTP requests:</th><td>%s</td></tr>"
1152 			              "<tr><th>- HTTP 1xx responses:</th><td>%s</td></tr>"
1153 			              "<tr><th>- HTTP 2xx responses:</th><td>%s</td></tr>"
1154 			              "<tr><th>&nbsp;&nbsp;Compressed 2xx:</th><td>%s</td><td>(%d%%)</td></tr>"
1155 			              "<tr><th>- HTTP 3xx responses:</th><td>%s</td></tr>"
1156 			              "<tr><th>- HTTP 4xx responses:</th><td>%s</td></tr>"
1157 			              "<tr><th>- HTTP 5xx responses:</th><td>%s</td></tr>"
1158 			              "<tr><th>- other responses:</th><td>%s</td></tr>"
1159 				      "<tr><th colspan=3>Avg over last 1024 success. conn.</th></tr>"
1160 			              "",
1161 			              U2H(stats[ST_F_REQ_TOT].u.u64),
1162 			              U2H(stats[ST_F_HRSP_1XX].u.u64),
1163 			              U2H(stats[ST_F_HRSP_2XX].u.u64),
1164 			              U2H(stats[ST_F_COMP_RSP].u.u64),
1165 			              stats[ST_F_HRSP_2XX].u.u64 ?
1166 			              (int)(100 * stats[ST_F_COMP_RSP].u.u64 / stats[ST_F_HRSP_2XX].u.u64) : 0,
1167 			              U2H(stats[ST_F_HRSP_3XX].u.u64),
1168 			              U2H(stats[ST_F_HRSP_4XX].u.u64),
1169 			              U2H(stats[ST_F_HRSP_5XX].u.u64),
1170 			              U2H(stats[ST_F_HRSP_OTHER].u.u64));
1171 		}
1172 
1173 		chunk_appendf(out, "<tr><th>- Queue time:</th><td>%s</td><td>ms</td></tr>",   U2H(stats[ST_F_QTIME].u.u32));
1174 		chunk_appendf(out, "<tr><th>- Connect time:</th><td>%s</td><td>ms</td></tr>", U2H(stats[ST_F_CTIME].u.u32));
1175 		if (strcmp(field_str(stats, ST_F_MODE), "http") == 0)
1176 			chunk_appendf(out, "<tr><th>- Response time:</th><td>%s</td><td>ms</td></tr>", U2H(stats[ST_F_RTIME].u.u32));
1177 		chunk_appendf(out, "<tr><th>- Total time:</th><td>%s</td><td>ms</td></tr>",   U2H(stats[ST_F_TTIME].u.u32));
1178 
1179 		chunk_appendf(out,
1180 		              "</table></div></u></td>"
1181 		              /* sessions: lbtot, last */
1182 		              "<td>%s</td><td>%s</td>"
1183 		              /* bytes: in */
1184 		              "<td>%s</td>"
1185 		              "",
1186 		              U2H(stats[ST_F_LBTOT].u.u64),
1187 		              human_time(stats[ST_F_LASTSESS].u.s32, 1),
1188 		              U2H(stats[ST_F_BIN].u.u64));
1189 
1190 		chunk_appendf(out,
1191 			      /* bytes:out + compression stats (via hover): comp_in, comp_out, comp_byp */
1192 		              "<td>%s%s<div class=tips><table class=det>"
1193 			      "<tr><th>Response bytes in:</th><td>%s</td></tr>"
1194 			      "<tr><th>Compression in:</th><td>%s</td></tr>"
1195 			      "<tr><th>Compression out:</th><td>%s</td><td>(%d%%)</td></tr>"
1196 			      "<tr><th>Compression bypass:</th><td>%s</td></tr>"
1197 			      "<tr><th>Total bytes saved:</th><td>%s</td><td>(%d%%)</td></tr>"
1198 			      "</table></div>%s</td>",
1199 		              (stats[ST_F_COMP_IN].u.u64 || stats[ST_F_COMP_BYP].u.u64) ? "<u>":"",
1200 		              U2H(stats[ST_F_BOUT].u.u64),
1201 		              U2H(stats[ST_F_BOUT].u.u64),
1202 		              U2H(stats[ST_F_COMP_IN].u.u64),
1203 			      U2H(stats[ST_F_COMP_OUT].u.u64),
1204 			      stats[ST_F_COMP_IN].u.u64 ? (int)(stats[ST_F_COMP_OUT].u.u64 * 100 / stats[ST_F_COMP_IN].u.u64) : 0,
1205 			      U2H(stats[ST_F_COMP_BYP].u.u64),
1206 			      U2H(stats[ST_F_COMP_IN].u.u64 - stats[ST_F_COMP_OUT].u.u64),
1207 			      stats[ST_F_BOUT].u.u64 ? (int)((stats[ST_F_COMP_IN].u.u64 - stats[ST_F_COMP_OUT].u.u64) * 100 / stats[ST_F_BOUT].u.u64) : 0,
1208 		              (stats[ST_F_COMP_IN].u.u64 || stats[ST_F_COMP_BYP].u.u64) ? "</u>":"");
1209 
1210 		chunk_appendf(out,
1211 		              /* denied: req, resp */
1212 		              "<td>%s</td><td>%s</td>"
1213 		              /* errors : request, connect */
1214 		              "<td></td><td>%s</td>"
1215 		              /* errors : response */
1216 		              "<td><u>%s<div class=tips>Connection resets during transfers: %lld client, %lld server</div></u></td>"
1217 		              /* warnings: retries, redispatches */
1218 		              "<td>%lld</td><td>%lld</td>"
1219 		              /* backend status: reflect backend status (up/down): we display UP
1220 		               * if the backend has known working servers or if it has no server at
1221 		               * all (eg: for stats). Then we display the total weight, number of
1222 		               * active and backups. */
1223 		              "<td class=ac>%s %s</td><td class=ac>&nbsp;</td><td class=ac>%d</td>"
1224 		              "<td class=ac>%d</td><td class=ac>%d</td>"
1225 		              "",
1226 		              U2H(stats[ST_F_DREQ].u.u64), U2H(stats[ST_F_DRESP].u.u64),
1227 		              U2H(stats[ST_F_ECON].u.u64),
1228 		              U2H(stats[ST_F_ERESP].u.u64),
1229 		              (long long)stats[ST_F_CLI_ABRT].u.u64,
1230 		              (long long)stats[ST_F_SRV_ABRT].u.u64,
1231 		              (long long)stats[ST_F_WRETR].u.u64, (long long)stats[ST_F_WREDIS].u.u64,
1232 		              human_time(stats[ST_F_LASTCHG].u.u32, 1),
1233 		              strcmp(field_str(stats, ST_F_STATUS), "DOWN") ? field_str(stats, ST_F_STATUS) : "<font color=\"red\"><b>DOWN</b></font>",
1234 		              stats[ST_F_WEIGHT].u.u32,
1235 		              stats[ST_F_ACT].u.u32, stats[ST_F_BCK].u.u32);
1236 
1237 		chunk_appendf(out,
1238 		              /* rest of backend: nothing, down transitions, total downtime, throttle */
1239 		              "<td class=ac>&nbsp;</td><td>%d</td>"
1240 		              "<td>%s</td>"
1241 		              "<td></td>"
1242 		              "</tr>",
1243 		              stats[ST_F_CHKDOWN].u.u32,
1244 		              stats[ST_F_DOWNTIME].type ? human_time(stats[ST_F_DOWNTIME].u.u32, 1) : "&nbsp;");
1245 	}
1246 	return 1;
1247 }
1248 
stats_dump_one_line(const struct field * stats,unsigned int flags,struct proxy * px,struct appctx * appctx)1249 int stats_dump_one_line(const struct field *stats, unsigned int flags, struct proxy *px, struct appctx *appctx)
1250 {
1251 	int ret;
1252 
1253 	if ((px->cap & PR_CAP_BE) && px->srv && (appctx->ctx.stats.flags & STAT_ADMIN))
1254 		flags |= ST_SHOWADMIN;
1255 
1256 	if (appctx->ctx.stats.flags & STAT_FMT_HTML)
1257 		ret = stats_dump_fields_html(&trash, stats, flags);
1258 	else if (appctx->ctx.stats.flags & STAT_FMT_TYPED)
1259 		ret = stats_dump_fields_typed(&trash, stats);
1260 	else if (appctx->ctx.stats.flags & STAT_FMT_JSON)
1261 		ret = stats_dump_fields_json(&trash, stats,
1262 					     !(appctx->ctx.stats.flags &
1263 					       STAT_STARTED));
1264 	else
1265 		ret = stats_dump_fields_csv(&trash, stats);
1266 
1267 	if (ret)
1268 		appctx->ctx.stats.flags |= STAT_STARTED;
1269 
1270 	return ret;
1271 }
1272 
1273 /* Fill <stats> with the frontend statistics. <stats> is
1274  * preallocated array of length <len>. The length of the array
1275  * must be at least ST_F_TOTAL_FIELDS. If this length is less then
1276  * this value, the function returns 0, otherwise, it returns 1.
1277  */
stats_fill_fe_stats(struct proxy * px,struct field * stats,int len)1278 int stats_fill_fe_stats(struct proxy *px, struct field *stats, int len)
1279 {
1280 	if (len < ST_F_TOTAL_FIELDS)
1281 		return 0;
1282 
1283 	memset(stats, 0, sizeof(*stats) * len);
1284 
1285 	stats[ST_F_PXNAME]   = mkf_str(FO_KEY|FN_NAME|FS_SERVICE, px->id);
1286 	stats[ST_F_SVNAME]   = mkf_str(FO_KEY|FN_NAME|FS_SERVICE, "FRONTEND");
1287 	stats[ST_F_MODE]     = mkf_str(FO_CONFIG|FS_SERVICE, proxy_mode_str(px->mode));
1288 	stats[ST_F_SCUR]     = mkf_u32(0, px->feconn);
1289 	stats[ST_F_SMAX]     = mkf_u32(FN_MAX, px->fe_counters.conn_max);
1290 	stats[ST_F_SLIM]     = mkf_u32(FO_CONFIG|FN_LIMIT, px->maxconn);
1291 	stats[ST_F_STOT]     = mkf_u64(FN_COUNTER, px->fe_counters.cum_sess);
1292 	stats[ST_F_BIN]      = mkf_u64(FN_COUNTER, px->fe_counters.bytes_in);
1293 	stats[ST_F_BOUT]     = mkf_u64(FN_COUNTER, px->fe_counters.bytes_out);
1294 	stats[ST_F_DREQ]     = mkf_u64(FN_COUNTER, px->fe_counters.denied_req);
1295 	stats[ST_F_DRESP]    = mkf_u64(FN_COUNTER, px->fe_counters.denied_resp);
1296 	stats[ST_F_EREQ]     = mkf_u64(FN_COUNTER, px->fe_counters.failed_req);
1297 	stats[ST_F_DCON]     = mkf_u64(FN_COUNTER, px->fe_counters.denied_conn);
1298 	stats[ST_F_DSES]     = mkf_u64(FN_COUNTER, px->fe_counters.denied_sess);
1299 	stats[ST_F_STATUS]   = mkf_str(FO_STATUS, px->state == PR_STREADY ? "OPEN" : px->state == PR_STFULL ? "FULL" : "STOP");
1300 	stats[ST_F_PID]      = mkf_u32(FO_KEY, relative_pid);
1301 	stats[ST_F_IID]      = mkf_u32(FO_KEY|FS_SERVICE, px->uuid);
1302 	stats[ST_F_SID]      = mkf_u32(FO_KEY|FS_SERVICE, 0);
1303 	stats[ST_F_TYPE]     = mkf_u32(FO_CONFIG|FS_SERVICE, STATS_TYPE_FE);
1304 	stats[ST_F_RATE]     = mkf_u32(FN_RATE, read_freq_ctr(&px->fe_sess_per_sec));
1305 	stats[ST_F_RATE_LIM] = mkf_u32(FO_CONFIG|FN_LIMIT, px->fe_sps_lim);
1306 	stats[ST_F_RATE_MAX] = mkf_u32(FN_MAX, px->fe_counters.sps_max);
1307 
1308 	/* http response: 1xx, 2xx, 3xx, 4xx, 5xx, other */
1309 	if (px->mode == PR_MODE_HTTP) {
1310 		stats[ST_F_HRSP_1XX]    = mkf_u64(FN_COUNTER, px->fe_counters.p.http.rsp[1]);
1311 		stats[ST_F_HRSP_2XX]    = mkf_u64(FN_COUNTER, px->fe_counters.p.http.rsp[2]);
1312 		stats[ST_F_HRSP_3XX]    = mkf_u64(FN_COUNTER, px->fe_counters.p.http.rsp[3]);
1313 		stats[ST_F_HRSP_4XX]    = mkf_u64(FN_COUNTER, px->fe_counters.p.http.rsp[4]);
1314 		stats[ST_F_HRSP_5XX]    = mkf_u64(FN_COUNTER, px->fe_counters.p.http.rsp[5]);
1315 		stats[ST_F_HRSP_OTHER]  = mkf_u64(FN_COUNTER, px->fe_counters.p.http.rsp[0]);
1316 		stats[ST_F_INTERCEPTED] = mkf_u64(FN_COUNTER, px->fe_counters.intercepted_req);
1317 	}
1318 
1319 	/* requests : req_rate, req_rate_max, req_tot, */
1320 	stats[ST_F_REQ_RATE]     = mkf_u32(FN_RATE, read_freq_ctr(&px->fe_req_per_sec));
1321 	stats[ST_F_REQ_RATE_MAX] = mkf_u32(FN_MAX, px->fe_counters.p.http.rps_max);
1322 	stats[ST_F_REQ_TOT]      = mkf_u64(FN_COUNTER, px->fe_counters.p.http.cum_req);
1323 
1324 	/* compression: in, out, bypassed, responses */
1325 	stats[ST_F_COMP_IN]      = mkf_u64(FN_COUNTER, px->fe_counters.comp_in);
1326 	stats[ST_F_COMP_OUT]     = mkf_u64(FN_COUNTER, px->fe_counters.comp_out);
1327 	stats[ST_F_COMP_BYP]     = mkf_u64(FN_COUNTER, px->fe_counters.comp_byp);
1328 	stats[ST_F_COMP_RSP]     = mkf_u64(FN_COUNTER, px->fe_counters.p.http.comp_rsp);
1329 
1330 	/* connections : conn_rate, conn_rate_max, conn_tot, conn_max */
1331 	stats[ST_F_CONN_RATE]     = mkf_u32(FN_RATE, read_freq_ctr(&px->fe_conn_per_sec));
1332 	stats[ST_F_CONN_RATE_MAX] = mkf_u32(FN_MAX, px->fe_counters.cps_max);
1333 	stats[ST_F_CONN_TOT]      = mkf_u64(FN_COUNTER, px->fe_counters.cum_conn);
1334 
1335 	return 1;
1336 }
1337 
1338 /* Dumps a frontend's line to the trash for the current proxy <px> and uses
1339  * the state from stream interface <si>. The caller is responsible for clearing
1340  * the trash if needed. Returns non-zero if it emits anything, zero otherwise.
1341  */
stats_dump_fe_stats(struct stream_interface * si,struct proxy * px)1342 static int stats_dump_fe_stats(struct stream_interface *si, struct proxy *px)
1343 {
1344 	struct appctx *appctx = __objt_appctx(si->end);
1345 
1346 	if (!(px->cap & PR_CAP_FE))
1347 		return 0;
1348 
1349 	if ((appctx->ctx.stats.flags & STAT_BOUND) && !(appctx->ctx.stats.type & (1 << STATS_TYPE_FE)))
1350 		return 0;
1351 
1352 	if (!stats_fill_fe_stats(px, stats, ST_F_TOTAL_FIELDS))
1353 		return 0;
1354 
1355 	return stats_dump_one_line(stats, 0, px, appctx);
1356 }
1357 
1358 /* Fill <stats> with the listener statistics. <stats> is
1359  * preallocated array of length <len>. The length of the array
1360  * must be at least ST_F_TOTAL_FIELDS. If this length is less
1361  * then this value, the function returns 0, otherwise, it
1362  * returns 1. <flags> can take the value ST_SHLGNDS.
1363  */
stats_fill_li_stats(struct proxy * px,struct listener * l,int flags,struct field * stats,int len)1364 int stats_fill_li_stats(struct proxy *px, struct listener *l, int flags,
1365                         struct field *stats, int len)
1366 {
1367 	struct chunk *out = get_trash_chunk();
1368 
1369 	if (len < ST_F_TOTAL_FIELDS)
1370 		return 0;
1371 
1372 	if (!l->counters)
1373 		return 0;
1374 
1375 	chunk_reset(out);
1376 	memset(stats, 0, sizeof(*stats) * len);
1377 
1378 	stats[ST_F_PXNAME]   = mkf_str(FO_KEY|FN_NAME|FS_SERVICE, px->id);
1379 	stats[ST_F_SVNAME]   = mkf_str(FO_KEY|FN_NAME|FS_SERVICE, l->name);
1380 	stats[ST_F_MODE]     = mkf_str(FO_CONFIG|FS_SERVICE, proxy_mode_str(px->mode));
1381 	stats[ST_F_SCUR]     = mkf_u32(0, l->nbconn);
1382 	stats[ST_F_SMAX]     = mkf_u32(FN_MAX, l->counters->conn_max);
1383 	stats[ST_F_SLIM]     = mkf_u32(FO_CONFIG|FN_LIMIT, l->maxconn);
1384 	stats[ST_F_STOT]     = mkf_u64(FN_COUNTER, l->counters->cum_conn);
1385 	stats[ST_F_BIN]      = mkf_u64(FN_COUNTER, l->counters->bytes_in);
1386 	stats[ST_F_BOUT]     = mkf_u64(FN_COUNTER, l->counters->bytes_out);
1387 	stats[ST_F_DREQ]     = mkf_u64(FN_COUNTER, l->counters->denied_req);
1388 	stats[ST_F_DRESP]    = mkf_u64(FN_COUNTER, l->counters->denied_resp);
1389 	stats[ST_F_EREQ]     = mkf_u64(FN_COUNTER, l->counters->failed_req);
1390 	stats[ST_F_DCON]     = mkf_u64(FN_COUNTER, l->counters->denied_conn);
1391 	stats[ST_F_DSES]     = mkf_u64(FN_COUNTER, l->counters->denied_sess);
1392 	stats[ST_F_STATUS]   = mkf_str(FO_STATUS, (l->nbconn < l->maxconn) ? (l->state == LI_LIMITED) ? "WAITING" : "OPEN" : "FULL");
1393 	stats[ST_F_PID]      = mkf_u32(FO_KEY, relative_pid);
1394 	stats[ST_F_IID]      = mkf_u32(FO_KEY|FS_SERVICE, px->uuid);
1395 	stats[ST_F_SID]      = mkf_u32(FO_KEY|FS_SERVICE, l->luid);
1396 	stats[ST_F_TYPE]     = mkf_u32(FO_CONFIG|FS_SERVICE, STATS_TYPE_SO);
1397 
1398 	if (flags & ST_SHLGNDS) {
1399 		char str[INET6_ADDRSTRLEN];
1400 		int port;
1401 
1402 		port = get_host_port(&l->addr);
1403 		switch (addr_to_str(&l->addr, str, sizeof(str))) {
1404 		case AF_INET:
1405 			stats[ST_F_ADDR] = mkf_str(FO_CONFIG|FS_SERVICE, chunk_newstr(out));
1406 			chunk_appendf(out, "%s:%d", str, port);
1407 			break;
1408 		case AF_INET6:
1409 			stats[ST_F_ADDR] = mkf_str(FO_CONFIG|FS_SERVICE, chunk_newstr(out));
1410 			chunk_appendf(out, "[%s]:%d", str, port);
1411 			break;
1412 		case AF_UNIX:
1413 			stats[ST_F_ADDR] = mkf_str(FO_CONFIG|FS_SERVICE, "unix");
1414 			break;
1415 		case -1:
1416 			stats[ST_F_ADDR] = mkf_str(FO_CONFIG|FS_SERVICE, chunk_newstr(out));
1417 			chunk_strcat(out, strerror(errno));
1418 			break;
1419 		default: /* address family not supported */
1420 			break;
1421 		}
1422 	}
1423 
1424 	return 1;
1425 }
1426 
1427 /* Dumps a line for listener <l> and proxy <px> to the trash and uses the state
1428  * from stream interface <si>, and stats flags <flags>. The caller is responsible
1429  * for clearing the trash if needed. Returns non-zero if it emits anything, zero
1430  * otherwise.
1431  */
stats_dump_li_stats(struct stream_interface * si,struct proxy * px,struct listener * l,int flags)1432 static int stats_dump_li_stats(struct stream_interface *si, struct proxy *px, struct listener *l, int flags)
1433 {
1434 	struct appctx *appctx = __objt_appctx(si->end);
1435 
1436 	if (!stats_fill_li_stats(px, l, flags, stats, ST_F_TOTAL_FIELDS))
1437 		return 0;
1438 
1439 	return stats_dump_one_line(stats, flags, px, appctx);
1440 }
1441 
1442 enum srv_stats_state {
1443 	SRV_STATS_STATE_DOWN = 0,
1444 	SRV_STATS_STATE_DOWN_AGENT,
1445 	SRV_STATS_STATE_GOING_UP,
1446 	SRV_STATS_STATE_UP_GOING_DOWN,
1447 	SRV_STATS_STATE_UP,
1448 	SRV_STATS_STATE_NOLB_GOING_DOWN,
1449 	SRV_STATS_STATE_NOLB,
1450 	SRV_STATS_STATE_DRAIN_GOING_DOWN,
1451 	SRV_STATS_STATE_DRAIN,
1452 	SRV_STATS_STATE_DRAIN_AGENT,
1453 	SRV_STATS_STATE_NO_CHECK,
1454 
1455 	SRV_STATS_STATE_COUNT, /* Must be last */
1456 };
1457 
1458 static const char *srv_hlt_st[SRV_STATS_STATE_COUNT] = {
1459 	[SRV_STATS_STATE_DOWN]			= "DOWN",
1460 	[SRV_STATS_STATE_DOWN_AGENT]		= "DOWN (agent)",
1461 	[SRV_STATS_STATE_GOING_UP]		= "DOWN %d/%d",
1462 	[SRV_STATS_STATE_UP_GOING_DOWN]		= "UP %d/%d",
1463 	[SRV_STATS_STATE_UP]			= "UP",
1464 	[SRV_STATS_STATE_NOLB_GOING_DOWN]	= "NOLB %d/%d",
1465 	[SRV_STATS_STATE_NOLB]			= "NOLB",
1466 	[SRV_STATS_STATE_DRAIN_GOING_DOWN]	= "DRAIN %d/%d",
1467 	[SRV_STATS_STATE_DRAIN]			= "DRAIN",
1468 	[SRV_STATS_STATE_DRAIN_AGENT]		= "DRAIN (agent)",
1469 	[SRV_STATS_STATE_NO_CHECK]		= "no check"
1470 };
1471 
1472 /* Fill <stats> with the server statistics. <stats> is
1473  * preallocated array of length <len>. The length of the array
1474  * must be at least ST_F_TOTAL_FIELDS. If this length is less
1475  * then this value, the function returns 0, otherwise, it
1476  * returns 1. <flags> can take the value ST_SHLGNDS.
1477  */
stats_fill_sv_stats(struct proxy * px,struct server * sv,int flags,struct field * stats,int len)1478 int stats_fill_sv_stats(struct proxy *px, struct server *sv, int flags,
1479                         struct field *stats, int len)
1480 {
1481 	struct server *via, *ref;
1482 	char str[INET6_ADDRSTRLEN];
1483 	struct chunk *out = get_trash_chunk();
1484 	enum srv_stats_state state;
1485 	char *fld_status;
1486 
1487 	if (len < ST_F_TOTAL_FIELDS)
1488 		return 0;
1489 
1490 	memset(stats, 0, sizeof(*stats) * len);
1491 
1492 	/* we have "via" which is the tracked server as described in the configuration,
1493 	 * and "ref" which is the checked server and the end of the chain.
1494 	 */
1495 	via = sv->track ? sv->track : sv;
1496 	ref = via;
1497 	while (ref->track)
1498 		ref = ref->track;
1499 
1500 	if (sv->cur_state == SRV_ST_RUNNING || sv->cur_state == SRV_ST_STARTING) {
1501 		if ((ref->check.state & CHK_ST_ENABLED) &&
1502 		    (ref->check.health < ref->check.rise + ref->check.fall - 1)) {
1503 			state = SRV_STATS_STATE_UP_GOING_DOWN;
1504 		} else {
1505 			state = SRV_STATS_STATE_UP;
1506 		}
1507 
1508 		if (sv->cur_admin & SRV_ADMF_DRAIN) {
1509 			if (ref->agent.state & CHK_ST_ENABLED)
1510 				state = SRV_STATS_STATE_DRAIN_AGENT;
1511 			else if (state == SRV_STATS_STATE_UP_GOING_DOWN)
1512 				state = SRV_STATS_STATE_DRAIN_GOING_DOWN;
1513 			else
1514 				state = SRV_STATS_STATE_DRAIN;
1515 		}
1516 
1517 		if (state == SRV_STATS_STATE_UP && !(ref->check.state & CHK_ST_ENABLED)) {
1518 			state = SRV_STATS_STATE_NO_CHECK;
1519 		}
1520 	}
1521 	else if (sv->cur_state == SRV_ST_STOPPING) {
1522 		if ((!(sv->check.state & CHK_ST_ENABLED) && !sv->track) ||
1523 		    (ref->check.health == ref->check.rise + ref->check.fall - 1)) {
1524 			state = SRV_STATS_STATE_NOLB;
1525 		} else {
1526 			state = SRV_STATS_STATE_NOLB_GOING_DOWN;
1527 		}
1528 	}
1529 	else {	/* stopped */
1530 		if ((ref->agent.state & CHK_ST_ENABLED) && !ref->agent.health) {
1531 			state = SRV_STATS_STATE_DOWN_AGENT;
1532 		} else if ((ref->check.state & CHK_ST_ENABLED) && !ref->check.health) {
1533 			state = SRV_STATS_STATE_DOWN; /* DOWN */
1534 		} else if ((ref->agent.state & CHK_ST_ENABLED) || (ref->check.state & CHK_ST_ENABLED)) {
1535 			state = SRV_STATS_STATE_GOING_UP;
1536 		} else {
1537 			state = SRV_STATS_STATE_DOWN; /* DOWN, unchecked */
1538 		}
1539 	}
1540 
1541 	chunk_reset(out);
1542 
1543 	stats[ST_F_PXNAME]   = mkf_str(FO_KEY|FN_NAME|FS_SERVICE, px->id);
1544 	stats[ST_F_SVNAME]   = mkf_str(FO_KEY|FN_NAME|FS_SERVICE, sv->id);
1545 	stats[ST_F_MODE]     = mkf_str(FO_CONFIG|FS_SERVICE, proxy_mode_str(px->mode));
1546 	stats[ST_F_QCUR]     = mkf_u32(0, sv->nbpend);
1547 	stats[ST_F_QMAX]     = mkf_u32(FN_MAX, sv->counters.nbpend_max);
1548 	stats[ST_F_SCUR]     = mkf_u32(0, sv->cur_sess);
1549 	stats[ST_F_SMAX]     = mkf_u32(FN_MAX, sv->counters.cur_sess_max);
1550 
1551 	if (sv->maxconn)
1552 		stats[ST_F_SLIM] = mkf_u32(FO_CONFIG|FN_LIMIT, sv->maxconn);
1553 
1554 	stats[ST_F_STOT]     = mkf_u64(FN_COUNTER, sv->counters.cum_sess);
1555 	stats[ST_F_BIN]      = mkf_u64(FN_COUNTER, sv->counters.bytes_in);
1556 	stats[ST_F_BOUT]     = mkf_u64(FN_COUNTER, sv->counters.bytes_out);
1557 	stats[ST_F_DRESP]    = mkf_u64(FN_COUNTER, sv->counters.failed_secu);
1558 	stats[ST_F_ECON]     = mkf_u64(FN_COUNTER, sv->counters.failed_conns);
1559 	stats[ST_F_ERESP]    = mkf_u64(FN_COUNTER, sv->counters.failed_resp);
1560 	stats[ST_F_WRETR]    = mkf_u64(FN_COUNTER, sv->counters.retries);
1561 	stats[ST_F_WREDIS]   = mkf_u64(FN_COUNTER, sv->counters.redispatches);
1562 
1563 	/* status */
1564 	fld_status = chunk_newstr(out);
1565 	if (sv->cur_admin & SRV_ADMF_RMAINT)
1566 		chunk_appendf(out, "MAINT (resolution)");
1567 	else if (sv->cur_admin & SRV_ADMF_IMAINT)
1568 		chunk_appendf(out, "MAINT (via %s/%s)", via->proxy->id, via->id);
1569 	else if (sv->cur_admin & SRV_ADMF_MAINT)
1570 		chunk_appendf(out, "MAINT");
1571 	else
1572 		chunk_appendf(out,
1573 			      srv_hlt_st[state],
1574 			      (ref->cur_state != SRV_ST_STOPPED) ? (ref->check.health - ref->check.rise + 1) : (ref->check.health),
1575 			      (ref->cur_state != SRV_ST_STOPPED) ? (ref->check.fall) : (ref->check.rise));
1576 
1577 	stats[ST_F_STATUS]   = mkf_str(FO_STATUS, fld_status);
1578 	stats[ST_F_LASTCHG]  = mkf_u32(FN_AGE, now.tv_sec - sv->last_change);
1579 	stats[ST_F_WEIGHT]   = mkf_u32(FN_AVG, (sv->cur_eweight * px->lbprm.wmult + px->lbprm.wdiv - 1) / px->lbprm.wdiv);
1580 	stats[ST_F_ACT]      = mkf_u32(FO_STATUS, (sv->flags & SRV_F_BACKUP) ? 0 : 1);
1581 	stats[ST_F_BCK]      = mkf_u32(FO_STATUS, (sv->flags & SRV_F_BACKUP) ? 1 : 0);
1582 
1583 	/* check failures: unique, fatal; last change, total downtime */
1584 	if (sv->check.state & CHK_ST_ENABLED) {
1585 		stats[ST_F_CHKFAIL]  = mkf_u64(FN_COUNTER, sv->counters.failed_checks);
1586 		stats[ST_F_CHKDOWN]  = mkf_u64(FN_COUNTER, sv->counters.down_trans);
1587 		stats[ST_F_DOWNTIME] = mkf_u32(FN_COUNTER, srv_downtime(sv));
1588 	}
1589 
1590 	if (sv->maxqueue)
1591 		stats[ST_F_QLIMIT]   = mkf_u32(FO_CONFIG|FS_SERVICE, sv->maxqueue);
1592 
1593 	stats[ST_F_PID]      = mkf_u32(FO_KEY, relative_pid);
1594 	stats[ST_F_IID]      = mkf_u32(FO_KEY|FS_SERVICE, px->uuid);
1595 	stats[ST_F_SID]      = mkf_u32(FO_KEY|FS_SERVICE, sv->puid);
1596 
1597 	if (sv->cur_state == SRV_ST_STARTING && !server_is_draining(sv))
1598 		stats[ST_F_THROTTLE] = mkf_u32(FN_AVG, server_throttle_rate(sv));
1599 
1600 	stats[ST_F_LBTOT]    = mkf_u64(FN_COUNTER, sv->counters.cum_lbconn);
1601 
1602 	if (sv->track) {
1603 		char *fld_track = chunk_newstr(out);
1604 
1605 		chunk_appendf(out, "%s/%s", sv->track->proxy->id, sv->track->id);
1606 		stats[ST_F_TRACKED] = mkf_str(FO_CONFIG|FN_NAME|FS_SERVICE, fld_track);
1607 	}
1608 
1609 	stats[ST_F_TYPE]     = mkf_u32(FO_CONFIG|FS_SERVICE, STATS_TYPE_SV);
1610 	stats[ST_F_RATE]     = mkf_u32(FN_RATE, read_freq_ctr(&sv->sess_per_sec));
1611 	stats[ST_F_RATE_MAX] = mkf_u32(FN_MAX, sv->counters.sps_max);
1612 
1613 	if ((sv->check.state & (CHK_ST_ENABLED|CHK_ST_PAUSED)) == CHK_ST_ENABLED) {
1614 		const char *fld_chksts;
1615 
1616 		fld_chksts = chunk_newstr(out);
1617 		chunk_strcat(out, "* "); // for check in progress
1618 		chunk_strcat(out, get_check_status_info(sv->check.status));
1619 		if (!(sv->check.state & CHK_ST_INPROGRESS))
1620 			fld_chksts += 2; // skip "* "
1621 		stats[ST_F_CHECK_STATUS] = mkf_str(FN_OUTPUT, fld_chksts);
1622 
1623 		if (sv->check.status >= HCHK_STATUS_L57DATA)
1624 			stats[ST_F_CHECK_CODE] = mkf_u32(FN_OUTPUT, sv->check.code);
1625 
1626 		if (sv->check.status >= HCHK_STATUS_CHECKED)
1627 			stats[ST_F_CHECK_DURATION] = mkf_u64(FN_DURATION, sv->check.duration);
1628 
1629 		stats[ST_F_CHECK_DESC] = mkf_str(FN_OUTPUT, get_check_status_description(sv->check.status));
1630 		stats[ST_F_LAST_CHK] = mkf_str(FN_OUTPUT, sv->check.desc);
1631 		stats[ST_F_CHECK_RISE]   = mkf_u32(FO_CONFIG|FS_SERVICE, ref->check.rise);
1632 		stats[ST_F_CHECK_FALL]   = mkf_u32(FO_CONFIG|FS_SERVICE, ref->check.fall);
1633 		stats[ST_F_CHECK_HEALTH] = mkf_u32(FO_CONFIG|FS_SERVICE, ref->check.health);
1634 	}
1635 
1636 	if ((sv->agent.state & (CHK_ST_ENABLED|CHK_ST_PAUSED)) == CHK_ST_ENABLED) {
1637 		const char *fld_chksts;
1638 
1639 		fld_chksts = chunk_newstr(out);
1640 		chunk_strcat(out, "* "); // for check in progress
1641 		chunk_strcat(out, get_check_status_info(sv->agent.status));
1642 		if (!(sv->agent.state & CHK_ST_INPROGRESS))
1643 			fld_chksts += 2; // skip "* "
1644 		stats[ST_F_AGENT_STATUS] = mkf_str(FN_OUTPUT, fld_chksts);
1645 
1646 		if (sv->agent.status >= HCHK_STATUS_L57DATA)
1647 			stats[ST_F_AGENT_CODE] = mkf_u32(FN_OUTPUT, sv->agent.code);
1648 
1649 		if (sv->agent.status >= HCHK_STATUS_CHECKED)
1650 			stats[ST_F_AGENT_DURATION] = mkf_u64(FN_DURATION, sv->agent.duration);
1651 
1652 		stats[ST_F_AGENT_DESC] = mkf_str(FN_OUTPUT, get_check_status_description(sv->agent.status));
1653 		stats[ST_F_LAST_AGT] = mkf_str(FN_OUTPUT, sv->agent.desc);
1654 		stats[ST_F_AGENT_RISE]   = mkf_u32(FO_CONFIG|FS_SERVICE, sv->agent.rise);
1655 		stats[ST_F_AGENT_FALL]   = mkf_u32(FO_CONFIG|FS_SERVICE, sv->agent.fall);
1656 		stats[ST_F_AGENT_HEALTH] = mkf_u32(FO_CONFIG|FS_SERVICE, sv->agent.health);
1657 	}
1658 
1659 	/* http response: 1xx, 2xx, 3xx, 4xx, 5xx, other */
1660 	if (px->mode == PR_MODE_HTTP) {
1661 		stats[ST_F_HRSP_1XX]   = mkf_u64(FN_COUNTER, sv->counters.p.http.rsp[1]);
1662 		stats[ST_F_HRSP_2XX]   = mkf_u64(FN_COUNTER, sv->counters.p.http.rsp[2]);
1663 		stats[ST_F_HRSP_3XX]   = mkf_u64(FN_COUNTER, sv->counters.p.http.rsp[3]);
1664 		stats[ST_F_HRSP_4XX]   = mkf_u64(FN_COUNTER, sv->counters.p.http.rsp[4]);
1665 		stats[ST_F_HRSP_5XX]   = mkf_u64(FN_COUNTER, sv->counters.p.http.rsp[5]);
1666 		stats[ST_F_HRSP_OTHER] = mkf_u64(FN_COUNTER, sv->counters.p.http.rsp[0]);
1667 	}
1668 
1669 	if (ref->observe)
1670 		stats[ST_F_HANAFAIL] = mkf_u64(FN_COUNTER, sv->counters.failed_hana);
1671 
1672 	stats[ST_F_CLI_ABRT] = mkf_u64(FN_COUNTER, sv->counters.cli_aborts);
1673 	stats[ST_F_SRV_ABRT] = mkf_u64(FN_COUNTER, sv->counters.srv_aborts);
1674 	stats[ST_F_LASTSESS] = mkf_s32(FN_AGE, srv_lastsession(sv));
1675 
1676 	stats[ST_F_QTIME] = mkf_u32(FN_AVG, swrate_avg(sv->counters.q_time, TIME_STATS_SAMPLES));
1677 	stats[ST_F_CTIME] = mkf_u32(FN_AVG, swrate_avg(sv->counters.c_time, TIME_STATS_SAMPLES));
1678 	stats[ST_F_RTIME] = mkf_u32(FN_AVG, swrate_avg(sv->counters.d_time, TIME_STATS_SAMPLES));
1679 	stats[ST_F_TTIME] = mkf_u32(FN_AVG, swrate_avg(sv->counters.t_time, TIME_STATS_SAMPLES));
1680 
1681 	if (flags & ST_SHLGNDS) {
1682 		switch (addr_to_str(&sv->addr, str, sizeof(str))) {
1683 		case AF_INET:
1684 			stats[ST_F_ADDR] = mkf_str(FO_CONFIG|FS_SERVICE, chunk_newstr(out));
1685 			chunk_appendf(out, "%s:%d", str, sv->svc_port);
1686 			break;
1687 		case AF_INET6:
1688 			stats[ST_F_ADDR] = mkf_str(FO_CONFIG|FS_SERVICE, chunk_newstr(out));
1689 			chunk_appendf(out, "[%s]:%d", str, sv->svc_port);
1690 			break;
1691 		case AF_UNIX:
1692 			stats[ST_F_ADDR] = mkf_str(FO_CONFIG|FS_SERVICE, "unix");
1693 			break;
1694 		case -1:
1695 			stats[ST_F_ADDR] = mkf_str(FO_CONFIG|FS_SERVICE, chunk_newstr(out));
1696 			chunk_strcat(out, strerror(errno));
1697 			break;
1698 		default: /* address family not supported */
1699 			break;
1700 		}
1701 
1702 		if (sv->cookie)
1703 			stats[ST_F_COOKIE] = mkf_str(FO_CONFIG|FN_NAME|FS_SERVICE, sv->cookie);
1704 	}
1705 
1706 	return 1;
1707 }
1708 
1709 /* Dumps a line for server <sv> and proxy <px> to the trash and uses the state
1710  * from stream interface <si>, stats flags <flags>, and server state <state>.
1711  * The caller is responsible for clearing the trash if needed. Returns non-zero
1712  * if it emits anything, zero otherwise.
1713  */
stats_dump_sv_stats(struct stream_interface * si,struct proxy * px,int flags,struct server * sv)1714 static int stats_dump_sv_stats(struct stream_interface *si, struct proxy *px, int flags, struct server *sv)
1715 {
1716 	struct appctx *appctx = __objt_appctx(si->end);
1717 
1718 	if (!stats_fill_sv_stats(px, sv, flags, stats, ST_F_TOTAL_FIELDS))
1719 		return 0;
1720 
1721 	return stats_dump_one_line(stats, flags, px, appctx);
1722 }
1723 
1724 /* Fill <stats> with the backend statistics. <stats> is
1725  * preallocated array of length <len>. The length of the array
1726  * must be at least ST_F_TOTAL_FIELDS. If this length is less
1727  * then this value, the function returns 0, otherwise, it
1728  * returns 1. <flags> can take the value ST_SHLGNDS.
1729  */
stats_fill_be_stats(struct proxy * px,int flags,struct field * stats,int len)1730 int stats_fill_be_stats(struct proxy *px, int flags, struct field *stats, int len)
1731 {
1732 	if (len < ST_F_TOTAL_FIELDS)
1733 		return 0;
1734 
1735 	memset(stats, 0, sizeof(*stats) * len);
1736 
1737 	stats[ST_F_PXNAME]   = mkf_str(FO_KEY|FN_NAME|FS_SERVICE, px->id);
1738 	stats[ST_F_SVNAME]   = mkf_str(FO_KEY|FN_NAME|FS_SERVICE, "BACKEND");
1739 	stats[ST_F_MODE]     = mkf_str(FO_CONFIG|FS_SERVICE, proxy_mode_str(px->mode));
1740 	stats[ST_F_QCUR]     = mkf_u32(0, px->nbpend);
1741 	stats[ST_F_QMAX]     = mkf_u32(FN_MAX, px->be_counters.nbpend_max);
1742 	stats[ST_F_SCUR]     = mkf_u32(0, px->beconn);
1743 	stats[ST_F_SMAX]     = mkf_u32(FN_MAX, px->be_counters.conn_max);
1744 	stats[ST_F_SLIM]     = mkf_u32(FO_CONFIG|FN_LIMIT, px->fullconn);
1745 	stats[ST_F_STOT]     = mkf_u64(FN_COUNTER, px->be_counters.cum_conn);
1746 	stats[ST_F_BIN]      = mkf_u64(FN_COUNTER, px->be_counters.bytes_in);
1747 	stats[ST_F_BOUT]     = mkf_u64(FN_COUNTER, px->be_counters.bytes_out);
1748 	stats[ST_F_DREQ]     = mkf_u64(FN_COUNTER, px->be_counters.denied_req);
1749 	stats[ST_F_DRESP]    = mkf_u64(FN_COUNTER, px->be_counters.denied_resp);
1750 	stats[ST_F_ECON]     = mkf_u64(FN_COUNTER, px->be_counters.failed_conns);
1751 	stats[ST_F_ERESP]    = mkf_u64(FN_COUNTER, px->be_counters.failed_resp);
1752 	stats[ST_F_WRETR]    = mkf_u64(FN_COUNTER, px->be_counters.retries);
1753 	stats[ST_F_WREDIS]   = mkf_u64(FN_COUNTER, px->be_counters.redispatches);
1754 	stats[ST_F_STATUS]   = mkf_str(FO_STATUS, (px->lbprm.tot_weight > 0 || !px->srv) ? "UP" : "DOWN");
1755 	stats[ST_F_WEIGHT]   = mkf_u32(FN_AVG, (px->lbprm.tot_weight * px->lbprm.wmult + px->lbprm.wdiv - 1) / px->lbprm.wdiv);
1756 	stats[ST_F_ACT]      = mkf_u32(0, px->srv_act);
1757 	stats[ST_F_BCK]      = mkf_u32(0, px->srv_bck);
1758 	stats[ST_F_CHKDOWN]  = mkf_u64(FN_COUNTER, px->down_trans);
1759 	stats[ST_F_LASTCHG]  = mkf_u32(FN_AGE, now.tv_sec - px->last_change);
1760 	if (px->srv)
1761 		stats[ST_F_DOWNTIME] = mkf_u32(FN_COUNTER, be_downtime(px));
1762 
1763 	stats[ST_F_PID]      = mkf_u32(FO_KEY, relative_pid);
1764 	stats[ST_F_IID]      = mkf_u32(FO_KEY|FS_SERVICE, px->uuid);
1765 	stats[ST_F_SID]      = mkf_u32(FO_KEY|FS_SERVICE, 0);
1766 	stats[ST_F_LBTOT]    = mkf_u64(FN_COUNTER, px->be_counters.cum_lbconn);
1767 	stats[ST_F_TYPE]     = mkf_u32(FO_CONFIG|FS_SERVICE, STATS_TYPE_BE);
1768 	stats[ST_F_RATE]     = mkf_u32(0, read_freq_ctr(&px->be_sess_per_sec));
1769 	stats[ST_F_RATE_MAX] = mkf_u32(0, px->be_counters.sps_max);
1770 
1771 	if (flags & ST_SHLGNDS) {
1772 		if (px->cookie_name)
1773 			stats[ST_F_COOKIE] = mkf_str(FO_CONFIG|FN_NAME|FS_SERVICE, px->cookie_name);
1774 		stats[ST_F_ALGO] = mkf_str(FO_CONFIG|FS_SERVICE, backend_lb_algo_str(px->lbprm.algo & BE_LB_ALGO));
1775 	}
1776 
1777 	/* http response: 1xx, 2xx, 3xx, 4xx, 5xx, other */
1778 	if (px->mode == PR_MODE_HTTP) {
1779 		stats[ST_F_REQ_TOT]     = mkf_u64(FN_COUNTER, px->be_counters.p.http.cum_req);
1780 		stats[ST_F_HRSP_1XX]    = mkf_u64(FN_COUNTER, px->be_counters.p.http.rsp[1]);
1781 		stats[ST_F_HRSP_2XX]    = mkf_u64(FN_COUNTER, px->be_counters.p.http.rsp[2]);
1782 		stats[ST_F_HRSP_3XX]    = mkf_u64(FN_COUNTER, px->be_counters.p.http.rsp[3]);
1783 		stats[ST_F_HRSP_4XX]    = mkf_u64(FN_COUNTER, px->be_counters.p.http.rsp[4]);
1784 		stats[ST_F_HRSP_5XX]    = mkf_u64(FN_COUNTER, px->be_counters.p.http.rsp[5]);
1785 		stats[ST_F_HRSP_OTHER]  = mkf_u64(FN_COUNTER, px->be_counters.p.http.rsp[0]);
1786 	}
1787 
1788 	stats[ST_F_CLI_ABRT]     = mkf_u64(FN_COUNTER, px->be_counters.cli_aborts);
1789 	stats[ST_F_SRV_ABRT]     = mkf_u64(FN_COUNTER, px->be_counters.srv_aborts);
1790 
1791 	/* compression: in, out, bypassed, responses */
1792 	stats[ST_F_COMP_IN]      = mkf_u64(FN_COUNTER, px->be_counters.comp_in);
1793 	stats[ST_F_COMP_OUT]     = mkf_u64(FN_COUNTER, px->be_counters.comp_out);
1794 	stats[ST_F_COMP_BYP]     = mkf_u64(FN_COUNTER, px->be_counters.comp_byp);
1795 	stats[ST_F_COMP_RSP]     = mkf_u64(FN_COUNTER, px->be_counters.p.http.comp_rsp);
1796 	stats[ST_F_LASTSESS]     = mkf_s32(FN_AGE, be_lastsession(px));
1797 
1798 	stats[ST_F_QTIME]        = mkf_u32(FN_AVG, swrate_avg(px->be_counters.q_time, TIME_STATS_SAMPLES));
1799 	stats[ST_F_CTIME]        = mkf_u32(FN_AVG, swrate_avg(px->be_counters.c_time, TIME_STATS_SAMPLES));
1800 	stats[ST_F_RTIME]        = mkf_u32(FN_AVG, swrate_avg(px->be_counters.d_time, TIME_STATS_SAMPLES));
1801 	stats[ST_F_TTIME]        = mkf_u32(FN_AVG, swrate_avg(px->be_counters.t_time, TIME_STATS_SAMPLES));
1802 
1803 	return 1;
1804 }
1805 
1806 /* Dumps a line for backend <px> to the trash for and uses the state from stream
1807  * interface <si> and stats flags <flags>. The caller is responsible for clearing
1808  * the trash if needed. Returns non-zero if it emits anything, zero otherwise.
1809  */
stats_dump_be_stats(struct stream_interface * si,struct proxy * px,int flags)1810 static int stats_dump_be_stats(struct stream_interface *si, struct proxy *px, int flags)
1811 {
1812 	struct appctx *appctx = __objt_appctx(si->end);
1813 
1814 	if (!(px->cap & PR_CAP_BE))
1815 		return 0;
1816 
1817 	if ((appctx->ctx.stats.flags & STAT_BOUND) && !(appctx->ctx.stats.type & (1 << STATS_TYPE_BE)))
1818 		return 0;
1819 
1820 	if (!stats_fill_be_stats(px, flags, stats, ST_F_TOTAL_FIELDS))
1821 		return 0;
1822 
1823 	return stats_dump_one_line(stats, flags, px, appctx);
1824 }
1825 
1826 /* Dumps the HTML table header for proxy <px> to the trash for and uses the state from
1827  * stream interface <si> and per-uri parameters <uri>. The caller is responsible
1828  * for clearing the trash if needed.
1829  */
stats_dump_html_px_hdr(struct stream_interface * si,struct proxy * px,struct uri_auth * uri)1830 static void stats_dump_html_px_hdr(struct stream_interface *si, struct proxy *px, struct uri_auth *uri)
1831 {
1832 	struct appctx *appctx = __objt_appctx(si->end);
1833 	char scope_txt[STAT_SCOPE_TXT_MAXLEN + sizeof STAT_SCOPE_PATTERN];
1834 
1835 	if (px->cap & PR_CAP_BE && px->srv && (appctx->ctx.stats.flags & STAT_ADMIN)) {
1836 		/* A form to enable/disable this proxy servers */
1837 
1838 		/* scope_txt = search pattern + search query, appctx->ctx.stats.scope_len is always <= STAT_SCOPE_TXT_MAXLEN */
1839 		scope_txt[0] = 0;
1840 		if (appctx->ctx.stats.scope_len) {
1841 			strcpy(scope_txt, STAT_SCOPE_PATTERN);
1842 			memcpy(scope_txt + strlen(STAT_SCOPE_PATTERN), bo_ptr(si_ob(si)) + appctx->ctx.stats.scope_str, appctx->ctx.stats.scope_len);
1843 			scope_txt[strlen(STAT_SCOPE_PATTERN) + appctx->ctx.stats.scope_len] = 0;
1844 		}
1845 
1846 		chunk_appendf(&trash,
1847 			      "<form method=\"post\">");
1848 	}
1849 
1850 	/* print a new table */
1851 	chunk_appendf(&trash,
1852 		      "<table class=\"tbl\" width=\"100%%\">\n"
1853 		      "<tr class=\"titre\">"
1854 		      "<th class=\"pxname\" width=\"10%%\">");
1855 
1856 	chunk_appendf(&trash,
1857 	              "<a name=\"%s\"></a>%s"
1858 	              "<a class=px href=\"#%s\">%s</a>",
1859 	              px->id,
1860 	              (uri->flags & ST_SHLGNDS) ? "<u>":"",
1861 	              px->id, px->id);
1862 
1863 	if (uri->flags & ST_SHLGNDS) {
1864 		/* cap, mode, id */
1865 		chunk_appendf(&trash, "<div class=tips>cap: %s, mode: %s, id: %d",
1866 		              proxy_cap_str(px->cap), proxy_mode_str(px->mode),
1867 		              px->uuid);
1868 		chunk_appendf(&trash, "</div>");
1869 	}
1870 
1871 	chunk_appendf(&trash,
1872 	              "%s</th>"
1873 	              "<th class=\"%s\" width=\"90%%\">%s</th>"
1874 	              "</tr>\n"
1875 	              "</table>\n"
1876 	              "<table class=\"tbl\" width=\"100%%\">\n"
1877 	              "<tr class=\"titre\">",
1878 	              (uri->flags & ST_SHLGNDS) ? "</u>":"",
1879 	              px->desc ? "desc" : "empty", px->desc ? px->desc : "");
1880 
1881 	if ((px->cap & PR_CAP_BE) && px->srv && (appctx->ctx.stats.flags & STAT_ADMIN)) {
1882 		/* Column heading for Enable or Disable server */
1883         chunk_appendf(&trash,
1884                 "<th rowspan=2 width=1><input type=\"checkbox\" \
1885                 onclick=\"for(c in document.getElementsByClassName('%s-checkbox')) \
1886                 document.getElementsByClassName('%s-checkbox').item(c).checked = this.checked\"></th>",
1887                 px->id,
1888                 px->id);
1889 	}
1890 
1891 	chunk_appendf(&trash,
1892 	              "<th rowspan=2></th>"
1893 	              "<th colspan=3>Queue</th>"
1894 	              "<th colspan=3>Session rate</th><th colspan=6>Sessions</th>"
1895 	              "<th colspan=2>Bytes</th><th colspan=2>Denied</th>"
1896 	              "<th colspan=3>Errors</th><th colspan=2>Warnings</th>"
1897 	              "<th colspan=9>Server</th>"
1898 	              "</tr>\n"
1899 	              "<tr class=\"titre\">"
1900 	              "<th>Cur</th><th>Max</th><th>Limit</th>"
1901 	              "<th>Cur</th><th>Max</th><th>Limit</th><th>Cur</th><th>Max</th>"
1902 	              "<th>Limit</th><th>Total</th><th>LbTot</th><th>Last</th><th>In</th><th>Out</th>"
1903 	              "<th>Req</th><th>Resp</th><th>Req</th><th>Conn</th>"
1904 	              "<th>Resp</th><th>Retr</th><th>Redis</th>"
1905 	              "<th>Status</th><th>LastChk</th><th>Wght</th><th>Act</th>"
1906 	              "<th>Bck</th><th>Chk</th><th>Dwn</th><th>Dwntme</th>"
1907 	              "<th>Thrtle</th>\n"
1908 	              "</tr>");
1909 }
1910 
1911 /* Dumps the HTML table trailer for proxy <px> to the trash for and uses the state from
1912  * stream interface <si>. The caller is responsible for clearing the trash if needed.
1913  */
stats_dump_html_px_end(struct stream_interface * si,struct proxy * px)1914 static void stats_dump_html_px_end(struct stream_interface *si, struct proxy *px)
1915 {
1916 	struct appctx *appctx = __objt_appctx(si->end);
1917 	chunk_appendf(&trash, "</table>");
1918 
1919 	if ((px->cap & PR_CAP_BE) && px->srv && (appctx->ctx.stats.flags & STAT_ADMIN)) {
1920 		/* close the form used to enable/disable this proxy servers */
1921 		chunk_appendf(&trash,
1922 			      "Choose the action to perform on the checked servers : "
1923 			      "<select name=action>"
1924 			      "<option value=\"\"></option>"
1925 			      "<option value=\"ready\">Set state to READY</option>"
1926 			      "<option value=\"drain\">Set state to DRAIN</option>"
1927 			      "<option value=\"maint\">Set state to MAINT</option>"
1928 			      "<option value=\"dhlth\">Health: disable checks</option>"
1929 			      "<option value=\"ehlth\">Health: enable checks</option>"
1930 			      "<option value=\"hrunn\">Health: force UP</option>"
1931 			      "<option value=\"hnolb\">Health: force NOLB</option>"
1932 			      "<option value=\"hdown\">Health: force DOWN</option>"
1933 			      "<option value=\"dagent\">Agent: disable checks</option>"
1934 			      "<option value=\"eagent\">Agent: enable checks</option>"
1935 			      "<option value=\"arunn\">Agent: force UP</option>"
1936 			      "<option value=\"adown\">Agent: force DOWN</option>"
1937 			      "<option value=\"shutdown\">Kill Sessions</option>"
1938 			      "</select>"
1939 			      "<input type=\"hidden\" name=\"b\" value=\"#%d\">"
1940 			      "&nbsp;<input type=\"submit\" value=\"Apply\">"
1941 			      "</form>",
1942 			      px->uuid);
1943 	}
1944 
1945 	chunk_appendf(&trash, "<p>\n");
1946 }
1947 
1948 /*
1949  * Dumps statistics for a proxy. The output is sent to the stream interface's
1950  * input buffer. Returns 0 if it had to stop dumping data because of lack of
1951  * buffer space, or non-zero if everything completed. This function is used
1952  * both by the CLI and the HTTP entry points, and is able to dump the output
1953  * in HTML or CSV formats. If the later, <uri> must be NULL.
1954  */
stats_dump_proxy_to_buffer(struct stream_interface * si,struct proxy * px,struct uri_auth * uri)1955 int stats_dump_proxy_to_buffer(struct stream_interface *si, struct proxy *px, struct uri_auth *uri)
1956 {
1957 	struct appctx *appctx = __objt_appctx(si->end);
1958 	struct stream *s = si_strm(si);
1959 	struct channel *rep = si_ic(si);
1960 	struct server *sv, *svs;	/* server and server-state, server-state=server or server->track */
1961 	struct listener *l;
1962 	unsigned int flags;
1963 
1964 	if (uri)
1965 		flags = uri->flags;
1966 	else if ((strm_li(s)->bind_conf->level & ACCESS_LVL_MASK) >= ACCESS_LVL_OPER)
1967 		flags = ST_SHLGNDS | ST_SHNODE | ST_SHDESC;
1968 	else
1969 		flags = ST_SHNODE | ST_SHDESC;
1970 
1971 	chunk_reset(&trash);
1972 
1973 	switch (appctx->ctx.stats.px_st) {
1974 	case STAT_PX_ST_INIT:
1975 		/* we are on a new proxy */
1976 		if (uri && uri->scope) {
1977 			/* we have a limited scope, we have to check the proxy name */
1978 			struct stat_scope *scope;
1979 			int len;
1980 
1981 			len = strlen(px->id);
1982 			scope = uri->scope;
1983 
1984 			while (scope) {
1985 				/* match exact proxy name */
1986 				if (scope->px_len == len && !memcmp(px->id, scope->px_id, len))
1987 					break;
1988 
1989 				/* match '.' which means 'self' proxy */
1990 				if (!strcmp(scope->px_id, ".") && px == s->be)
1991 					break;
1992 				scope = scope->next;
1993 			}
1994 
1995 			/* proxy name not found : don't dump anything */
1996 			if (scope == NULL)
1997 				return 1;
1998 		}
1999 
2000 		/* if the user has requested a limited output and the proxy
2001 		 * name does not match, skip it.
2002 		 */
2003 		if (appctx->ctx.stats.scope_len &&
2004 		    strnistr(px->id, strlen(px->id), bo_ptr(si_ob(si)) + appctx->ctx.stats.scope_str, appctx->ctx.stats.scope_len) == NULL)
2005 			return 1;
2006 
2007 		if ((appctx->ctx.stats.flags & STAT_BOUND) &&
2008 		    (appctx->ctx.stats.iid != -1) &&
2009 		    (px->uuid != appctx->ctx.stats.iid))
2010 			return 1;
2011 
2012 		appctx->ctx.stats.px_st = STAT_PX_ST_TH;
2013 		/* fall through */
2014 
2015 	case STAT_PX_ST_TH:
2016 		if (appctx->ctx.stats.flags & STAT_FMT_HTML) {
2017 			stats_dump_html_px_hdr(si, px, uri);
2018 			if (ci_putchk(rep, &trash) == -1) {
2019 				si_applet_cant_put(si);
2020 				return 0;
2021 			}
2022 		}
2023 
2024 		appctx->ctx.stats.px_st = STAT_PX_ST_FE;
2025 		/* fall through */
2026 
2027 	case STAT_PX_ST_FE:
2028 		/* print the frontend */
2029 		if (stats_dump_fe_stats(si, px)) {
2030 			if (ci_putchk(rep, &trash) == -1) {
2031 				si_applet_cant_put(si);
2032 				return 0;
2033 			}
2034 		}
2035 
2036 		appctx->ctx.stats.l = px->conf.listeners.n;
2037 		appctx->ctx.stats.px_st = STAT_PX_ST_LI;
2038 		/* fall through */
2039 
2040 	case STAT_PX_ST_LI:
2041 		/* stats.l has been initialized above */
2042 		for (; appctx->ctx.stats.l != &px->conf.listeners; appctx->ctx.stats.l = l->by_fe.n) {
2043 			if (buffer_almost_full(rep->buf)) {
2044 				si_applet_cant_put(si);
2045 				return 0;
2046 			}
2047 
2048 			l = LIST_ELEM(appctx->ctx.stats.l, struct listener *, by_fe);
2049 			if (!l->counters)
2050 				continue;
2051 
2052 			if (appctx->ctx.stats.flags & STAT_BOUND) {
2053 				if (!(appctx->ctx.stats.type & (1 << STATS_TYPE_SO)))
2054 					break;
2055 
2056 				if (appctx->ctx.stats.sid != -1 && l->luid != appctx->ctx.stats.sid)
2057 					continue;
2058 			}
2059 
2060 			/* print the frontend */
2061 			if (stats_dump_li_stats(si, px, l, flags)) {
2062 				if (ci_putchk(rep, &trash) == -1) {
2063 					si_applet_cant_put(si);
2064 					return 0;
2065 				}
2066 			}
2067 		}
2068 
2069 		appctx->ctx.stats.sv = px->srv; /* may be NULL */
2070 		appctx->ctx.stats.px_st = STAT_PX_ST_SV;
2071 		/* fall through */
2072 
2073 	case STAT_PX_ST_SV:
2074 		/* stats.sv has been initialized above */
2075 		for (; appctx->ctx.stats.sv != NULL; appctx->ctx.stats.sv = sv->next) {
2076 			if (buffer_almost_full(rep->buf)) {
2077 				si_applet_cant_put(si);
2078 				return 0;
2079 			}
2080 
2081 			sv = appctx->ctx.stats.sv;
2082 
2083 			if (appctx->ctx.stats.flags & STAT_BOUND) {
2084 				if (!(appctx->ctx.stats.type & (1 << STATS_TYPE_SV)))
2085 					break;
2086 
2087 				if (appctx->ctx.stats.sid != -1 && sv->puid != appctx->ctx.stats.sid)
2088 					continue;
2089 			}
2090 
2091 			svs = sv;
2092 			while (svs->track)
2093 				svs = svs->track;
2094 
2095 			/* do not report servers which are DOWN and not changing state */
2096 			if ((appctx->ctx.stats.flags & STAT_HIDE_DOWN) &&
2097 			    ((sv->cur_admin & SRV_ADMF_MAINT) || /* server is in maintenance */
2098 			     (sv->cur_state == SRV_ST_STOPPED && /* server is down */
2099 			      (!((svs->agent.state | svs->check.state) & CHK_ST_ENABLED) ||
2100 			       ((svs->agent.state & CHK_ST_ENABLED) && !svs->agent.health) ||
2101 			       ((svs->check.state & CHK_ST_ENABLED) && !svs->check.health))))) {
2102 				continue;
2103 			}
2104 
2105 			if (stats_dump_sv_stats(si, px, flags, sv)) {
2106 				if (ci_putchk(rep, &trash) == -1) {
2107 					si_applet_cant_put(si);
2108 					return 0;
2109 				}
2110 			}
2111 		} /* for sv */
2112 
2113 		appctx->ctx.stats.px_st = STAT_PX_ST_BE;
2114 		/* fall through */
2115 
2116 	case STAT_PX_ST_BE:
2117 		/* print the backend */
2118 		if (stats_dump_be_stats(si, px, flags)) {
2119 			if (ci_putchk(rep, &trash) == -1) {
2120 				si_applet_cant_put(si);
2121 				return 0;
2122 			}
2123 		}
2124 
2125 		appctx->ctx.stats.px_st = STAT_PX_ST_END;
2126 		/* fall through */
2127 
2128 	case STAT_PX_ST_END:
2129 		if (appctx->ctx.stats.flags & STAT_FMT_HTML) {
2130 			stats_dump_html_px_end(si, px);
2131 			if (ci_putchk(rep, &trash) == -1) {
2132 				si_applet_cant_put(si);
2133 				return 0;
2134 			}
2135 		}
2136 
2137 		appctx->ctx.stats.px_st = STAT_PX_ST_FIN;
2138 		/* fall through */
2139 
2140 	case STAT_PX_ST_FIN:
2141 		return 1;
2142 
2143 	default:
2144 		/* unknown state, we should put an abort() here ! */
2145 		return 1;
2146 	}
2147 }
2148 
2149 /* Dumps the HTTP stats head block to the trash for and uses the per-uri
2150  * parameters <uri>. The caller is responsible for clearing the trash if needed.
2151  */
stats_dump_html_head(struct uri_auth * uri)2152 static void stats_dump_html_head(struct uri_auth *uri)
2153 {
2154 	/* WARNING! This must fit in the first buffer !!! */
2155 	chunk_appendf(&trash,
2156 	              "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\n"
2157 	              "\"http://www.w3.org/TR/html4/loose.dtd\">\n"
2158 	              "<html><head><title>Statistics Report for " PRODUCT_NAME "%s%s</title>\n"
2159 	              "<meta http-equiv=\"content-type\" content=\"text/html; charset=iso-8859-1\">\n"
2160 	              "<style type=\"text/css\"><!--\n"
2161 	              "body {"
2162 	              " font-family: arial, helvetica, sans-serif;"
2163 	              " font-size: 12px;"
2164 	              " font-weight: normal;"
2165 	              " color: black;"
2166 	              " background: white;"
2167 	              "}\n"
2168 	              "th,td {"
2169 	              " font-size: 10px;"
2170 	              "}\n"
2171 	              "h1 {"
2172 	              " font-size: x-large;"
2173 	              " margin-bottom: 0.5em;"
2174 	              "}\n"
2175 	              "h2 {"
2176 	              " font-family: helvetica, arial;"
2177 	              " font-size: x-large;"
2178 	              " font-weight: bold;"
2179 	              " font-style: italic;"
2180 	              " color: #6020a0;"
2181 	              " margin-top: 0em;"
2182 	              " margin-bottom: 0em;"
2183 	              "}\n"
2184 	              "h3 {"
2185 	              " font-family: helvetica, arial;"
2186 	              " font-size: 16px;"
2187 	              " font-weight: bold;"
2188 	              " color: #b00040;"
2189 	              " background: #e8e8d0;"
2190 	              " margin-top: 0em;"
2191 	              " margin-bottom: 0em;"
2192 	              "}\n"
2193 	              "li {"
2194 	              " margin-top: 0.25em;"
2195 	              " margin-right: 2em;"
2196 	              "}\n"
2197 	              ".hr {margin-top: 0.25em;"
2198 	              " border-color: black;"
2199 	              " border-bottom-style: solid;"
2200 	              "}\n"
2201 	              ".titre	{background: #20D0D0;color: #000000; font-weight: bold; text-align: center;}\n"
2202 	              ".total	{background: #20D0D0;color: #ffff80;}\n"
2203 	              ".frontend	{background: #e8e8d0;}\n"
2204 	              ".socket	{background: #d0d0d0;}\n"
2205 	              ".backend	{background: #e8e8d0;}\n"
2206 	              ".active_down		{background: #ff9090;}\n"
2207 	              ".active_going_up		{background: #ffd020;}\n"
2208 	              ".active_going_down	{background: #ffffa0;}\n"
2209 	              ".active_up		{background: #c0ffc0;}\n"
2210 	              ".active_nolb		{background: #20a0ff;}\n"
2211 	              ".active_draining		{background: #20a0FF;}\n"
2212 	              ".active_no_check		{background: #e0e0e0;}\n"
2213 	              ".backup_down		{background: #ff9090;}\n"
2214 	              ".backup_going_up		{background: #ff80ff;}\n"
2215 	              ".backup_going_down	{background: #c060ff;}\n"
2216 	              ".backup_up		{background: #b0d0ff;}\n"
2217 	              ".backup_nolb		{background: #90b0e0;}\n"
2218 	              ".backup_draining		{background: #cc9900;}\n"
2219 	              ".backup_no_check		{background: #e0e0e0;}\n"
2220 	              ".maintain	{background: #c07820;}\n"
2221 	              ".rls      {letter-spacing: 0.2em; margin-right: 1px;}\n" /* right letter spacing (used for grouping digits) */
2222 	              "\n"
2223 	              "a.px:link {color: #ffff40; text-decoration: none;}"
2224 	              "a.px:visited {color: #ffff40; text-decoration: none;}"
2225 	              "a.px:hover {color: #ffffff; text-decoration: none;}"
2226 	              "a.lfsb:link {color: #000000; text-decoration: none;}"
2227 	              "a.lfsb:visited {color: #000000; text-decoration: none;}"
2228 	              "a.lfsb:hover {color: #505050; text-decoration: none;}"
2229 	              "\n"
2230 	              "table.tbl { border-collapse: collapse; border-style: none;}\n"
2231 	              "table.tbl td { text-align: right; border-width: 1px 1px 1px 1px; border-style: solid solid solid solid; padding: 2px 3px; border-color: gray; white-space: nowrap;}\n"
2232 	              "table.tbl td.ac { text-align: center;}\n"
2233 	              "table.tbl th { border-width: 1px; border-style: solid solid solid solid; border-color: gray;}\n"
2234 	              "table.tbl th.pxname { background: #b00040; color: #ffff40; font-weight: bold; border-style: solid solid none solid; padding: 2px 3px; white-space: nowrap;}\n"
2235 	              "table.tbl th.empty { border-style: none; empty-cells: hide; background: white;}\n"
2236 	              "table.tbl th.desc { background: white; border-style: solid solid none solid; text-align: left; padding: 2px 3px;}\n"
2237 	              "\n"
2238 	              "table.lgd { border-collapse: collapse; border-width: 1px; border-style: none none none solid; border-color: black;}\n"
2239 	              "table.lgd td { border-width: 1px; border-style: solid solid solid solid; border-color: gray; padding: 2px;}\n"
2240 	              "table.lgd td.noborder { border-style: none; padding: 2px; white-space: nowrap;}\n"
2241 	              "table.det { border-collapse: collapse; border-style: none; }\n"
2242 	              "table.det th { text-align: left; border-width: 0px; padding: 0px 1px 0px 0px; font-style:normal;font-size:11px;font-weight:bold;font-family: sans-serif;}\n"
2243 	              "table.det td { text-align: right; border-width: 0px; padding: 0px 0px 0px 4px; white-space: nowrap; font-style:normal;font-size:11px;font-weight:normal;}\n"
2244 	              "u {text-decoration:none; border-bottom: 1px dotted black;}\n"
2245 		      "div.tips {\n"
2246 		      " display:block;\n"
2247 		      " visibility:hidden;\n"
2248 		      " z-index:2147483647;\n"
2249 		      " position:absolute;\n"
2250 		      " padding:2px 4px 3px;\n"
2251 		      " background:#f0f060; color:#000000;\n"
2252 		      " border:1px solid #7040c0;\n"
2253 		      " white-space:nowrap;\n"
2254 		      " font-style:normal;font-size:11px;font-weight:normal;\n"
2255 		      " -moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;\n"
2256 		      " -moz-box-shadow:gray 2px 2px 3px;-webkit-box-shadow:gray 2px 2px 3px;box-shadow:gray 2px 2px 3px;\n"
2257 		      "}\n"
2258 		      "u:hover div.tips {visibility:visible;}\n"
2259 	              "-->\n"
2260 	              "</style></head>\n",
2261 	              (uri->flags & ST_SHNODE) ? " on " : "",
2262 	              (uri->flags & ST_SHNODE) ? (uri->node ? uri->node : global.node) : ""
2263 	              );
2264 }
2265 
2266 /* Dumps the HTML stats information block to the trash for and uses the state from
2267  * stream interface <si> and per-uri parameters <uri>. The caller is responsible
2268  * for clearing the trash if needed.
2269  */
stats_dump_html_info(struct stream_interface * si,struct uri_auth * uri)2270 static void stats_dump_html_info(struct stream_interface *si, struct uri_auth *uri)
2271 {
2272 	struct appctx *appctx = __objt_appctx(si->end);
2273 	unsigned int up = (now.tv_sec - start_date.tv_sec);
2274 	char scope_txt[STAT_SCOPE_TXT_MAXLEN + sizeof STAT_SCOPE_PATTERN];
2275 
2276 	/* WARNING! this has to fit the first packet too.
2277 	 * We are around 3.5 kB, add adding entries will
2278 	 * become tricky if we want to support 4kB buffers !
2279 	 */
2280 	chunk_appendf(&trash,
2281 	              "<body><h1><a href=\"" PRODUCT_URL "\" style=\"text-decoration: none;\">"
2282 	              PRODUCT_NAME "%s</a></h1>\n"
2283 	              "<h2>Statistics Report for pid %d%s%s%s%s</h2>\n"
2284 	              "<hr width=\"100%%\" class=\"hr\">\n"
2285 	              "<h3>&gt; General process information</h3>\n"
2286 	              "<table border=0><tr><td align=\"left\" nowrap width=\"1%%\">\n"
2287 	              "<p><b>pid = </b> %d (process #%d, nbproc = %d, nbthread = %d)<br>\n"
2288 	              "<b>uptime = </b> %dd %dh%02dm%02ds<br>\n"
2289 	              "<b>system limits:</b> memmax = %s%s; ulimit-n = %d<br>\n"
2290 	              "<b>maxsock = </b> %d; <b>maxconn = </b> %d; <b>maxpipes = </b> %d<br>\n"
2291 	              "current conns = %d; current pipes = %d/%d; conn rate = %d/sec<br>\n"
2292 	              "Running tasks: %d/%d; idle = %d %%<br>\n"
2293 	              "</td><td align=\"center\" nowrap>\n"
2294 	              "<table class=\"lgd\"><tr>\n"
2295 	              "<td class=\"active_up\">&nbsp;</td><td class=\"noborder\">active UP </td>"
2296 	              "<td class=\"backup_up\">&nbsp;</td><td class=\"noborder\">backup UP </td>"
2297 	              "</tr><tr>\n"
2298 	              "<td class=\"active_going_down\"></td><td class=\"noborder\">active UP, going down </td>"
2299 	              "<td class=\"backup_going_down\"></td><td class=\"noborder\">backup UP, going down </td>"
2300 	              "</tr><tr>\n"
2301 	              "<td class=\"active_going_up\"></td><td class=\"noborder\">active DOWN, going up </td>"
2302 	              "<td class=\"backup_going_up\"></td><td class=\"noborder\">backup DOWN, going up </td>"
2303 	              "</tr><tr>\n"
2304 	              "<td class=\"active_down\"></td><td class=\"noborder\">active or backup DOWN &nbsp;</td>"
2305 	              "<td class=\"active_no_check\"></td><td class=\"noborder\">not checked </td>"
2306 	              "</tr><tr>\n"
2307 	              "<td class=\"maintain\"></td><td class=\"noborder\" colspan=\"3\">active or backup DOWN for maintenance (MAINT) &nbsp;</td>"
2308 	              "</tr><tr>\n"
2309 	              "<td class=\"active_draining\"></td><td class=\"noborder\" colspan=\"3\">active or backup SOFT STOPPED for maintenance &nbsp;</td>"
2310 	              "</tr></table>\n"
2311 	              "Note: \"NOLB\"/\"DRAIN\" = UP with load-balancing disabled."
2312 	              "</td>"
2313 	              "<td align=\"left\" valign=\"top\" nowrap width=\"1%%\">"
2314 	              "<b>Display option:</b><ul style=\"margin-top: 0.25em;\">"
2315 	              "",
2316 	              (uri->flags & ST_HIDEVER) ? "" : (STATS_VERSION_STRING),
2317 	              pid, (uri->flags & ST_SHNODE) ? " on " : "",
2318 		      (uri->flags & ST_SHNODE) ? (uri->node ? uri->node : global.node) : "",
2319 	              (uri->flags & ST_SHDESC) ? ": " : "",
2320 		      (uri->flags & ST_SHDESC) ? (uri->desc ? uri->desc : global.desc) : "",
2321 	              pid, relative_pid, global.nbproc, global.nbthread,
2322 	              up / 86400, (up % 86400) / 3600,
2323 	              (up % 3600) / 60, (up % 60),
2324 	              global.rlimit_memmax ? ultoa(global.rlimit_memmax) : "unlimited",
2325 	              global.rlimit_memmax ? " MB" : "",
2326 	              global.rlimit_nofile,
2327 	              global.maxsock, global.maxconn, global.maxpipes,
2328 	              actconn, pipes_used, pipes_used+pipes_free, read_freq_ctr(&global.conn_per_sec),
2329 	              tasks_run_queue_cur, nb_tasks_cur, idle_pct
2330 	              );
2331 
2332 	/* scope_txt = search query, appctx->ctx.stats.scope_len is always <= STAT_SCOPE_TXT_MAXLEN */
2333 	memcpy(scope_txt, bo_ptr(si_ob(si)) + appctx->ctx.stats.scope_str, appctx->ctx.stats.scope_len);
2334 	scope_txt[appctx->ctx.stats.scope_len] = '\0';
2335 
2336 	chunk_appendf(&trash,
2337 		      "<li><form method=\"GET\">Scope : <input value=\"%s\" name=\"" STAT_SCOPE_INPUT_NAME "\" size=\"8\" maxlength=\"%d\" tabindex=\"1\"/></form>\n",
2338 		      (appctx->ctx.stats.scope_len > 0) ? scope_txt : "",
2339 		      STAT_SCOPE_TXT_MAXLEN);
2340 
2341 	/* scope_txt = search pattern + search query, appctx->ctx.stats.scope_len is always <= STAT_SCOPE_TXT_MAXLEN */
2342 	scope_txt[0] = 0;
2343 	if (appctx->ctx.stats.scope_len) {
2344 		strcpy(scope_txt, STAT_SCOPE_PATTERN);
2345 		memcpy(scope_txt + strlen(STAT_SCOPE_PATTERN), bo_ptr(si_ob(si)) + appctx->ctx.stats.scope_str, appctx->ctx.stats.scope_len);
2346 		scope_txt[strlen(STAT_SCOPE_PATTERN) + appctx->ctx.stats.scope_len] = 0;
2347 	}
2348 
2349 	if (appctx->ctx.stats.flags & STAT_HIDE_DOWN)
2350 		chunk_appendf(&trash,
2351 		              "<li><a href=\"%s%s%s%s\">Show all servers</a><br>\n",
2352 		              uri->uri_prefix,
2353 		              "",
2354 		              (appctx->ctx.stats.flags & STAT_NO_REFRESH) ? ";norefresh" : "",
2355 			      scope_txt);
2356 	else
2357 		chunk_appendf(&trash,
2358 		              "<li><a href=\"%s%s%s%s\">Hide 'DOWN' servers</a><br>\n",
2359 		              uri->uri_prefix,
2360 		              ";up",
2361 		              (appctx->ctx.stats.flags & STAT_NO_REFRESH) ? ";norefresh" : "",
2362 			      scope_txt);
2363 
2364 	if (uri->refresh > 0) {
2365 		if (appctx->ctx.stats.flags & STAT_NO_REFRESH)
2366 			chunk_appendf(&trash,
2367 			              "<li><a href=\"%s%s%s%s\">Enable refresh</a><br>\n",
2368 			              uri->uri_prefix,
2369 			              (appctx->ctx.stats.flags & STAT_HIDE_DOWN) ? ";up" : "",
2370 			              "",
2371 				      scope_txt);
2372 		else
2373 			chunk_appendf(&trash,
2374 			              "<li><a href=\"%s%s%s%s\">Disable refresh</a><br>\n",
2375 			              uri->uri_prefix,
2376 			              (appctx->ctx.stats.flags & STAT_HIDE_DOWN) ? ";up" : "",
2377 			              ";norefresh",
2378 				      scope_txt);
2379 	}
2380 
2381 	chunk_appendf(&trash,
2382 	              "<li><a href=\"%s%s%s%s\">Refresh now</a><br>\n",
2383 	              uri->uri_prefix,
2384 	              (appctx->ctx.stats.flags & STAT_HIDE_DOWN) ? ";up" : "",
2385 	              (appctx->ctx.stats.flags & STAT_NO_REFRESH) ? ";norefresh" : "",
2386 		      scope_txt);
2387 
2388 	chunk_appendf(&trash,
2389 	              "<li><a href=\"%s;csv%s%s\">CSV export</a><br>\n",
2390 	              uri->uri_prefix,
2391 	              (uri->refresh > 0) ? ";norefresh" : "",
2392 		      scope_txt);
2393 
2394 	chunk_appendf(&trash,
2395 	              "</ul></td>"
2396 	              "<td align=\"left\" valign=\"top\" nowrap width=\"1%%\">"
2397 	              "<b>External resources:</b><ul style=\"margin-top: 0.25em;\">\n"
2398 	              "<li><a href=\"" PRODUCT_URL "\">Primary site</a><br>\n"
2399 	              "<li><a href=\"" PRODUCT_URL_UPD "\">Updates (v" PRODUCT_BRANCH ")</a><br>\n"
2400 	              "<li><a href=\"" PRODUCT_URL_DOC "\">Online manual</a><br>\n"
2401 	              "</ul>"
2402 	              "</td>"
2403 	              "</tr></table>\n"
2404 	              ""
2405 	              );
2406 
2407 	if (appctx->ctx.stats.st_code) {
2408 		switch (appctx->ctx.stats.st_code) {
2409 		case STAT_STATUS_DONE:
2410 			chunk_appendf(&trash,
2411 			              "<p><div class=active_up>"
2412 			              "<a class=lfsb href=\"%s%s%s%s\" title=\"Remove this message\">[X]</a> "
2413 			              "Action processed successfully."
2414 			              "</div>\n", uri->uri_prefix,
2415 			              (appctx->ctx.stats.flags & STAT_HIDE_DOWN) ? ";up" : "",
2416 			              (appctx->ctx.stats.flags & STAT_NO_REFRESH) ? ";norefresh" : "",
2417 			              scope_txt);
2418 			break;
2419 		case STAT_STATUS_NONE:
2420 			chunk_appendf(&trash,
2421 			              "<p><div class=active_going_down>"
2422 			              "<a class=lfsb href=\"%s%s%s%s\" title=\"Remove this message\">[X]</a> "
2423 			              "Nothing has changed."
2424 			              "</div>\n", uri->uri_prefix,
2425 			              (appctx->ctx.stats.flags & STAT_HIDE_DOWN) ? ";up" : "",
2426 			              (appctx->ctx.stats.flags & STAT_NO_REFRESH) ? ";norefresh" : "",
2427 			              scope_txt);
2428 			break;
2429 		case STAT_STATUS_PART:
2430 			chunk_appendf(&trash,
2431 			              "<p><div class=active_going_down>"
2432 			              "<a class=lfsb href=\"%s%s%s%s\" title=\"Remove this message\">[X]</a> "
2433 			              "Action partially processed.<br>"
2434 			              "Some server names are probably unknown or ambiguous (duplicated names in the backend)."
2435 			              "</div>\n", uri->uri_prefix,
2436 			              (appctx->ctx.stats.flags & STAT_HIDE_DOWN) ? ";up" : "",
2437 			              (appctx->ctx.stats.flags & STAT_NO_REFRESH) ? ";norefresh" : "",
2438 			              scope_txt);
2439 			break;
2440 		case STAT_STATUS_ERRP:
2441 			chunk_appendf(&trash,
2442 			              "<p><div class=active_down>"
2443 			              "<a class=lfsb href=\"%s%s%s%s\" title=\"Remove this message\">[X]</a> "
2444 			              "Action not processed because of invalid parameters."
2445 			              "<ul>"
2446 			              "<li>The action is maybe unknown.</li>"
2447 				      "<li>Invalid key parameter (empty or too long).</li>"
2448 			              "<li>The backend name is probably unknown or ambiguous (duplicated names).</li>"
2449 			              "<li>Some server names are probably unknown or ambiguous (duplicated names in the backend).</li>"
2450 			              "</ul>"
2451 			              "</div>\n", uri->uri_prefix,
2452 			              (appctx->ctx.stats.flags & STAT_HIDE_DOWN) ? ";up" : "",
2453 			              (appctx->ctx.stats.flags & STAT_NO_REFRESH) ? ";norefresh" : "",
2454 			              scope_txt);
2455 			break;
2456 		case STAT_STATUS_EXCD:
2457 			chunk_appendf(&trash,
2458 			              "<p><div class=active_down>"
2459 			              "<a class=lfsb href=\"%s%s%s%s\" title=\"Remove this message\">[X]</a> "
2460 			              "<b>Action not processed : the buffer couldn't store all the data.<br>"
2461 			              "You should retry with less servers at a time.</b>"
2462 			              "</div>\n", uri->uri_prefix,
2463 			              (appctx->ctx.stats.flags & STAT_HIDE_DOWN) ? ";up" : "",
2464 			              (appctx->ctx.stats.flags & STAT_NO_REFRESH) ? ";norefresh" : "",
2465 			              scope_txt);
2466 			break;
2467 		case STAT_STATUS_DENY:
2468 			chunk_appendf(&trash,
2469 			              "<p><div class=active_down>"
2470 			              "<a class=lfsb href=\"%s%s%s%s\" title=\"Remove this message\">[X]</a> "
2471 			              "<b>Action denied.</b>"
2472 			              "</div>\n", uri->uri_prefix,
2473 			              (appctx->ctx.stats.flags & STAT_HIDE_DOWN) ? ";up" : "",
2474 			              (appctx->ctx.stats.flags & STAT_NO_REFRESH) ? ";norefresh" : "",
2475 			              scope_txt);
2476 			break;
2477 		default:
2478 			chunk_appendf(&trash,
2479 			              "<p><div class=active_no_check>"
2480 			              "<a class=lfsb href=\"%s%s%s%s\" title=\"Remove this message\">[X]</a> "
2481 			              "Unexpected result."
2482 			              "</div>\n", uri->uri_prefix,
2483 			              (appctx->ctx.stats.flags & STAT_HIDE_DOWN) ? ";up" : "",
2484 			              (appctx->ctx.stats.flags & STAT_NO_REFRESH) ? ";norefresh" : "",
2485 			              scope_txt);
2486 		}
2487 		chunk_appendf(&trash, "<p>\n");
2488 	}
2489 }
2490 
2491 /* Dumps the HTML stats trailer block to the trash. The caller is responsible
2492  * for clearing the trash if needed.
2493  */
stats_dump_html_end()2494 static void stats_dump_html_end()
2495 {
2496 	chunk_appendf(&trash, "</body></html>\n");
2497 }
2498 
2499 /* Dumps the stats JSON header to the trash buffer which. The caller is responsible
2500  * for clearing it if needed.
2501  */
stats_dump_json_header()2502 static void stats_dump_json_header()
2503 {
2504 	chunk_strcat(&trash, "[");
2505 }
2506 
2507 
2508 /* Dumps the JSON stats trailer block to the trash. The caller is responsible
2509  * for clearing the trash if needed.
2510  */
stats_dump_json_end()2511 static void stats_dump_json_end()
2512 {
2513 	chunk_strcat(&trash, "]");
2514 }
2515 
2516 /* This function dumps statistics onto the stream interface's read buffer in
2517  * either CSV or HTML format. <uri> contains some HTML-specific parameters that
2518  * are ignored for CSV format (hence <uri> may be NULL there). It returns 0 if
2519  * it had to stop writing data and an I/O is needed, 1 if the dump is finished
2520  * and the stream must be closed, or -1 in case of any error. This function is
2521  * used by both the CLI and the HTTP handlers.
2522  */
stats_dump_stat_to_buffer(struct stream_interface * si,struct uri_auth * uri)2523 static int stats_dump_stat_to_buffer(struct stream_interface *si, struct uri_auth *uri)
2524 {
2525 	struct appctx *appctx = __objt_appctx(si->end);
2526 	struct channel *rep = si_ic(si);
2527 	struct proxy *px;
2528 
2529 	chunk_reset(&trash);
2530 
2531 	switch (appctx->st2) {
2532 	case STAT_ST_INIT:
2533 		appctx->st2 = STAT_ST_HEAD; /* let's start producing data */
2534 		/* fall through */
2535 
2536 	case STAT_ST_HEAD:
2537 		if (appctx->ctx.stats.flags & STAT_FMT_HTML)
2538 			stats_dump_html_head(uri);
2539 		else if (appctx->ctx.stats.flags & STAT_FMT_JSON)
2540 			stats_dump_json_header();
2541 		else if (!(appctx->ctx.stats.flags & STAT_FMT_TYPED))
2542 			stats_dump_csv_header();
2543 
2544 		if (ci_putchk(rep, &trash) == -1) {
2545 			si_applet_cant_put(si);
2546 			return 0;
2547 		}
2548 
2549 		appctx->st2 = STAT_ST_INFO;
2550 		/* fall through */
2551 
2552 	case STAT_ST_INFO:
2553 		if (appctx->ctx.stats.flags & STAT_FMT_HTML) {
2554 			stats_dump_html_info(si, uri);
2555 			if (ci_putchk(rep, &trash) == -1) {
2556 				si_applet_cant_put(si);
2557 				return 0;
2558 			}
2559 		}
2560 
2561 		appctx->ctx.stats.px = proxies_list;
2562 		appctx->ctx.stats.px_st = STAT_PX_ST_INIT;
2563 		appctx->st2 = STAT_ST_LIST;
2564 		/* fall through */
2565 
2566 	case STAT_ST_LIST:
2567 		/* dump proxies */
2568 		while (appctx->ctx.stats.px) {
2569 			if (buffer_almost_full(rep->buf)) {
2570 				si_applet_cant_put(si);
2571 				return 0;
2572 			}
2573 
2574 			px = appctx->ctx.stats.px;
2575 			/* skip the disabled proxies, global frontend and non-networked ones */
2576 			if (px->state != PR_STSTOPPED && px->uuid > 0 && (px->cap & (PR_CAP_FE | PR_CAP_BE)))
2577 				if (stats_dump_proxy_to_buffer(si, px, uri) == 0)
2578 					return 0;
2579 
2580 			appctx->ctx.stats.px = px->next;
2581 			appctx->ctx.stats.px_st = STAT_PX_ST_INIT;
2582 		}
2583 		/* here, we just have reached the last proxy */
2584 
2585 		appctx->st2 = STAT_ST_END;
2586 		/* fall through */
2587 
2588 	case STAT_ST_END:
2589 		if (appctx->ctx.stats.flags & (STAT_FMT_HTML|STAT_FMT_JSON)) {
2590 			if (appctx->ctx.stats.flags & STAT_FMT_HTML)
2591 				stats_dump_html_end();
2592 			else
2593 				stats_dump_json_end();
2594 			if (ci_putchk(rep, &trash) == -1) {
2595 				si_applet_cant_put(si);
2596 				return 0;
2597 			}
2598 		}
2599 
2600 		appctx->st2 = STAT_ST_FIN;
2601 		/* fall through */
2602 
2603 	case STAT_ST_FIN:
2604 		return 1;
2605 
2606 	default:
2607 		/* unknown state ! */
2608 		appctx->st2 = STAT_ST_FIN;
2609 		return -1;
2610 	}
2611 }
2612 
2613 /* We reached the stats page through a POST request. The appctx is
2614  * expected to have already been allocated by the caller.
2615  * Parse the posted data and enable/disable servers if necessary.
2616  * Returns 1 if request was parsed or zero if it needs more data.
2617  */
stats_process_http_post(struct stream_interface * si)2618 static int stats_process_http_post(struct stream_interface *si)
2619 {
2620 	struct stream *s = si_strm(si);
2621 	struct appctx *appctx = objt_appctx(si->end);
2622 
2623 	struct proxy *px = NULL;
2624 	struct server *sv = NULL;
2625 
2626 	char key[LINESIZE];
2627 	int action = ST_ADM_ACTION_NONE;
2628 	int reprocess = 0;
2629 
2630 	int total_servers = 0;
2631 	int altered_servers = 0;
2632 
2633 	char *first_param, *cur_param, *next_param, *end_params;
2634 	char *st_cur_param = NULL;
2635 	char *st_next_param = NULL;
2636 
2637 	struct chunk *temp;
2638 	int reql;
2639 
2640 	temp = get_trash_chunk();
2641 
2642 	/* we need more data */
2643 	if (s->txn->req.msg_state < HTTP_MSG_DONE) {
2644 		/* check if we can receive more */
2645 		if (buffer_total_space(s->req.buf) <= global.tune.maxrewrite) {
2646 			appctx->ctx.stats.st_code = STAT_STATUS_EXCD;
2647 			goto out;
2648 		}
2649 		goto wait;
2650 	}
2651 	reql = co_getblk(si_oc(si), temp->str, s->txn->req.body_len, s->txn->req.eoh + 2);
2652 	if (reql <= 0) {
2653 		appctx->ctx.stats.st_code = STAT_STATUS_EXCD;
2654 		goto out;
2655 	}
2656 
2657 	first_param = temp->str;
2658 	end_params  = temp->str + reql;
2659 	cur_param = next_param = end_params;
2660 	*end_params = '\0';
2661 
2662 	appctx->ctx.stats.st_code = STAT_STATUS_NONE;
2663 
2664 	/*
2665 	 * Parse the parameters in reverse order to only store the last value.
2666 	 * From the html form, the backend and the action are at the end.
2667 	 */
2668 	while (cur_param > first_param) {
2669 		char *value;
2670 		int poffset, plen;
2671 
2672 		cur_param--;
2673 
2674 		if ((*cur_param == '&') || (cur_param == first_param)) {
2675 		reprocess_servers:
2676 			/* Parse the key */
2677 			poffset = (cur_param != first_param ? 1 : 0);
2678 			plen = next_param - cur_param + (cur_param == first_param ? 1 : 0);
2679 			if ((plen > 0) && (plen <= sizeof(key))) {
2680 				strncpy(key, cur_param + poffset, plen);
2681 				key[plen - 1] = '\0';
2682 			} else {
2683 				appctx->ctx.stats.st_code = STAT_STATUS_ERRP;
2684 				goto out;
2685 			}
2686 
2687 			/* Parse the value */
2688 			value = key;
2689 			while (*value != '\0' && *value != '=') {
2690 				value++;
2691 			}
2692 			if (*value == '=') {
2693 				/* Ok, a value is found, we can mark the end of the key */
2694 				*value++ = '\0';
2695 			}
2696 			if (url_decode(key, 1) < 0 || url_decode(value, 1) < 0)
2697 				break;
2698 
2699 			/* Now we can check the key to see what to do */
2700 			if (!px && (strcmp(key, "b") == 0)) {
2701 				if ((px = proxy_be_by_name(value)) == NULL) {
2702 					/* the backend name is unknown or ambiguous (duplicate names) */
2703 					appctx->ctx.stats.st_code = STAT_STATUS_ERRP;
2704 					goto out;
2705 				}
2706 			}
2707 			else if (!action && (strcmp(key, "action") == 0)) {
2708 				if (strcmp(value, "ready") == 0) {
2709 					action = ST_ADM_ACTION_READY;
2710 				}
2711 				else if (strcmp(value, "drain") == 0) {
2712 					action = ST_ADM_ACTION_DRAIN;
2713 				}
2714 				else if (strcmp(value, "maint") == 0) {
2715 					action = ST_ADM_ACTION_MAINT;
2716 				}
2717 				else if (strcmp(value, "shutdown") == 0) {
2718 					action = ST_ADM_ACTION_SHUTDOWN;
2719 				}
2720 				else if (strcmp(value, "dhlth") == 0) {
2721 					action = ST_ADM_ACTION_DHLTH;
2722 				}
2723 				else if (strcmp(value, "ehlth") == 0) {
2724 					action = ST_ADM_ACTION_EHLTH;
2725 				}
2726 				else if (strcmp(value, "hrunn") == 0) {
2727 					action = ST_ADM_ACTION_HRUNN;
2728 				}
2729 				else if (strcmp(value, "hnolb") == 0) {
2730 					action = ST_ADM_ACTION_HNOLB;
2731 				}
2732 				else if (strcmp(value, "hdown") == 0) {
2733 					action = ST_ADM_ACTION_HDOWN;
2734 				}
2735 				else if (strcmp(value, "dagent") == 0) {
2736 					action = ST_ADM_ACTION_DAGENT;
2737 				}
2738 				else if (strcmp(value, "eagent") == 0) {
2739 					action = ST_ADM_ACTION_EAGENT;
2740 				}
2741 				else if (strcmp(value, "arunn") == 0) {
2742 					action = ST_ADM_ACTION_ARUNN;
2743 				}
2744 				else if (strcmp(value, "adown") == 0) {
2745 					action = ST_ADM_ACTION_ADOWN;
2746 				}
2747 				/* else these are the old supported methods */
2748 				else if (strcmp(value, "disable") == 0) {
2749 					action = ST_ADM_ACTION_DISABLE;
2750 				}
2751 				else if (strcmp(value, "enable") == 0) {
2752 					action = ST_ADM_ACTION_ENABLE;
2753 				}
2754 				else if (strcmp(value, "stop") == 0) {
2755 					action = ST_ADM_ACTION_STOP;
2756 				}
2757 				else if (strcmp(value, "start") == 0) {
2758 					action = ST_ADM_ACTION_START;
2759 				}
2760 				else {
2761 					appctx->ctx.stats.st_code = STAT_STATUS_ERRP;
2762 					goto out;
2763 				}
2764 			}
2765 			else if (strcmp(key, "s") == 0) {
2766 				if (!(px && action)) {
2767 					/*
2768 					 * Indicates that we'll need to reprocess the parameters
2769 					 * as soon as backend and action are known
2770 					 */
2771 					if (!reprocess) {
2772 						st_cur_param  = cur_param;
2773 						st_next_param = next_param;
2774 					}
2775 					reprocess = 1;
2776 				}
2777 				else if ((sv = findserver(px, value)) != NULL) {
2778 					HA_SPIN_LOCK(SERVER_LOCK, &sv->lock);
2779 					switch (action) {
2780 					case ST_ADM_ACTION_DISABLE:
2781 						if (!(sv->cur_admin & SRV_ADMF_FMAINT)) {
2782 							altered_servers++;
2783 							total_servers++;
2784 							srv_set_admin_flag(sv, SRV_ADMF_FMAINT, "'disable' on stats page");
2785 						}
2786 						break;
2787 					case ST_ADM_ACTION_ENABLE:
2788 						if (sv->cur_admin & SRV_ADMF_FMAINT) {
2789 							altered_servers++;
2790 							total_servers++;
2791 							srv_clr_admin_flag(sv, SRV_ADMF_FMAINT);
2792 						}
2793 						break;
2794 					case ST_ADM_ACTION_STOP:
2795 						if (!(sv->cur_admin & SRV_ADMF_FDRAIN)) {
2796 							srv_set_admin_flag(sv, SRV_ADMF_FDRAIN, "'stop' on stats page");
2797 							altered_servers++;
2798 							total_servers++;
2799 						}
2800 						break;
2801 					case ST_ADM_ACTION_START:
2802 						if (sv->cur_admin & SRV_ADMF_FDRAIN) {
2803 							srv_clr_admin_flag(sv, SRV_ADMF_FDRAIN);
2804 							altered_servers++;
2805 							total_servers++;
2806 						}
2807 						break;
2808 					case ST_ADM_ACTION_DHLTH:
2809 						if (sv->check.state & CHK_ST_CONFIGURED) {
2810 							sv->check.state &= ~CHK_ST_ENABLED;
2811 							altered_servers++;
2812 							total_servers++;
2813 						}
2814 						break;
2815 					case ST_ADM_ACTION_EHLTH:
2816 						if (sv->check.state & CHK_ST_CONFIGURED) {
2817 							sv->check.state |= CHK_ST_ENABLED;
2818 							altered_servers++;
2819 							total_servers++;
2820 						}
2821 						break;
2822 					case ST_ADM_ACTION_HRUNN:
2823 						if (!(sv->track)) {
2824 							sv->check.health = sv->check.rise + sv->check.fall - 1;
2825 							srv_set_running(sv, "changed from Web interface", NULL);
2826 							altered_servers++;
2827 							total_servers++;
2828 						}
2829 						break;
2830 					case ST_ADM_ACTION_HNOLB:
2831 						if (!(sv->track)) {
2832 							sv->check.health = sv->check.rise + sv->check.fall - 1;
2833 							srv_set_stopping(sv, "changed from Web interface", NULL);
2834 							altered_servers++;
2835 							total_servers++;
2836 						}
2837 						break;
2838 					case ST_ADM_ACTION_HDOWN:
2839 						if (!(sv->track)) {
2840 							sv->check.health = 0;
2841 							srv_set_stopped(sv, "changed from Web interface", NULL);
2842 							altered_servers++;
2843 							total_servers++;
2844 						}
2845 						break;
2846 					case ST_ADM_ACTION_DAGENT:
2847 						if (sv->agent.state & CHK_ST_CONFIGURED) {
2848 							sv->agent.state &= ~CHK_ST_ENABLED;
2849 							altered_servers++;
2850 							total_servers++;
2851 						}
2852 						break;
2853 					case ST_ADM_ACTION_EAGENT:
2854 						if (sv->agent.state & CHK_ST_CONFIGURED) {
2855 							sv->agent.state |= CHK_ST_ENABLED;
2856 							altered_servers++;
2857 							total_servers++;
2858 						}
2859 						break;
2860 					case ST_ADM_ACTION_ARUNN:
2861 						if (sv->agent.state & CHK_ST_ENABLED) {
2862 							sv->agent.health = sv->agent.rise + sv->agent.fall - 1;
2863 							srv_set_running(sv, "changed from Web interface", NULL);
2864 							altered_servers++;
2865 							total_servers++;
2866 						}
2867 						break;
2868 					case ST_ADM_ACTION_ADOWN:
2869 						if (sv->agent.state & CHK_ST_ENABLED) {
2870 							sv->agent.health = 0;
2871 							srv_set_stopped(sv, "changed from Web interface", NULL);
2872 							altered_servers++;
2873 							total_servers++;
2874 						}
2875 						break;
2876 					case ST_ADM_ACTION_READY:
2877 						srv_adm_set_ready(sv);
2878 						altered_servers++;
2879 						total_servers++;
2880 						break;
2881 					case ST_ADM_ACTION_DRAIN:
2882 						srv_adm_set_drain(sv);
2883 						altered_servers++;
2884 						total_servers++;
2885 						break;
2886 					case ST_ADM_ACTION_MAINT:
2887 						srv_adm_set_maint(sv);
2888 						altered_servers++;
2889 						total_servers++;
2890 						break;
2891 					case ST_ADM_ACTION_SHUTDOWN:
2892 						if (px->state != PR_STSTOPPED) {
2893 							struct stream *sess, *sess_bck;
2894 
2895 							list_for_each_entry_safe(sess, sess_bck, &sv->actconns, by_srv)
2896 								if (sess->srv_conn == sv)
2897 									stream_shutdown(sess, SF_ERR_KILLED);
2898 
2899 							altered_servers++;
2900 							total_servers++;
2901 						}
2902 						break;
2903 					}
2904 					HA_SPIN_UNLOCK(SERVER_LOCK, &sv->lock);
2905 				} else {
2906 					/* the server name is unknown or ambiguous (duplicate names) */
2907 					total_servers++;
2908 				}
2909 			}
2910 			if (reprocess && px && action) {
2911 				/* Now, we know the backend and the action chosen by the user.
2912 				 * We can safely restart from the first server parameter
2913 				 * to reprocess them
2914 				 */
2915 				cur_param  = st_cur_param;
2916 				next_param = st_next_param;
2917 				reprocess = 0;
2918 				goto reprocess_servers;
2919 			}
2920 
2921 			next_param = cur_param;
2922 		}
2923 	}
2924 
2925 	if (total_servers == 0) {
2926 		appctx->ctx.stats.st_code = STAT_STATUS_NONE;
2927 	}
2928 	else if (altered_servers == 0) {
2929 		appctx->ctx.stats.st_code = STAT_STATUS_ERRP;
2930 	}
2931 	else if (altered_servers == total_servers) {
2932 		appctx->ctx.stats.st_code = STAT_STATUS_DONE;
2933 	}
2934 	else {
2935 		appctx->ctx.stats.st_code = STAT_STATUS_PART;
2936 	}
2937  out:
2938 	return 1;
2939  wait:
2940 	appctx->ctx.stats.st_code = STAT_STATUS_NONE;
2941 	return 0;
2942 }
2943 
2944 
stats_send_http_headers(struct stream_interface * si)2945 static int stats_send_http_headers(struct stream_interface *si)
2946 {
2947 	struct stream *s = si_strm(si);
2948 	struct uri_auth *uri = s->be->uri_auth;
2949 	struct appctx *appctx = objt_appctx(si->end);
2950 
2951 	chunk_printf(&trash,
2952 		     "HTTP/1.1 200 OK\r\n"
2953 		     "Cache-Control: no-cache\r\n"
2954 		     "Connection: close\r\n"
2955 		     "Content-Type: %s\r\n",
2956 		     (appctx->ctx.stats.flags & STAT_FMT_HTML) ? "text/html" : "text/plain");
2957 
2958 	if (uri->refresh > 0 && !(appctx->ctx.stats.flags & STAT_NO_REFRESH))
2959 		chunk_appendf(&trash, "Refresh: %d\r\n",
2960 			      uri->refresh);
2961 
2962 	/* we don't send the CRLF in chunked mode, it will be sent with the first chunk's size */
2963 
2964 	if (appctx->ctx.stats.flags & STAT_CHUNKED)
2965 		chunk_appendf(&trash, "Transfer-Encoding: chunked\r\n");
2966 	else
2967 		chunk_appendf(&trash, "\r\n");
2968 
2969 	if (ci_putchk(si_ic(si), &trash) == -1) {
2970 		si_applet_cant_put(si);
2971 		return 0;
2972 	}
2973 
2974 	return 1;
2975 }
2976 
stats_send_http_redirect(struct stream_interface * si)2977 static int stats_send_http_redirect(struct stream_interface *si)
2978 {
2979 	char scope_txt[STAT_SCOPE_TXT_MAXLEN + sizeof STAT_SCOPE_PATTERN];
2980 	struct stream *s = si_strm(si);
2981 	struct uri_auth *uri = s->be->uri_auth;
2982 	struct appctx *appctx = objt_appctx(si->end);
2983 
2984 	/* scope_txt = search pattern + search query, appctx->ctx.stats.scope_len is always <= STAT_SCOPE_TXT_MAXLEN */
2985 	scope_txt[0] = 0;
2986 	if (appctx->ctx.stats.scope_len) {
2987 		strcpy(scope_txt, STAT_SCOPE_PATTERN);
2988 		memcpy(scope_txt + strlen(STAT_SCOPE_PATTERN), bo_ptr(si_ob(si)) + appctx->ctx.stats.scope_str, appctx->ctx.stats.scope_len);
2989 		scope_txt[strlen(STAT_SCOPE_PATTERN) + appctx->ctx.stats.scope_len] = 0;
2990 	}
2991 
2992 	/* We don't want to land on the posted stats page because a refresh will
2993 	 * repost the data. We don't want this to happen on accident so we redirect
2994 	 * the browse to the stats page with a GET.
2995 	 */
2996 	chunk_printf(&trash,
2997 		     "HTTP/1.1 303 See Other\r\n"
2998 		     "Cache-Control: no-cache\r\n"
2999 		     "Content-Type: text/plain\r\n"
3000 		     "Connection: close\r\n"
3001 		     "Location: %s;st=%s%s%s%s\r\n"
3002 		     "Content-length: 0\r\n"
3003 		     "\r\n",
3004 		     uri->uri_prefix,
3005 		     ((appctx->ctx.stats.st_code > STAT_STATUS_INIT) &&
3006 		      (appctx->ctx.stats.st_code < STAT_STATUS_SIZE) &&
3007 		      stat_status_codes[appctx->ctx.stats.st_code]) ?
3008 		     stat_status_codes[appctx->ctx.stats.st_code] :
3009 		     stat_status_codes[STAT_STATUS_UNKN],
3010 		     (appctx->ctx.stats.flags & STAT_HIDE_DOWN) ? ";up" : "",
3011 		     (appctx->ctx.stats.flags & STAT_NO_REFRESH) ? ";norefresh" : "",
3012 		     scope_txt);
3013 
3014 	if (ci_putchk(si_ic(si), &trash) == -1) {
3015 		si_applet_cant_put(si);
3016 		return 0;
3017 	}
3018 
3019 	return 1;
3020 }
3021 
3022 
3023 /* This I/O handler runs as an applet embedded in a stream interface. It is
3024  * used to send HTTP stats over a TCP socket. The mechanism is very simple.
3025  * appctx->st0 contains the operation in progress (dump, done). The handler
3026  * automatically unregisters itself once transfer is complete.
3027  */
http_stats_io_handler(struct appctx * appctx)3028 static void http_stats_io_handler(struct appctx *appctx)
3029 {
3030 	struct stream_interface *si = appctx->owner;
3031 	struct stream *s = si_strm(si);
3032 	struct channel *req = si_oc(si);
3033 	struct channel *res = si_ic(si);
3034 
3035 	if (unlikely(si->state == SI_ST_DIS || si->state == SI_ST_CLO))
3036 		goto out;
3037 
3038 	/* Check if the input buffer is avalaible. */
3039 	if (res->buf->size == 0) {
3040 		si_applet_cant_put(si);
3041 		goto out;
3042 	}
3043 
3044 	/* check that the output is not closed */
3045 	if (res->flags & (CF_SHUTW|CF_SHUTW_NOW))
3046 		appctx->st0 = STAT_HTTP_DONE;
3047 
3048 	/* all states are processed in sequence */
3049 	if (appctx->st0 == STAT_HTTP_HEAD) {
3050 		if (stats_send_http_headers(si)) {
3051 			if (s->txn->meth == HTTP_METH_HEAD)
3052 				appctx->st0 = STAT_HTTP_DONE;
3053 			else
3054 				appctx->st0 = STAT_HTTP_DUMP;
3055 		}
3056 	}
3057 
3058 	if (appctx->st0 == STAT_HTTP_DUMP) {
3059 		unsigned int prev_len = si_ib(si)->i;
3060 		unsigned int data_len;
3061 		unsigned int last_len;
3062 		unsigned int last_fwd = 0;
3063 
3064 		if (appctx->ctx.stats.flags & STAT_CHUNKED) {
3065 			/* One difficulty we're facing is that we must prevent
3066 			 * the input data from being automatically forwarded to
3067 			 * the output area. For this, we temporarily disable
3068 			 * forwarding on the channel.
3069 			 */
3070 			last_fwd = si_ic(si)->to_forward;
3071 			si_ic(si)->to_forward = 0;
3072 			chunk_printf(&trash, "\r\n000000\r\n");
3073 			if (ci_putchk(si_ic(si), &trash) == -1) {
3074 				si_applet_cant_put(si);
3075 				si_ic(si)->to_forward = last_fwd;
3076 				goto out;
3077 			}
3078 		}
3079 
3080 		data_len = si_ib(si)->i;
3081 		if (stats_dump_stat_to_buffer(si, s->be->uri_auth))
3082 			appctx->st0 = STAT_HTTP_DONE;
3083 
3084 		last_len = si_ib(si)->i;
3085 
3086 		/* Now we must either adjust or remove the chunk size. This is
3087 		 * not easy because the chunk size might wrap at the end of the
3088 		 * buffer, so we pretend we have nothing in the buffer, we write
3089 		 * the size, then restore the buffer's contents. Note that we can
3090 		 * only do that because no forwarding is scheduled on the stats
3091 		 * applet.
3092 		 */
3093 		if (appctx->ctx.stats.flags & STAT_CHUNKED) {
3094 			si_ic(si)->total -= (last_len - prev_len);
3095 			si_ib(si)->i     -= (last_len - prev_len);
3096 
3097 			if (last_len != data_len) {
3098 				chunk_printf(&trash, "\r\n%06x\r\n", (last_len - data_len));
3099 				if (ci_putchk(si_ic(si), &trash) == -1)
3100 					si_applet_cant_put(si);
3101 
3102 				si_ic(si)->total += (last_len - data_len);
3103 				si_ib(si)->i     += (last_len - data_len);
3104 			}
3105 			/* now re-enable forwarding */
3106 			channel_forward(si_ic(si), last_fwd);
3107 		}
3108 	}
3109 
3110 	if (appctx->st0 == STAT_HTTP_POST) {
3111 		if (stats_process_http_post(si))
3112 			appctx->st0 = STAT_HTTP_LAST;
3113 		else if (si_oc(si)->flags & CF_SHUTR)
3114 			appctx->st0 = STAT_HTTP_DONE;
3115 	}
3116 
3117 	if (appctx->st0 == STAT_HTTP_LAST) {
3118 		if (stats_send_http_redirect(si))
3119 			appctx->st0 = STAT_HTTP_DONE;
3120 	}
3121 
3122 	if (appctx->st0 == STAT_HTTP_DONE) {
3123 		if (appctx->ctx.stats.flags & STAT_CHUNKED) {
3124 			chunk_printf(&trash, "\r\n0\r\n\r\n");
3125 			if (ci_putchk(si_ic(si), &trash) == -1) {
3126 				si_applet_cant_put(si);
3127 				goto out;
3128 			}
3129 		}
3130 		/* eat the whole request */
3131 		co_skip(si_oc(si), si_ob(si)->o);
3132 		res->flags |= CF_READ_NULL;
3133 		si_shutr(si);
3134 	}
3135 
3136 	if ((res->flags & CF_SHUTR) && (si->state == SI_ST_EST))
3137 		si_shutw(si);
3138 
3139 	if (appctx->st0 == STAT_HTTP_DONE) {
3140 		if ((req->flags & CF_SHUTW) && (si->state == SI_ST_EST)) {
3141 			si_shutr(si);
3142 			res->flags |= CF_READ_NULL;
3143 		}
3144 	}
3145  out:
3146 	/* we have left the request in the buffer for the case where we
3147 	 * process a POST, and this automatically re-enables activity on
3148 	 * read. It's better to indicate that we want to stop reading when
3149 	 * we're sending, so that we know there's at most one direction
3150 	 * deciding to wake the applet up. It saves it from looping when
3151 	 * emitting large blocks into small TCP windows.
3152 	 */
3153 	if (!channel_is_empty(res))
3154 		si_applet_stop_get(si);
3155 }
3156 
3157 /* Dump all fields from <info> into <out> using the "show info" format (name: value) */
stats_dump_info_fields(struct chunk * out,const struct field * info)3158 static int stats_dump_info_fields(struct chunk *out, const struct field *info)
3159 {
3160 	int field;
3161 
3162 	for (field = 0; field < INF_TOTAL_FIELDS; field++) {
3163 		if (!field_format(info, field))
3164 			continue;
3165 
3166 		if (!chunk_appendf(out, "%s: ", info_field_names[field]))
3167 			return 0;
3168 		if (!stats_emit_raw_data_field(out, &info[field]))
3169 			return 0;
3170 		if (!chunk_strcat(out, "\n"))
3171 			return 0;
3172 	}
3173 	return 1;
3174 }
3175 
3176 /* Dump all fields from <info> into <out> using the "show info typed" format */
stats_dump_typed_info_fields(struct chunk * out,const struct field * info)3177 static int stats_dump_typed_info_fields(struct chunk *out, const struct field *info)
3178 {
3179 	int field;
3180 
3181 	for (field = 0; field < INF_TOTAL_FIELDS; field++) {
3182 		if (!field_format(info, field))
3183 			continue;
3184 
3185 		if (!chunk_appendf(out, "%d.%s.%u:", field, info_field_names[field], info[INF_PROCESS_NUM].u.u32))
3186 			return 0;
3187 		if (!stats_emit_field_tags(out, &info[field], ':'))
3188 			return 0;
3189 		if (!stats_emit_typed_data_field(out, &info[field]))
3190 			return 0;
3191 		if (!chunk_strcat(out, "\n"))
3192 			return 0;
3193 	}
3194 	return 1;
3195 }
3196 
3197 /* Fill <info> with HAProxy global info. <info> is preallocated
3198  * array of length <len>. The length of the aray must be
3199  * INF_TOTAL_FIELDS. If this length is less then this value, the
3200  * function returns 0, otherwise, it returns 1.
3201  */
stats_fill_info(struct field * info,int len)3202 int stats_fill_info(struct field *info, int len)
3203 {
3204 	unsigned int up = (now.tv_sec - start_date.tv_sec);
3205 	struct chunk *out = get_trash_chunk();
3206 
3207 #ifdef USE_OPENSSL
3208 	int ssl_sess_rate = read_freq_ctr(&global.ssl_per_sec);
3209 	int ssl_key_rate = read_freq_ctr(&global.ssl_fe_keys_per_sec);
3210 	int ssl_reuse = 0;
3211 
3212 	if (ssl_key_rate < ssl_sess_rate) {
3213 		/* count the ssl reuse ratio and avoid overflows in both directions */
3214 		ssl_reuse = 100 - (100 * ssl_key_rate + (ssl_sess_rate - 1) / 2) / ssl_sess_rate;
3215 	}
3216 #endif
3217 
3218 	if (len < INF_TOTAL_FIELDS)
3219 		return 0;
3220 
3221 	chunk_reset(out);
3222 	memset(info, 0, sizeof(*info) * len);
3223 
3224 	info[INF_NAME]                           = mkf_str(FO_PRODUCT|FN_OUTPUT|FS_SERVICE, PRODUCT_NAME);
3225 	info[INF_VERSION]                        = mkf_str(FO_PRODUCT|FN_OUTPUT|FS_SERVICE, HAPROXY_VERSION);
3226 	info[INF_RELEASE_DATE]                   = mkf_str(FO_PRODUCT|FN_OUTPUT|FS_SERVICE, HAPROXY_DATE);
3227 
3228 	info[INF_NBTHREAD]                       = mkf_u32(FO_CONFIG|FS_SERVICE, global.nbthread);
3229 	info[INF_NBPROC]                         = mkf_u32(FO_CONFIG|FS_SERVICE, global.nbproc);
3230 	info[INF_PROCESS_NUM]                    = mkf_u32(FO_KEY, relative_pid);
3231 	info[INF_PID]                            = mkf_u32(FO_STATUS, pid);
3232 
3233 	info[INF_UPTIME]                         = mkf_str(FN_DURATION, chunk_newstr(out));
3234 	chunk_appendf(out, "%ud %uh%02um%02us", up / 86400, (up % 86400) / 3600, (up % 3600) / 60, (up % 60));
3235 
3236 	info[INF_UPTIME_SEC]                     = mkf_u32(FN_DURATION, up);
3237 	info[INF_MEMMAX_MB]                      = mkf_u32(FO_CONFIG|FN_LIMIT, global.rlimit_memmax);
3238 	info[INF_POOL_ALLOC_MB]                  = mkf_u32(0, (unsigned)(pool_total_allocated() / 1048576L));
3239 	info[INF_POOL_USED_MB]                   = mkf_u32(0, (unsigned)(pool_total_used() / 1048576L));
3240 	info[INF_POOL_FAILED]                    = mkf_u32(FN_COUNTER, pool_total_failures());
3241 	info[INF_ULIMIT_N]                       = mkf_u32(FO_CONFIG|FN_LIMIT, global.rlimit_nofile);
3242 	info[INF_MAXSOCK]                        = mkf_u32(FO_CONFIG|FN_LIMIT, global.maxsock);
3243 	info[INF_MAXCONN]                        = mkf_u32(FO_CONFIG|FN_LIMIT, global.maxconn);
3244 	info[INF_HARD_MAXCONN]                   = mkf_u32(FO_CONFIG|FN_LIMIT, global.hardmaxconn);
3245 	info[INF_CURR_CONN]                      = mkf_u32(0, actconn);
3246 	info[INF_CUM_CONN]                       = mkf_u32(FN_COUNTER, totalconn);
3247 	info[INF_CUM_REQ]                        = mkf_u32(FN_COUNTER, global.req_count);
3248 #ifdef USE_OPENSSL
3249 	info[INF_MAX_SSL_CONNS]                  = mkf_u32(FN_MAX, global.maxsslconn);
3250 	info[INF_CURR_SSL_CONNS]                 = mkf_u32(0, sslconns);
3251 	info[INF_CUM_SSL_CONNS]                  = mkf_u32(FN_COUNTER, totalsslconns);
3252 #endif
3253 	info[INF_MAXPIPES]                       = mkf_u32(FO_CONFIG|FN_LIMIT, global.maxpipes);
3254 	info[INF_PIPES_USED]                     = mkf_u32(0, pipes_used);
3255 	info[INF_PIPES_FREE]                     = mkf_u32(0, pipes_free);
3256 	info[INF_CONN_RATE]                      = mkf_u32(FN_RATE, read_freq_ctr(&global.conn_per_sec));
3257 	info[INF_CONN_RATE_LIMIT]                = mkf_u32(FO_CONFIG|FN_LIMIT, global.cps_lim);
3258 	info[INF_MAX_CONN_RATE]                  = mkf_u32(FN_MAX, global.cps_max);
3259 	info[INF_SESS_RATE]                      = mkf_u32(FN_RATE, read_freq_ctr(&global.sess_per_sec));
3260 	info[INF_SESS_RATE_LIMIT]                = mkf_u32(FO_CONFIG|FN_LIMIT, global.sps_lim);
3261 	info[INF_MAX_SESS_RATE]                  = mkf_u32(FN_RATE, global.sps_max);
3262 
3263 #ifdef USE_OPENSSL
3264 	info[INF_SSL_RATE]                       = mkf_u32(FN_RATE, ssl_sess_rate);
3265 	info[INF_SSL_RATE_LIMIT]                 = mkf_u32(FO_CONFIG|FN_LIMIT, global.ssl_lim);
3266 	info[INF_MAX_SSL_RATE]                   = mkf_u32(FN_MAX, global.ssl_max);
3267 	info[INF_SSL_FRONTEND_KEY_RATE]          = mkf_u32(0, ssl_key_rate);
3268 	info[INF_SSL_FRONTEND_MAX_KEY_RATE]      = mkf_u32(FN_MAX, global.ssl_fe_keys_max);
3269 	info[INF_SSL_FRONTEND_SESSION_REUSE_PCT] = mkf_u32(0, ssl_reuse);
3270 	info[INF_SSL_BACKEND_KEY_RATE]           = mkf_u32(FN_RATE, read_freq_ctr(&global.ssl_be_keys_per_sec));
3271 	info[INF_SSL_BACKEND_MAX_KEY_RATE]       = mkf_u32(FN_MAX, global.ssl_be_keys_max);
3272 	info[INF_SSL_CACHE_LOOKUPS]              = mkf_u32(FN_COUNTER, global.shctx_lookups);
3273 	info[INF_SSL_CACHE_MISSES]               = mkf_u32(FN_COUNTER, global.shctx_misses);
3274 #endif
3275 	info[INF_COMPRESS_BPS_IN]                = mkf_u32(FN_RATE, read_freq_ctr(&global.comp_bps_in));
3276 	info[INF_COMPRESS_BPS_OUT]               = mkf_u32(FN_RATE, read_freq_ctr(&global.comp_bps_out));
3277 	info[INF_COMPRESS_BPS_RATE_LIM]          = mkf_u32(FO_CONFIG|FN_LIMIT, global.comp_rate_lim);
3278 #ifdef USE_ZLIB
3279 	info[INF_ZLIB_MEM_USAGE]                 = mkf_u32(0, zlib_used_memory);
3280 	info[INF_MAX_ZLIB_MEM_USAGE]             = mkf_u32(FO_CONFIG|FN_LIMIT, global.maxzlibmem);
3281 #endif
3282 	info[INF_TASKS]                          = mkf_u32(0, nb_tasks_cur);
3283 	info[INF_RUN_QUEUE]                      = mkf_u32(0, tasks_run_queue_cur);
3284 	info[INF_IDLE_PCT]                       = mkf_u32(FN_AVG, idle_pct);
3285 	info[INF_NODE]                           = mkf_str(FO_CONFIG|FN_OUTPUT|FS_SERVICE, global.node);
3286 	if (global.desc)
3287 		info[INF_DESCRIPTION]            = mkf_str(FO_CONFIG|FN_OUTPUT|FS_SERVICE, global.desc);
3288 	info[INF_STOPPING]                       = mkf_u32(0, stopping);
3289 	info[INF_JOBS]                           = mkf_u32(0, jobs);
3290 	info[INF_LISTENERS]                      = mkf_u32(0, listeners);
3291 
3292 	return 1;
3293 }
3294 
3295 /* This function dumps information onto the stream interface's read buffer.
3296  * It returns 0 as long as it does not complete, non-zero upon completion.
3297  * No state is used.
3298  */
stats_dump_info_to_buffer(struct stream_interface * si)3299 static int stats_dump_info_to_buffer(struct stream_interface *si)
3300 {
3301 	struct appctx *appctx = __objt_appctx(si->end);
3302 
3303 	if (!stats_fill_info(info, INF_TOTAL_FIELDS))
3304 		return 0;
3305 
3306 	chunk_reset(&trash);
3307 
3308 	if (appctx->ctx.stats.flags & STAT_FMT_TYPED)
3309 		stats_dump_typed_info_fields(&trash, info);
3310 	else if (appctx->ctx.stats.flags & STAT_FMT_JSON)
3311 		stats_dump_json_info_fields(&trash, info);
3312 	else
3313 		stats_dump_info_fields(&trash, info);
3314 
3315 	if (ci_putchk(si_ic(si), &trash) == -1) {
3316 		si_applet_cant_put(si);
3317 		return 0;
3318 	}
3319 
3320 	return 1;
3321 }
3322 
3323 /* This function dumps the schema onto the stream interface's read buffer.
3324  * It returns 0 as long as it does not complete, non-zero upon completion.
3325  * No state is used.
3326  *
3327  * Integer values bouned to the range [-(2**53)+1, (2**53)-1] as
3328  * per the recommendation for interoperable integers in section 6 of RFC 7159.
3329  */
stats_dump_json_schema(struct chunk * out)3330 static void stats_dump_json_schema(struct chunk *out)
3331 {
3332 
3333 	int old_len = out->len;
3334 
3335 	chunk_strcat(out,
3336 		     "{"
3337 		      "\"$schema\":\"http://json-schema.org/draft-04/schema#\","
3338 		      "\"oneOf\":["
3339 		       "{"
3340 			"\"title\":\"Info\","
3341 			"\"type\":\"array\","
3342 			"\"items\":{"
3343 			 "\"title\":\"InfoItem\","
3344 			 "\"type\":\"object\","
3345 			 "\"properties\":{"
3346 			  "\"field\":{\"$ref\":\"#/definitions/field\"},"
3347 			  "\"processNum\":{\"$ref\":\"#/definitions/processNum\"},"
3348 			  "\"tags\":{\"$ref\":\"#/definitions/tags\"},"
3349 			  "\"value\":{\"$ref\":\"#/definitions/typedValue\"}"
3350 			 "},"
3351 			 "\"required\":[\"field\",\"processNum\",\"tags\","
3352 				       "\"value\"]"
3353 			"}"
3354 		       "},"
3355 		       "{"
3356 			"\"title\":\"Stat\","
3357 			"\"type\":\"array\","
3358 			"\"items\":{"
3359 			 "\"title\":\"InfoItem\","
3360 			 "\"type\":\"object\","
3361 			 "\"properties\":{"
3362 			  "\"objType\":{"
3363 			   "\"enum\":[\"Frontend\",\"Backend\",\"Listener\","
3364 				     "\"Server\",\"Unknown\"]"
3365 			  "},"
3366 			  "\"proxyId\":{"
3367 			   "\"type\":\"integer\","
3368 			   "\"minimum\":0"
3369 			  "},"
3370 			  "\"id\":{"
3371 			   "\"type\":\"integer\","
3372 			   "\"minimum\":0"
3373 			  "},"
3374 			  "\"field\":{\"$ref\":\"#/definitions/field\"},"
3375 			  "\"processNum\":{\"$ref\":\"#/definitions/processNum\"},"
3376 			  "\"tags\":{\"$ref\":\"#/definitions/tags\"},"
3377 			  "\"typedValue\":{\"$ref\":\"#/definitions/typedValue\"}"
3378 			 "},"
3379 			 "\"required\":[\"objType\",\"proxyId\",\"id\","
3380 				       "\"field\",\"processNum\",\"tags\","
3381 				       "\"value\"]"
3382 			"}"
3383 		       "},"
3384 		       "{"
3385 			"\"title\":\"Error\","
3386 			"\"type\":\"object\","
3387 			"\"properties\":{"
3388 			 "\"errorStr\":{"
3389 			  "\"type\":\"string\""
3390 			 "}"
3391 			"},"
3392 			"\"required\":[\"errorStr\"]"
3393 		       "}"
3394 		      "],"
3395 		      "\"definitions\":{"
3396 		       "\"field\":{"
3397 			"\"type\":\"object\","
3398 			"\"pos\":{"
3399 			 "\"type\":\"integer\","
3400 			 "\"minimum\":0"
3401 			"},"
3402 			"\"name\":{"
3403 			 "\"type\":\"string\""
3404 			"},"
3405 			"\"required\":[\"pos\",\"name\"]"
3406 		       "},"
3407 		       "\"processNum\":{"
3408 			"\"type\":\"integer\","
3409 			"\"minimum\":1"
3410 		       "},"
3411 		       "\"tags\":{"
3412 			"\"type\":\"object\","
3413 			"\"origin\":{"
3414 			 "\"type\":\"string\","
3415 			 "\"enum\":[\"Metric\",\"Status\",\"Key\","
3416 				   "\"Config\",\"Product\",\"Unknown\"]"
3417 			"},"
3418 			"\"nature\":{"
3419 			 "\"type\":\"string\","
3420 			 "\"enum\":[\"Gauge\",\"Limit\",\"Min\",\"Max\","
3421 				   "\"Rate\",\"Counter\",\"Duration\","
3422 				   "\"Age\",\"Time\",\"Name\",\"Output\","
3423 				   "\"Avg\", \"Unknown\"]"
3424 			"},"
3425 			"\"scope\":{"
3426 			 "\"type\":\"string\","
3427 			 "\"enum\":[\"Cluster\",\"Process\",\"Service\","
3428 				   "\"System\",\"Unknown\"]"
3429 			"},"
3430 			"\"required\":[\"origin\",\"nature\",\"scope\"]"
3431 		       "},"
3432 		       "\"typedValue\":{"
3433 			"\"type\":\"object\","
3434 			"\"oneOf\":["
3435 			 "{\"$ref\":\"#/definitions/typedValue/definitions/s32Value\"},"
3436 			 "{\"$ref\":\"#/definitions/typedValue/definitions/s64Value\"},"
3437 			 "{\"$ref\":\"#/definitions/typedValue/definitions/u32Value\"},"
3438 			 "{\"$ref\":\"#/definitions/typedValue/definitions/u64Value\"},"
3439 			 "{\"$ref\":\"#/definitions/typedValue/definitions/strValue\"}"
3440 			"],"
3441 			"\"definitions\":{"
3442 			 "\"s32Value\":{"
3443 			  "\"properties\":{"
3444 			   "\"type\":{"
3445 			    "\"type\":\"string\","
3446 			    "\"enum\":[\"s32\"]"
3447 			   "},"
3448 			   "\"value\":{"
3449 			    "\"type\":\"integer\","
3450 			    "\"minimum\":-2147483648,"
3451 			    "\"maximum\":2147483647"
3452 			   "}"
3453 			  "},"
3454 			  "\"required\":[\"type\",\"value\"]"
3455 			 "},"
3456 			 "\"s64Value\":{"
3457 			  "\"properties\":{"
3458 			   "\"type\":{"
3459 			    "\"type\":\"string\","
3460 			    "\"enum\":[\"s64\"]"
3461 			   "},"
3462 			   "\"value\":{"
3463 			    "\"type\":\"integer\","
3464 			    "\"minimum\":-9007199254740991,"
3465 			    "\"maximum\":9007199254740991"
3466 			   "}"
3467 			  "},"
3468 			  "\"required\":[\"type\",\"value\"]"
3469 			 "},"
3470 			 "\"u32Value\":{"
3471 			  "\"properties\":{"
3472 			   "\"type\":{"
3473 			    "\"type\":\"string\","
3474 			    "\"enum\":[\"u32\"]"
3475 			   "},"
3476 			   "\"value\":{"
3477 			    "\"type\":\"integer\","
3478 			    "\"minimum\":0,"
3479 			    "\"maximum\":4294967295"
3480 			   "}"
3481 			  "},"
3482 			  "\"required\":[\"type\",\"value\"]"
3483 			 "},"
3484 			 "\"u64Value\":{"
3485 			  "\"properties\":{"
3486 			   "\"type\":{"
3487 			    "\"type\":\"string\","
3488 			    "\"enum\":[\"u64\"]"
3489 			   "},"
3490 			   "\"value\":{"
3491 			    "\"type\":\"integer\","
3492 			    "\"minimum\":0,"
3493 			    "\"maximum\":9007199254740991"
3494 			   "}"
3495 			  "},"
3496 			  "\"required\":[\"type\",\"value\"]"
3497 			 "},"
3498 			 "\"strValue\":{"
3499 			  "\"properties\":{"
3500 			   "\"type\":{"
3501 			    "\"type\":\"string\","
3502 			    "\"enum\":[\"str\"]"
3503 			   "},"
3504 			   "\"value\":{\"type\":\"string\"}"
3505 			  "},"
3506 			  "\"required\":[\"type\",\"value\"]"
3507 			 "},"
3508 			 "\"unknownValue\":{"
3509 			  "\"properties\":{"
3510 			   "\"type\":{"
3511 			    "\"type\":\"integer\","
3512 			    "\"minimum\":0"
3513 			   "},"
3514 			   "\"value\":{"
3515 			    "\"type\":\"string\","
3516 			    "\"enum\":[\"unknown\"]"
3517 			   "}"
3518 			  "},"
3519 			  "\"required\":[\"type\",\"value\"]"
3520 			 "}"
3521 			"}"
3522 		       "}"
3523 		      "}"
3524 		     "}");
3525 
3526 	if (old_len == out->len) {
3527 		chunk_reset(out);
3528 		chunk_appendf(out,
3529 			      "{\"errorStr\":\"output buffer too short\"}");
3530 	}
3531 }
3532 
3533 /* This function dumps the schema onto the stream interface's read buffer.
3534  * It returns 0 as long as it does not complete, non-zero upon completion.
3535  * No state is used.
3536  */
stats_dump_json_schema_to_buffer(struct stream_interface * si)3537 static int stats_dump_json_schema_to_buffer(struct stream_interface *si)
3538 {
3539 	chunk_reset(&trash);
3540 
3541 	stats_dump_json_schema(&trash);
3542 
3543 	if (ci_putchk(si_ic(si), &trash) == -1) {
3544 		si_applet_cant_put(si);
3545 		return 0;
3546 	}
3547 
3548 	return 1;
3549 }
3550 
cli_parse_clear_counters(char ** args,struct appctx * appctx,void * private)3551 static int cli_parse_clear_counters(char **args, struct appctx *appctx, void *private)
3552 {
3553 	struct proxy *px;
3554 	struct server *sv;
3555 	struct listener *li;
3556 	int clrall = 0;
3557 
3558 	if (strcmp(args[2], "all") == 0)
3559 		clrall = 1;
3560 
3561 	/* check permissions */
3562 	if (!cli_has_level(appctx, ACCESS_LVL_OPER) ||
3563 	    (clrall && !cli_has_level(appctx, ACCESS_LVL_ADMIN)))
3564 		return 1;
3565 
3566 	for (px = proxies_list; px; px = px->next) {
3567 		if (clrall) {
3568 			memset(&px->be_counters, 0, sizeof(px->be_counters));
3569 			memset(&px->fe_counters, 0, sizeof(px->fe_counters));
3570 		}
3571 		else {
3572 			px->be_counters.conn_max = 0;
3573 			px->be_counters.p.http.rps_max = 0;
3574 			px->be_counters.sps_max = 0;
3575 			px->be_counters.cps_max = 0;
3576 			px->be_counters.nbpend_max = 0;
3577 
3578 			px->fe_counters.conn_max = 0;
3579 			px->fe_counters.p.http.rps_max = 0;
3580 			px->fe_counters.sps_max = 0;
3581 			px->fe_counters.cps_max = 0;
3582 		}
3583 
3584 		for (sv = px->srv; sv; sv = sv->next)
3585 			if (clrall)
3586 				memset(&sv->counters, 0, sizeof(sv->counters));
3587 			else {
3588 				sv->counters.cur_sess_max = 0;
3589 				sv->counters.nbpend_max = 0;
3590 				sv->counters.sps_max = 0;
3591 			}
3592 
3593 		list_for_each_entry(li, &px->conf.listeners, by_fe)
3594 			if (li->counters) {
3595 				if (clrall)
3596 					memset(li->counters, 0, sizeof(*li->counters));
3597 				else
3598 					li->counters->conn_max = 0;
3599 			}
3600 	}
3601 
3602 	global.cps_max = 0;
3603 	global.sps_max = 0;
3604 	global.ssl_max = 0;
3605 	global.ssl_fe_keys_max = 0;
3606 	global.ssl_be_keys_max = 0;
3607 
3608 	memset(activity, 0, sizeof(activity));
3609 	return 1;
3610 }
3611 
3612 
cli_parse_show_info(char ** args,struct appctx * appctx,void * private)3613 static int cli_parse_show_info(char **args, struct appctx *appctx, void *private)
3614 {
3615 	appctx->ctx.stats.scope_str = 0;
3616 	appctx->ctx.stats.scope_len = 0;
3617 	appctx->ctx.stats.flags = 0;
3618 
3619 	if (strcmp(args[2], "typed") == 0)
3620 		appctx->ctx.stats.flags |= STAT_FMT_TYPED;
3621 	else if (strcmp(args[2], "json") == 0)
3622 		appctx->ctx.stats.flags |= STAT_FMT_JSON;
3623 	return 0;
3624 }
3625 
3626 
cli_parse_show_stat(char ** args,struct appctx * appctx,void * private)3627 static int cli_parse_show_stat(char **args, struct appctx *appctx, void *private)
3628 {
3629 	appctx->ctx.stats.scope_str = 0;
3630 	appctx->ctx.stats.scope_len = 0;
3631 	appctx->ctx.stats.flags = 0;
3632 
3633 	if (*args[2] && *args[3] && *args[4]) {
3634 		struct proxy *px;
3635 
3636 		px = proxy_find_by_name(args[2], 0, 0);
3637 		if (px)
3638 			appctx->ctx.stats.iid = px->uuid;
3639 		else
3640 			appctx->ctx.stats.iid = atoi(args[2]);
3641 
3642 		if (!appctx->ctx.stats.iid) {
3643 			appctx->ctx.cli.severity = LOG_ERR;
3644 			appctx->ctx.cli.msg = "No such proxy.\n";
3645 			appctx->st0 = CLI_ST_PRINT;
3646 			return 1;
3647 		}
3648 
3649 		appctx->ctx.stats.flags |= STAT_BOUND;
3650 		appctx->ctx.stats.type = atoi(args[3]);
3651 		appctx->ctx.stats.sid = atoi(args[4]);
3652 		if (strcmp(args[5], "typed") == 0)
3653 			appctx->ctx.stats.flags |= STAT_FMT_TYPED;
3654 		else if (strcmp(args[5], "json") == 0)
3655 			appctx->ctx.stats.flags |= STAT_FMT_JSON;
3656 	}
3657 	else if (strcmp(args[2], "typed") == 0)
3658 		appctx->ctx.stats.flags |= STAT_FMT_TYPED;
3659 	else if (strcmp(args[2], "json") == 0)
3660 		appctx->ctx.stats.flags |= STAT_FMT_JSON;
3661 
3662 	return 0;
3663 }
3664 
cli_io_handler_dump_info(struct appctx * appctx)3665 static int cli_io_handler_dump_info(struct appctx *appctx)
3666 {
3667 	return stats_dump_info_to_buffer(appctx->owner);
3668 }
3669 
3670 /* This I/O handler runs as an applet embedded in a stream interface. It is
3671  * used to send raw stats over a socket.
3672  */
cli_io_handler_dump_stat(struct appctx * appctx)3673 static int cli_io_handler_dump_stat(struct appctx *appctx)
3674 {
3675 	return stats_dump_stat_to_buffer(appctx->owner, NULL);
3676 }
3677 
cli_io_handler_dump_json_schema(struct appctx * appctx)3678 static int cli_io_handler_dump_json_schema(struct appctx *appctx)
3679 {
3680 	return stats_dump_json_schema_to_buffer(appctx->owner);
3681 }
3682 
3683 /* register cli keywords */
3684 static struct cli_kw_list cli_kws = {{ },{
3685 	{ { "clear", "counters",  NULL }, "clear counters : clear max statistics counters (add 'all' for all counters)", cli_parse_clear_counters, NULL, NULL },
3686 	{ { "show", "info",  NULL }, "show info      : report information about the running process [json|typed]", cli_parse_show_info, cli_io_handler_dump_info, NULL },
3687 	{ { "show", "stat",  NULL }, "show stat      : report counters for each proxy and server [json|typed]", cli_parse_show_stat, cli_io_handler_dump_stat, NULL },
3688 	{ { "show", "schema",  "json", NULL }, "show schema json : report schema used for stats", NULL, cli_io_handler_dump_json_schema, NULL },
3689 	{{},}
3690 }};
3691 
3692 struct applet http_stats_applet = {
3693 	.obj_type = OBJ_TYPE_APPLET,
3694 	.name = "<STATS>", /* used for logging */
3695 	.fct = http_stats_io_handler,
3696 	.release = NULL,
3697 };
3698 
3699 __attribute__((constructor))
__stat_init(void)3700 static void __stat_init(void)
3701 {
3702 	cli_register_kw(&cli_kws);
3703 }
3704 
3705 /*
3706  * Local variables:
3707  *  c-indent-level: 8
3708  *  c-basic-offset: 8
3709  * End:
3710  */
3711