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.
This commit is contained in:
Amaury Denoyelle 2024-03-28 14:53:52 +01:00
parent 83281303f6
commit e74148fb7c
5 changed files with 204 additions and 1 deletions

View File

@ -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

View File

@ -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 <backend>/<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 <backend>/<server>
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
----------------------------------------------

View File

@ -0,0 +1,15 @@
#ifndef _HAPROXY_STATS_FILE_H
#define _HAPROXY_STATS_FILE_H
#include <sys/types.h>
#include <haproxy/buf-t.h>
#include <haproxy/stats-t.h>
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 */

92
src/stats-file.c Normal file
View File

@ -0,0 +1,92 @@
#include <haproxy/stats-file.h>
#include <haproxy/api.h>
#include <haproxy/buf.h>
#include <haproxy/chunk.h>
#include <haproxy/guid-t.h>
#include <haproxy/list.h>
#include <haproxy/listener-t.h>
#include <haproxy/obj_type.h>
#include <haproxy/proxy-t.h>
#include <haproxy/server-t.h>
#include <haproxy/stats.h>
/* Dump all fields from <stats> into <out> 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");
}

View File

@ -57,6 +57,7 @@
#include <haproxy/server.h>
#include <haproxy/session.h>
#include <haproxy/stats.h>
#include <haproxy/stats-file.h>
#include <haproxy/stats-html.h>
#include <haproxy/stats-json.h>
#include <haproxy/stconn.h>
@ -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 },
{{},}
}};