From cff6d757e3ba609c08ef2aaa00f07e53551e5bf6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Mon, 3 Jun 2024 07:11:10 +0200 Subject: Adding upstream version 3.0.0. Signed-off-by: Daniel Baumann --- src/stats-html.c | 2081 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2081 insertions(+) create mode 100644 src/stats-html.c (limited to 'src/stats-html.c') diff --git a/src/stats-html.c b/src/stats-html.c new file mode 100644 index 0000000..41eaa9e --- /dev/null +++ b/src/stats-html.c @@ -0,0 +1,2081 @@ +#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_F_SHNODE) ? " on " : "", + (ctx->flags & STAT_F_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_F_HIDEVER) ? "" : (stats_version_string), + pid, (ctx->flags & STAT_F_SHNODE) ? " on " : "", + (ctx->flags & STAT_F_SHNODE) ? (uri->node ? uri->node : global.node) : "", + (ctx->flags & STAT_F_SHDESC) ? ": " : "", + (ctx->flags & STAT_F_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_F_HIDE_DOWN) + chunk_appendf(chk, + "
  • Show all servers
    \n", + uri->uri_prefix, + "", + (ctx->flags & STAT_F_NO_REFRESH) ? ";norefresh" : "", + scope_txt); + else + chunk_appendf(chk, + "
  • Hide 'DOWN' servers
    \n", + uri->uri_prefix, + ";up", + (ctx->flags & STAT_F_NO_REFRESH) ? ";norefresh" : "", + scope_txt); + + if (uri->refresh > 0) { + if (ctx->flags & STAT_F_NO_REFRESH) + chunk_appendf(chk, + "
  • Enable refresh
    \n", + uri->uri_prefix, + (ctx->flags & STAT_F_HIDE_DOWN) ? ";up" : "", + "", + scope_txt); + else + chunk_appendf(chk, + "
  • Disable refresh
    \n", + uri->uri_prefix, + (ctx->flags & STAT_F_HIDE_DOWN) ? ";up" : "", + ";norefresh", + scope_txt); + } + + chunk_appendf(chk, + "
  • Refresh now
    \n", + uri->uri_prefix, + (ctx->flags & STAT_F_HIDE_DOWN) ? ";up" : "", + (ctx->flags & STAT_F_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_F_HIDE_DOWN) ? ";up" : "", + (ctx->flags & STAT_F_NO_REFRESH) ? ";norefresh" : "", + scope_txt); + break; + case STAT_STATUS_NONE: + chunk_appendf(chk, + "

" + "[X] " + "Nothing has changed." + "
\n", uri->uri_prefix, + (ctx->flags & STAT_F_HIDE_DOWN) ? ";up" : "", + (ctx->flags & STAT_F_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_F_HIDE_DOWN) ? ";up" : "", + (ctx->flags & STAT_F_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_F_HIDE_DOWN) ? ";up" : "", + (ctx->flags & STAT_F_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_F_HIDE_DOWN) ? ";up" : "", + (ctx->flags & STAT_F_NO_REFRESH) ? ";norefresh" : "", + scope_txt); + break; + case STAT_STATUS_DENY: + chunk_appendf(chk, + "

" + "[X] " + "Action denied." + "
\n", uri->uri_prefix, + (ctx->flags & STAT_F_HIDE_DOWN) ? ";up" : "", + (ctx->flags & STAT_F_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_F_HIDE_DOWN) ? ";up" : "", + (ctx->flags & STAT_F_NO_REFRESH) ? ";norefresh" : "", + scope_txt); + break; + default: + chunk_appendf(chk, + "

" + "[X] " + "Unexpected result." + "
\n", uri->uri_prefix, + (ctx->flags & STAT_F_HIDE_DOWN) ? ";up" : "", + (ctx->flags & STAT_F_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_F_ADMIN is set in . Some extra info + * are provided if STAT_F_SHLGNDS is present in . The statistics from + * extra modules are displayed at the end of the lines if STAT_F_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_I_PX_TYPE].u.u32 == STATS_TYPE_FE) { + chunk_appendf(out, + /* name, queue */ + ""); + + if (flags & STAT_F_ADMIN) { + /* Column sub-heading for Enable or Disable server */ + chunk_appendf(out, ""); + } + + chunk_appendf(out, + "" + "" + "Frontend" + "" + "", + field_str(stats, ST_I_PX_PXNAME), field_str(stats, ST_I_PX_PXNAME)); + + chunk_appendf(out, + /* sessions rate : current */ + "%s

