diff --git a/Makefile b/Makefile index 6417c69c1..b97d69b7b 100644 --- a/Makefile +++ b/Makefile @@ -975,7 +975,7 @@ OBJS += src/mux_h2.o src/mux_fcgi.o src/mux_h1.o src/tcpcheck.o \ src/dynbuf.o src/wdt.o src/pipe.o src/init.o src/http_acl.o \ src/hpack-huff.o src/hpack-enc.o src/dict.o src/freq_ctr.o \ src/ebtree.o src/hash.o src/dgram.o src/version.o src/proto_rhttp.o \ - src/guid.o + src/guid.o src/stats-html.o ifneq ($(TRACE),) OBJS += src/calltrace.o diff --git a/include/haproxy/stats-html-t.h b/include/haproxy/stats-html-t.h new file mode 100644 index 000000000..1b77aea5d --- /dev/null +++ b/include/haproxy/stats-html-t.h @@ -0,0 +1,21 @@ +#ifndef _HAPROXY_STATS_HTML_T_H +#define _HAPROXY_STATS_HTML_T_H + +/* HTTP stats : applet.st0 */ +enum { + STAT_HTTP_INIT = 0, /* Initial state */ + STAT_HTTP_HEAD, /* send headers before dump */ + STAT_HTTP_DUMP, /* dumping stats */ + STAT_HTTP_POST, /* waiting post data */ + STAT_HTTP_LAST, /* sending last chunk of response */ + STAT_HTTP_DONE, /* dump is finished */ + STAT_HTTP_END, /* finished */ +}; + +/* HTML form to limit output scope */ +#define STAT_SCOPE_TXT_MAXLEN 20 /* max len for scope substring */ +#define STAT_SCOPE_INPUT_NAME "scope" /* pattern form scope name in html form */ +#define STAT_SCOPE_PATTERN "?" STAT_SCOPE_INPUT_NAME "=" + + +#endif /* _HAPROXY_STATS_HTML_T_H */ diff --git a/include/haproxy/stats-html.h b/include/haproxy/stats-html.h new file mode 100644 index 000000000..3102290c7 --- /dev/null +++ b/include/haproxy/stats-html.h @@ -0,0 +1,22 @@ +#ifndef _HAPROXY_STATS_HTML_H +#define _HAPROXY_STATS_HTML_H + +#include + +#include +#include +#include +#include +#include + +void stats_dump_html_head(struct appctx *appctx); +void stats_dump_html_info(struct stconn *sc); +int stats_dump_fields_html(struct buffer *out, const struct field *stats, + struct show_stat_ctx *ctx); +void stats_dump_html_px_hdr(struct stconn *sc, struct proxy *px); +void stats_dump_html_px_end(struct stconn *sc, struct proxy *px); +void stats_dump_html_end(); + +extern struct applet http_stats_applet; + +#endif /* _HAPROXY_STATS_HTML_H */ diff --git a/include/haproxy/stats-t.h b/include/haproxy/stats-t.h index 1c5e86e14..6f07a5812 100644 --- a/include/haproxy/stats-t.h +++ b/include/haproxy/stats-t.h @@ -58,17 +58,6 @@ #define STATS_DOMAIN (0) /* used for bitshifting, type of statistics: proxy or dns */ #define STATS_PX_CAP (8) /* used for bitshifting, differentiate obj1 type for proxy statistics */ -/* HTTP stats : applet.st0 */ -enum { - STAT_HTTP_INIT = 0, /* Initial state */ - STAT_HTTP_HEAD, /* send headers before dump */ - STAT_HTTP_DUMP, /* dumping stats */ - STAT_HTTP_POST, /* waiting post data */ - STAT_HTTP_LAST, /* sending last chunk of response */ - STAT_HTTP_DONE, /* dump is finished */ - STAT_HTTP_END, /* finished */ -}; - /* status codes available for the stats admin page */ enum { STAT_STATUS_INIT = 0, @@ -83,11 +72,6 @@ enum { STAT_STATUS_SIZE }; -/* HTML form to limit output scope */ -#define STAT_SCOPE_TXT_MAXLEN 20 /* max len for scope substring */ -#define STAT_SCOPE_INPUT_NAME "scope" /* pattern form scope name in html form */ -#define STAT_SCOPE_PATTERN "?" STAT_SCOPE_INPUT_NAME "=" - /* Actions available for the stats admin forms */ enum { ST_ADM_ACTION_NONE = 0, diff --git a/include/haproxy/stats.h b/include/haproxy/stats.h index 031e9d9a9..6affd5e95 100644 --- a/include/haproxy/stats.h +++ b/include/haproxy/stats.h @@ -33,6 +33,7 @@ struct buffer; struct proxy; struct appctx; struct htx; +struct stconn; /* These two structs contains all field names and descriptions according to * the the number of entries in "enum stat_field" and "enum info_field" @@ -48,6 +49,8 @@ extern THREAD_LOCAL struct field *stat_l[]; struct htx; int stats_putchk(struct appctx *appctx, struct buffer *buf, struct htx *htx); +const char *stats_scope_ptr(struct appctx *appctx); + int stats_dump_one_line(const struct field *stats, size_t stats_count, struct appctx *appctx); int stats_fill_info(struct field *info, int len, uint flags); @@ -60,6 +63,8 @@ int stats_fill_sv_stats(struct proxy *px, struct server *sv, int flags, int stats_fill_be_stats(struct proxy *px, int flags, struct field *stats, int len, enum stat_field *selected_field); +int stats_dump_stat_to_buffer(struct stconn *sc, struct buffer *buf, struct htx *htx); + int stats_emit_raw_data_field(struct buffer *out, const struct field *f); int stats_emit_typed_data_field(struct buffer *out, const struct field *f); int stats_emit_field_tags(struct buffer *out, const struct field *f, diff --git a/src/http_ana.c b/src/http_ana.c index 4c1ff98eb..a19163fd2 100644 --- a/src/http_ana.c +++ b/src/http_ana.c @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include diff --git a/src/stats-html.c b/src/stats-html.c new file mode 100644 index 000000000..de4103faf --- /dev/null +++ b/src/stats-html.c @@ -0,0 +1,2082 @@ +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const char *field_to_html_str(const struct field *f) +{ + switch (field_format(f, 0)) { + case FF_S32: return U2H(f->u.s32); + case FF_S64: return U2H(f->u.s64); + case FF_U64: return U2H(f->u.u64); + case FF_U32: return U2H(f->u.u32); + case FF_FLT: return F2H(f->u.flt); + case FF_STR: return field_str(f, 0); + case FF_EMPTY: + default: + return ""; + } +} + +/* Dumps the HTTP stats head block to chunk ctx buffer and uses the per-uri + * parameters from the parent proxy. The caller is responsible for clearing + * chunk ctx buffer if needed. + */ +void stats_dump_html_head(struct appctx *appctx) +{ + struct show_stat_ctx *ctx = appctx->svcctx; + struct buffer *chk = &ctx->chunk; + struct uri_auth *uri; + + BUG_ON(!ctx->http_px); + uri = ctx->http_px->uri_auth; + + /* WARNING! This must fit in the first buffer !!! */ + chunk_appendf(chk, + "\n" + "Statistics Report for " PRODUCT_NAME "%s%s\n" + "\n" + "\n" + "\n", + (ctx->flags & STAT_SHNODE) ? " on " : "", + (ctx->flags & STAT_SHNODE) ? (uri && uri->node ? uri->node : global.node) : "" + ); +} + +/* Dumps the HTML stats information block to chunk ctx buffer and uses the + * state from stream connector and per-uri parameter from the parent + * proxy. The caller is responsible for clearing chunk ctx buffer if needed. + */ +void stats_dump_html_info(struct stconn *sc) +{ + struct appctx *appctx = __sc_appctx(sc); + struct show_stat_ctx *ctx = appctx->svcctx; + struct buffer *chk = &ctx->chunk; + unsigned int up = ns_to_sec(now_ns - start_time_ns); + char scope_txt[STAT_SCOPE_TXT_MAXLEN + sizeof STAT_SCOPE_PATTERN]; + const char *scope_ptr = stats_scope_ptr(appctx); + struct uri_auth *uri; + unsigned long long bps; + int thr; + + BUG_ON(!ctx->http_px); + uri = ctx->http_px->uri_auth; + for (bps = thr = 0; thr < global.nbthread; thr++) + bps += 32ULL * read_freq_ctr(&ha_thread_ctx[thr].out_32bps); + + /* Turn the bytes per second to bits per second and take care of the + * usual ethernet overhead in order to help figure how far we are from + * interface saturation since it's the only case which usually matters. + * For this we count the total size of an Ethernet frame on the wire + * including preamble and IFG (1538) for the largest TCP segment it + * transports (1448 with TCP timestamps). This is not valid for smaller + * packets (under-estimated), but it gives a reasonably accurate + * estimation of how far we are from uplink saturation. + */ + bps = bps * 8 * 1538 / 1448; + + /* WARNING! this has to fit the first packet too. + * We are around 3.5 kB, add adding entries will + * become tricky if we want to support 4kB buffers ! + */ + chunk_appendf(chk, + "

" + PRODUCT_NAME "%s

\n" + "

Statistics Report for pid %d%s%s%s%s

\n" + "
\n" + "

> General process information

\n" + "" + "" + "" + "
\n" + "

