From e74148fb7c10a3d1a2ecdf930cc8f1cb9a760a34 Mon Sep 17 00:00:00 2001 From: Amaury Denoyelle Date: Thu, 28 Mar 2024 14:53:52 +0100 Subject: [PATCH] MEDIUM: stats: implement dump stats-file CLI Define a new CLI command "dump stats-file" with its handler cli_parse_dump_stat_file(). It will loop twice on proxies_list to dump first frontend and then backend side. It reuses the common function stats_dump_stat_to_buffer(), using STAT_F_BOUND to restrict on the correct side. A new module stats-file.c is added to regroup function specifics to stats-file. It defines two main functions : * stats_dump_file_header() to generate the list of column list prefixed by the line context, either "#fe" or "#be" * stats_dump_fields_file() to generate each stat lines. Object without GUID are skipped. Each stat entry is separated by a comma. For the moment, stats-file does not support statistics modules. As such, stats_dump_*_line() functions are updated to prevent looping over stats module on stats-file output. --- Makefile | 2 +- doc/management.txt | 28 +++++++++++ include/haproxy/stats-file.h | 15 ++++++ src/stats-file.c | 92 ++++++++++++++++++++++++++++++++++++ src/stats.c | 68 ++++++++++++++++++++++++++ 5 files changed, 204 insertions(+), 1 deletion(-) create mode 100644 include/haproxy/stats-file.h create mode 100644 src/stats-file.c diff --git a/Makefile b/Makefile index 57be1b145..fb41a047f 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/stats-html.o src/stats-json.o + src/guid.o src/stats-html.o src/stats-json.o src/stats-file.o ifneq ($(TRACE),) OBJS += src/calltrace.o diff --git a/doc/management.txt b/doc/management.txt index 893226ef7..2fc8ea2e6 100644 --- a/doc/management.txt +++ b/doc/management.txt @@ -32,6 +32,7 @@ Summary 9.3. Unix Socket commands 9.4. Master CLI 9.4.1. Master CLI commands +9.5. Stats-file 10. Tricks for easier configuration management 11. Well-known traps to avoid 12. Debugging and performance issues @@ -2067,6 +2068,10 @@ disable server / This command is restricted and can only be issued on sockets configured for level "admin". +dump stats-file + Generate a stats-file which can be used to preload haproxy counters values on + startup. See "Stats-file" section for more detail. + enable agent / Resume auxiliary agent check that was temporarily stopped. @@ -4235,6 +4240,29 @@ show startup-logs Those messages are also dumped with the "reload" command. + +9.5. Stats-file +-------------- + +A so-called stats-file can be used to preload internal haproxy counters on +process startup with non-null values. Its main purpose is to preserve +statistics for worker processes accross reloads. Only an excerpt of all the +exposed haproxy statistics is present in a stats-file as it only makes sense to +preload metric-type values. + +For the moment, only proxy counters are supported in stats-file. This allows to +preload values for frontends, backends, servers and listeners. However only +objects instances with a non-empty GUID are stored in a stats-file. This +guarantees that value will be preloaded for object with matching type and GUID, +even if other parameters differ. + +The CLI command "dump stats-file" purpose is to generate a stats-file. Format +of the stats-file is internally defined and freely subject to future changes +and extension. It is designed to be compatible at least accross adjacent +haproxy stable branch releases, but may require optional extra configuration +when loading a stats-file to a process running on an older version. + + 10. Tricks for easier configuration management ---------------------------------------------- diff --git a/include/haproxy/stats-file.h b/include/haproxy/stats-file.h new file mode 100644 index 000000000..90634870f --- /dev/null +++ b/include/haproxy/stats-file.h @@ -0,0 +1,15 @@ +#ifndef _HAPROXY_STATS_FILE_H +#define _HAPROXY_STATS_FILE_H + +#include + +#include +#include + +int stats_dump_fields_file(struct buffer *out, + const struct field *stats, size_t stats_count, + struct show_stat_ctx *ctx); + +void stats_dump_file_header(int type, struct buffer *out); + +#endif /* _HAPROXY_STATS_FILE_H */ diff --git a/src/stats-file.c b/src/stats-file.c new file mode 100644 index 000000000..f3e0db934 --- /dev/null +++ b/src/stats-file.c @@ -0,0 +1,92 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Dump all fields from into for stats-file. */ +int stats_dump_fields_file(struct buffer *out, + const struct field *line, size_t stats_count, + struct show_stat_ctx *ctx) +{ + struct guid_node *guid; + struct listener *l; + int i; + + switch (ctx->px_st) { + case STAT_PX_ST_FE: + case STAT_PX_ST_BE: + guid = &__objt_proxy(ctx->obj1)->guid; + break; + + case STAT_PX_ST_LI: + l = LIST_ELEM(ctx->obj2, struct listener *, by_fe); + guid = &l->guid; + break; + + case STAT_PX_ST_SV: + guid = &__objt_server(ctx->obj2)->guid; + break; + + default: + ABORT_NOW(); + return 1; + } + + /* Skip objects without GUID. */ + if (!guid->node.key) + return 1; + + chunk_appendf(out, "%s,", (char *)guid->node.key); + + for (i = 0; i < stats_count; ++i) { + /* Empty field for stats-file is used to skip its output, + * including any separator. + */ + if (field_format(line, i) == FF_EMPTY) + continue; + + if (!stats_emit_raw_data_field(out, &line[i])) + return 0; + if (!chunk_strcat(out, ",")) + return 0; + } + + chunk_strcat(out, "\n"); + return 1; +} + +void stats_dump_file_header(int type, struct buffer *out) +{ + const struct stat_col *col; + int i; + + /* Caller must specified ither FE or BE. */ + BUG_ON(!(type & ((1 << STATS_TYPE_FE) | (1 << STATS_TYPE_BE)))); + + if (type & (1 << STATS_TYPE_FE)) { + chunk_strcat(out, "#fe guid,"); + for (i = 0; i < ST_I_PX_MAX; ++i) { + col = &stat_cols_px[i]; + if (stcol_nature(col) == FN_COUNTER && (col->cap & (STATS_PX_CAP_FE|STATS_PX_CAP_LI))) + chunk_appendf(out, "%s,", col->name); + } + } + else { + chunk_appendf(out, "#be guid,"); + for (i = 0; i < ST_I_PX_MAX; ++i) { + col = &stat_cols_px[i]; + if (stcol_nature(col) == FN_COUNTER && (col->cap & (STATS_PX_CAP_BE|STATS_PX_CAP_SRV))) + chunk_appendf(out, "%s,", col->name); + } + } + + chunk_strcat(out, "\n"); +} diff --git a/src/stats.c b/src/stats.c index 991dc9f83..1280a1eea 100644 --- a/src/stats.c +++ b/src/stats.c @@ -57,6 +57,7 @@ #include #include #include +#include #include #include #include @@ -631,6 +632,8 @@ int stats_dump_one_line(const struct field *line, size_t stats_count, ret = stats_dump_fields_typed(chk, line, stats_count, ctx); else if (ctx->flags & STAT_F_FMT_JSON) ret = stats_dump_fields_json(chk, line, stats_count, ctx); + else if (ctx->flags & STAT_F_FMT_FILE) + ret = stats_dump_fields_file(chk, line, stats_count, ctx); else ret = stats_dump_fields_csv(chk, line, stats_count, ctx); @@ -909,6 +912,9 @@ static int stats_dump_fe_line(struct stconn *sc, struct proxy *px) list_for_each_entry(mod, &stats_module_list[STATS_DOMAIN_PROXY], list) { void *counters; + if (ctx->flags & STAT_F_FMT_FILE) + continue; + if (!(stats_px_get_cap(mod->domain_flags) & STATS_PX_CAP_FE)) { stats_count += mod->stats_count; continue; @@ -1055,6 +1061,9 @@ static int stats_dump_li_line(struct stconn *sc, struct proxy *px, struct listen list_for_each_entry(mod, &stats_module_list[STATS_DOMAIN_PROXY], list) { void *counters; + if (ctx->flags & STAT_F_FMT_FILE) + continue; + if (!(stats_px_get_cap(mod->domain_flags) & STATS_PX_CAP_LI)) { stats_count += mod->stats_count; continue; @@ -1498,6 +1507,9 @@ static int stats_dump_sv_line(struct stconn *sc, struct proxy *px, struct server list_for_each_entry(mod, &stats_module_list[STATS_DOMAIN_PROXY], list) { void *counters; + if (ctx->flags & STAT_F_FMT_FILE) + continue; + if (stats_get_domain(mod->domain_flags) != STATS_DOMAIN_PROXY) continue; @@ -1747,6 +1759,9 @@ static int stats_dump_be_line(struct stconn *sc, struct proxy *px) list_for_each_entry(mod, &stats_module_list[STATS_DOMAIN_PROXY], list) { struct extra_counters *counters; + if (ctx->flags & STAT_F_FMT_FILE) + continue; + if (stats_get_domain(mod->domain_flags) != STATS_DOMAIN_PROXY) continue; @@ -2070,6 +2085,8 @@ int stats_dump_stat_to_buffer(struct stconn *sc, struct buffer *buf, struct htx stats_dump_json_schema(chk); else if (ctx->flags & STAT_F_FMT_JSON) stats_dump_json_header(chk); + else if (ctx->flags & STAT_F_FMT_FILE) + stats_dump_file_header(ctx->type, chk); else if (!(ctx->flags & STAT_F_FMT_TYPED)) stats_dump_csv_header(ctx->domain, chk); @@ -2611,6 +2628,56 @@ static int cli_io_handler_dump_json_schema(struct appctx *appctx) return stats_dump_json_schema_to_buffer(appctx); } +static int cli_parse_dump_stat_file(char **args, char *payload, + struct appctx *appctx, void *private) +{ + struct show_stat_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx)); + + ctx->chunk = b_make(trash.area, trash.size, 0, 0); + ctx->domain = STATS_DOMAIN_PROXY; + ctx->flags |= STAT_F_FMT_FILE; + + return 0; +} + +/* Returns 1 on completion else 0. */ +static int cli_io_handler_dump_stat_file(struct appctx *appctx) +{ + struct show_stat_ctx *ctx = appctx->svcctx; + int ret; + + /* Frontend and backend sides are ouputted separatedly on stats-file. + * As such, use STAT_F_BOUND to restrict proxies looping over frontend + * side first before first stats_dump_stat_to_buffer(). A second + * iteration is conducted for backend side after. + */ + ctx->flags |= STAT_F_BOUND; + + if (!(ctx->type & (1 << STATS_TYPE_BE))) { + /* Restrict to frontend side. */ + ctx->type = (1 << STATS_TYPE_FE) | (1 << STATS_TYPE_SO); + ctx->iid = ctx->sid = -1; + + ret = stats_dump_stat_to_buffer(appctx_sc(appctx), NULL, NULL); + if (!ret) + return 0; + + chunk_strcat(&ctx->chunk, "\n"); + if (!stats_putchk(appctx, NULL, NULL)) + return 0; + + /* Switch to backend side. */ + ctx->state = STAT_STATE_INIT; + ctx->type = (1 << STATS_TYPE_BE) | (1 << STATS_TYPE_SV); + } + + return stats_dump_stat_to_buffer(appctx_sc(appctx), NULL, NULL); +} + +static void cli_io_handler_release_dump_stat_file(struct appctx *appctx) +{ +} + int stats_allocate_proxy_counters_internal(struct extra_counters **counters, int type, int px_cap) { @@ -2854,6 +2921,7 @@ static struct cli_kw_list cli_kws = {{ },{ { { "show", "info", NULL }, "show info [desc|json|typed|float]* : report information about the running process", cli_parse_show_info, cli_io_handler_dump_info, NULL }, { { "show", "stat", NULL }, "show stat [desc|json|no-maint|typed|up]*: report counters for each proxy and server", cli_parse_show_stat, cli_io_handler_dump_stat, cli_io_handler_release_stat }, { { "show", "schema", "json", NULL }, "show schema json : report schema used for stats", NULL, cli_io_handler_dump_json_schema, NULL }, + { { "dump", "stats-file", NULL }, "dump stats-file : dump stats for restore", cli_parse_dump_stat_file, cli_io_handler_dump_stat_file, cli_io_handler_release_dump_stat_file }, {{},} }};