" + "" + "" + "", + U2H(stats[ST_I_PX_RATE].u.u32), + U2H(stats[ST_I_PX_CONN_RATE].u.u32), + U2H(stats[ST_I_PX_RATE].u.u32)); + + if (strcmp(field_str(stats, ST_I_PX_MODE), "http") == 0) + chunk_appendf(out, + "", + U2H(stats[ST_I_PX_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_I_PX_RATE_MAX].u.u32), + U2H(stats[ST_I_PX_CONN_RATE_MAX].u.u32), + U2H(stats[ST_I_PX_RATE_MAX].u.u32)); + + if (strcmp(field_str(stats, ST_I_PX_MODE), "http") == 0) + chunk_appendf(out, + "", + U2H(stats[ST_I_PX_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_I_PX_RATE_LIM].u.u32, "-")); + + chunk_appendf(out, + /* sessions: current, max, limit, total */ + "%s%s%s" + "%s
" + "" + "" + "", + U2H(stats[ST_I_PX_SCUR].u.u32), U2H(stats[ST_I_PX_SMAX].u.u32), U2H(stats[ST_I_PX_SLIM].u.u32), + U2H(stats[ST_I_PX_STOT].u.u64), + U2H(stats[ST_I_PX_CONN_TOT].u.u64), + U2H(stats[ST_I_PX_STOT].u.u64)); + + /* http response (via hover): 1xx, 2xx, 3xx, 4xx, 5xx, other */ + if (strcmp(field_str(stats, ST_I_PX_MODE), "http") == 0) { + chunk_appendf(out, + "" + "" + "" + "" + "" + "" + "" + "" + "" + "", + U2H(stats[ST_I_PX_H1SESS].u.u64), + U2H(stats[ST_I_PX_H2SESS].u.u64), + U2H(stats[ST_I_PX_H3SESS].u.u64), + U2H(stats[ST_I_PX_SESS_OTHER].u.u64), + U2H(stats[ST_I_PX_REQ_TOT].u.u64), + U2H(stats[ST_I_PX_H1REQ].u.u64), + U2H(stats[ST_I_PX_H2REQ].u.u64), + U2H(stats[ST_I_PX_H3REQ].u.u64), + U2H(stats[ST_I_PX_REQ_OTHER].u.u64)); + + chunk_appendf(out, + "" + "" + "" + "" + "" + "" + "" + "", + U2H(stats[ST_I_PX_HRSP_1XX].u.u64), + U2H(stats[ST_I_PX_HRSP_2XX].u.u64), + U2H(stats[ST_I_PX_COMP_RSP].u.u64), + stats[ST_I_PX_HRSP_2XX].u.u64 ? + (int)(100 * stats[ST_I_PX_COMP_RSP].u.u64 / stats[ST_I_PX_HRSP_2XX].u.u64) : 0, + U2H(stats[ST_I_PX_HRSP_3XX].u.u64), + U2H(stats[ST_I_PX_HRSP_4XX].u.u64), + U2H(stats[ST_I_PX_HRSP_5XX].u.u64), + U2H(stats[ST_I_PX_HRSP_OTHER].u.u64)); + + chunk_appendf(out, + "" + "" + "" + "" + "" + "", + U2H(stats[ST_I_PX_INTERCEPTED].u.u64), + U2H(stats[ST_I_PX_CACHE_LOOKUPS].u.u64), + U2H(stats[ST_I_PX_CACHE_HITS].u.u64), + stats[ST_I_PX_CACHE_LOOKUPS].u.u64 ? + (int)(100 * stats[ST_I_PX_CACHE_HITS].u.u64 / stats[ST_I_PX_CACHE_LOOKUPS].u.u64) : 0, + U2H(stats[ST_I_PX_WREW].u.u64), + U2H(stats[ST_I_PX_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_I_PX_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_I_PX_COMP_IN].u.u64 || stats[ST_I_PX_COMP_BYP].u.u64) ? "":"", + U2H(stats[ST_I_PX_BOUT].u.u64), + U2H(stats[ST_I_PX_BOUT].u.u64), + U2H(stats[ST_I_PX_COMP_IN].u.u64), + U2H(stats[ST_I_PX_COMP_OUT].u.u64), + stats[ST_I_PX_COMP_IN].u.u64 ? (int)(stats[ST_I_PX_COMP_OUT].u.u64 * 100 / stats[ST_I_PX_COMP_IN].u.u64) : 0, + U2H(stats[ST_I_PX_COMP_BYP].u.u64), + U2H(stats[ST_I_PX_COMP_IN].u.u64 - stats[ST_I_PX_COMP_OUT].u.u64), + stats[ST_I_PX_BOUT].u.u64 ? (int)((stats[ST_I_PX_COMP_IN].u.u64 - stats[ST_I_PX_COMP_OUT].u.u64) * 100 / stats[ST_I_PX_BOUT].u.u64) : 0, + (stats[ST_I_PX_COMP_IN].u.u64 || stats[ST_I_PX_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_I_PX_DREQ].u.u64), U2H(stats[ST_I_PX_DRESP].u.u64), + U2H(stats[ST_I_PX_EREQ].u.u64), + field_str(stats, ST_I_PX_STATUS)); + + if (flags & STAT_F_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_I_PX_MAX + i])); + ++i; + } + chunk_appendf(out, "
%s%s
"); + } else { + i += mod->stats_count; + } + + chunk_appendf(out, ""); + } + } + + chunk_appendf(out, ""); + } + else if (stats[ST_I_PX_TYPE].u.u32 == STATS_TYPE_SO) { + chunk_appendf(out, ""); + if (flags & STAT_F_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_I_PX_PXNAME), field_str(stats, ST_I_PX_SVNAME), + (flags & STAT_F_SHLGNDS)?"":"", + field_str(stats, ST_I_PX_PXNAME), field_str(stats, ST_I_PX_SVNAME), field_str(stats, ST_I_PX_SVNAME)); + + if (flags & STAT_F_SHLGNDS) { + chunk_appendf(out, "
"); + + if (isdigit((unsigned char)*field_str(stats, ST_I_PX_ADDR))) + chunk_appendf(out, "IPv4: %s, ", field_str(stats, ST_I_PX_ADDR)); + else if (*field_str(stats, ST_I_PX_ADDR) == '[') + chunk_appendf(out, "IPv6: %s, ", field_str(stats, ST_I_PX_ADDR)); + else if (*field_str(stats, ST_I_PX_ADDR)) + chunk_appendf(out, "%s, ", field_str(stats, ST_I_PX_ADDR)); + + chunk_appendf(out, "proto=%s, ", field_str(stats, ST_I_PX_PROTO)); + + /* id */ + chunk_appendf(out, "id: %d
", stats[ST_I_PX_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_F_SHLGNDS)?"
":"", + U2H(stats[ST_I_PX_SCUR].u.u32), U2H(stats[ST_I_PX_SMAX].u.u32), U2H(stats[ST_I_PX_SLIM].u.u32), + U2H(stats[ST_I_PX_STOT].u.u64), U2H(stats[ST_I_PX_BIN].u.u64), U2H(stats[ST_I_PX_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_I_PX_DREQ].u.u64), U2H(stats[ST_I_PX_DRESP].u.u64), + U2H(stats[ST_I_PX_EREQ].u.u64), + field_str(stats, ST_I_PX_STATUS)); + + if (flags & STAT_F_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_I_PX_MAX + i])); + ++i; + } + chunk_appendf(out, "
%s%s
"); + } else { + i += mod->stats_count; + } + + chunk_appendf(out, ""); + } + } + + chunk_appendf(out, ""); + } + else if (stats[ST_I_PX_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_I_PX_STATUS), "DOWN") == 0 || + strcmp(field_str(stats, ST_I_PX_STATUS), "DOWN (agent)") == 0) { + style = "down"; + } + else if (strncmp(field_str(stats, ST_I_PX_STATUS), "DOWN ", strlen("DOWN ")) == 0) { + style = "going_up"; + } + else if (strcmp(field_str(stats, ST_I_PX_STATUS), "DRAIN") == 0) { + style = "draining"; + } + else if (strncmp(field_str(stats, ST_I_PX_STATUS), "NOLB ", strlen("NOLB ")) == 0) { + style = "going_down"; + } + else if (strcmp(field_str(stats, ST_I_PX_STATUS), "NOLB") == 0) { + style = "nolb"; + } + else if (strcmp(field_str(stats, ST_I_PX_STATUS), "no check") == 0) { + style = "no_check"; + } + else if (!stats[ST_I_PX_CHKFAIL].type || + stats[ST_I_PX_CHECK_HEALTH].u.u32 == stats[ST_I_PX_CHECK_RISE].u.u32 + stats[ST_I_PX_CHECK_FALL].u.u32 - 1) { + /* no check or max health = UP */ + if (stats[ST_I_PX_WEIGHT].u.u32) + style = "up"; + else + style = "draining"; + } + else { + style = "going_down"; + } + + if (strncmp(field_str(stats, ST_I_PX_STATUS), "MAINT", 5) == 0) + chunk_appendf(out, ""); + else + chunk_appendf(out, + "", + (stats[ST_I_PX_BCK].u.u32) ? "backup" : "active", style); + + + if (flags & STAT_F_ADMIN) + chunk_appendf(out, + "", + field_str(stats, ST_I_PX_PXNAME), + field_str(stats, ST_I_PX_SVNAME)); + + chunk_appendf(out, + "%s" + "%s" + "", + field_str(stats, ST_I_PX_PXNAME), field_str(stats, ST_I_PX_SVNAME), + (flags & STAT_F_SHLGNDS) ? "" : "", + field_str(stats, ST_I_PX_PXNAME), field_str(stats, ST_I_PX_SVNAME), field_str(stats, ST_I_PX_SVNAME)); + + if (flags & STAT_F_SHLGNDS) { + chunk_appendf(out, "
"); + + if (isdigit((unsigned char)*field_str(stats, ST_I_PX_ADDR))) + chunk_appendf(out, "IPv4: %s, ", field_str(stats, ST_I_PX_ADDR)); + else if (*field_str(stats, ST_I_PX_ADDR) == '[') + chunk_appendf(out, "IPv6: %s, ", field_str(stats, ST_I_PX_ADDR)); + else if (*field_str(stats, ST_I_PX_ADDR)) + chunk_appendf(out, "%s, ", field_str(stats, ST_I_PX_ADDR)); + + /* id */ + chunk_appendf(out, "id: %d, rid: %d", stats[ST_I_PX_SID].u.u32, stats[ST_I_PX_SRID].u.u32); + + /* cookie */ + if (stats[ST_I_PX_COOKIE].type) { + chunk_appendf(out, ", cookie: '"); + chunk_initstr(&src, field_str(stats, ST_I_PX_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_F_SHLGNDS) ? "
" : "", + U2H(stats[ST_I_PX_QCUR].u.u32), U2H(stats[ST_I_PX_QMAX].u.u32), LIM2A(stats[ST_I_PX_QLIMIT].u.u32, "-"), + U2H(stats[ST_I_PX_RATE].u.u32), U2H(stats[ST_I_PX_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_I_PX_SCUR].u.u32), + U2H(stats[ST_I_PX_SCUR].u.u32), + U2H(stats[ST_I_PX_USED_CONN_CUR].u.u32), + U2H(stats[ST_I_PX_SRV_ICUR].u.u32), + U2H(stats[ST_I_PX_IDLE_CONN_CUR].u.u32), + U2H(stats[ST_I_PX_SAFE_CONN_CUR].u.u32), + U2H(stats[ST_I_PX_NEED_CONN_EST].u.u32), + + LIM2A(stats[ST_I_PX_SLIM].u.u32, "-"), + stats[ST_I_PX_SRV_ILIM].type ? U2H(stats[ST_I_PX_SRV_ILIM].u.u32) : "-", + U2H(stats[ST_I_PX_SMAX].u.u32), LIM2A(stats[ST_I_PX_SLIM].u.u32, "-"), + U2H(stats[ST_I_PX_STOT].u.u64), + U2H(stats[ST_I_PX_STOT].u.u64)); + + /* http response (via hover): 1xx, 2xx, 3xx, 4xx, 5xx, other */ + if (strcmp(field_str(stats, ST_I_PX_MODE), "http") == 0) { + chunk_appendf(out, + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "", + U2H(stats[ST_I_PX_CONNECT].u.u64), + U2H(stats[ST_I_PX_REUSE].u.u64), + (stats[ST_I_PX_CONNECT].u.u64 + stats[ST_I_PX_REUSE].u.u64) ? + (int)(100 * stats[ST_I_PX_REUSE].u.u64 / (stats[ST_I_PX_CONNECT].u.u64 + stats[ST_I_PX_REUSE].u.u64)) : 0, + U2H(stats[ST_I_PX_REQ_TOT].u.u64), + U2H(stats[ST_I_PX_HRSP_1XX].u.u64), stats[ST_I_PX_REQ_TOT].u.u64 ? + (int)(100 * stats[ST_I_PX_HRSP_1XX].u.u64 / stats[ST_I_PX_REQ_TOT].u.u64) : 0, + U2H(stats[ST_I_PX_HRSP_2XX].u.u64), stats[ST_I_PX_REQ_TOT].u.u64 ? + (int)(100 * stats[ST_I_PX_HRSP_2XX].u.u64 / stats[ST_I_PX_REQ_TOT].u.u64) : 0, + U2H(stats[ST_I_PX_HRSP_3XX].u.u64), stats[ST_I_PX_REQ_TOT].u.u64 ? + (int)(100 * stats[ST_I_PX_HRSP_3XX].u.u64 / stats[ST_I_PX_REQ_TOT].u.u64) : 0, + U2H(stats[ST_I_PX_HRSP_4XX].u.u64), stats[ST_I_PX_REQ_TOT].u.u64 ? + (int)(100 * stats[ST_I_PX_HRSP_4XX].u.u64 / stats[ST_I_PX_REQ_TOT].u.u64) : 0, + U2H(stats[ST_I_PX_HRSP_5XX].u.u64), stats[ST_I_PX_REQ_TOT].u.u64 ? + (int)(100 * stats[ST_I_PX_HRSP_5XX].u.u64 / stats[ST_I_PX_REQ_TOT].u.u64) : 0, + U2H(stats[ST_I_PX_HRSP_OTHER].u.u64), stats[ST_I_PX_REQ_TOT].u.u64 ? + (int)(100 * stats[ST_I_PX_HRSP_OTHER].u.u64 / stats[ST_I_PX_REQ_TOT].u.u64) : 0, + U2H(stats[ST_I_PX_WREW].u.u64), + U2H(stats[ST_I_PX_EINT].u.u64)); + } + + chunk_appendf(out, ""); + chunk_appendf(out, "", + U2H(stats[ST_I_PX_QT_MAX].u.u32), U2H(stats[ST_I_PX_QTIME].u.u32)); + chunk_appendf(out, "", + U2H(stats[ST_I_PX_CT_MAX].u.u32), U2H(stats[ST_I_PX_CTIME].u.u32)); + if (strcmp(field_str(stats, ST_I_PX_MODE), "http") == 0) + chunk_appendf(out, "", + U2H(stats[ST_I_PX_RT_MAX].u.u32), U2H(stats[ST_I_PX_RTIME].u.u32)); + chunk_appendf(out, "", + U2H(stats[ST_I_PX_TT_MAX].u.u32), U2H(stats[ST_I_PX_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_I_PX_LBTOT].u.u64), + human_time(stats[ST_I_PX_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_I_PX_BIN].u.u64), U2H(stats[ST_I_PX_BOUT].u.u64), + U2H(stats[ST_I_PX_DRESP].u.u64), + U2H(stats[ST_I_PX_ECON].u.u64), + U2H(stats[ST_I_PX_ERESP].u.u64), + (long long)stats[ST_I_PX_CLI_ABRT].u.u64, + (long long)stats[ST_I_PX_SRV_ABRT].u.u64, + (long long)stats[ST_I_PX_WRETR].u.u64, + (long long)stats[ST_I_PX_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_I_PX_STATUS), "MAINT", 5) == 0) { + chunk_appendf(out, "%s MAINT", human_time(stats[ST_I_PX_LASTCHG].u.u32, 1)); + } + else if (strcmp(field_str(stats, ST_I_PX_STATUS), "no check") == 0) { + chunk_strcat(out, "no check"); + } + else { + chunk_appendf(out, "%s %s", human_time(stats[ST_I_PX_LASTCHG].u.u32, 1), field_str(stats, ST_I_PX_STATUS)); + if (strncmp(field_str(stats, ST_I_PX_STATUS), "DOWN", 4) == 0) { + if (stats[ST_I_PX_CHECK_HEALTH].u.u32) + chunk_strcat(out, " ↑"); + } + else if (stats[ST_I_PX_CHECK_HEALTH].u.u32 < stats[ST_I_PX_CHECK_RISE].u.u32 + stats[ST_I_PX_CHECK_FALL].u.u32 - 1) + chunk_strcat(out, " ↓"); + } + + if (strncmp(field_str(stats, ST_I_PX_STATUS), "DOWN", 4) == 0 && + stats[ST_I_PX_AGENT_STATUS].type && !stats[ST_I_PX_AGENT_HEALTH].u.u32) { + chunk_appendf(out, + " %s", + field_str(stats, ST_I_PX_AGENT_STATUS)); + + if (stats[ST_I_PX_AGENT_CODE].type) + chunk_appendf(out, "/%d", stats[ST_I_PX_AGENT_CODE].u.u32); + + if (stats[ST_I_PX_AGENT_DURATION].type) + chunk_appendf(out, " in %lums", (long)stats[ST_I_PX_AGENT_DURATION].u.u64); + + chunk_appendf(out, "
%s", field_str(stats, ST_I_PX_AGENT_DESC)); + + if (*field_str(stats, ST_I_PX_LAST_AGT)) { + chunk_appendf(out, ": "); + chunk_initstr(&src, field_str(stats, ST_I_PX_LAST_AGT)); + chunk_htmlencode(out, &src); + } + chunk_appendf(out, "
"); + } + else if (stats[ST_I_PX_CHECK_STATUS].type) { + chunk_appendf(out, + " %s", + field_str(stats, ST_I_PX_CHECK_STATUS)); + + if (stats[ST_I_PX_CHECK_CODE].type) + chunk_appendf(out, "/%d", stats[ST_I_PX_CHECK_CODE].u.u32); + + if (stats[ST_I_PX_CHECK_DURATION].type) + chunk_appendf(out, " in %lums", (long)stats[ST_I_PX_CHECK_DURATION].u.u64); + + chunk_appendf(out, "
%s", field_str(stats, ST_I_PX_CHECK_DESC)); + + if (*field_str(stats, ST_I_PX_LAST_CHK)) { + chunk_appendf(out, ": "); + chunk_initstr(&src, field_str(stats, ST_I_PX_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_I_PX_WEIGHT].u.u32, stats[ST_I_PX_UWEIGHT].u.u32, + stats[ST_I_PX_BCK].u.u32 ? "-" : "Y", + stats[ST_I_PX_BCK].u.u32 ? "Y" : "-"); + + /* check failures: unique, fatal, down time */ + if (strcmp(field_str(stats, ST_I_PX_STATUS), "MAINT (resolution)") == 0) { + chunk_appendf(out, "resolution"); + } + else if (stats[ST_I_PX_CHKFAIL].type) { + chunk_appendf(out, "%lld", (long long)stats[ST_I_PX_CHKFAIL].u.u64); + + if (stats[ST_I_PX_HANAFAIL].type) + chunk_appendf(out, "/%lld", (long long)stats[ST_I_PX_HANAFAIL].u.u64); + + chunk_appendf(out, + "
Failed Health Checks%s
" + "%lld%s" + "", + stats[ST_I_PX_HANAFAIL].type ? "/Health Analyses" : "", + (long long)stats[ST_I_PX_CHKDOWN].u.u64, human_time(stats[ST_I_PX_DOWNTIME].u.u32, 1)); + } + else if (strcmp(field_str(stats, ST_I_PX_STATUS), "MAINT") != 0 && field_format(stats, ST_I_PX_TRACKED) == FF_STR) { + /* tracking a server (hence inherited maint would appear as "MAINT (via...)" */ + chunk_appendf(out, + "via %s", + field_str(stats, ST_I_PX_TRACKED), field_str(stats, ST_I_PX_TRACKED)); + } + else + chunk_appendf(out, ""); + + /* throttle */ + if (stats[ST_I_PX_THROTTLE].type) + chunk_appendf(out, "%d %%\n", stats[ST_I_PX_THROTTLE].u.u32); + else + chunk_appendf(out, "-"); + + if (flags & STAT_F_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_I_PX_MAX + i])); + ++i; + } + chunk_appendf(out, "
%s%s
"); + } else { + i += mod->stats_count; + } + + chunk_appendf(out, ""); + } + } + + chunk_appendf(out, "\n"); + } + else if (stats[ST_I_PX_TYPE].u.u32 == STATS_TYPE_BE) { + chunk_appendf(out, ""); + if (flags & STAT_F_ADMIN) { + /* Column sub-heading for Enable or Disable server */ + chunk_appendf(out, ""); + } + chunk_appendf(out, + "" + /* name */ + "%s" + "Backend" + "", + (flags & STAT_F_SHLGNDS)?"":"", + field_str(stats, ST_I_PX_PXNAME), field_str(stats, ST_I_PX_PXNAME)); + + if (flags & STAT_F_SHLGNDS) { + /* balancing */ + chunk_appendf(out, "
balancing: %s", + field_str(stats, ST_I_PX_ALGO)); + + /* cookie */ + if (stats[ST_I_PX_COOKIE].type) { + chunk_appendf(out, ", cookie: '"); + chunk_initstr(&src, field_str(stats, ST_I_PX_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_F_SHLGNDS)?"
":"", + U2H(stats[ST_I_PX_QCUR].u.u32), U2H(stats[ST_I_PX_QMAX].u.u32), + U2H(stats[ST_I_PX_RATE].u.u32), U2H(stats[ST_I_PX_RATE_MAX].u.u32)); + + chunk_appendf(out, + /* sessions: current, max, limit, total */ + "%s%s%s" + "%s
" + "" + "", + U2H(stats[ST_I_PX_SCUR].u.u32), U2H(stats[ST_I_PX_SMAX].u.u32), U2H(stats[ST_I_PX_SLIM].u.u32), + U2H(stats[ST_I_PX_STOT].u.u64), + U2H(stats[ST_I_PX_STOT].u.u64)); + + /* http response (via hover): 1xx, 2xx, 3xx, 4xx, 5xx, other */ + if (strcmp(field_str(stats, ST_I_PX_MODE), "http") == 0) { + chunk_appendf(out, + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "", + U2H(stats[ST_I_PX_CONNECT].u.u64), + U2H(stats[ST_I_PX_REUSE].u.u64), + (stats[ST_I_PX_CONNECT].u.u64 + stats[ST_I_PX_REUSE].u.u64) ? + (int)(100 * stats[ST_I_PX_REUSE].u.u64 / (stats[ST_I_PX_CONNECT].u.u64 + stats[ST_I_PX_REUSE].u.u64)) : 0, + U2H(stats[ST_I_PX_REQ_TOT].u.u64), + U2H(stats[ST_I_PX_HRSP_1XX].u.u64), + U2H(stats[ST_I_PX_HRSP_2XX].u.u64), + U2H(stats[ST_I_PX_COMP_RSP].u.u64), + stats[ST_I_PX_HRSP_2XX].u.u64 ? + (int)(100 * stats[ST_I_PX_COMP_RSP].u.u64 / stats[ST_I_PX_HRSP_2XX].u.u64) : 0, + U2H(stats[ST_I_PX_HRSP_3XX].u.u64), + U2H(stats[ST_I_PX_HRSP_4XX].u.u64), + U2H(stats[ST_I_PX_HRSP_5XX].u.u64), + U2H(stats[ST_I_PX_HRSP_OTHER].u.u64), + U2H(stats[ST_I_PX_CACHE_LOOKUPS].u.u64), + U2H(stats[ST_I_PX_CACHE_HITS].u.u64), + stats[ST_I_PX_CACHE_LOOKUPS].u.u64 ? + (int)(100 * stats[ST_I_PX_CACHE_HITS].u.u64 / stats[ST_I_PX_CACHE_LOOKUPS].u.u64) : 0, + U2H(stats[ST_I_PX_WREW].u.u64), + U2H(stats[ST_I_PX_EINT].u.u64)); + } + + chunk_appendf(out, ""); + chunk_appendf(out, "", + U2H(stats[ST_I_PX_QT_MAX].u.u32), U2H(stats[ST_I_PX_QTIME].u.u32)); + chunk_appendf(out, "", + U2H(stats[ST_I_PX_CT_MAX].u.u32), U2H(stats[ST_I_PX_CTIME].u.u32)); + if (strcmp(field_str(stats, ST_I_PX_MODE), "http") == 0) + chunk_appendf(out, "", + U2H(stats[ST_I_PX_RT_MAX].u.u32), U2H(stats[ST_I_PX_RTIME].u.u32)); + chunk_appendf(out, "", + U2H(stats[ST_I_PX_TT_MAX].u.u32), U2H(stats[ST_I_PX_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_I_PX_LBTOT].u.u64), + human_time(stats[ST_I_PX_LASTSESS].u.s32, 1), + U2H(stats[ST_I_PX_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_I_PX_COMP_IN].u.u64 || stats[ST_I_PX_COMP_BYP].u.u64) ? "":"", + U2H(stats[ST_I_PX_BOUT].u.u64), + U2H(stats[ST_I_PX_BOUT].u.u64), + U2H(stats[ST_I_PX_COMP_IN].u.u64), + U2H(stats[ST_I_PX_COMP_OUT].u.u64), + stats[ST_I_PX_COMP_IN].u.u64 ? (int)(stats[ST_I_PX_COMP_OUT].u.u64 * 100 / stats[ST_I_PX_COMP_IN].u.u64) : 0, + U2H(stats[ST_I_PX_COMP_BYP].u.u64), + U2H(stats[ST_I_PX_COMP_IN].u.u64 - stats[ST_I_PX_COMP_OUT].u.u64), + stats[ST_I_PX_BOUT].u.u64 ? (int)((stats[ST_I_PX_COMP_IN].u.u64 - stats[ST_I_PX_COMP_OUT].u.u64) * 100 / stats[ST_I_PX_BOUT].u.u64) : 0, + (stats[ST_I_PX_COMP_IN].u.u64 || stats[ST_I_PX_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_I_PX_DREQ].u.u64), U2H(stats[ST_I_PX_DRESP].u.u64), + U2H(stats[ST_I_PX_ECON].u.u64), + U2H(stats[ST_I_PX_ERESP].u.u64), + (long long)stats[ST_I_PX_CLI_ABRT].u.u64, + (long long)stats[ST_I_PX_SRV_ABRT].u.u64, + (long long)stats[ST_I_PX_WRETR].u.u64, (long long)stats[ST_I_PX_WREDIS].u.u64, + human_time(stats[ST_I_PX_LASTCHG].u.u32, 1), + strcmp(field_str(stats, ST_I_PX_STATUS), "DOWN") ? field_str(stats, ST_I_PX_STATUS) : "DOWN", + stats[ST_I_PX_WEIGHT].u.u32, stats[ST_I_PX_UWEIGHT].u.u32, + stats[ST_I_PX_ACT].u.u32, stats[ST_I_PX_BCK].u.u32); + + chunk_appendf(out, + /* rest of backend: nothing, down transitions, total downtime, throttle */ + " %d" + "%s" + "", + stats[ST_I_PX_CHKDOWN].u.u32, + stats[ST_I_PX_DOWNTIME].type ? human_time(stats[ST_I_PX_DOWNTIME].u.u32, 1) : " "); + + if (flags & STAT_F_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_I_PX_MAX + 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_F_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_F_SHLGNDS) ? "":"", + px->id, px->id); + + if (ctx->flags & STAT_F_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_F_SHLGNDS) ? "":"", + px->desc ? "desc" : "empty", px->desc ? px->desc : ""); + + if (ctx->flags & STAT_F_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_F_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_F_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_F_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_F_FMT_HTML) { + if (!htx_add_header(htx, ist("Content-Type"), ist("text/html"))) + goto full; + } + else if (ctx->flags & (STAT_F_FMT_JSON|STAT_F_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_F_NO_REFRESH)) { + const char *refresh = U2A(uri->refresh); + if (!htx_add_header(htx, ist("Refresh"), ist(refresh))) + goto full; + } + + if (ctx->flags & STAT_F_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_F_HIDE_DOWN) ? ";up" : "", + (ctx->flags & STAT_F_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_BODYLESS); + 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)) { + 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, +}; -- cgit v1.2.3