pid = %d (process #%d, nbproc = %d, nbthread = %d)
\n" + "uptime = %dd %dh%02dm%02ds; warnings = %u
\n" + "system limits: memmax = %s%s; ulimit-n = %d
\n" + "maxsock = %d; maxconn = %d; reached = %llu; maxpipes = %d
\n" + "current conns = %d; current pipes = %d/%d; conn rate = %d/sec; bit rate = %.3f %cbps
\n" + "Running tasks: %d/%d (%d niced); idle = %d %%
\n" + "

\n" + "\n" + "" + "" + "\n" + "" + "" + "\n" + "" + "" + "\n" + "" + "" + "\n" + "" + "\n" + "" + "
 active UP  backup UP
active UP, going down backup UP, going down
active DOWN, going up backup DOWN, going up
active or backup DOWN  not checked
active or backup DOWN for maintenance (MAINT)  
active or backup SOFT STOPPED for maintenance  
\n" + "Note: \"NOLB\"/\"DRAIN\" = UP with load-balancing disabled." + "
" + "Display option:
    " + "", + (ctx->flags & STAT_HIDEVER) ? "" : (stats_version_string), + pid, (ctx->flags & STAT_SHNODE) ? " on " : "", + (ctx->flags & STAT_SHNODE) ? (uri->node ? uri->node : global.node) : "", + (ctx->flags & STAT_SHDESC) ? ": " : "", + (ctx->flags & STAT_SHDESC) ? (uri->desc ? uri->desc : global.desc) : "", + pid, 1, 1, global.nbthread, + up / 86400, (up % 86400) / 3600, + (up % 3600) / 60, (up % 60), + HA_ATOMIC_LOAD(&tot_warnings), + global.rlimit_memmax ? ultoa(global.rlimit_memmax) : "unlimited", + global.rlimit_memmax ? " MB" : "", + global.rlimit_nofile, + global.maxsock, global.maxconn, HA_ATOMIC_LOAD(&maxconn_reached), global.maxpipes, + actconn, pipes_used, pipes_used+pipes_free, read_freq_ctr(&global.conn_per_sec), + bps >= 1000000000UL ? (bps / 1000000000.0) : bps >= 1000000UL ? (bps / 1000000.0) : (bps / 1000.0), + bps >= 1000000000UL ? 'G' : bps >= 1000000UL ? 'M' : 'k', + total_run_queues(), total_allocated_tasks(), total_niced_running_tasks(), clock_report_idle()); + + /* scope_txt = search query, ctx->scope_len is always <= STAT_SCOPE_TXT_MAXLEN */ + memcpy(scope_txt, scope_ptr, ctx->scope_len); + scope_txt[ctx->scope_len] = '\0'; + + chunk_appendf(chk, + "
  • Scope :
    \n", + (ctx->scope_len > 0) ? scope_txt : "", + STAT_SCOPE_TXT_MAXLEN); + + /* scope_txt = search pattern + search query, ctx->scope_len is always <= STAT_SCOPE_TXT_MAXLEN */ + scope_txt[0] = 0; + if (ctx->scope_len) { + strlcpy2(scope_txt, STAT_SCOPE_PATTERN, sizeof(scope_txt)); + memcpy(scope_txt + strlen(STAT_SCOPE_PATTERN), scope_ptr, ctx->scope_len); + scope_txt[strlen(STAT_SCOPE_PATTERN) + ctx->scope_len] = 0; + } + + if (ctx->flags & STAT_HIDE_DOWN) + chunk_appendf(chk, + "
  • Show all servers
    \n", + uri->uri_prefix, + "", + (ctx->flags & STAT_NO_REFRESH) ? ";norefresh" : "", + scope_txt); + else + chunk_appendf(chk, + "
  • Hide 'DOWN' servers
    \n", + uri->uri_prefix, + ";up", + (ctx->flags & STAT_NO_REFRESH) ? ";norefresh" : "", + scope_txt); + + if (uri->refresh > 0) { + if (ctx->flags & STAT_NO_REFRESH) + chunk_appendf(chk, + "
  • Enable refresh
    \n", + uri->uri_prefix, + (ctx->flags & STAT_HIDE_DOWN) ? ";up" : "", + "", + scope_txt); + else + chunk_appendf(chk, + "
  • Disable refresh
    \n", + uri->uri_prefix, + (ctx->flags & STAT_HIDE_DOWN) ? ";up" : "", + ";norefresh", + scope_txt); + } + + chunk_appendf(chk, + "
  • Refresh now
    \n", + uri->uri_prefix, + (ctx->flags & STAT_HIDE_DOWN) ? ";up" : "", + (ctx->flags & STAT_NO_REFRESH) ? ";norefresh" : "", + scope_txt); + + chunk_appendf(chk, + "
  • CSV export
    \n", + uri->uri_prefix, + (uri->refresh > 0) ? ";norefresh" : "", + scope_txt); + + chunk_appendf(chk, + "
  • JSON export (schema)
    \n", + uri->uri_prefix, + (uri->refresh > 0) ? ";norefresh" : "", + scope_txt, uri->uri_prefix); + + chunk_appendf(chk, + "
" + "External resources:" + "
\n" + "" + ); + + if (ctx->st_code) { + switch (ctx->st_code) { + case STAT_STATUS_DONE: + chunk_appendf(chk, + "

" + "[X] " + "Action processed successfully." + "
\n", uri->uri_prefix, + (ctx->flags & STAT_HIDE_DOWN) ? ";up" : "", + (ctx->flags & STAT_NO_REFRESH) ? ";norefresh" : "", + scope_txt); + break; + case STAT_STATUS_NONE: + chunk_appendf(chk, + "

" + "[X] " + "Nothing has changed." + "
\n", uri->uri_prefix, + (ctx->flags & STAT_HIDE_DOWN) ? ";up" : "", + (ctx->flags & STAT_NO_REFRESH) ? ";norefresh" : "", + scope_txt); + break; + case STAT_STATUS_PART: + chunk_appendf(chk, + "

" + "[X] " + "Action partially processed.
" + "Some server names are probably unknown or ambiguous (duplicated names in the backend)." + "
\n", uri->uri_prefix, + (ctx->flags & STAT_HIDE_DOWN) ? ";up" : "", + (ctx->flags & STAT_NO_REFRESH) ? ";norefresh" : "", + scope_txt); + break; + case STAT_STATUS_ERRP: + chunk_appendf(chk, + "

" + "[X] " + "Action not processed because of invalid parameters." + "
    " + "
  • The action is maybe unknown.
  • " + "
  • Invalid key parameter (empty or too long).
  • " + "
  • The backend name is probably unknown or ambiguous (duplicated names).
  • " + "
  • Some server names are probably unknown or ambiguous (duplicated names in the backend).
  • " + "
" + "
\n", uri->uri_prefix, + (ctx->flags & STAT_HIDE_DOWN) ? ";up" : "", + (ctx->flags & STAT_NO_REFRESH) ? ";norefresh" : "", + scope_txt); + break; + case STAT_STATUS_EXCD: + chunk_appendf(chk, + "

" + "[X] " + "Action not processed : the buffer couldn't store all the data.
" + "You should retry with less servers at a time.
" + "
\n", uri->uri_prefix, + (ctx->flags & STAT_HIDE_DOWN) ? ";up" : "", + (ctx->flags & STAT_NO_REFRESH) ? ";norefresh" : "", + scope_txt); + break; + case STAT_STATUS_DENY: + chunk_appendf(chk, + "

" + "[X] " + "Action denied." + "
\n", uri->uri_prefix, + (ctx->flags & STAT_HIDE_DOWN) ? ";up" : "", + (ctx->flags & STAT_NO_REFRESH) ? ";norefresh" : "", + scope_txt); + break; + case STAT_STATUS_IVAL: + chunk_appendf(chk, + "

" + "[X] " + "Invalid requests (unsupported method or chunked encoded request)." + "
\n", uri->uri_prefix, + (ctx->flags & STAT_HIDE_DOWN) ? ";up" : "", + (ctx->flags & STAT_NO_REFRESH) ? ";norefresh" : "", + scope_txt); + break; + default: + chunk_appendf(chk, + "

" + "[X] " + "Unexpected result." + "
\n", uri->uri_prefix, + (ctx->flags & STAT_HIDE_DOWN) ? ";up" : "", + (ctx->flags & STAT_NO_REFRESH) ? ";norefresh" : "", + scope_txt); + } + chunk_appendf(chk, "

\n"); + } +} + +/* Dump all fields from into using the HTML format. A column is + * reserved for the checkbox is STAT_ADMIN is set in . Some extra info + * are provided if STAT_SHLGNDS is present in . The statistics from + * extra modules are displayed at the end of the lines if STAT_SHMODULES is + * present in . + */ +int stats_dump_fields_html(struct buffer *out, + const struct field *stats, + struct show_stat_ctx *ctx) +{ + struct buffer src; + struct stats_module *mod; + int flags = ctx->flags; + int i = 0, j = 0; + + if (stats[ST_F_TYPE].u.u32 == STATS_TYPE_FE) { + chunk_appendf(out, + /* name, queue */ + ""); + + if (flags & STAT_ADMIN) { + /* Column sub-heading for Enable or Disable server */ + chunk_appendf(out, ""); + } + + chunk_appendf(out, + "" + "" + "Frontend" + "" + "", + field_str(stats, ST_F_PXNAME), field_str(stats, ST_F_PXNAME)); + + chunk_appendf(out, + /* sessions rate : current */ + "%s

" + "" + "" + "", + U2H(stats[ST_F_RATE].u.u32), + U2H(stats[ST_F_CONN_RATE].u.u32), + U2H(stats[ST_F_RATE].u.u32)); + + if (strcmp(field_str(stats, ST_F_MODE), "http") == 0) + chunk_appendf(out, + "", + U2H(stats[ST_F_REQ_RATE].u.u32)); + + chunk_appendf(out, + "
Current connection rate:%s/s
Current session rate:%s/s
Current request rate:%s/s
" + /* sessions rate : max */ + "%s
" + "" + "" + "", + U2H(stats[ST_F_RATE_MAX].u.u32), + U2H(stats[ST_F_CONN_RATE_MAX].u.u32), + U2H(stats[ST_F_RATE_MAX].u.u32)); + + if (strcmp(field_str(stats, ST_F_MODE), "http") == 0) + chunk_appendf(out, + "", + U2H(stats[ST_F_REQ_RATE_MAX].u.u32)); + + chunk_appendf(out, + "
Max connection rate:%s/s
Max session rate:%s/s
Max request rate:%s/s
" + /* sessions rate : limit */ + "%s", + LIM2A(stats[ST_F_RATE_LIM].u.u32, "-")); + + chunk_appendf(out, + /* sessions: current, max, limit, total */ + "%s%s%s" + "%s
" + "" + "" + "", + U2H(stats[ST_F_SCUR].u.u32), U2H(stats[ST_F_SMAX].u.u32), U2H(stats[ST_F_SLIM].u.u32), + U2H(stats[ST_F_STOT].u.u64), + U2H(stats[ST_F_CONN_TOT].u.u64), + U2H(stats[ST_F_STOT].u.u64)); + + /* http response (via hover): 1xx, 2xx, 3xx, 4xx, 5xx, other */ + if (strcmp(field_str(stats, ST_F_MODE), "http") == 0) { + chunk_appendf(out, + "" + "" + "" + "" + "" + "" + "" + "" + "" + "", + U2H(stats[ST_F_H1SESS].u.u64), + U2H(stats[ST_F_H2SESS].u.u64), + U2H(stats[ST_F_H3SESS].u.u64), + U2H(stats[ST_F_SESS_OTHER].u.u64), + U2H(stats[ST_F_REQ_TOT].u.u64), + U2H(stats[ST_F_H1REQ].u.u64), + U2H(stats[ST_F_H2REQ].u.u64), + U2H(stats[ST_F_H3REQ].u.u64), + U2H(stats[ST_F_REQ_OTHER].u.u64)); + + chunk_appendf(out, + "" + "" + "" + "" + "" + "" + "" + "", + U2H(stats[ST_F_HRSP_1XX].u.u64), + U2H(stats[ST_F_HRSP_2XX].u.u64), + U2H(stats[ST_F_COMP_RSP].u.u64), + stats[ST_F_HRSP_2XX].u.u64 ? + (int)(100 * stats[ST_F_COMP_RSP].u.u64 / stats[ST_F_HRSP_2XX].u.u64) : 0, + U2H(stats[ST_F_HRSP_3XX].u.u64), + U2H(stats[ST_F_HRSP_4XX].u.u64), + U2H(stats[ST_F_HRSP_5XX].u.u64), + U2H(stats[ST_F_HRSP_OTHER].u.u64)); + + chunk_appendf(out, + "" + "" + "" + "" + "" + "", + U2H(stats[ST_F_INTERCEPTED].u.u64), + U2H(stats[ST_F_CACHE_LOOKUPS].u.u64), + U2H(stats[ST_F_CACHE_HITS].u.u64), + stats[ST_F_CACHE_LOOKUPS].u.u64 ? + (int)(100 * stats[ST_F_CACHE_HITS].u.u64 / stats[ST_F_CACHE_LOOKUPS].u.u64) : 0, + U2H(stats[ST_F_WREW].u.u64), + U2H(stats[ST_F_EINT].u.u64)); + } + + chunk_appendf(out, + "
Cum. connections:%s
Cum. sessions:%s
- HTTP/1 sessions:%s
- HTTP/2 sessions:%s
- HTTP/3 sessions:%s
- other sessions:%s
Cum. HTTP requests:%s
- HTTP/1 requests:%s
- HTTP/2 requests:%s
- HTTP/3 requests:%s
- other requests:%s
- HTTP 1xx responses:%s
- HTTP 2xx responses:%s
  Compressed 2xx:%s(%d%%)
- HTTP 3xx responses:%s
- HTTP 4xx responses:%s
- HTTP 5xx responses:%s
- other responses:%s
Intercepted requests:%s
Cache lookups:%s
Cache hits:%s(%d%%)
Failed hdr rewrites:%s
Internal errors:%s
" + /* sessions: lbtot, lastsess */ + "" + /* bytes : in */ + "%s" + "", + U2H(stats[ST_F_BIN].u.u64)); + + chunk_appendf(out, + /* bytes:out + compression stats (via hover): comp_in, comp_out, comp_byp */ + "%s%s
" + "" + "" + "" + "" + "" + "
Response bytes in:%s
Compression in:%s
Compression out:%s(%d%%)
Compression bypass:%s
Total bytes saved:%s(%d%%)
%s", + (stats[ST_F_COMP_IN].u.u64 || stats[ST_F_COMP_BYP].u.u64) ? "":"", + U2H(stats[ST_F_BOUT].u.u64), + U2H(stats[ST_F_BOUT].u.u64), + U2H(stats[ST_F_COMP_IN].u.u64), + U2H(stats[ST_F_COMP_OUT].u.u64), + stats[ST_F_COMP_IN].u.u64 ? (int)(stats[ST_F_COMP_OUT].u.u64 * 100 / stats[ST_F_COMP_IN].u.u64) : 0, + U2H(stats[ST_F_COMP_BYP].u.u64), + U2H(stats[ST_F_COMP_IN].u.u64 - stats[ST_F_COMP_OUT].u.u64), + 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, + (stats[ST_F_COMP_IN].u.u64 || stats[ST_F_COMP_BYP].u.u64) ? "":""); + + chunk_appendf(out, + /* denied: req, resp */ + "%s%s" + /* errors : request, connect, response */ + "%s" + /* warnings: retries, redispatches */ + "" + /* server status : reflect frontend status */ + "%s" + /* rest of server: nothing */ + "" + "", + U2H(stats[ST_F_DREQ].u.u64), U2H(stats[ST_F_DRESP].u.u64), + U2H(stats[ST_F_EREQ].u.u64), + field_str(stats, ST_F_STATUS)); + + if (flags & STAT_SHMODULES) { + list_for_each_entry(mod, &stats_module_list[STATS_DOMAIN_PROXY], list) { + chunk_appendf(out, ""); + + if (stats_px_get_cap(mod->domain_flags) & STATS_PX_CAP_FE) { + chunk_appendf(out, + "%s
", + mod->name); + for (j = 0; j < mod->stats_count; ++j) { + chunk_appendf(out, + "", + mod->stats[j].desc, field_to_html_str(&stats[ST_F_TOTAL_FIELDS + i])); + ++i; + } + chunk_appendf(out, "
%s%s
"); + } else { + i += mod->stats_count; + } + + chunk_appendf(out, ""); + } + } + + chunk_appendf(out, ""); + } + else if (stats[ST_F_TYPE].u.u32 == STATS_TYPE_SO) { + chunk_appendf(out, ""); + if (flags & STAT_ADMIN) { + /* Column sub-heading for Enable or Disable server */ + chunk_appendf(out, ""); + } + + chunk_appendf(out, + /* frontend name, listener name */ + "%s" + "%s" + "", + field_str(stats, ST_F_PXNAME), field_str(stats, ST_F_SVNAME), + (flags & STAT_SHLGNDS)?"":"", + field_str(stats, ST_F_PXNAME), field_str(stats, ST_F_SVNAME), field_str(stats, ST_F_SVNAME)); + + if (flags & STAT_SHLGNDS) { + chunk_appendf(out, "
"); + + if (isdigit((unsigned char)*field_str(stats, ST_F_ADDR))) + chunk_appendf(out, "IPv4: %s, ", field_str(stats, ST_F_ADDR)); + else if (*field_str(stats, ST_F_ADDR) == '[') + chunk_appendf(out, "IPv6: %s, ", field_str(stats, ST_F_ADDR)); + else if (*field_str(stats, ST_F_ADDR)) + chunk_appendf(out, "%s, ", field_str(stats, ST_F_ADDR)); + + chunk_appendf(out, "proto=%s, ", field_str(stats, ST_F_PROTO)); + + /* id */ + chunk_appendf(out, "id: %d
", stats[ST_F_SID].u.u32); + } + + chunk_appendf(out, + /* queue */ + "%s" + /* sessions rate: current, max, limit */ + " " + /* sessions: current, max, limit, total, lbtot, lastsess */ + "%s%s%s" + "%s  " + /* bytes: in, out */ + "%s%s" + "", + (flags & STAT_SHLGNDS)?"
":"", + U2H(stats[ST_F_SCUR].u.u32), U2H(stats[ST_F_SMAX].u.u32), U2H(stats[ST_F_SLIM].u.u32), + U2H(stats[ST_F_STOT].u.u64), U2H(stats[ST_F_BIN].u.u64), U2H(stats[ST_F_BOUT].u.u64)); + + chunk_appendf(out, + /* denied: req, resp */ + "%s%s" + /* errors: request, connect, response */ + "%s" + /* warnings: retries, redispatches */ + "" + /* server status: reflect listener status */ + "%s" + /* rest of server: nothing */ + "" + "", + U2H(stats[ST_F_DREQ].u.u64), U2H(stats[ST_F_DRESP].u.u64), + U2H(stats[ST_F_EREQ].u.u64), + field_str(stats, ST_F_STATUS)); + + if (flags & STAT_SHMODULES) { + list_for_each_entry(mod, &stats_module_list[STATS_DOMAIN_PROXY], list) { + chunk_appendf(out, ""); + + if (stats_px_get_cap(mod->domain_flags) & STATS_PX_CAP_LI) { + chunk_appendf(out, + "%s
", + mod->name); + for (j = 0; j < mod->stats_count; ++j) { + chunk_appendf(out, + "", + mod->stats[j].desc, field_to_html_str(&stats[ST_F_TOTAL_FIELDS + i])); + ++i; + } + chunk_appendf(out, "
%s%s
"); + } else { + i += mod->stats_count; + } + + chunk_appendf(out, ""); + } + } + + chunk_appendf(out, ""); + } + else if (stats[ST_F_TYPE].u.u32 == STATS_TYPE_SV) { + const char *style; + + /* determine the style to use depending on the server's state, + * its health and weight. There isn't a 1-to-1 mapping between + * state and styles for the cases where the server is (still) + * up. The reason is that we don't want to report nolb and + * drain with the same color. + */ + + if (strcmp(field_str(stats, ST_F_STATUS), "DOWN") == 0 || + strcmp(field_str(stats, ST_F_STATUS), "DOWN (agent)") == 0) { + style = "down"; + } + else if (strncmp(field_str(stats, ST_F_STATUS), "DOWN ", strlen("DOWN ")) == 0) { + style = "going_up"; + } + else if (strcmp(field_str(stats, ST_F_STATUS), "DRAIN") == 0) { + style = "draining"; + } + else if (strncmp(field_str(stats, ST_F_STATUS), "NOLB ", strlen("NOLB ")) == 0) { + style = "going_down"; + } + else if (strcmp(field_str(stats, ST_F_STATUS), "NOLB") == 0) { + style = "nolb"; + } + else if (strcmp(field_str(stats, ST_F_STATUS), "no check") == 0) { + style = "no_check"; + } + else if (!stats[ST_F_CHKFAIL].type || + stats[ST_F_CHECK_HEALTH].u.u32 == stats[ST_F_CHECK_RISE].u.u32 + stats[ST_F_CHECK_FALL].u.u32 - 1) { + /* no check or max health = UP */ + if (stats[ST_F_WEIGHT].u.u32) + style = "up"; + else + style = "draining"; + } + else { + style = "going_down"; + } + + if (strncmp(field_str(stats, ST_F_STATUS), "MAINT", 5) == 0) + chunk_appendf(out, ""); + else + chunk_appendf(out, + "", + (stats[ST_F_BCK].u.u32) ? "backup" : "active", style); + + + if (flags & STAT_ADMIN) + chunk_appendf(out, + "", + field_str(stats, ST_F_PXNAME), + field_str(stats, ST_F_SVNAME)); + + chunk_appendf(out, + "%s" + "%s" + "", + field_str(stats, ST_F_PXNAME), field_str(stats, ST_F_SVNAME), + (flags & STAT_SHLGNDS) ? "" : "", + field_str(stats, ST_F_PXNAME), field_str(stats, ST_F_SVNAME), field_str(stats, ST_F_SVNAME)); + + if (flags & STAT_SHLGNDS) { + chunk_appendf(out, "
"); + + if (isdigit((unsigned char)*field_str(stats, ST_F_ADDR))) + chunk_appendf(out, "IPv4: %s, ", field_str(stats, ST_F_ADDR)); + else if (*field_str(stats, ST_F_ADDR) == '[') + chunk_appendf(out, "IPv6: %s, ", field_str(stats, ST_F_ADDR)); + else if (*field_str(stats, ST_F_ADDR)) + chunk_appendf(out, "%s, ", field_str(stats, ST_F_ADDR)); + + /* id */ + chunk_appendf(out, "id: %d, rid: %d", stats[ST_F_SID].u.u32, stats[ST_F_SRID].u.u32); + + /* cookie */ + if (stats[ST_F_COOKIE].type) { + chunk_appendf(out, ", cookie: '"); + chunk_initstr(&src, field_str(stats, ST_F_COOKIE)); + chunk_htmlencode(out, &src); + chunk_appendf(out, "'"); + } + + chunk_appendf(out, "
"); + } + + chunk_appendf(out, + /* queue : current, max, limit */ + "%s%s%s%s" + /* sessions rate : current, max, limit */ + "%s%s" + "", + (flags & STAT_SHLGNDS) ? "
" : "", + U2H(stats[ST_F_QCUR].u.u32), U2H(stats[ST_F_QMAX].u.u32), LIM2A(stats[ST_F_QLIMIT].u.u32, "-"), + U2H(stats[ST_F_RATE].u.u32), U2H(stats[ST_F_RATE_MAX].u.u32)); + + chunk_appendf(out, + /* sessions: current, max, limit, total */ + "%s
" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "
Current active connections:%s
Current used connections:%s
Current idle connections:%s
- unsafe:%s
- safe:%s
Estimated need of connections:%s
Active connections limit:%s
Idle connections limit:%s
" + "%s%s" + "%s
" + "" + "", + U2H(stats[ST_F_SCUR].u.u32), + U2H(stats[ST_F_SCUR].u.u32), + U2H(stats[ST_F_USED_CONN_CUR].u.u32), + U2H(stats[ST_F_SRV_ICUR].u.u32), + U2H(stats[ST_F_IDLE_CONN_CUR].u.u32), + U2H(stats[ST_F_SAFE_CONN_CUR].u.u32), + U2H(stats[ST_F_NEED_CONN_EST].u.u32), + + LIM2A(stats[ST_F_SLIM].u.u32, "-"), + stats[ST_F_SRV_ILIM].type ? U2H(stats[ST_F_SRV_ILIM].u.u32) : "-", + U2H(stats[ST_F_SMAX].u.u32), LIM2A(stats[ST_F_SLIM].u.u32, "-"), + U2H(stats[ST_F_STOT].u.u64), + U2H(stats[ST_F_STOT].u.u64)); + + /* http response (via hover): 1xx, 2xx, 3xx, 4xx, 5xx, other */ + if (strcmp(field_str(stats, ST_F_MODE), "http") == 0) { + chunk_appendf(out, + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "", + U2H(stats[ST_F_CONNECT].u.u64), + U2H(stats[ST_F_REUSE].u.u64), + (stats[ST_F_CONNECT].u.u64 + stats[ST_F_REUSE].u.u64) ? + (int)(100 * stats[ST_F_REUSE].u.u64 / (stats[ST_F_CONNECT].u.u64 + stats[ST_F_REUSE].u.u64)) : 0, + U2H(stats[ST_F_REQ_TOT].u.u64), + U2H(stats[ST_F_HRSP_1XX].u.u64), stats[ST_F_REQ_TOT].u.u64 ? + (int)(100 * stats[ST_F_HRSP_1XX].u.u64 / stats[ST_F_REQ_TOT].u.u64) : 0, + U2H(stats[ST_F_HRSP_2XX].u.u64), stats[ST_F_REQ_TOT].u.u64 ? + (int)(100 * stats[ST_F_HRSP_2XX].u.u64 / stats[ST_F_REQ_TOT].u.u64) : 0, + U2H(stats[ST_F_HRSP_3XX].u.u64), stats[ST_F_REQ_TOT].u.u64 ? + (int)(100 * stats[ST_F_HRSP_3XX].u.u64 / stats[ST_F_REQ_TOT].u.u64) : 0, + U2H(stats[ST_F_HRSP_4XX].u.u64), stats[ST_F_REQ_TOT].u.u64 ? + (int)(100 * stats[ST_F_HRSP_4XX].u.u64 / stats[ST_F_REQ_TOT].u.u64) : 0, + U2H(stats[ST_F_HRSP_5XX].u.u64), stats[ST_F_REQ_TOT].u.u64 ? + (int)(100 * stats[ST_F_HRSP_5XX].u.u64 / stats[ST_F_REQ_TOT].u.u64) : 0, + U2H(stats[ST_F_HRSP_OTHER].u.u64), stats[ST_F_REQ_TOT].u.u64 ? + (int)(100 * stats[ST_F_HRSP_OTHER].u.u64 / stats[ST_F_REQ_TOT].u.u64) : 0, + U2H(stats[ST_F_WREW].u.u64), + U2H(stats[ST_F_EINT].u.u64)); + } + + chunk_appendf(out, ""); + chunk_appendf(out, "", + U2H(stats[ST_F_QT_MAX].u.u32), U2H(stats[ST_F_QTIME].u.u32)); + chunk_appendf(out, "", + U2H(stats[ST_F_CT_MAX].u.u32), U2H(stats[ST_F_CTIME].u.u32)); + if (strcmp(field_str(stats, ST_F_MODE), "http") == 0) + chunk_appendf(out, "", + U2H(stats[ST_F_RT_MAX].u.u32), U2H(stats[ST_F_RTIME].u.u32)); + chunk_appendf(out, "", + U2H(stats[ST_F_TT_MAX].u.u32), U2H(stats[ST_F_TTIME].u.u32)); + + chunk_appendf(out, + "
Cum. sessions:%s
New connections:%s
Reused connections:%s(%d%%)
Cum. HTTP requests:%s
- HTTP 1xx responses:%s(%d%%)
- HTTP 2xx responses:%s(%d%%)
- HTTP 3xx responses:%s(%d%%)
- HTTP 4xx responses:%s(%d%%)
- HTTP 5xx responses:%s(%d%%)
- other responses:%s(%d%%)
Failed hdr rewrites:%s
Internal error:%s
Max / Avg over last 1024 success. conn.
- Queue time:%s / %sms
- Connect time:%s / %sms
- Responses time:%s / %sms
- Total time:%s / %sms
" + /* sessions: lbtot, last */ + "%s%s", + U2H(stats[ST_F_LBTOT].u.u64), + human_time(stats[ST_F_LASTSESS].u.s32, 1)); + + chunk_appendf(out, + /* bytes : in, out */ + "%s%s" + /* denied: req, resp */ + "%s" + /* errors : request, connect */ + "%s" + /* errors : response */ + "%s
Connection resets during transfers: %lld client, %lld server
" + /* warnings: retries, redispatches */ + "%lld%lld" + "", + U2H(stats[ST_F_BIN].u.u64), U2H(stats[ST_F_BOUT].u.u64), + U2H(stats[ST_F_DRESP].u.u64), + U2H(stats[ST_F_ECON].u.u64), + U2H(stats[ST_F_ERESP].u.u64), + (long long)stats[ST_F_CLI_ABRT].u.u64, + (long long)stats[ST_F_SRV_ABRT].u.u64, + (long long)stats[ST_F_WRETR].u.u64, + (long long)stats[ST_F_WREDIS].u.u64); + + /* status, last change */ + chunk_appendf(out, ""); + + /* FIXME!!!! + * LASTCHG should contain the last change for *this* server and must be computed + * properly above, as was done below, ie: this server if maint, otherwise ref server + * if tracking. Note that ref is either local or remote depending on tracking. + */ + + + if (strncmp(field_str(stats, ST_F_STATUS), "MAINT", 5) == 0) { + chunk_appendf(out, "%s MAINT", human_time(stats[ST_F_LASTCHG].u.u32, 1)); + } + else if (strcmp(field_str(stats, ST_F_STATUS), "no check") == 0) { + chunk_strcat(out, "no check"); + } + else { + chunk_appendf(out, "%s %s", human_time(stats[ST_F_LASTCHG].u.u32, 1), field_str(stats, ST_F_STATUS)); + if (strncmp(field_str(stats, ST_F_STATUS), "DOWN", 4) == 0) { + if (stats[ST_F_CHECK_HEALTH].u.u32) + chunk_strcat(out, " ↑"); + } + else if (stats[ST_F_CHECK_HEALTH].u.u32 < stats[ST_F_CHECK_RISE].u.u32 + stats[ST_F_CHECK_FALL].u.u32 - 1) + chunk_strcat(out, " ↓"); + } + + if (strncmp(field_str(stats, ST_F_STATUS), "DOWN", 4) == 0 && + stats[ST_F_AGENT_STATUS].type && !stats[ST_F_AGENT_HEALTH].u.u32) { + chunk_appendf(out, + " %s", + field_str(stats, ST_F_AGENT_STATUS)); + + if (stats[ST_F_AGENT_CODE].type) + chunk_appendf(out, "/%d", stats[ST_F_AGENT_CODE].u.u32); + + if (stats[ST_F_AGENT_DURATION].type) + chunk_appendf(out, " in %lums", (long)stats[ST_F_AGENT_DURATION].u.u64); + + chunk_appendf(out, "
%s", field_str(stats, ST_F_AGENT_DESC)); + + if (*field_str(stats, ST_F_LAST_AGT)) { + chunk_appendf(out, ": "); + chunk_initstr(&src, field_str(stats, ST_F_LAST_AGT)); + chunk_htmlencode(out, &src); + } + chunk_appendf(out, "
"); + } + else if (stats[ST_F_CHECK_STATUS].type) { + chunk_appendf(out, + " %s", + field_str(stats, ST_F_CHECK_STATUS)); + + if (stats[ST_F_CHECK_CODE].type) + chunk_appendf(out, "/%d", stats[ST_F_CHECK_CODE].u.u32); + + if (stats[ST_F_CHECK_DURATION].type) + chunk_appendf(out, " in %lums", (long)stats[ST_F_CHECK_DURATION].u.u64); + + chunk_appendf(out, "
%s", field_str(stats, ST_F_CHECK_DESC)); + + if (*field_str(stats, ST_F_LAST_CHK)) { + chunk_appendf(out, ": "); + chunk_initstr(&src, field_str(stats, ST_F_LAST_CHK)); + chunk_htmlencode(out, &src); + } + chunk_appendf(out, "
"); + } + else + chunk_appendf(out, ""); + + chunk_appendf(out, + /* weight / uweight */ + "%d/%d" + /* act, bck */ + "%s%s" + "", + stats[ST_F_WEIGHT].u.u32, stats[ST_F_UWEIGHT].u.u32, + stats[ST_F_BCK].u.u32 ? "-" : "Y", + stats[ST_F_BCK].u.u32 ? "Y" : "-"); + + /* check failures: unique, fatal, down time */ + if (strcmp(field_str(stats, ST_F_STATUS), "MAINT (resolution)") == 0) { + chunk_appendf(out, "resolution"); + } + else if (stats[ST_F_CHKFAIL].type) { + chunk_appendf(out, "%lld", (long long)stats[ST_F_CHKFAIL].u.u64); + + if (stats[ST_F_HANAFAIL].type) + chunk_appendf(out, "/%lld", (long long)stats[ST_F_HANAFAIL].u.u64); + + chunk_appendf(out, + "
Failed Health Checks%s
" + "%lld%s" + "", + stats[ST_F_HANAFAIL].type ? "/Health Analyses" : "", + (long long)stats[ST_F_CHKDOWN].u.u64, human_time(stats[ST_F_DOWNTIME].u.u32, 1)); + } + else if (strcmp(field_str(stats, ST_F_STATUS), "MAINT") != 0 && field_format(stats, ST_F_TRACKED) == FF_STR) { + /* tracking a server (hence inherited maint would appear as "MAINT (via...)" */ + chunk_appendf(out, + "via %s", + field_str(stats, ST_F_TRACKED), field_str(stats, ST_F_TRACKED)); + } + else + chunk_appendf(out, ""); + + /* throttle */ + if (stats[ST_F_THROTTLE].type) + chunk_appendf(out, "%d %%\n", stats[ST_F_THROTTLE].u.u32); + else + chunk_appendf(out, "-"); + + if (flags & STAT_SHMODULES) { + list_for_each_entry(mod, &stats_module_list[STATS_DOMAIN_PROXY], list) { + chunk_appendf(out, ""); + + if (stats_px_get_cap(mod->domain_flags) & STATS_PX_CAP_SRV) { + chunk_appendf(out, + "%s
", + mod->name); + for (j = 0; j < mod->stats_count; ++j) { + chunk_appendf(out, + "", + mod->stats[j].desc, field_to_html_str(&stats[ST_F_TOTAL_FIELDS + i])); + ++i; + } + chunk_appendf(out, "
%s%s
"); + } else { + i += mod->stats_count; + } + + chunk_appendf(out, ""); + } + } + + chunk_appendf(out, "\n"); + } + else if (stats[ST_F_TYPE].u.u32 == STATS_TYPE_BE) { + chunk_appendf(out, ""); + if (flags & STAT_ADMIN) { + /* Column sub-heading for Enable or Disable server */ + chunk_appendf(out, ""); + } + chunk_appendf(out, + "" + /* name */ + "%s" + "Backend" + "", + (flags & STAT_SHLGNDS)?"":"", + field_str(stats, ST_F_PXNAME), field_str(stats, ST_F_PXNAME)); + + if (flags & STAT_SHLGNDS) { + /* balancing */ + chunk_appendf(out, "
balancing: %s", + field_str(stats, ST_F_ALGO)); + + /* cookie */ + if (stats[ST_F_COOKIE].type) { + chunk_appendf(out, ", cookie: '"); + chunk_initstr(&src, field_str(stats, ST_F_COOKIE)); + chunk_htmlencode(out, &src); + chunk_appendf(out, "'"); + } + chunk_appendf(out, "
"); + } + + chunk_appendf(out, + "%s" + /* queue : current, max */ + "%s%s" + /* sessions rate : current, max, limit */ + "%s%s" + "", + (flags & STAT_SHLGNDS)?"
":"", + U2H(stats[ST_F_QCUR].u.u32), U2H(stats[ST_F_QMAX].u.u32), + U2H(stats[ST_F_RATE].u.u32), U2H(stats[ST_F_RATE_MAX].u.u32)); + + chunk_appendf(out, + /* sessions: current, max, limit, total */ + "%s%s%s" + "%s
" + "" + "", + U2H(stats[ST_F_SCUR].u.u32), U2H(stats[ST_F_SMAX].u.u32), U2H(stats[ST_F_SLIM].u.u32), + U2H(stats[ST_F_STOT].u.u64), + U2H(stats[ST_F_STOT].u.u64)); + + /* http response (via hover): 1xx, 2xx, 3xx, 4xx, 5xx, other */ + if (strcmp(field_str(stats, ST_F_MODE), "http") == 0) { + chunk_appendf(out, + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "", + U2H(stats[ST_F_CONNECT].u.u64), + U2H(stats[ST_F_REUSE].u.u64), + (stats[ST_F_CONNECT].u.u64 + stats[ST_F_REUSE].u.u64) ? + (int)(100 * stats[ST_F_REUSE].u.u64 / (stats[ST_F_CONNECT].u.u64 + stats[ST_F_REUSE].u.u64)) : 0, + U2H(stats[ST_F_REQ_TOT].u.u64), + U2H(stats[ST_F_HRSP_1XX].u.u64), + U2H(stats[ST_F_HRSP_2XX].u.u64), + U2H(stats[ST_F_COMP_RSP].u.u64), + stats[ST_F_HRSP_2XX].u.u64 ? + (int)(100 * stats[ST_F_COMP_RSP].u.u64 / stats[ST_F_HRSP_2XX].u.u64) : 0, + U2H(stats[ST_F_HRSP_3XX].u.u64), + U2H(stats[ST_F_HRSP_4XX].u.u64), + U2H(stats[ST_F_HRSP_5XX].u.u64), + U2H(stats[ST_F_HRSP_OTHER].u.u64), + U2H(stats[ST_F_CACHE_LOOKUPS].u.u64), + U2H(stats[ST_F_CACHE_HITS].u.u64), + stats[ST_F_CACHE_LOOKUPS].u.u64 ? + (int)(100 * stats[ST_F_CACHE_HITS].u.u64 / stats[ST_F_CACHE_LOOKUPS].u.u64) : 0, + U2H(stats[ST_F_WREW].u.u64), + U2H(stats[ST_F_EINT].u.u64)); + } + + chunk_appendf(out, ""); + chunk_appendf(out, "", + U2H(stats[ST_F_QT_MAX].u.u32), U2H(stats[ST_F_QTIME].u.u32)); + chunk_appendf(out, "", + U2H(stats[ST_F_CT_MAX].u.u32), U2H(stats[ST_F_CTIME].u.u32)); + if (strcmp(field_str(stats, ST_F_MODE), "http") == 0) + chunk_appendf(out, "", + U2H(stats[ST_F_RT_MAX].u.u32), U2H(stats[ST_F_RTIME].u.u32)); + chunk_appendf(out, "", + U2H(stats[ST_F_TT_MAX].u.u32), U2H(stats[ST_F_TTIME].u.u32)); + + chunk_appendf(out, + "
Cum. sessions:%s
New connections:%s
Reused connections:%s(%d%%)
Cum. HTTP requests:%s
- HTTP 1xx responses:%s
- HTTP 2xx responses:%s
  Compressed 2xx:%s(%d%%)
- HTTP 3xx responses:%s
- HTTP 4xx responses:%s
- HTTP 5xx responses:%s
- other responses:%s
Cache lookups:%s
Cache hits:%s(%d%%)
Failed hdr rewrites:%s
Internal errors:%s
Max / Avg over last 1024 success. conn.
- Queue time:%s / %sms
- Connect time:%s / %sms
- Responses time:%s / %sms
- Total time:%s / %sms
" + /* sessions: lbtot, last */ + "%s%s" + /* bytes: in */ + "%s" + "", + U2H(stats[ST_F_LBTOT].u.u64), + human_time(stats[ST_F_LASTSESS].u.s32, 1), + U2H(stats[ST_F_BIN].u.u64)); + + chunk_appendf(out, + /* bytes:out + compression stats (via hover): comp_in, comp_out, comp_byp */ + "%s%s
" + "" + "" + "" + "" + "" + "
Response bytes in:%s
Compression in:%s
Compression out:%s(%d%%)
Compression bypass:%s
Total bytes saved:%s(%d%%)
%s", + (stats[ST_F_COMP_IN].u.u64 || stats[ST_F_COMP_BYP].u.u64) ? "":"", + U2H(stats[ST_F_BOUT].u.u64), + U2H(stats[ST_F_BOUT].u.u64), + U2H(stats[ST_F_COMP_IN].u.u64), + U2H(stats[ST_F_COMP_OUT].u.u64), + stats[ST_F_COMP_IN].u.u64 ? (int)(stats[ST_F_COMP_OUT].u.u64 * 100 / stats[ST_F_COMP_IN].u.u64) : 0, + U2H(stats[ST_F_COMP_BYP].u.u64), + U2H(stats[ST_F_COMP_IN].u.u64 - stats[ST_F_COMP_OUT].u.u64), + 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, + (stats[ST_F_COMP_IN].u.u64 || stats[ST_F_COMP_BYP].u.u64) ? "":""); + + chunk_appendf(out, + /* denied: req, resp */ + "%s%s" + /* errors : request, connect */ + "%s" + /* errors : response */ + "%s
Connection resets during transfers: %lld client, %lld server
" + /* warnings: retries, redispatches */ + "%lld%lld" + /* backend status: reflect backend status (up/down): we display UP + * if the backend has known working servers or if it has no server at + * all (eg: for stats). Then we display the total weight, number of + * active and backups. */ + "%s %s %d/%d" + "%d%d" + "", + U2H(stats[ST_F_DREQ].u.u64), U2H(stats[ST_F_DRESP].u.u64), + U2H(stats[ST_F_ECON].u.u64), + U2H(stats[ST_F_ERESP].u.u64), + (long long)stats[ST_F_CLI_ABRT].u.u64, + (long long)stats[ST_F_SRV_ABRT].u.u64, + (long long)stats[ST_F_WRETR].u.u64, (long long)stats[ST_F_WREDIS].u.u64, + human_time(stats[ST_F_LASTCHG].u.u32, 1), + strcmp(field_str(stats, ST_F_STATUS), "DOWN") ? field_str(stats, ST_F_STATUS) : "DOWN", + stats[ST_F_WEIGHT].u.u32, stats[ST_F_UWEIGHT].u.u32, + stats[ST_F_ACT].u.u32, stats[ST_F_BCK].u.u32); + + chunk_appendf(out, + /* rest of backend: nothing, down transitions, total downtime, throttle */ + " %d" + "%s" + "", + stats[ST_F_CHKDOWN].u.u32, + stats[ST_F_DOWNTIME].type ? human_time(stats[ST_F_DOWNTIME].u.u32, 1) : " "); + + if (flags & STAT_SHMODULES) { + list_for_each_entry(mod, &stats_module_list[STATS_DOMAIN_PROXY], list) { + chunk_appendf(out, ""); + + if (stats_px_get_cap(mod->domain_flags) & STATS_PX_CAP_BE) { + chunk_appendf(out, + "%s
", + mod->name); + for (j = 0; j < mod->stats_count; ++j) { + chunk_appendf(out, + "", + mod->stats[j].desc, field_to_html_str(&stats[ST_F_TOTAL_FIELDS + i])); + ++i; + } + chunk_appendf(out, "
%s%s
"); + } else { + i += mod->stats_count; + } + + chunk_appendf(out, ""); + } + } + + chunk_appendf(out, ""); + } + + return 1; +} + +/* Dumps the HTML table header for proxy to chunk ctx buffer and uses the + * state from stream connector . The caller is responsible for clearing + * chunk ctx buffer if needed. + */ +void stats_dump_html_px_hdr(struct stconn *sc, struct proxy *px) +{ + struct appctx *appctx = __sc_appctx(sc); + struct show_stat_ctx *ctx = appctx->svcctx; + struct buffer *chk = &ctx->chunk; + char scope_txt[STAT_SCOPE_TXT_MAXLEN + sizeof STAT_SCOPE_PATTERN]; + struct stats_module *mod; + int stats_module_len = 0; + + if (px->cap & PR_CAP_BE && px->srv && (ctx->flags & STAT_ADMIN)) { + /* A form to enable/disable this proxy servers */ + + /* scope_txt = search pattern + search query, ctx->scope_len is always <= STAT_SCOPE_TXT_MAXLEN */ + scope_txt[0] = 0; + if (ctx->scope_len) { + const char *scope_ptr = stats_scope_ptr(appctx); + + strlcpy2(scope_txt, STAT_SCOPE_PATTERN, sizeof(scope_txt)); + memcpy(scope_txt + strlen(STAT_SCOPE_PATTERN), scope_ptr, ctx->scope_len); + scope_txt[strlen(STAT_SCOPE_PATTERN) + ctx->scope_len] = 0; + } + + chunk_appendf(chk, + "
"); + } + + /* print a new table */ + chunk_appendf(chk, + "\n" + "" + "" + "" + "\n" + "
"); + + chunk_appendf(chk, + "%s" + "%s", + px->id, + (ctx->flags & STAT_SHLGNDS) ? "":"", + px->id, px->id); + + if (ctx->flags & STAT_SHLGNDS) { + /* cap, mode, id */ + chunk_appendf(chk, "
cap: %s, mode: %s, id: %d", + proxy_cap_str(px->cap), proxy_mode_str(px->mode), + px->uuid); + chunk_appendf(chk, "
"); + } + + chunk_appendf(chk, + "%s
%s
\n" + "\n" + "", + (ctx->flags & STAT_SHLGNDS) ? "":"", + px->desc ? "desc" : "empty", px->desc ? px->desc : ""); + + if (ctx->flags & STAT_ADMIN) { + /* Column heading for Enable or Disable server */ + if ((px->cap & PR_CAP_BE) && px->srv) + chunk_appendf(chk, + "", + px->id, + px->id); + else + chunk_appendf(chk, ""); + } + + chunk_appendf(chk, + "" + "" + "" + "" + "" + ""); + + if (ctx->flags & STAT_SHMODULES) { + // calculate the count of module for colspan attribute + list_for_each_entry(mod, &stats_module_list[STATS_DOMAIN_PROXY], list) { + ++stats_module_len; + } + chunk_appendf(chk, "", + stats_module_len); + } + + chunk_appendf(chk, + "\n" + "" + "" + "" + "" + "" + "" + "" + "" + "\n"); + + if (ctx->flags & STAT_SHMODULES) { + list_for_each_entry(mod, &stats_module_list[STATS_DOMAIN_PROXY], list) { + chunk_appendf(chk, "", mod->name); + } + } + + chunk_appendf(chk, ""); +} + +/* Dumps the HTML table trailer for proxy to chunk ctx buffer and uses the + * state from stream connector . The caller is responsible for clearing + * chunk ctx buffer if needed. + */ +void stats_dump_html_px_end(struct stconn *sc, struct proxy *px) +{ + struct appctx *appctx = __sc_appctx(sc); + struct show_stat_ctx *ctx = appctx->svcctx; + struct buffer *chk = &ctx->chunk; + + chunk_appendf(chk, "
QueueSession rateSessionsBytesDeniedErrorsWarningsServerExtra modules
CurMaxLimitCurMaxLimitCurMaxLimitTotalLbTotLastInOutReqRespReqConnRespRetrRedisStatusLastChkWghtActBckChkDwnDwntmeThrtle%s
"); + + if ((px->cap & PR_CAP_BE) && px->srv && (ctx->flags & STAT_ADMIN)) { + /* close the form used to enable/disable this proxy servers */ + chunk_appendf(chk, + "Choose the action to perform on the checked servers : " + "" + "" + " " + "
", + px->uuid); + } + + chunk_appendf(chk, "

\n"); +} + +/* Dumps the HTML stats trailer block to buffer. The caller is + * responsible for clearing it if needed. + */ +void stats_dump_html_end(struct buffer *out) +{ + chunk_appendf(out, "\n"); +} + + +static int stats_send_http_headers(struct stconn *sc, struct htx *htx) +{ + struct uri_auth *uri; + struct appctx *appctx = __sc_appctx(sc); + struct show_stat_ctx *ctx = appctx->svcctx; + struct htx_sl *sl; + unsigned int flags; + + BUG_ON(!ctx->http_px); + uri = ctx->http_px->uri_auth; + + flags = (HTX_SL_F_IS_RESP|HTX_SL_F_VER_11|HTX_SL_F_XFER_ENC|HTX_SL_F_XFER_LEN|HTX_SL_F_CHNK); + sl = htx_add_stline(htx, HTX_BLK_RES_SL, flags, ist("HTTP/1.1"), ist("200"), ist("OK")); + if (!sl) + goto full; + sl->info.res.status = 200; + + if (!htx_add_header(htx, ist("Cache-Control"), ist("no-cache"))) + goto full; + if (ctx->flags & STAT_FMT_HTML) { + if (!htx_add_header(htx, ist("Content-Type"), ist("text/html"))) + goto full; + } + else if (ctx->flags & (STAT_FMT_JSON|STAT_JSON_SCHM)) { + if (!htx_add_header(htx, ist("Content-Type"), ist("application/json"))) + goto full; + } + else { + if (!htx_add_header(htx, ist("Content-Type"), ist("text/plain"))) + goto full; + } + + if (uri->refresh > 0 && !(ctx->flags & STAT_NO_REFRESH)) { + const char *refresh = U2A(uri->refresh); + if (!htx_add_header(htx, ist("Refresh"), ist(refresh))) + goto full; + } + + if (ctx->flags & STAT_CHUNKED) { + if (!htx_add_header(htx, ist("Transfer-Encoding"), ist("chunked"))) + goto full; + } + + if (!htx_add_endof(htx, HTX_BLK_EOH)) + goto full; + return 1; + + full: + htx_reset(htx); + applet_set_eos(appctx); + applet_set_error(appctx); + return 0; +} + +static int stats_send_http_redirect(struct stconn *sc, struct htx *htx) +{ + char scope_txt[STAT_SCOPE_TXT_MAXLEN + sizeof STAT_SCOPE_PATTERN]; + struct uri_auth *uri; + struct appctx *appctx = __sc_appctx(sc); + struct show_stat_ctx *ctx = appctx->svcctx; + struct htx_sl *sl; + unsigned int flags; + + BUG_ON(!ctx->http_px); + uri = ctx->http_px->uri_auth; + + /* scope_txt = search pattern + search query, ctx->scope_len is always <= STAT_SCOPE_TXT_MAXLEN */ + scope_txt[0] = 0; + if (ctx->scope_len) { + const char *scope_ptr = stats_scope_ptr(appctx); + + strlcpy2(scope_txt, STAT_SCOPE_PATTERN, sizeof(scope_txt)); + memcpy(scope_txt + strlen(STAT_SCOPE_PATTERN), scope_ptr, ctx->scope_len); + scope_txt[strlen(STAT_SCOPE_PATTERN) + ctx->scope_len] = 0; + } + + /* We don't want to land on the posted stats page because a refresh will + * repost the data. We don't want this to happen on accident so we redirect + * the browse to the stats page with a GET. + */ + chunk_printf(&trash, "%s;st=%s%s%s%s", + uri->uri_prefix, + ((ctx->st_code > STAT_STATUS_INIT) && + (ctx->st_code < STAT_STATUS_SIZE) && + stat_status_codes[ctx->st_code]) ? + stat_status_codes[ctx->st_code] : + stat_status_codes[STAT_STATUS_UNKN], + (ctx->flags & STAT_HIDE_DOWN) ? ";up" : "", + (ctx->flags & STAT_NO_REFRESH) ? ";norefresh" : "", + scope_txt); + + flags = (HTX_SL_F_IS_RESP|HTX_SL_F_VER_11|HTX_SL_F_XFER_LEN|HTX_SL_F_CLEN|HTX_SL_F_CHNK); + sl = htx_add_stline(htx, HTX_BLK_RES_SL, flags, ist("HTTP/1.1"), ist("303"), ist("See Other")); + if (!sl) + goto full; + sl->info.res.status = 303; + + if (!htx_add_header(htx, ist("Cache-Control"), ist("no-cache")) || + !htx_add_header(htx, ist("Content-Type"), ist("text/plain")) || + !htx_add_header(htx, ist("Content-Length"), ist("0")) || + !htx_add_header(htx, ist("Location"), ist2(trash.area, trash.data))) + goto full; + + if (!htx_add_endof(htx, HTX_BLK_EOH)) + goto full; + + return 1; + + full: + htx_reset(htx); + applet_set_eos(appctx); + applet_set_error(appctx); + return 0; +} + +/* We reached the stats page through a POST request. The appctx is + * expected to have already been allocated by the caller. + * Parse the posted data and enable/disable servers if necessary. + * Returns 1 if request was parsed or zero if it needs more data. + */ +static int stats_process_http_post(struct stconn *sc) +{ + struct appctx *appctx = __sc_appctx(sc); + struct show_stat_ctx *ctx = appctx->svcctx; + + struct proxy *px = NULL; + struct server *sv = NULL; + + char key[LINESIZE]; + int action = ST_ADM_ACTION_NONE; + int reprocess = 0; + + int total_servers = 0; + int altered_servers = 0; + + char *first_param, *cur_param, *next_param, *end_params; + char *st_cur_param = NULL; + char *st_next_param = NULL; + + struct buffer *temp = get_trash_chunk(); + + struct htx *htx = htxbuf(&appctx->inbuf); + struct htx_blk *blk; + + /* we need more data */ + if (!(htx->flags & HTX_FL_EOM)) { + /* check if we can receive more */ + if (applet_fl_test(appctx, APPCTX_FL_INBLK_FULL)) { + ctx->st_code = STAT_STATUS_EXCD; + goto out; + } + goto wait; + } + + /* The request was fully received. Copy data */ + blk = htx_get_head_blk(htx); + while (blk) { + enum htx_blk_type type = htx_get_blk_type(blk); + + if (type == HTX_BLK_TLR || type == HTX_BLK_EOT) + break; + if (type == HTX_BLK_DATA) { + struct ist v = htx_get_blk_value(htx, blk); + + if (!chunk_memcat(temp, v.ptr, v.len)) { + ctx->st_code = STAT_STATUS_EXCD; + goto out; + } + } + blk = htx_get_next_blk(htx, blk); + } + + first_param = temp->area; + end_params = temp->area + temp->data; + cur_param = next_param = end_params; + *end_params = '\0'; + + ctx->st_code = STAT_STATUS_NONE; + + /* + * Parse the parameters in reverse order to only store the last value. + * From the html form, the backend and the action are at the end. + */ + while (cur_param > first_param) { + char *value; + int poffset, plen; + + cur_param--; + + if ((*cur_param == '&') || (cur_param == first_param)) { + reprocess_servers: + /* Parse the key */ + poffset = (cur_param != first_param ? 1 : 0); + plen = next_param - cur_param + (cur_param == first_param ? 1 : 0); + if ((plen > 0) && (plen <= sizeof(key))) { + strncpy(key, cur_param + poffset, plen); + key[plen - 1] = '\0'; + } else { + ctx->st_code = STAT_STATUS_ERRP; + goto out; + } + + /* Parse the value */ + value = key; + while (*value != '\0' && *value != '=') { + value++; + } + if (*value == '=') { + /* Ok, a value is found, we can mark the end of the key */ + *value++ = '\0'; + } + if (url_decode(key, 1) < 0 || url_decode(value, 1) < 0) + break; + + /* Now we can check the key to see what to do */ + if (!px && (strcmp(key, "b") == 0)) { + if ((px = proxy_be_by_name(value)) == NULL) { + /* the backend name is unknown or ambiguous (duplicate names) */ + ctx->st_code = STAT_STATUS_ERRP; + goto out; + } + } + else if (!action && (strcmp(key, "action") == 0)) { + if (strcmp(value, "ready") == 0) { + action = ST_ADM_ACTION_READY; + } + else if (strcmp(value, "drain") == 0) { + action = ST_ADM_ACTION_DRAIN; + } + else if (strcmp(value, "maint") == 0) { + action = ST_ADM_ACTION_MAINT; + } + else if (strcmp(value, "shutdown") == 0) { + action = ST_ADM_ACTION_SHUTDOWN; + } + else if (strcmp(value, "dhlth") == 0) { + action = ST_ADM_ACTION_DHLTH; + } + else if (strcmp(value, "ehlth") == 0) { + action = ST_ADM_ACTION_EHLTH; + } + else if (strcmp(value, "hrunn") == 0) { + action = ST_ADM_ACTION_HRUNN; + } + else if (strcmp(value, "hnolb") == 0) { + action = ST_ADM_ACTION_HNOLB; + } + else if (strcmp(value, "hdown") == 0) { + action = ST_ADM_ACTION_HDOWN; + } + else if (strcmp(value, "dagent") == 0) { + action = ST_ADM_ACTION_DAGENT; + } + else if (strcmp(value, "eagent") == 0) { + action = ST_ADM_ACTION_EAGENT; + } + else if (strcmp(value, "arunn") == 0) { + action = ST_ADM_ACTION_ARUNN; + } + else if (strcmp(value, "adown") == 0) { + action = ST_ADM_ACTION_ADOWN; + } + /* else these are the old supported methods */ + else if (strcmp(value, "disable") == 0) { + action = ST_ADM_ACTION_DISABLE; + } + else if (strcmp(value, "enable") == 0) { + action = ST_ADM_ACTION_ENABLE; + } + else if (strcmp(value, "stop") == 0) { + action = ST_ADM_ACTION_STOP; + } + else if (strcmp(value, "start") == 0) { + action = ST_ADM_ACTION_START; + } + else { + ctx->st_code = STAT_STATUS_ERRP; + goto out; + } + } + else if (strcmp(key, "s") == 0) { + if (!(px && action)) { + /* + * Indicates that we'll need to reprocess the parameters + * as soon as backend and action are known + */ + if (!reprocess) { + st_cur_param = cur_param; + st_next_param = next_param; + } + reprocess = 1; + } + else if ((sv = findserver(px, value)) != NULL) { + HA_SPIN_LOCK(SERVER_LOCK, &sv->lock); + switch (action) { + case ST_ADM_ACTION_DISABLE: + if (!(sv->cur_admin & SRV_ADMF_FMAINT)) { + altered_servers++; + total_servers++; + srv_set_admin_flag(sv, SRV_ADMF_FMAINT, SRV_ADM_STCHGC_STATS_DISABLE); + } + break; + case ST_ADM_ACTION_ENABLE: + if (sv->cur_admin & SRV_ADMF_FMAINT) { + altered_servers++; + total_servers++; + srv_clr_admin_flag(sv, SRV_ADMF_FMAINT); + } + break; + case ST_ADM_ACTION_STOP: + if (!(sv->cur_admin & SRV_ADMF_FDRAIN)) { + srv_set_admin_flag(sv, SRV_ADMF_FDRAIN, SRV_ADM_STCHGC_STATS_STOP); + altered_servers++; + total_servers++; + } + break; + case ST_ADM_ACTION_START: + if (sv->cur_admin & SRV_ADMF_FDRAIN) { + srv_clr_admin_flag(sv, SRV_ADMF_FDRAIN); + altered_servers++; + total_servers++; + } + break; + case ST_ADM_ACTION_DHLTH: + if (sv->check.state & CHK_ST_CONFIGURED) { + sv->check.state &= ~CHK_ST_ENABLED; + altered_servers++; + total_servers++; + } + break; + case ST_ADM_ACTION_EHLTH: + if (sv->check.state & CHK_ST_CONFIGURED) { + sv->check.state |= CHK_ST_ENABLED; + altered_servers++; + total_servers++; + } + break; + case ST_ADM_ACTION_HRUNN: + if (!(sv->track)) { + sv->check.health = sv->check.rise + sv->check.fall - 1; + srv_set_running(sv, SRV_OP_STCHGC_STATS_WEB); + altered_servers++; + total_servers++; + } + break; + case ST_ADM_ACTION_HNOLB: + if (!(sv->track)) { + sv->check.health = sv->check.rise + sv->check.fall - 1; + srv_set_stopping(sv, SRV_OP_STCHGC_STATS_WEB); + altered_servers++; + total_servers++; + } + break; + case ST_ADM_ACTION_HDOWN: + if (!(sv->track)) { + sv->check.health = 0; + srv_set_stopped(sv, SRV_OP_STCHGC_STATS_WEB); + altered_servers++; + total_servers++; + } + break; + case ST_ADM_ACTION_DAGENT: + if (sv->agent.state & CHK_ST_CONFIGURED) { + sv->agent.state &= ~CHK_ST_ENABLED; + altered_servers++; + total_servers++; + } + break; + case ST_ADM_ACTION_EAGENT: + if (sv->agent.state & CHK_ST_CONFIGURED) { + sv->agent.state |= CHK_ST_ENABLED; + altered_servers++; + total_servers++; + } + break; + case ST_ADM_ACTION_ARUNN: + if (sv->agent.state & CHK_ST_ENABLED) { + sv->agent.health = sv->agent.rise + sv->agent.fall - 1; + srv_set_running(sv, SRV_OP_STCHGC_STATS_WEB); + altered_servers++; + total_servers++; + } + break; + case ST_ADM_ACTION_ADOWN: + if (sv->agent.state & CHK_ST_ENABLED) { + sv->agent.health = 0; + srv_set_stopped(sv, SRV_OP_STCHGC_STATS_WEB); + altered_servers++; + total_servers++; + } + break; + case ST_ADM_ACTION_READY: + srv_adm_set_ready(sv); + altered_servers++; + total_servers++; + break; + case ST_ADM_ACTION_DRAIN: + srv_adm_set_drain(sv); + altered_servers++; + total_servers++; + break; + case ST_ADM_ACTION_MAINT: + srv_adm_set_maint(sv); + altered_servers++; + total_servers++; + break; + case ST_ADM_ACTION_SHUTDOWN: + if (!(px->flags & (PR_FL_DISABLED|PR_FL_STOPPED))) { + srv_shutdown_streams(sv, SF_ERR_KILLED); + altered_servers++; + total_servers++; + } + break; + } + HA_SPIN_UNLOCK(SERVER_LOCK, &sv->lock); + } else { + /* the server name is unknown or ambiguous (duplicate names) */ + total_servers++; + } + } + if (reprocess && px && action) { + /* Now, we know the backend and the action chosen by the user. + * We can safely restart from the first server parameter + * to reprocess them + */ + cur_param = st_cur_param; + next_param = st_next_param; + reprocess = 0; + goto reprocess_servers; + } + + next_param = cur_param; + } + } + + if (total_servers == 0) { + ctx->st_code = STAT_STATUS_NONE; + } + else if (altered_servers == 0) { + ctx->st_code = STAT_STATUS_ERRP; + } + else if (altered_servers == total_servers) { + ctx->st_code = STAT_STATUS_DONE; + } + else { + ctx->st_code = STAT_STATUS_PART; + } + out: + return 1; + wait: + ctx->st_code = STAT_STATUS_NONE; + return 0; +} + +/* This I/O handler runs as an applet embedded in a stream connector. It is + * used to send HTTP stats over a TCP socket. The mechanism is very simple. + * appctx->st0 contains the operation in progress (dump, done). The handler + * automatically unregisters itself once transfer is complete. + */ +static void http_stats_io_handler(struct appctx *appctx) +{ + struct show_stat_ctx *ctx = appctx->svcctx; + struct stconn *sc = appctx_sc(appctx); + struct htx *res_htx = NULL; + + /* only proxy stats are available via http */ + ctx->domain = STATS_DOMAIN_PROXY; + + if (applet_fl_test(appctx, APPCTX_FL_INBLK_ALLOC|APPCTX_FL_OUTBLK_ALLOC|APPCTX_FL_OUTBLK_FULL)) + goto out; + + if (applet_fl_test(appctx, APPCTX_FL_FASTFWD) && se_fl_test(appctx->sedesc, SE_FL_MAY_FASTFWD_PROD)) + goto out; + + if (!appctx_get_buf(appctx, &appctx->outbuf)) { + applet_fl_set(appctx, APPCTX_FL_OUTBLK_ALLOC); + goto out; + } + + res_htx = htx_from_buf(&appctx->outbuf); + + if (unlikely(applet_fl_test(appctx, APPCTX_FL_EOS|APPCTX_FL_ERROR))) { + appctx->st0 = STAT_HTTP_END; + goto out; + } + + /* all states are processed in sequence */ + if (appctx->st0 == STAT_HTTP_HEAD) { + if (stats_send_http_headers(sc, res_htx)) { + struct ist meth = htx_sl_req_meth(http_get_stline(htxbuf(&appctx->inbuf))); + + if (find_http_meth(istptr(meth), istlen(meth)) == HTTP_METH_HEAD) + appctx->st0 = STAT_HTTP_DONE; + else { + if (!(global.tune.no_zero_copy_fwd & NO_ZERO_COPY_FWD_APPLET)) + se_fl_set(appctx->sedesc, SE_FL_MAY_FASTFWD_PROD); + appctx->st0 = STAT_HTTP_DUMP; + } + } + } + + if (appctx->st0 == STAT_HTTP_DUMP) { + ctx->chunk = b_make(trash.area, appctx->outbuf.size, 0, 0); + /* adjust buffer size to take htx overhead into account, + * make sure to perform this call on an empty buffer + */ + ctx->chunk.size = buf_room_for_htx_data(&ctx->chunk); + if (stats_dump_stat_to_buffer(sc, NULL, res_htx)) + appctx->st0 = STAT_HTTP_DONE; + } + + if (appctx->st0 == STAT_HTTP_POST) { + if (stats_process_http_post(sc)) + appctx->st0 = STAT_HTTP_LAST; + } + + if (appctx->st0 == STAT_HTTP_LAST) { + if (stats_send_http_redirect(sc, res_htx)) + appctx->st0 = STAT_HTTP_DONE; + } + + if (appctx->st0 == STAT_HTTP_DONE) { + /* no more data are expected. If the response buffer is empty, + * be sure to add something (EOT block in this case) to have + * something to send. It is important to be sure the EOM flags + * will be handled by the endpoint. + */ + if (htx_is_empty(res_htx)) { + if (!htx_add_endof(res_htx, HTX_BLK_EOT)) { + applet_fl_set(appctx, APPCTX_FL_OUTBLK_FULL); + goto out; + } + } + res_htx->flags |= HTX_FL_EOM; + applet_set_eoi(appctx); + se_fl_clr(appctx->sedesc, SE_FL_MAY_FASTFWD_PROD); + applet_fl_clr(appctx, APPCTX_FL_FASTFWD); + appctx->st0 = STAT_HTTP_END; + } + + if (appctx->st0 == STAT_HTTP_END) { + applet_set_eos(appctx); + applet_will_consume(appctx); + } + + out: + /* we have left the request in the buffer for the case where we + * process a POST, and this automatically re-enables activity on + * read. It's better to indicate that we want to stop reading when + * we're sending, so that we know there's at most one direction + * deciding to wake the applet up. It saves it from looping when + * emitting large blocks into small TCP windows. + */ + if (res_htx) + htx_to_buf(res_htx, &appctx->outbuf); + + if (appctx->st0 == STAT_HTTP_END) { + /* eat the whole request */ + b_reset(&appctx->inbuf); + applet_fl_clr(appctx, APPCTX_FL_INBLK_FULL); + appctx->sedesc->iobuf.flags &= ~IOBUF_FL_FF_BLOCKED; + } + else if (applet_fl_test(appctx, APPCTX_FL_OUTBLK_FULL)) + applet_wont_consume(appctx); +} + +static size_t http_stats_fastfwd(struct appctx *appctx, struct buffer *buf, + size_t count, unsigned int flags) +{ + struct stconn *sc = appctx_sc(appctx); + size_t ret = 0; + + ret = b_data(buf); + if (stats_dump_stat_to_buffer(sc, buf, NULL)) { + se_fl_clr(appctx->sedesc, SE_FL_MAY_FASTFWD_PROD); + applet_fl_clr(appctx, APPCTX_FL_FASTFWD); + appctx->st0 = STAT_HTTP_DONE; + } + + ret = b_data(buf) - ret; + return ret; +} + +static void http_stats_release(struct appctx *appctx) +{ + struct show_stat_ctx *ctx = appctx->svcctx; + + if (ctx->px_st == STAT_PX_ST_SV) + srv_drop(ctx->obj2); +} + +struct applet http_stats_applet = { + .obj_type = OBJ_TYPE_APPLET, + .name = "", /* used for logging */ + .fct = http_stats_io_handler, + .rcv_buf = appctx_htx_rcv_buf, + .snd_buf = appctx_htx_snd_buf, + .fastfwd = http_stats_fastfwd, + .release = http_stats_release, +}; diff --git a/src/stats.c b/src/stats.c index 8ed5ec092..fa035f016 100644 --- a/src/stats.c +++ b/src/stats.c @@ -57,6 +57,7 @@ #include #include #include +#include #include #include #include @@ -356,7 +357,7 @@ int stats_is_full(struct appctx *appctx, struct buffer *buf, struct htx *htx) return 1; } -static const char *stats_scope_ptr(struct appctx *appctx) +const char *stats_scope_ptr(struct appctx *appctx) { struct show_stat_ctx *ctx = appctx->svcctx; struct htx *htx = htxbuf(&appctx->inbuf); @@ -435,21 +436,6 @@ int stats_emit_raw_data_field(struct buffer *out, const struct field *f) } } -const char *field_to_html_str(const struct field *f) -{ - switch (field_format(f, 0)) { - case FF_S32: return U2H(f->u.s32); - case FF_S64: return U2H(f->u.s64); - case FF_U64: return U2H(f->u.u64); - case FF_U32: return U2H(f->u.u32); - case FF_FLT: return F2H(f->u.flt); - case FF_STR: return field_str(f, 0); - case FF_EMPTY: - default: - return ""; - } -} - /* Emits a stats field prefixed with its type. No CSV encoding is prepared, the * output is supposed to be used on its own line. Returns non-zero on success, 0 * if the buffer is full. @@ -874,841 +860,6 @@ static int stats_dump_fields_json(struct buffer *out, return 1; } -/* Dump all fields from into using the HTML format. A column is - * reserved for the checkbox is STAT_ADMIN is set in . Some extra info - * are provided if STAT_SHLGNDS is present in . The statistics from - * extra modules are displayed at the end of the lines if STAT_SHMODULES is - * present in . - */ -static int stats_dump_fields_html(struct buffer *out, - const struct field *stats, - struct show_stat_ctx *ctx) -{ - struct buffer src; - struct stats_module *mod; - int flags = ctx->flags; - int i = 0, j = 0; - - if (stats[ST_F_TYPE].u.u32 == STATS_TYPE_FE) { - chunk_appendf(out, - /* name, queue */ - ""); - - if (flags & STAT_ADMIN) { - /* Column sub-heading for Enable or Disable server */ - chunk_appendf(out, ""); - } - - chunk_appendf(out, - "" - "" - "Frontend" - "" - "", - field_str(stats, ST_F_PXNAME), field_str(stats, ST_F_PXNAME)); - - chunk_appendf(out, - /* sessions rate : current */ - "%s

" - "" - "" - "", - U2H(stats[ST_F_RATE].u.u32), - U2H(stats[ST_F_CONN_RATE].u.u32), - U2H(stats[ST_F_RATE].u.u32)); - - if (strcmp(field_str(stats, ST_F_MODE), "http") == 0) - chunk_appendf(out, - "", - U2H(stats[ST_F_REQ_RATE].u.u32)); - - chunk_appendf(out, - "
Current connection rate:%s/s
Current session rate:%s/s
Current request rate:%s/s
" - /* sessions rate : max */ - "%s
" - "" - "" - "", - U2H(stats[ST_F_RATE_MAX].u.u32), - U2H(stats[ST_F_CONN_RATE_MAX].u.u32), - U2H(stats[ST_F_RATE_MAX].u.u32)); - - if (strcmp(field_str(stats, ST_F_MODE), "http") == 0) - chunk_appendf(out, - "", - U2H(stats[ST_F_REQ_RATE_MAX].u.u32)); - - chunk_appendf(out, - "
Max connection rate:%s/s
Max session rate:%s/s
Max request rate:%s/s
" - /* sessions rate : limit */ - "%s", - LIM2A(stats[ST_F_RATE_LIM].u.u32, "-")); - - chunk_appendf(out, - /* sessions: current, max, limit, total */ - "%s%s%s" - "%s
" - "" - "" - "", - U2H(stats[ST_F_SCUR].u.u32), U2H(stats[ST_F_SMAX].u.u32), U2H(stats[ST_F_SLIM].u.u32), - U2H(stats[ST_F_STOT].u.u64), - U2H(stats[ST_F_CONN_TOT].u.u64), - U2H(stats[ST_F_STOT].u.u64)); - - /* http response (via hover): 1xx, 2xx, 3xx, 4xx, 5xx, other */ - if (strcmp(field_str(stats, ST_F_MODE), "http") == 0) { - chunk_appendf(out, - "" - "" - "" - "" - "" - "" - "" - "" - "" - "", - U2H(stats[ST_F_H1SESS].u.u64), - U2H(stats[ST_F_H2SESS].u.u64), - U2H(stats[ST_F_H3SESS].u.u64), - U2H(stats[ST_F_SESS_OTHER].u.u64), - U2H(stats[ST_F_REQ_TOT].u.u64), - U2H(stats[ST_F_H1REQ].u.u64), - U2H(stats[ST_F_H2REQ].u.u64), - U2H(stats[ST_F_H3REQ].u.u64), - U2H(stats[ST_F_REQ_OTHER].u.u64)); - - chunk_appendf(out, - "" - "" - "" - "" - "" - "" - "" - "", - U2H(stats[ST_F_HRSP_1XX].u.u64), - U2H(stats[ST_F_HRSP_2XX].u.u64), - U2H(stats[ST_F_COMP_RSP].u.u64), - stats[ST_F_HRSP_2XX].u.u64 ? - (int)(100 * stats[ST_F_COMP_RSP].u.u64 / stats[ST_F_HRSP_2XX].u.u64) : 0, - U2H(stats[ST_F_HRSP_3XX].u.u64), - U2H(stats[ST_F_HRSP_4XX].u.u64), - U2H(stats[ST_F_HRSP_5XX].u.u64), - U2H(stats[ST_F_HRSP_OTHER].u.u64)); - - chunk_appendf(out, - "" - "" - "" - "" - "" - "", - U2H(stats[ST_F_INTERCEPTED].u.u64), - U2H(stats[ST_F_CACHE_LOOKUPS].u.u64), - U2H(stats[ST_F_CACHE_HITS].u.u64), - stats[ST_F_CACHE_LOOKUPS].u.u64 ? - (int)(100 * stats[ST_F_CACHE_HITS].u.u64 / stats[ST_F_CACHE_LOOKUPS].u.u64) : 0, - U2H(stats[ST_F_WREW].u.u64), - U2H(stats[ST_F_EINT].u.u64)); - } - - chunk_appendf(out, - "
Cum. connections:%s
Cum. sessions:%s
- HTTP/1 sessions:%s
- HTTP/2 sessions:%s
- HTTP/3 sessions:%s
- other sessions:%s
Cum. HTTP requests:%s
- HTTP/1 requests:%s
- HTTP/2 requests:%s
- HTTP/3 requests:%s
- other requests:%s
- HTTP 1xx responses:%s
- HTTP 2xx responses:%s
  Compressed 2xx:%s(%d%%)
- HTTP 3xx responses:%s
- HTTP 4xx responses:%s
- HTTP 5xx responses:%s
- other responses:%s
Intercepted requests:%s
Cache lookups:%s
Cache hits:%s(%d%%)
Failed hdr rewrites:%s
Internal errors:%s
" - /* sessions: lbtot, lastsess */ - "" - /* bytes : in */ - "%s" - "", - U2H(stats[ST_F_BIN].u.u64)); - - chunk_appendf(out, - /* bytes:out + compression stats (via hover): comp_in, comp_out, comp_byp */ - "%s%s
" - "" - "" - "" - "" - "" - "
Response bytes in:%s
Compression in:%s
Compression out:%s(%d%%)
Compression bypass:%s
Total bytes saved:%s(%d%%)
%s", - (stats[ST_F_COMP_IN].u.u64 || stats[ST_F_COMP_BYP].u.u64) ? "":"", - U2H(stats[ST_F_BOUT].u.u64), - U2H(stats[ST_F_BOUT].u.u64), - U2H(stats[ST_F_COMP_IN].u.u64), - U2H(stats[ST_F_COMP_OUT].u.u64), - stats[ST_F_COMP_IN].u.u64 ? (int)(stats[ST_F_COMP_OUT].u.u64 * 100 / stats[ST_F_COMP_IN].u.u64) : 0, - U2H(stats[ST_F_COMP_BYP].u.u64), - U2H(stats[ST_F_COMP_IN].u.u64 - stats[ST_F_COMP_OUT].u.u64), - 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, - (stats[ST_F_COMP_IN].u.u64 || stats[ST_F_COMP_BYP].u.u64) ? "":""); - - chunk_appendf(out, - /* denied: req, resp */ - "%s%s" - /* errors : request, connect, response */ - "%s" - /* warnings: retries, redispatches */ - "" - /* server status : reflect frontend status */ - "%s" - /* rest of server: nothing */ - "" - "", - U2H(stats[ST_F_DREQ].u.u64), U2H(stats[ST_F_DRESP].u.u64), - U2H(stats[ST_F_EREQ].u.u64), - field_str(stats, ST_F_STATUS)); - - if (flags & STAT_SHMODULES) { - list_for_each_entry(mod, &stats_module_list[STATS_DOMAIN_PROXY], list) { - chunk_appendf(out, ""); - - if (stats_px_get_cap(mod->domain_flags) & STATS_PX_CAP_FE) { - chunk_appendf(out, - "%s
", - mod->name); - for (j = 0; j < mod->stats_count; ++j) { - chunk_appendf(out, - "", - mod->stats[j].desc, field_to_html_str(&stats[ST_F_TOTAL_FIELDS + i])); - ++i; - } - chunk_appendf(out, "
%s%s
"); - } else { - i += mod->stats_count; - } - - chunk_appendf(out, ""); - } - } - - chunk_appendf(out, ""); - } - else if (stats[ST_F_TYPE].u.u32 == STATS_TYPE_SO) { - chunk_appendf(out, ""); - if (flags & STAT_ADMIN) { - /* Column sub-heading for Enable or Disable server */ - chunk_appendf(out, ""); - } - - chunk_appendf(out, - /* frontend name, listener name */ - "%s" - "%s" - "", - field_str(stats, ST_F_PXNAME), field_str(stats, ST_F_SVNAME), - (flags & STAT_SHLGNDS)?"":"", - field_str(stats, ST_F_PXNAME), field_str(stats, ST_F_SVNAME), field_str(stats, ST_F_SVNAME)); - - if (flags & STAT_SHLGNDS) { - chunk_appendf(out, "
"); - - if (isdigit((unsigned char)*field_str(stats, ST_F_ADDR))) - chunk_appendf(out, "IPv4: %s, ", field_str(stats, ST_F_ADDR)); - else if (*field_str(stats, ST_F_ADDR) == '[') - chunk_appendf(out, "IPv6: %s, ", field_str(stats, ST_F_ADDR)); - else if (*field_str(stats, ST_F_ADDR)) - chunk_appendf(out, "%s, ", field_str(stats, ST_F_ADDR)); - - chunk_appendf(out, "proto=%s, ", field_str(stats, ST_F_PROTO)); - - /* id */ - chunk_appendf(out, "id: %d
", stats[ST_F_SID].u.u32); - } - - chunk_appendf(out, - /* queue */ - "%s" - /* sessions rate: current, max, limit */ - " " - /* sessions: current, max, limit, total, lbtot, lastsess */ - "%s%s%s" - "%s  " - /* bytes: in, out */ - "%s%s" - "", - (flags & STAT_SHLGNDS)?"
":"", - U2H(stats[ST_F_SCUR].u.u32), U2H(stats[ST_F_SMAX].u.u32), U2H(stats[ST_F_SLIM].u.u32), - U2H(stats[ST_F_STOT].u.u64), U2H(stats[ST_F_BIN].u.u64), U2H(stats[ST_F_BOUT].u.u64)); - - chunk_appendf(out, - /* denied: req, resp */ - "%s%s" - /* errors: request, connect, response */ - "%s" - /* warnings: retries, redispatches */ - "" - /* server status: reflect listener status */ - "%s" - /* rest of server: nothing */ - "" - "", - U2H(stats[ST_F_DREQ].u.u64), U2H(stats[ST_F_DRESP].u.u64), - U2H(stats[ST_F_EREQ].u.u64), - field_str(stats, ST_F_STATUS)); - - if (flags & STAT_SHMODULES) { - list_for_each_entry(mod, &stats_module_list[STATS_DOMAIN_PROXY], list) { - chunk_appendf(out, ""); - - if (stats_px_get_cap(mod->domain_flags) & STATS_PX_CAP_LI) { - chunk_appendf(out, - "%s
", - mod->name); - for (j = 0; j < mod->stats_count; ++j) { - chunk_appendf(out, - "", - mod->stats[j].desc, field_to_html_str(&stats[ST_F_TOTAL_FIELDS + i])); - ++i; - } - chunk_appendf(out, "
%s%s
"); - } else { - i += mod->stats_count; - } - - chunk_appendf(out, ""); - } - } - - chunk_appendf(out, ""); - } - else if (stats[ST_F_TYPE].u.u32 == STATS_TYPE_SV) { - const char *style; - - /* determine the style to use depending on the server's state, - * its health and weight. There isn't a 1-to-1 mapping between - * state and styles for the cases where the server is (still) - * up. The reason is that we don't want to report nolb and - * drain with the same color. - */ - - if (strcmp(field_str(stats, ST_F_STATUS), "DOWN") == 0 || - strcmp(field_str(stats, ST_F_STATUS), "DOWN (agent)") == 0) { - style = "down"; - } - else if (strncmp(field_str(stats, ST_F_STATUS), "DOWN ", strlen("DOWN ")) == 0) { - style = "going_up"; - } - else if (strcmp(field_str(stats, ST_F_STATUS), "DRAIN") == 0) { - style = "draining"; - } - else if (strncmp(field_str(stats, ST_F_STATUS), "NOLB ", strlen("NOLB ")) == 0) { - style = "going_down"; - } - else if (strcmp(field_str(stats, ST_F_STATUS), "NOLB") == 0) { - style = "nolb"; - } - else if (strcmp(field_str(stats, ST_F_STATUS), "no check") == 0) { - style = "no_check"; - } - else if (!stats[ST_F_CHKFAIL].type || - stats[ST_F_CHECK_HEALTH].u.u32 == stats[ST_F_CHECK_RISE].u.u32 + stats[ST_F_CHECK_FALL].u.u32 - 1) { - /* no check or max health = UP */ - if (stats[ST_F_WEIGHT].u.u32) - style = "up"; - else - style = "draining"; - } - else { - style = "going_down"; - } - - if (strncmp(field_str(stats, ST_F_STATUS), "MAINT", 5) == 0) - chunk_appendf(out, ""); - else - chunk_appendf(out, - "", - (stats[ST_F_BCK].u.u32) ? "backup" : "active", style); - - - if (flags & STAT_ADMIN) - chunk_appendf(out, - "", - field_str(stats, ST_F_PXNAME), - field_str(stats, ST_F_SVNAME)); - - chunk_appendf(out, - "%s" - "%s" - "", - field_str(stats, ST_F_PXNAME), field_str(stats, ST_F_SVNAME), - (flags & STAT_SHLGNDS) ? "" : "", - field_str(stats, ST_F_PXNAME), field_str(stats, ST_F_SVNAME), field_str(stats, ST_F_SVNAME)); - - if (flags & STAT_SHLGNDS) { - chunk_appendf(out, "
"); - - if (isdigit((unsigned char)*field_str(stats, ST_F_ADDR))) - chunk_appendf(out, "IPv4: %s, ", field_str(stats, ST_F_ADDR)); - else if (*field_str(stats, ST_F_ADDR) == '[') - chunk_appendf(out, "IPv6: %s, ", field_str(stats, ST_F_ADDR)); - else if (*field_str(stats, ST_F_ADDR)) - chunk_appendf(out, "%s, ", field_str(stats, ST_F_ADDR)); - - /* id */ - chunk_appendf(out, "id: %d, rid: %d", stats[ST_F_SID].u.u32, stats[ST_F_SRID].u.u32); - - /* cookie */ - if (stats[ST_F_COOKIE].type) { - chunk_appendf(out, ", cookie: '"); - chunk_initstr(&src, field_str(stats, ST_F_COOKIE)); - chunk_htmlencode(out, &src); - chunk_appendf(out, "'"); - } - - chunk_appendf(out, "
"); - } - - chunk_appendf(out, - /* queue : current, max, limit */ - "%s%s%s%s" - /* sessions rate : current, max, limit */ - "%s%s" - "", - (flags & STAT_SHLGNDS) ? "
" : "", - U2H(stats[ST_F_QCUR].u.u32), U2H(stats[ST_F_QMAX].u.u32), LIM2A(stats[ST_F_QLIMIT].u.u32, "-"), - U2H(stats[ST_F_RATE].u.u32), U2H(stats[ST_F_RATE_MAX].u.u32)); - - chunk_appendf(out, - /* sessions: current, max, limit, total */ - "%s
" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "
Current active connections:%s
Current used connections:%s
Current idle connections:%s
- unsafe:%s
- safe:%s
Estimated need of connections:%s
Active connections limit:%s
Idle connections limit:%s
" - "%s%s" - "%s
" - "" - "", - U2H(stats[ST_F_SCUR].u.u32), - U2H(stats[ST_F_SCUR].u.u32), - U2H(stats[ST_F_USED_CONN_CUR].u.u32), - U2H(stats[ST_F_SRV_ICUR].u.u32), - U2H(stats[ST_F_IDLE_CONN_CUR].u.u32), - U2H(stats[ST_F_SAFE_CONN_CUR].u.u32), - U2H(stats[ST_F_NEED_CONN_EST].u.u32), - - LIM2A(stats[ST_F_SLIM].u.u32, "-"), - stats[ST_F_SRV_ILIM].type ? U2H(stats[ST_F_SRV_ILIM].u.u32) : "-", - U2H(stats[ST_F_SMAX].u.u32), LIM2A(stats[ST_F_SLIM].u.u32, "-"), - U2H(stats[ST_F_STOT].u.u64), - U2H(stats[ST_F_STOT].u.u64)); - - /* http response (via hover): 1xx, 2xx, 3xx, 4xx, 5xx, other */ - if (strcmp(field_str(stats, ST_F_MODE), "http") == 0) { - chunk_appendf(out, - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "", - U2H(stats[ST_F_CONNECT].u.u64), - U2H(stats[ST_F_REUSE].u.u64), - (stats[ST_F_CONNECT].u.u64 + stats[ST_F_REUSE].u.u64) ? - (int)(100 * stats[ST_F_REUSE].u.u64 / (stats[ST_F_CONNECT].u.u64 + stats[ST_F_REUSE].u.u64)) : 0, - U2H(stats[ST_F_REQ_TOT].u.u64), - U2H(stats[ST_F_HRSP_1XX].u.u64), stats[ST_F_REQ_TOT].u.u64 ? - (int)(100 * stats[ST_F_HRSP_1XX].u.u64 / stats[ST_F_REQ_TOT].u.u64) : 0, - U2H(stats[ST_F_HRSP_2XX].u.u64), stats[ST_F_REQ_TOT].u.u64 ? - (int)(100 * stats[ST_F_HRSP_2XX].u.u64 / stats[ST_F_REQ_TOT].u.u64) : 0, - U2H(stats[ST_F_HRSP_3XX].u.u64), stats[ST_F_REQ_TOT].u.u64 ? - (int)(100 * stats[ST_F_HRSP_3XX].u.u64 / stats[ST_F_REQ_TOT].u.u64) : 0, - U2H(stats[ST_F_HRSP_4XX].u.u64), stats[ST_F_REQ_TOT].u.u64 ? - (int)(100 * stats[ST_F_HRSP_4XX].u.u64 / stats[ST_F_REQ_TOT].u.u64) : 0, - U2H(stats[ST_F_HRSP_5XX].u.u64), stats[ST_F_REQ_TOT].u.u64 ? - (int)(100 * stats[ST_F_HRSP_5XX].u.u64 / stats[ST_F_REQ_TOT].u.u64) : 0, - U2H(stats[ST_F_HRSP_OTHER].u.u64), stats[ST_F_REQ_TOT].u.u64 ? - (int)(100 * stats[ST_F_HRSP_OTHER].u.u64 / stats[ST_F_REQ_TOT].u.u64) : 0, - U2H(stats[ST_F_WREW].u.u64), - U2H(stats[ST_F_EINT].u.u64)); - } - - chunk_appendf(out, ""); - chunk_appendf(out, "", - U2H(stats[ST_F_QT_MAX].u.u32), U2H(stats[ST_F_QTIME].u.u32)); - chunk_appendf(out, "", - U2H(stats[ST_F_CT_MAX].u.u32), U2H(stats[ST_F_CTIME].u.u32)); - if (strcmp(field_str(stats, ST_F_MODE), "http") == 0) - chunk_appendf(out, "", - U2H(stats[ST_F_RT_MAX].u.u32), U2H(stats[ST_F_RTIME].u.u32)); - chunk_appendf(out, "", - U2H(stats[ST_F_TT_MAX].u.u32), U2H(stats[ST_F_TTIME].u.u32)); - - chunk_appendf(out, - "
Cum. sessions:%s
New connections:%s
Reused connections:%s(%d%%)
Cum. HTTP requests:%s
- HTTP 1xx responses:%s(%d%%)
- HTTP 2xx responses:%s(%d%%)
- HTTP 3xx responses:%s(%d%%)
- HTTP 4xx responses:%s(%d%%)
- HTTP 5xx responses:%s(%d%%)
- other responses:%s(%d%%)
Failed hdr rewrites:%s
Internal error:%s
Max / Avg over last 1024 success. conn.
- Queue time:%s / %sms
- Connect time:%s / %sms
- Responses time:%s / %sms
- Total time:%s / %sms
" - /* sessions: lbtot, last */ - "%s%s", - U2H(stats[ST_F_LBTOT].u.u64), - human_time(stats[ST_F_LASTSESS].u.s32, 1)); - - chunk_appendf(out, - /* bytes : in, out */ - "%s%s" - /* denied: req, resp */ - "%s" - /* errors : request, connect */ - "%s" - /* errors : response */ - "%s
Connection resets during transfers: %lld client, %lld server
" - /* warnings: retries, redispatches */ - "%lld%lld" - "", - U2H(stats[ST_F_BIN].u.u64), U2H(stats[ST_F_BOUT].u.u64), - U2H(stats[ST_F_DRESP].u.u64), - U2H(stats[ST_F_ECON].u.u64), - U2H(stats[ST_F_ERESP].u.u64), - (long long)stats[ST_F_CLI_ABRT].u.u64, - (long long)stats[ST_F_SRV_ABRT].u.u64, - (long long)stats[ST_F_WRETR].u.u64, - (long long)stats[ST_F_WREDIS].u.u64); - - /* status, last change */ - chunk_appendf(out, ""); - - /* FIXME!!!! - * LASTCHG should contain the last change for *this* server and must be computed - * properly above, as was done below, ie: this server if maint, otherwise ref server - * if tracking. Note that ref is either local or remote depending on tracking. - */ - - - if (strncmp(field_str(stats, ST_F_STATUS), "MAINT", 5) == 0) { - chunk_appendf(out, "%s MAINT", human_time(stats[ST_F_LASTCHG].u.u32, 1)); - } - else if (strcmp(field_str(stats, ST_F_STATUS), "no check") == 0) { - chunk_strcat(out, "no check"); - } - else { - chunk_appendf(out, "%s %s", human_time(stats[ST_F_LASTCHG].u.u32, 1), field_str(stats, ST_F_STATUS)); - if (strncmp(field_str(stats, ST_F_STATUS), "DOWN", 4) == 0) { - if (stats[ST_F_CHECK_HEALTH].u.u32) - chunk_strcat(out, " ↑"); - } - else if (stats[ST_F_CHECK_HEALTH].u.u32 < stats[ST_F_CHECK_RISE].u.u32 + stats[ST_F_CHECK_FALL].u.u32 - 1) - chunk_strcat(out, " ↓"); - } - - if (strncmp(field_str(stats, ST_F_STATUS), "DOWN", 4) == 0 && - stats[ST_F_AGENT_STATUS].type && !stats[ST_F_AGENT_HEALTH].u.u32) { - chunk_appendf(out, - " %s", - field_str(stats, ST_F_AGENT_STATUS)); - - if (stats[ST_F_AGENT_CODE].type) - chunk_appendf(out, "/%d", stats[ST_F_AGENT_CODE].u.u32); - - if (stats[ST_F_AGENT_DURATION].type) - chunk_appendf(out, " in %lums", (long)stats[ST_F_AGENT_DURATION].u.u64); - - chunk_appendf(out, "
%s", field_str(stats, ST_F_AGENT_DESC)); - - if (*field_str(stats, ST_F_LAST_AGT)) { - chunk_appendf(out, ": "); - chunk_initstr(&src, field_str(stats, ST_F_LAST_AGT)); - chunk_htmlencode(out, &src); - } - chunk_appendf(out, "
"); - } - else if (stats[ST_F_CHECK_STATUS].type) { - chunk_appendf(out, - " %s", - field_str(stats, ST_F_CHECK_STATUS)); - - if (stats[ST_F_CHECK_CODE].type) - chunk_appendf(out, "/%d", stats[ST_F_CHECK_CODE].u.u32); - - if (stats[ST_F_CHECK_DURATION].type) - chunk_appendf(out, " in %lums", (long)stats[ST_F_CHECK_DURATION].u.u64); - - chunk_appendf(out, "
%s", field_str(stats, ST_F_CHECK_DESC)); - - if (*field_str(stats, ST_F_LAST_CHK)) { - chunk_appendf(out, ": "); - chunk_initstr(&src, field_str(stats, ST_F_LAST_CHK)); - chunk_htmlencode(out, &src); - } - chunk_appendf(out, "
"); - } - else - chunk_appendf(out, ""); - - chunk_appendf(out, - /* weight / uweight */ - "%d/%d" - /* act, bck */ - "%s%s" - "", - stats[ST_F_WEIGHT].u.u32, stats[ST_F_UWEIGHT].u.u32, - stats[ST_F_BCK].u.u32 ? "-" : "Y", - stats[ST_F_BCK].u.u32 ? "Y" : "-"); - - /* check failures: unique, fatal, down time */ - if (strcmp(field_str(stats, ST_F_STATUS), "MAINT (resolution)") == 0) { - chunk_appendf(out, "resolution"); - } - else if (stats[ST_F_CHKFAIL].type) { - chunk_appendf(out, "%lld", (long long)stats[ST_F_CHKFAIL].u.u64); - - if (stats[ST_F_HANAFAIL].type) - chunk_appendf(out, "/%lld", (long long)stats[ST_F_HANAFAIL].u.u64); - - chunk_appendf(out, - "
Failed Health Checks%s
" - "%lld%s" - "", - stats[ST_F_HANAFAIL].type ? "/Health Analyses" : "", - (long long)stats[ST_F_CHKDOWN].u.u64, human_time(stats[ST_F_DOWNTIME].u.u32, 1)); - } - else if (strcmp(field_str(stats, ST_F_STATUS), "MAINT") != 0 && field_format(stats, ST_F_TRACKED) == FF_STR) { - /* tracking a server (hence inherited maint would appear as "MAINT (via...)" */ - chunk_appendf(out, - "via %s", - field_str(stats, ST_F_TRACKED), field_str(stats, ST_F_TRACKED)); - } - else - chunk_appendf(out, ""); - - /* throttle */ - if (stats[ST_F_THROTTLE].type) - chunk_appendf(out, "%d %%\n", stats[ST_F_THROTTLE].u.u32); - else - chunk_appendf(out, "-"); - - if (flags & STAT_SHMODULES) { - list_for_each_entry(mod, &stats_module_list[STATS_DOMAIN_PROXY], list) { - chunk_appendf(out, ""); - - if (stats_px_get_cap(mod->domain_flags) & STATS_PX_CAP_SRV) { - chunk_appendf(out, - "%s
", - mod->name); - for (j = 0; j < mod->stats_count; ++j) { - chunk_appendf(out, - "", - mod->stats[j].desc, field_to_html_str(&stats[ST_F_TOTAL_FIELDS + i])); - ++i; - } - chunk_appendf(out, "
%s%s
"); - } else { - i += mod->stats_count; - } - - chunk_appendf(out, ""); - } - } - - chunk_appendf(out, "\n"); - } - else if (stats[ST_F_TYPE].u.u32 == STATS_TYPE_BE) { - chunk_appendf(out, ""); - if (flags & STAT_ADMIN) { - /* Column sub-heading for Enable or Disable server */ - chunk_appendf(out, ""); - } - chunk_appendf(out, - "" - /* name */ - "%s" - "Backend" - "", - (flags & STAT_SHLGNDS)?"":"", - field_str(stats, ST_F_PXNAME), field_str(stats, ST_F_PXNAME)); - - if (flags & STAT_SHLGNDS) { - /* balancing */ - chunk_appendf(out, "
balancing: %s", - field_str(stats, ST_F_ALGO)); - - /* cookie */ - if (stats[ST_F_COOKIE].type) { - chunk_appendf(out, ", cookie: '"); - chunk_initstr(&src, field_str(stats, ST_F_COOKIE)); - chunk_htmlencode(out, &src); - chunk_appendf(out, "'"); - } - chunk_appendf(out, "
"); - } - - chunk_appendf(out, - "%s" - /* queue : current, max */ - "%s%s" - /* sessions rate : current, max, limit */ - "%s%s" - "", - (flags & STAT_SHLGNDS)?"
":"", - U2H(stats[ST_F_QCUR].u.u32), U2H(stats[ST_F_QMAX].u.u32), - U2H(stats[ST_F_RATE].u.u32), U2H(stats[ST_F_RATE_MAX].u.u32)); - - chunk_appendf(out, - /* sessions: current, max, limit, total */ - "%s%s%s" - "%s
" - "" - "", - U2H(stats[ST_F_SCUR].u.u32), U2H(stats[ST_F_SMAX].u.u32), U2H(stats[ST_F_SLIM].u.u32), - U2H(stats[ST_F_STOT].u.u64), - U2H(stats[ST_F_STOT].u.u64)); - - /* http response (via hover): 1xx, 2xx, 3xx, 4xx, 5xx, other */ - if (strcmp(field_str(stats, ST_F_MODE), "http") == 0) { - chunk_appendf(out, - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "", - U2H(stats[ST_F_CONNECT].u.u64), - U2H(stats[ST_F_REUSE].u.u64), - (stats[ST_F_CONNECT].u.u64 + stats[ST_F_REUSE].u.u64) ? - (int)(100 * stats[ST_F_REUSE].u.u64 / (stats[ST_F_CONNECT].u.u64 + stats[ST_F_REUSE].u.u64)) : 0, - U2H(stats[ST_F_REQ_TOT].u.u64), - U2H(stats[ST_F_HRSP_1XX].u.u64), - U2H(stats[ST_F_HRSP_2XX].u.u64), - U2H(stats[ST_F_COMP_RSP].u.u64), - stats[ST_F_HRSP_2XX].u.u64 ? - (int)(100 * stats[ST_F_COMP_RSP].u.u64 / stats[ST_F_HRSP_2XX].u.u64) : 0, - U2H(stats[ST_F_HRSP_3XX].u.u64), - U2H(stats[ST_F_HRSP_4XX].u.u64), - U2H(stats[ST_F_HRSP_5XX].u.u64), - U2H(stats[ST_F_HRSP_OTHER].u.u64), - U2H(stats[ST_F_CACHE_LOOKUPS].u.u64), - U2H(stats[ST_F_CACHE_HITS].u.u64), - stats[ST_F_CACHE_LOOKUPS].u.u64 ? - (int)(100 * stats[ST_F_CACHE_HITS].u.u64 / stats[ST_F_CACHE_LOOKUPS].u.u64) : 0, - U2H(stats[ST_F_WREW].u.u64), - U2H(stats[ST_F_EINT].u.u64)); - } - - chunk_appendf(out, ""); - chunk_appendf(out, "", - U2H(stats[ST_F_QT_MAX].u.u32), U2H(stats[ST_F_QTIME].u.u32)); - chunk_appendf(out, "", - U2H(stats[ST_F_CT_MAX].u.u32), U2H(stats[ST_F_CTIME].u.u32)); - if (strcmp(field_str(stats, ST_F_MODE), "http") == 0) - chunk_appendf(out, "", - U2H(stats[ST_F_RT_MAX].u.u32), U2H(stats[ST_F_RTIME].u.u32)); - chunk_appendf(out, "", - U2H(stats[ST_F_TT_MAX].u.u32), U2H(stats[ST_F_TTIME].u.u32)); - - chunk_appendf(out, - "
Cum. sessions:%s
New connections:%s
Reused connections:%s(%d%%)
Cum. HTTP requests:%s
- HTTP 1xx responses:%s
- HTTP 2xx responses:%s
  Compressed 2xx:%s(%d%%)
- HTTP 3xx responses:%s
- HTTP 4xx responses:%s
- HTTP 5xx responses:%s
- other responses:%s
Cache lookups:%s
Cache hits:%s(%d%%)
Failed hdr rewrites:%s
Internal errors:%s
Max / Avg over last 1024 success. conn.
- Queue time:%s / %sms
- Connect time:%s / %sms
- Responses time:%s / %sms
- Total time:%s / %sms
" - /* sessions: lbtot, last */ - "%s%s" - /* bytes: in */ - "%s" - "", - U2H(stats[ST_F_LBTOT].u.u64), - human_time(stats[ST_F_LASTSESS].u.s32, 1), - U2H(stats[ST_F_BIN].u.u64)); - - chunk_appendf(out, - /* bytes:out + compression stats (via hover): comp_in, comp_out, comp_byp */ - "%s%s
" - "" - "" - "" - "" - "" - "
Response bytes in:%s
Compression in:%s
Compression out:%s(%d%%)
Compression bypass:%s
Total bytes saved:%s(%d%%)
%s", - (stats[ST_F_COMP_IN].u.u64 || stats[ST_F_COMP_BYP].u.u64) ? "":"", - U2H(stats[ST_F_BOUT].u.u64), - U2H(stats[ST_F_BOUT].u.u64), - U2H(stats[ST_F_COMP_IN].u.u64), - U2H(stats[ST_F_COMP_OUT].u.u64), - stats[ST_F_COMP_IN].u.u64 ? (int)(stats[ST_F_COMP_OUT].u.u64 * 100 / stats[ST_F_COMP_IN].u.u64) : 0, - U2H(stats[ST_F_COMP_BYP].u.u64), - U2H(stats[ST_F_COMP_IN].u.u64 - stats[ST_F_COMP_OUT].u.u64), - 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, - (stats[ST_F_COMP_IN].u.u64 || stats[ST_F_COMP_BYP].u.u64) ? "":""); - - chunk_appendf(out, - /* denied: req, resp */ - "%s%s" - /* errors : request, connect */ - "%s" - /* errors : response */ - "%s
Connection resets during transfers: %lld client, %lld server
" - /* warnings: retries, redispatches */ - "%lld%lld" - /* backend status: reflect backend status (up/down): we display UP - * if the backend has known working servers or if it has no server at - * all (eg: for stats). Then we display the total weight, number of - * active and backups. */ - "%s %s %d/%d" - "%d%d" - "", - U2H(stats[ST_F_DREQ].u.u64), U2H(stats[ST_F_DRESP].u.u64), - U2H(stats[ST_F_ECON].u.u64), - U2H(stats[ST_F_ERESP].u.u64), - (long long)stats[ST_F_CLI_ABRT].u.u64, - (long long)stats[ST_F_SRV_ABRT].u.u64, - (long long)stats[ST_F_WRETR].u.u64, (long long)stats[ST_F_WREDIS].u.u64, - human_time(stats[ST_F_LASTCHG].u.u32, 1), - strcmp(field_str(stats, ST_F_STATUS), "DOWN") ? field_str(stats, ST_F_STATUS) : "DOWN", - stats[ST_F_WEIGHT].u.u32, stats[ST_F_UWEIGHT].u.u32, - stats[ST_F_ACT].u.u32, stats[ST_F_BCK].u.u32); - - chunk_appendf(out, - /* rest of backend: nothing, down transitions, total downtime, throttle */ - " %d" - "%s" - "", - stats[ST_F_CHKDOWN].u.u32, - stats[ST_F_DOWNTIME].type ? human_time(stats[ST_F_DOWNTIME].u.u32, 1) : " "); - - if (flags & STAT_SHMODULES) { - list_for_each_entry(mod, &stats_module_list[STATS_DOMAIN_PROXY], list) { - chunk_appendf(out, ""); - - if (stats_px_get_cap(mod->domain_flags) & STATS_PX_CAP_BE) { - chunk_appendf(out, - "%s
", - mod->name); - for (j = 0; j < mod->stats_count; ++j) { - chunk_appendf(out, - "", - mod->stats[j].desc, field_to_html_str(&stats[ST_F_TOTAL_FIELDS + i])); - ++i; - } - chunk_appendf(out, "
%s%s
"); - } else { - i += mod->stats_count; - } - - chunk_appendf(out, ""); - } - } - - chunk_appendf(out, ""); - } - - return 1; -} - int stats_dump_one_line(const struct field *stats, size_t stats_count, struct appctx *appctx) { @@ -3015,159 +2166,6 @@ static int stats_dump_be_stats(struct stconn *sc, struct proxy *px) return stats_dump_one_line(stats, stats_count, appctx); } -/* Dumps the HTML table header for proxy to chunk ctx buffer and uses the - * state from stream connector . The caller is responsible for clearing - * chunk ctx buffer if needed. - */ -static void stats_dump_html_px_hdr(struct stconn *sc, struct proxy *px) -{ - struct appctx *appctx = __sc_appctx(sc); - struct show_stat_ctx *ctx = appctx->svcctx; - struct buffer *chk = &ctx->chunk; - char scope_txt[STAT_SCOPE_TXT_MAXLEN + sizeof STAT_SCOPE_PATTERN]; - struct stats_module *mod; - int stats_module_len = 0; - - if (px->cap & PR_CAP_BE && px->srv && (ctx->flags & STAT_ADMIN)) { - /* A form to enable/disable this proxy servers */ - - /* scope_txt = search pattern + search query, ctx->scope_len is always <= STAT_SCOPE_TXT_MAXLEN */ - scope_txt[0] = 0; - if (ctx->scope_len) { - const char *scope_ptr = stats_scope_ptr(appctx); - - strlcpy2(scope_txt, STAT_SCOPE_PATTERN, sizeof(scope_txt)); - memcpy(scope_txt + strlen(STAT_SCOPE_PATTERN), scope_ptr, ctx->scope_len); - scope_txt[strlen(STAT_SCOPE_PATTERN) + ctx->scope_len] = 0; - } - - chunk_appendf(chk, - "
"); - } - - /* print a new table */ - chunk_appendf(chk, - "\n" - "" - "" - "" - "\n" - "
"); - - chunk_appendf(chk, - "%s" - "%s", - px->id, - (ctx->flags & STAT_SHLGNDS) ? "":"", - px->id, px->id); - - if (ctx->flags & STAT_SHLGNDS) { - /* cap, mode, id */ - chunk_appendf(chk, "
cap: %s, mode: %s, id: %d", - proxy_cap_str(px->cap), proxy_mode_str(px->mode), - px->uuid); - chunk_appendf(chk, "
"); - } - - chunk_appendf(chk, - "%s
%s
\n" - "\n" - "", - (ctx->flags & STAT_SHLGNDS) ? "":"", - px->desc ? "desc" : "empty", px->desc ? px->desc : ""); - - if (ctx->flags & STAT_ADMIN) { - /* Column heading for Enable or Disable server */ - if ((px->cap & PR_CAP_BE) && px->srv) - chunk_appendf(chk, - "", - px->id, - px->id); - else - chunk_appendf(chk, ""); - } - - chunk_appendf(chk, - "" - "" - "" - "" - "" - ""); - - if (ctx->flags & STAT_SHMODULES) { - // calculate the count of module for colspan attribute - list_for_each_entry(mod, &stats_module_list[STATS_DOMAIN_PROXY], list) { - ++stats_module_len; - } - chunk_appendf(chk, "", - stats_module_len); - } - - chunk_appendf(chk, - "\n" - "" - "" - "" - "" - "" - "" - "" - "" - "\n"); - - if (ctx->flags & STAT_SHMODULES) { - list_for_each_entry(mod, &stats_module_list[STATS_DOMAIN_PROXY], list) { - chunk_appendf(chk, "", mod->name); - } - } - - chunk_appendf(chk, ""); -} - -/* Dumps the HTML table trailer for proxy to chunk ctx buffer and uses the - * state from stream connector . The caller is responsible for clearing - * chunk ctx buffer if needed. - */ -static void stats_dump_html_px_end(struct stconn *sc, struct proxy *px) -{ - struct appctx *appctx = __sc_appctx(sc); - struct show_stat_ctx *ctx = appctx->svcctx; - struct buffer *chk = &ctx->chunk; - - chunk_appendf(chk, "
QueueSession rateSessionsBytesDeniedErrorsWarningsServerExtra modules
CurMaxLimitCurMaxLimitCurMaxLimitTotalLbTotLastInOutReqRespReqConnRespRetrRedisStatusLastChkWghtActBckChkDwnDwntmeThrtle%s
"); - - if ((px->cap & PR_CAP_BE) && px->srv && (ctx->flags & STAT_ADMIN)) { - /* close the form used to enable/disable this proxy servers */ - chunk_appendf(chk, - "Choose the action to perform on the checked servers : " - "" - "" - " " - "
", - px->uuid); - } - - chunk_appendf(chk, "

\n"); -} - /* * Dumps statistics for a proxy. The output is sent to the stream connector's * input buffer. Returns 0 if it had to stop dumping data because of lack of @@ -3410,453 +2408,6 @@ int stats_dump_proxy_to_buffer(struct stconn *sc, struct buffer *buf, struct htx return 0; } -/* Dumps the HTTP stats head block to chunk ctx buffer and uses the per-uri - * parameters from the parent proxy. The caller is responsible for clearing - * chunk ctx buffer if needed. - */ -static void stats_dump_html_head(struct appctx *appctx) -{ - struct show_stat_ctx *ctx = appctx->svcctx; - struct buffer *chk = &ctx->chunk; - struct uri_auth *uri; - - BUG_ON(!ctx->http_px); - uri = ctx->http_px->uri_auth; - - /* WARNING! This must fit in the first buffer !!! */ - chunk_appendf(chk, - "\n" - "Statistics Report for " PRODUCT_NAME "%s%s\n" - "\n" - "\n" - "\n", - (ctx->flags & STAT_SHNODE) ? " on " : "", - (ctx->flags & STAT_SHNODE) ? (uri && uri->node ? uri->node : global.node) : "" - ); -} - -/* Dumps the HTML stats information block to chunk ctx buffer and uses the - * state from stream connector and per-uri parameter from the parent - * proxy. The caller is responsible for clearing chunk ctx buffer if needed. - */ -static void stats_dump_html_info(struct stconn *sc) -{ - struct appctx *appctx = __sc_appctx(sc); - struct show_stat_ctx *ctx = appctx->svcctx; - struct buffer *chk = &ctx->chunk; - unsigned int up = ns_to_sec(now_ns - start_time_ns); - char scope_txt[STAT_SCOPE_TXT_MAXLEN + sizeof STAT_SCOPE_PATTERN]; - const char *scope_ptr = stats_scope_ptr(appctx); - struct uri_auth *uri; - unsigned long long bps; - int thr; - - BUG_ON(!ctx->http_px); - uri = ctx->http_px->uri_auth; - for (bps = thr = 0; thr < global.nbthread; thr++) - bps += 32ULL * read_freq_ctr(&ha_thread_ctx[thr].out_32bps); - - /* Turn the bytes per second to bits per second and take care of the - * usual ethernet overhead in order to help figure how far we are from - * interface saturation since it's the only case which usually matters. - * For this we count the total size of an Ethernet frame on the wire - * including preamble and IFG (1538) for the largest TCP segment it - * transports (1448 with TCP timestamps). This is not valid for smaller - * packets (under-estimated), but it gives a reasonably accurate - * estimation of how far we are from uplink saturation. - */ - bps = bps * 8 * 1538 / 1448; - - /* WARNING! this has to fit the first packet too. - * We are around 3.5 kB, add adding entries will - * become tricky if we want to support 4kB buffers ! - */ - chunk_appendf(chk, - "

" - PRODUCT_NAME "%s

\n" - "

Statistics Report for pid %d%s%s%s%s

\n" - "
\n" - "

> General process information

\n" - "" - "" - "" - "
\n" - "

pid = %d (process #%d, nbproc = %d, nbthread = %d)
\n" - "uptime = %dd %dh%02dm%02ds; warnings = %u
\n" - "system limits: memmax = %s%s; ulimit-n = %d
\n" - "maxsock = %d; maxconn = %d; reached = %llu; maxpipes = %d
\n" - "current conns = %d; current pipes = %d/%d; conn rate = %d/sec; bit rate = %.3f %cbps
\n" - "Running tasks: %d/%d (%d niced); idle = %d %%
\n" - "

\n" - "\n" - "" - "" - "\n" - "" - "" - "\n" - "" - "" - "\n" - "" - "" - "\n" - "" - "\n" - "" - "
 active UP  backup UP
active UP, going down backup UP, going down
active DOWN, going up backup DOWN, going up
active or backup DOWN  not checked
active or backup DOWN for maintenance (MAINT)  
active or backup SOFT STOPPED for maintenance  
\n" - "Note: \"NOLB\"/\"DRAIN\" = UP with load-balancing disabled." - "
" - "Display option:
    " - "", - (ctx->flags & STAT_HIDEVER) ? "" : (stats_version_string), - pid, (ctx->flags & STAT_SHNODE) ? " on " : "", - (ctx->flags & STAT_SHNODE) ? (uri->node ? uri->node : global.node) : "", - (ctx->flags & STAT_SHDESC) ? ": " : "", - (ctx->flags & STAT_SHDESC) ? (uri->desc ? uri->desc : global.desc) : "", - pid, 1, 1, global.nbthread, - up / 86400, (up % 86400) / 3600, - (up % 3600) / 60, (up % 60), - HA_ATOMIC_LOAD(&tot_warnings), - global.rlimit_memmax ? ultoa(global.rlimit_memmax) : "unlimited", - global.rlimit_memmax ? " MB" : "", - global.rlimit_nofile, - global.maxsock, global.maxconn, HA_ATOMIC_LOAD(&maxconn_reached), global.maxpipes, - actconn, pipes_used, pipes_used+pipes_free, read_freq_ctr(&global.conn_per_sec), - bps >= 1000000000UL ? (bps / 1000000000.0) : bps >= 1000000UL ? (bps / 1000000.0) : (bps / 1000.0), - bps >= 1000000000UL ? 'G' : bps >= 1000000UL ? 'M' : 'k', - total_run_queues(), total_allocated_tasks(), total_niced_running_tasks(), clock_report_idle()); - - /* scope_txt = search query, ctx->scope_len is always <= STAT_SCOPE_TXT_MAXLEN */ - memcpy(scope_txt, scope_ptr, ctx->scope_len); - scope_txt[ctx->scope_len] = '\0'; - - chunk_appendf(chk, - "
  • Scope :
    \n", - (ctx->scope_len > 0) ? scope_txt : "", - STAT_SCOPE_TXT_MAXLEN); - - /* scope_txt = search pattern + search query, ctx->scope_len is always <= STAT_SCOPE_TXT_MAXLEN */ - scope_txt[0] = 0; - if (ctx->scope_len) { - strlcpy2(scope_txt, STAT_SCOPE_PATTERN, sizeof(scope_txt)); - memcpy(scope_txt + strlen(STAT_SCOPE_PATTERN), scope_ptr, ctx->scope_len); - scope_txt[strlen(STAT_SCOPE_PATTERN) + ctx->scope_len] = 0; - } - - if (ctx->flags & STAT_HIDE_DOWN) - chunk_appendf(chk, - "
  • Show all servers
    \n", - uri->uri_prefix, - "", - (ctx->flags & STAT_NO_REFRESH) ? ";norefresh" : "", - scope_txt); - else - chunk_appendf(chk, - "
  • Hide 'DOWN' servers
    \n", - uri->uri_prefix, - ";up", - (ctx->flags & STAT_NO_REFRESH) ? ";norefresh" : "", - scope_txt); - - if (uri->refresh > 0) { - if (ctx->flags & STAT_NO_REFRESH) - chunk_appendf(chk, - "
  • Enable refresh
    \n", - uri->uri_prefix, - (ctx->flags & STAT_HIDE_DOWN) ? ";up" : "", - "", - scope_txt); - else - chunk_appendf(chk, - "
  • Disable refresh
    \n", - uri->uri_prefix, - (ctx->flags & STAT_HIDE_DOWN) ? ";up" : "", - ";norefresh", - scope_txt); - } - - chunk_appendf(chk, - "
  • Refresh now
    \n", - uri->uri_prefix, - (ctx->flags & STAT_HIDE_DOWN) ? ";up" : "", - (ctx->flags & STAT_NO_REFRESH) ? ";norefresh" : "", - scope_txt); - - chunk_appendf(chk, - "
  • CSV export
    \n", - uri->uri_prefix, - (uri->refresh > 0) ? ";norefresh" : "", - scope_txt); - - chunk_appendf(chk, - "
  • JSON export (schema)
    \n", - uri->uri_prefix, - (uri->refresh > 0) ? ";norefresh" : "", - scope_txt, uri->uri_prefix); - - chunk_appendf(chk, - "
" - "External resources:" - "
\n" - "" - ); - - if (ctx->st_code) { - switch (ctx->st_code) { - case STAT_STATUS_DONE: - chunk_appendf(chk, - "

" - "[X] " - "Action processed successfully." - "
\n", uri->uri_prefix, - (ctx->flags & STAT_HIDE_DOWN) ? ";up" : "", - (ctx->flags & STAT_NO_REFRESH) ? ";norefresh" : "", - scope_txt); - break; - case STAT_STATUS_NONE: - chunk_appendf(chk, - "

" - "[X] " - "Nothing has changed." - "
\n", uri->uri_prefix, - (ctx->flags & STAT_HIDE_DOWN) ? ";up" : "", - (ctx->flags & STAT_NO_REFRESH) ? ";norefresh" : "", - scope_txt); - break; - case STAT_STATUS_PART: - chunk_appendf(chk, - "

" - "[X] " - "Action partially processed.
" - "Some server names are probably unknown or ambiguous (duplicated names in the backend)." - "
\n", uri->uri_prefix, - (ctx->flags & STAT_HIDE_DOWN) ? ";up" : "", - (ctx->flags & STAT_NO_REFRESH) ? ";norefresh" : "", - scope_txt); - break; - case STAT_STATUS_ERRP: - chunk_appendf(chk, - "

" - "[X] " - "Action not processed because of invalid parameters." - "
    " - "
  • The action is maybe unknown.
  • " - "
  • Invalid key parameter (empty or too long).
  • " - "
  • The backend name is probably unknown or ambiguous (duplicated names).
  • " - "
  • Some server names are probably unknown or ambiguous (duplicated names in the backend).
  • " - "
" - "
\n", uri->uri_prefix, - (ctx->flags & STAT_HIDE_DOWN) ? ";up" : "", - (ctx->flags & STAT_NO_REFRESH) ? ";norefresh" : "", - scope_txt); - break; - case STAT_STATUS_EXCD: - chunk_appendf(chk, - "

" - "[X] " - "Action not processed : the buffer couldn't store all the data.
" - "You should retry with less servers at a time.
" - "
\n", uri->uri_prefix, - (ctx->flags & STAT_HIDE_DOWN) ? ";up" : "", - (ctx->flags & STAT_NO_REFRESH) ? ";norefresh" : "", - scope_txt); - break; - case STAT_STATUS_DENY: - chunk_appendf(chk, - "

" - "[X] " - "Action denied." - "
\n", uri->uri_prefix, - (ctx->flags & STAT_HIDE_DOWN) ? ";up" : "", - (ctx->flags & STAT_NO_REFRESH) ? ";norefresh" : "", - scope_txt); - break; - case STAT_STATUS_IVAL: - chunk_appendf(chk, - "

" - "[X] " - "Invalid requests (unsupported method or chunked encoded request)." - "
\n", uri->uri_prefix, - (ctx->flags & STAT_HIDE_DOWN) ? ";up" : "", - (ctx->flags & STAT_NO_REFRESH) ? ";norefresh" : "", - scope_txt); - break; - default: - chunk_appendf(chk, - "

" - "[X] " - "Unexpected result." - "
\n", uri->uri_prefix, - (ctx->flags & STAT_HIDE_DOWN) ? ";up" : "", - (ctx->flags & STAT_NO_REFRESH) ? ";norefresh" : "", - scope_txt); - } - chunk_appendf(chk, "

\n"); - } -} - -/* Dumps the HTML stats trailer block to buffer. The caller is - * responsible for clearing it if needed. - */ -static void stats_dump_html_end(struct buffer *out) -{ - chunk_appendf(out, "\n"); -} - /* Dumps the stats JSON header to buffer. The caller is responsible for * clearing it if needed. */ @@ -3917,7 +2468,7 @@ static int stats_dump_proxies(struct stconn *sc, struct buffer *buf, * or -1 in case of any error. This function is used by both the CLI and the * HTTP handlers. */ -static int stats_dump_stat_to_buffer(struct stconn *sc, struct buffer *buf, struct htx *htx) +int stats_dump_stat_to_buffer(struct stconn *sc, struct buffer *buf, struct htx *htx) { struct appctx *appctx = __sc_appctx(sc); struct show_stat_ctx *ctx = appctx->svcctx; @@ -4014,590 +2565,6 @@ static int stats_dump_stat_to_buffer(struct stconn *sc, struct buffer *buf, stru } -/* We reached the stats page through a POST request. The appctx is - * expected to have already been allocated by the caller. - * Parse the posted data and enable/disable servers if necessary. - * Returns 1 if request was parsed or zero if it needs more data. - */ -static int stats_process_http_post(struct stconn *sc) -{ - struct appctx *appctx = __sc_appctx(sc); - struct show_stat_ctx *ctx = appctx->svcctx; - - struct proxy *px = NULL; - struct server *sv = NULL; - - char key[LINESIZE]; - int action = ST_ADM_ACTION_NONE; - int reprocess = 0; - - int total_servers = 0; - int altered_servers = 0; - - char *first_param, *cur_param, *next_param, *end_params; - char *st_cur_param = NULL; - char *st_next_param = NULL; - - struct buffer *temp = get_trash_chunk(); - - struct htx *htx = htxbuf(&appctx->inbuf); - struct htx_blk *blk; - - /* we need more data */ - if (!(htx->flags & HTX_FL_EOM)) { - /* check if we can receive more */ - if (applet_fl_test(appctx, APPCTX_FL_INBLK_FULL)) { - ctx->st_code = STAT_STATUS_EXCD; - goto out; - } - goto wait; - } - - /* The request was fully received. Copy data */ - blk = htx_get_head_blk(htx); - while (blk) { - enum htx_blk_type type = htx_get_blk_type(blk); - - if (type == HTX_BLK_TLR || type == HTX_BLK_EOT) - break; - if (type == HTX_BLK_DATA) { - struct ist v = htx_get_blk_value(htx, blk); - - if (!chunk_memcat(temp, v.ptr, v.len)) { - ctx->st_code = STAT_STATUS_EXCD; - goto out; - } - } - blk = htx_get_next_blk(htx, blk); - } - - first_param = temp->area; - end_params = temp->area + temp->data; - cur_param = next_param = end_params; - *end_params = '\0'; - - ctx->st_code = STAT_STATUS_NONE; - - /* - * Parse the parameters in reverse order to only store the last value. - * From the html form, the backend and the action are at the end. - */ - while (cur_param > first_param) { - char *value; - int poffset, plen; - - cur_param--; - - if ((*cur_param == '&') || (cur_param == first_param)) { - reprocess_servers: - /* Parse the key */ - poffset = (cur_param != first_param ? 1 : 0); - plen = next_param - cur_param + (cur_param == first_param ? 1 : 0); - if ((plen > 0) && (plen <= sizeof(key))) { - strncpy(key, cur_param + poffset, plen); - key[plen - 1] = '\0'; - } else { - ctx->st_code = STAT_STATUS_ERRP; - goto out; - } - - /* Parse the value */ - value = key; - while (*value != '\0' && *value != '=') { - value++; - } - if (*value == '=') { - /* Ok, a value is found, we can mark the end of the key */ - *value++ = '\0'; - } - if (url_decode(key, 1) < 0 || url_decode(value, 1) < 0) - break; - - /* Now we can check the key to see what to do */ - if (!px && (strcmp(key, "b") == 0)) { - if ((px = proxy_be_by_name(value)) == NULL) { - /* the backend name is unknown or ambiguous (duplicate names) */ - ctx->st_code = STAT_STATUS_ERRP; - goto out; - } - } - else if (!action && (strcmp(key, "action") == 0)) { - if (strcmp(value, "ready") == 0) { - action = ST_ADM_ACTION_READY; - } - else if (strcmp(value, "drain") == 0) { - action = ST_ADM_ACTION_DRAIN; - } - else if (strcmp(value, "maint") == 0) { - action = ST_ADM_ACTION_MAINT; - } - else if (strcmp(value, "shutdown") == 0) { - action = ST_ADM_ACTION_SHUTDOWN; - } - else if (strcmp(value, "dhlth") == 0) { - action = ST_ADM_ACTION_DHLTH; - } - else if (strcmp(value, "ehlth") == 0) { - action = ST_ADM_ACTION_EHLTH; - } - else if (strcmp(value, "hrunn") == 0) { - action = ST_ADM_ACTION_HRUNN; - } - else if (strcmp(value, "hnolb") == 0) { - action = ST_ADM_ACTION_HNOLB; - } - else if (strcmp(value, "hdown") == 0) { - action = ST_ADM_ACTION_HDOWN; - } - else if (strcmp(value, "dagent") == 0) { - action = ST_ADM_ACTION_DAGENT; - } - else if (strcmp(value, "eagent") == 0) { - action = ST_ADM_ACTION_EAGENT; - } - else if (strcmp(value, "arunn") == 0) { - action = ST_ADM_ACTION_ARUNN; - } - else if (strcmp(value, "adown") == 0) { - action = ST_ADM_ACTION_ADOWN; - } - /* else these are the old supported methods */ - else if (strcmp(value, "disable") == 0) { - action = ST_ADM_ACTION_DISABLE; - } - else if (strcmp(value, "enable") == 0) { - action = ST_ADM_ACTION_ENABLE; - } - else if (strcmp(value, "stop") == 0) { - action = ST_ADM_ACTION_STOP; - } - else if (strcmp(value, "start") == 0) { - action = ST_ADM_ACTION_START; - } - else { - ctx->st_code = STAT_STATUS_ERRP; - goto out; - } - } - else if (strcmp(key, "s") == 0) { - if (!(px && action)) { - /* - * Indicates that we'll need to reprocess the parameters - * as soon as backend and action are known - */ - if (!reprocess) { - st_cur_param = cur_param; - st_next_param = next_param; - } - reprocess = 1; - } - else if ((sv = findserver(px, value)) != NULL) { - HA_SPIN_LOCK(SERVER_LOCK, &sv->lock); - switch (action) { - case ST_ADM_ACTION_DISABLE: - if (!(sv->cur_admin & SRV_ADMF_FMAINT)) { - altered_servers++; - total_servers++; - srv_set_admin_flag(sv, SRV_ADMF_FMAINT, SRV_ADM_STCHGC_STATS_DISABLE); - } - break; - case ST_ADM_ACTION_ENABLE: - if (sv->cur_admin & SRV_ADMF_FMAINT) { - altered_servers++; - total_servers++; - srv_clr_admin_flag(sv, SRV_ADMF_FMAINT); - } - break; - case ST_ADM_ACTION_STOP: - if (!(sv->cur_admin & SRV_ADMF_FDRAIN)) { - srv_set_admin_flag(sv, SRV_ADMF_FDRAIN, SRV_ADM_STCHGC_STATS_STOP); - altered_servers++; - total_servers++; - } - break; - case ST_ADM_ACTION_START: - if (sv->cur_admin & SRV_ADMF_FDRAIN) { - srv_clr_admin_flag(sv, SRV_ADMF_FDRAIN); - altered_servers++; - total_servers++; - } - break; - case ST_ADM_ACTION_DHLTH: - if (sv->check.state & CHK_ST_CONFIGURED) { - sv->check.state &= ~CHK_ST_ENABLED; - altered_servers++; - total_servers++; - } - break; - case ST_ADM_ACTION_EHLTH: - if (sv->check.state & CHK_ST_CONFIGURED) { - sv->check.state |= CHK_ST_ENABLED; - altered_servers++; - total_servers++; - } - break; - case ST_ADM_ACTION_HRUNN: - if (!(sv->track)) { - sv->check.health = sv->check.rise + sv->check.fall - 1; - srv_set_running(sv, SRV_OP_STCHGC_STATS_WEB); - altered_servers++; - total_servers++; - } - break; - case ST_ADM_ACTION_HNOLB: - if (!(sv->track)) { - sv->check.health = sv->check.rise + sv->check.fall - 1; - srv_set_stopping(sv, SRV_OP_STCHGC_STATS_WEB); - altered_servers++; - total_servers++; - } - break; - case ST_ADM_ACTION_HDOWN: - if (!(sv->track)) { - sv->check.health = 0; - srv_set_stopped(sv, SRV_OP_STCHGC_STATS_WEB); - altered_servers++; - total_servers++; - } - break; - case ST_ADM_ACTION_DAGENT: - if (sv->agent.state & CHK_ST_CONFIGURED) { - sv->agent.state &= ~CHK_ST_ENABLED; - altered_servers++; - total_servers++; - } - break; - case ST_ADM_ACTION_EAGENT: - if (sv->agent.state & CHK_ST_CONFIGURED) { - sv->agent.state |= CHK_ST_ENABLED; - altered_servers++; - total_servers++; - } - break; - case ST_ADM_ACTION_ARUNN: - if (sv->agent.state & CHK_ST_ENABLED) { - sv->agent.health = sv->agent.rise + sv->agent.fall - 1; - srv_set_running(sv, SRV_OP_STCHGC_STATS_WEB); - altered_servers++; - total_servers++; - } - break; - case ST_ADM_ACTION_ADOWN: - if (sv->agent.state & CHK_ST_ENABLED) { - sv->agent.health = 0; - srv_set_stopped(sv, SRV_OP_STCHGC_STATS_WEB); - altered_servers++; - total_servers++; - } - break; - case ST_ADM_ACTION_READY: - srv_adm_set_ready(sv); - altered_servers++; - total_servers++; - break; - case ST_ADM_ACTION_DRAIN: - srv_adm_set_drain(sv); - altered_servers++; - total_servers++; - break; - case ST_ADM_ACTION_MAINT: - srv_adm_set_maint(sv); - altered_servers++; - total_servers++; - break; - case ST_ADM_ACTION_SHUTDOWN: - if (!(px->flags & (PR_FL_DISABLED|PR_FL_STOPPED))) { - srv_shutdown_streams(sv, SF_ERR_KILLED); - altered_servers++; - total_servers++; - } - break; - } - HA_SPIN_UNLOCK(SERVER_LOCK, &sv->lock); - } else { - /* the server name is unknown or ambiguous (duplicate names) */ - total_servers++; - } - } - if (reprocess && px && action) { - /* Now, we know the backend and the action chosen by the user. - * We can safely restart from the first server parameter - * to reprocess them - */ - cur_param = st_cur_param; - next_param = st_next_param; - reprocess = 0; - goto reprocess_servers; - } - - next_param = cur_param; - } - } - - if (total_servers == 0) { - ctx->st_code = STAT_STATUS_NONE; - } - else if (altered_servers == 0) { - ctx->st_code = STAT_STATUS_ERRP; - } - else if (altered_servers == total_servers) { - ctx->st_code = STAT_STATUS_DONE; - } - else { - ctx->st_code = STAT_STATUS_PART; - } - out: - return 1; - wait: - ctx->st_code = STAT_STATUS_NONE; - return 0; -} - - -static int stats_send_http_headers(struct stconn *sc, struct htx *htx) -{ - struct uri_auth *uri; - struct appctx *appctx = __sc_appctx(sc); - struct show_stat_ctx *ctx = appctx->svcctx; - struct htx_sl *sl; - unsigned int flags; - - BUG_ON(!ctx->http_px); - uri = ctx->http_px->uri_auth; - - flags = (HTX_SL_F_IS_RESP|HTX_SL_F_VER_11|HTX_SL_F_XFER_ENC|HTX_SL_F_XFER_LEN|HTX_SL_F_CHNK); - sl = htx_add_stline(htx, HTX_BLK_RES_SL, flags, ist("HTTP/1.1"), ist("200"), ist("OK")); - if (!sl) - goto full; - sl->info.res.status = 200; - - if (!htx_add_header(htx, ist("Cache-Control"), ist("no-cache"))) - goto full; - if (ctx->flags & STAT_FMT_HTML) { - if (!htx_add_header(htx, ist("Content-Type"), ist("text/html"))) - goto full; - } - else if (ctx->flags & (STAT_FMT_JSON|STAT_JSON_SCHM)) { - if (!htx_add_header(htx, ist("Content-Type"), ist("application/json"))) - goto full; - } - else { - if (!htx_add_header(htx, ist("Content-Type"), ist("text/plain"))) - goto full; - } - - if (uri->refresh > 0 && !(ctx->flags & STAT_NO_REFRESH)) { - const char *refresh = U2A(uri->refresh); - if (!htx_add_header(htx, ist("Refresh"), ist(refresh))) - goto full; - } - - if (ctx->flags & STAT_CHUNKED) { - if (!htx_add_header(htx, ist("Transfer-Encoding"), ist("chunked"))) - goto full; - } - - if (!htx_add_endof(htx, HTX_BLK_EOH)) - goto full; - return 1; - - full: - htx_reset(htx); - applet_set_eos(appctx); - applet_set_error(appctx); - return 0; -} - - -static int stats_send_http_redirect(struct stconn *sc, struct htx *htx) -{ - char scope_txt[STAT_SCOPE_TXT_MAXLEN + sizeof STAT_SCOPE_PATTERN]; - struct uri_auth *uri; - struct appctx *appctx = __sc_appctx(sc); - struct show_stat_ctx *ctx = appctx->svcctx; - struct htx_sl *sl; - unsigned int flags; - - BUG_ON(!ctx->http_px); - uri = ctx->http_px->uri_auth; - - /* scope_txt = search pattern + search query, ctx->scope_len is always <= STAT_SCOPE_TXT_MAXLEN */ - scope_txt[0] = 0; - if (ctx->scope_len) { - const char *scope_ptr = stats_scope_ptr(appctx); - - strlcpy2(scope_txt, STAT_SCOPE_PATTERN, sizeof(scope_txt)); - memcpy(scope_txt + strlen(STAT_SCOPE_PATTERN), scope_ptr, ctx->scope_len); - scope_txt[strlen(STAT_SCOPE_PATTERN) + ctx->scope_len] = 0; - } - - /* We don't want to land on the posted stats page because a refresh will - * repost the data. We don't want this to happen on accident so we redirect - * the browse to the stats page with a GET. - */ - chunk_printf(&trash, "%s;st=%s%s%s%s", - uri->uri_prefix, - ((ctx->st_code > STAT_STATUS_INIT) && - (ctx->st_code < STAT_STATUS_SIZE) && - stat_status_codes[ctx->st_code]) ? - stat_status_codes[ctx->st_code] : - stat_status_codes[STAT_STATUS_UNKN], - (ctx->flags & STAT_HIDE_DOWN) ? ";up" : "", - (ctx->flags & STAT_NO_REFRESH) ? ";norefresh" : "", - scope_txt); - - flags = (HTX_SL_F_IS_RESP|HTX_SL_F_VER_11|HTX_SL_F_XFER_LEN|HTX_SL_F_CLEN|HTX_SL_F_CHNK); - sl = htx_add_stline(htx, HTX_BLK_RES_SL, flags, ist("HTTP/1.1"), ist("303"), ist("See Other")); - if (!sl) - goto full; - sl->info.res.status = 303; - - if (!htx_add_header(htx, ist("Cache-Control"), ist("no-cache")) || - !htx_add_header(htx, ist("Content-Type"), ist("text/plain")) || - !htx_add_header(htx, ist("Content-Length"), ist("0")) || - !htx_add_header(htx, ist("Location"), ist2(trash.area, trash.data))) - goto full; - - if (!htx_add_endof(htx, HTX_BLK_EOH)) - goto full; - - return 1; - - full: - htx_reset(htx); - applet_set_eos(appctx); - applet_set_error(appctx); - return 0; -} - -static size_t http_stats_fastfwd(struct appctx *appctx, struct buffer *buf, size_t count, unsigned int flags) -{ - struct stconn *sc = appctx_sc(appctx); - size_t ret = 0; - - ret = b_data(buf); - if (stats_dump_stat_to_buffer(sc, buf, NULL)) { - se_fl_clr(appctx->sedesc, SE_FL_MAY_FASTFWD_PROD); - applet_fl_clr(appctx, APPCTX_FL_FASTFWD); - appctx->st0 = STAT_HTTP_DONE; - } - - ret = b_data(buf) - ret; - return ret; -} - -/* This I/O handler runs as an applet embedded in a stream connector. It is - * used to send HTTP stats over a TCP socket. The mechanism is very simple. - * appctx->st0 contains the operation in progress (dump, done). The handler - * automatically unregisters itself once transfer is complete. - */ -static void http_stats_io_handler(struct appctx *appctx) -{ - struct show_stat_ctx *ctx = appctx->svcctx; - struct stconn *sc = appctx_sc(appctx); - struct htx *res_htx = NULL; - - /* only proxy stats are available via http */ - ctx->domain = STATS_DOMAIN_PROXY; - - if (applet_fl_test(appctx, APPCTX_FL_INBLK_ALLOC|APPCTX_FL_OUTBLK_ALLOC|APPCTX_FL_OUTBLK_FULL)) - goto out; - - if (applet_fl_test(appctx, APPCTX_FL_FASTFWD) && se_fl_test(appctx->sedesc, SE_FL_MAY_FASTFWD_PROD)) - goto out; - - if (!appctx_get_buf(appctx, &appctx->outbuf)) { - applet_fl_set(appctx, APPCTX_FL_OUTBLK_ALLOC); - goto out; - } - - res_htx = htx_from_buf(&appctx->outbuf); - - if (unlikely(applet_fl_test(appctx, APPCTX_FL_EOS|APPCTX_FL_ERROR))) { - appctx->st0 = STAT_HTTP_END; - goto out; - } - - /* all states are processed in sequence */ - if (appctx->st0 == STAT_HTTP_HEAD) { - if (stats_send_http_headers(sc, res_htx)) { - struct ist meth = htx_sl_req_meth(http_get_stline(htxbuf(&appctx->inbuf))); - - if (find_http_meth(istptr(meth), istlen(meth)) == HTTP_METH_HEAD) - appctx->st0 = STAT_HTTP_DONE; - else { - if (!(global.tune.no_zero_copy_fwd & NO_ZERO_COPY_FWD_APPLET)) - se_fl_set(appctx->sedesc, SE_FL_MAY_FASTFWD_PROD); - appctx->st0 = STAT_HTTP_DUMP; - } - } - } - - if (appctx->st0 == STAT_HTTP_DUMP) { - ctx->chunk = b_make(trash.area, appctx->outbuf.size, 0, 0); - /* adjust buffer size to take htx overhead into account, - * make sure to perform this call on an empty buffer - */ - ctx->chunk.size = buf_room_for_htx_data(&ctx->chunk); - if (stats_dump_stat_to_buffer(sc, NULL, res_htx)) - appctx->st0 = STAT_HTTP_DONE; - } - - if (appctx->st0 == STAT_HTTP_POST) { - if (stats_process_http_post(sc)) - appctx->st0 = STAT_HTTP_LAST; - } - - if (appctx->st0 == STAT_HTTP_LAST) { - if (stats_send_http_redirect(sc, res_htx)) - appctx->st0 = STAT_HTTP_DONE; - } - - if (appctx->st0 == STAT_HTTP_DONE) { - /* no more data are expected. If the response buffer is empty, - * be sure to add something (EOT block in this case) to have - * something to send. It is important to be sure the EOM flags - * will be handled by the endpoint. - */ - if (htx_is_empty(res_htx)) { - if (!htx_add_endof(res_htx, HTX_BLK_EOT)) { - applet_fl_set(appctx, APPCTX_FL_OUTBLK_FULL); - goto out; - } - } - res_htx->flags |= HTX_FL_EOM; - applet_set_eoi(appctx); - se_fl_clr(appctx->sedesc, SE_FL_MAY_FASTFWD_PROD); - applet_fl_clr(appctx, APPCTX_FL_FASTFWD); - appctx->st0 = STAT_HTTP_END; - } - - if (appctx->st0 == STAT_HTTP_END) { - applet_set_eos(appctx); - applet_will_consume(appctx); - } - - out: - /* we have left the request in the buffer for the case where we - * process a POST, and this automatically re-enables activity on - * read. It's better to indicate that we want to stop reading when - * we're sending, so that we know there's at most one direction - * deciding to wake the applet up. It saves it from looping when - * emitting large blocks into small TCP windows. - */ - if (res_htx) - htx_to_buf(res_htx, &appctx->outbuf); - - if (appctx->st0 == STAT_HTTP_END) { - /* eat the whole request */ - b_reset(&appctx->inbuf); - applet_fl_clr(appctx, APPCTX_FL_INBLK_FULL); - appctx->sedesc->iobuf.flags &= ~IOBUF_FL_FF_BLOCKED; - } - else if (applet_fl_test(appctx, APPCTX_FL_OUTBLK_FULL)) - applet_wont_consume(appctx); -} - /* Dump all fields from into using the "show info" format (name: value) */ static int stats_dump_info_fields(struct buffer *out, const struct field *info, @@ -5056,14 +3023,6 @@ static int stats_dump_json_schema_to_buffer(struct appctx *appctx) return 1; } -static void http_stats_release(struct appctx *appctx) -{ - struct show_stat_ctx *ctx = appctx->svcctx; - - if (ctx->px_st == STAT_PX_ST_SV) - srv_drop(ctx->obj2); -} - static int cli_parse_clear_counters(char **args, char *payload, struct appctx *appctx, void *private) { struct proxy *px; @@ -5545,16 +3504,6 @@ static struct cli_kw_list cli_kws = {{ },{ INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws); -struct applet http_stats_applet = { - .obj_type = OBJ_TYPE_APPLET, - .name = "", /* used for logging */ - .fct = http_stats_io_handler, - .rcv_buf = appctx_htx_rcv_buf, - .snd_buf = appctx_htx_snd_buf, - .fastfwd = http_stats_fastfwd, - .release = http_stats_release, -}; - /* * Local variables: * c-indent-level: 8