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:
parent
83281303f6
commit
e74148fb7c
2
Makefile
2
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/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/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/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),)
|
ifneq ($(TRACE),)
|
||||||
OBJS += src/calltrace.o
|
OBJS += src/calltrace.o
|
||||||
|
@ -32,6 +32,7 @@ Summary
|
|||||||
9.3. Unix Socket commands
|
9.3. Unix Socket commands
|
||||||
9.4. Master CLI
|
9.4. Master CLI
|
||||||
9.4.1. Master CLI commands
|
9.4.1. Master CLI commands
|
||||||
|
9.5. Stats-file
|
||||||
10. Tricks for easier configuration management
|
10. Tricks for easier configuration management
|
||||||
11. Well-known traps to avoid
|
11. Well-known traps to avoid
|
||||||
12. Debugging and performance issues
|
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
|
This command is restricted and can only be issued on sockets configured for
|
||||||
level "admin".
|
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>
|
enable agent <backend>/<server>
|
||||||
Resume auxiliary agent check that was temporarily stopped.
|
Resume auxiliary agent check that was temporarily stopped.
|
||||||
|
|
||||||
@ -4235,6 +4240,29 @@ show startup-logs
|
|||||||
|
|
||||||
Those messages are also dumped with the "reload" command.
|
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
|
10. Tricks for easier configuration management
|
||||||
----------------------------------------------
|
----------------------------------------------
|
||||||
|
|
||||||
|
15
include/haproxy/stats-file.h
Normal file
15
include/haproxy/stats-file.h
Normal 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
92
src/stats-file.c
Normal 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");
|
||||||
|
}
|
68
src/stats.c
68
src/stats.c
@ -57,6 +57,7 @@
|
|||||||
#include <haproxy/server.h>
|
#include <haproxy/server.h>
|
||||||
#include <haproxy/session.h>
|
#include <haproxy/session.h>
|
||||||
#include <haproxy/stats.h>
|
#include <haproxy/stats.h>
|
||||||
|
#include <haproxy/stats-file.h>
|
||||||
#include <haproxy/stats-html.h>
|
#include <haproxy/stats-html.h>
|
||||||
#include <haproxy/stats-json.h>
|
#include <haproxy/stats-json.h>
|
||||||
#include <haproxy/stconn.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);
|
ret = stats_dump_fields_typed(chk, line, stats_count, ctx);
|
||||||
else if (ctx->flags & STAT_F_FMT_JSON)
|
else if (ctx->flags & STAT_F_FMT_JSON)
|
||||||
ret = stats_dump_fields_json(chk, line, stats_count, ctx);
|
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
|
else
|
||||||
ret = stats_dump_fields_csv(chk, line, stats_count, ctx);
|
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) {
|
list_for_each_entry(mod, &stats_module_list[STATS_DOMAIN_PROXY], list) {
|
||||||
void *counters;
|
void *counters;
|
||||||
|
|
||||||
|
if (ctx->flags & STAT_F_FMT_FILE)
|
||||||
|
continue;
|
||||||
|
|
||||||
if (!(stats_px_get_cap(mod->domain_flags) & STATS_PX_CAP_FE)) {
|
if (!(stats_px_get_cap(mod->domain_flags) & STATS_PX_CAP_FE)) {
|
||||||
stats_count += mod->stats_count;
|
stats_count += mod->stats_count;
|
||||||
continue;
|
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) {
|
list_for_each_entry(mod, &stats_module_list[STATS_DOMAIN_PROXY], list) {
|
||||||
void *counters;
|
void *counters;
|
||||||
|
|
||||||
|
if (ctx->flags & STAT_F_FMT_FILE)
|
||||||
|
continue;
|
||||||
|
|
||||||
if (!(stats_px_get_cap(mod->domain_flags) & STATS_PX_CAP_LI)) {
|
if (!(stats_px_get_cap(mod->domain_flags) & STATS_PX_CAP_LI)) {
|
||||||
stats_count += mod->stats_count;
|
stats_count += mod->stats_count;
|
||||||
continue;
|
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) {
|
list_for_each_entry(mod, &stats_module_list[STATS_DOMAIN_PROXY], list) {
|
||||||
void *counters;
|
void *counters;
|
||||||
|
|
||||||
|
if (ctx->flags & STAT_F_FMT_FILE)
|
||||||
|
continue;
|
||||||
|
|
||||||
if (stats_get_domain(mod->domain_flags) != STATS_DOMAIN_PROXY)
|
if (stats_get_domain(mod->domain_flags) != STATS_DOMAIN_PROXY)
|
||||||
continue;
|
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) {
|
list_for_each_entry(mod, &stats_module_list[STATS_DOMAIN_PROXY], list) {
|
||||||
struct extra_counters *counters;
|
struct extra_counters *counters;
|
||||||
|
|
||||||
|
if (ctx->flags & STAT_F_FMT_FILE)
|
||||||
|
continue;
|
||||||
|
|
||||||
if (stats_get_domain(mod->domain_flags) != STATS_DOMAIN_PROXY)
|
if (stats_get_domain(mod->domain_flags) != STATS_DOMAIN_PROXY)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@ -2070,6 +2085,8 @@ int stats_dump_stat_to_buffer(struct stconn *sc, struct buffer *buf, struct htx
|
|||||||
stats_dump_json_schema(chk);
|
stats_dump_json_schema(chk);
|
||||||
else if (ctx->flags & STAT_F_FMT_JSON)
|
else if (ctx->flags & STAT_F_FMT_JSON)
|
||||||
stats_dump_json_header(chk);
|
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))
|
else if (!(ctx->flags & STAT_F_FMT_TYPED))
|
||||||
stats_dump_csv_header(ctx->domain, chk);
|
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);
|
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 stats_allocate_proxy_counters_internal(struct extra_counters **counters,
|
||||||
int type, int px_cap)
|
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", "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", "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 },
|
{ { "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 },
|
||||||
{{},}
|
{{},}
|
||||||
}};
|
}};
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user