]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: stats: implement dump stats-file CLI
authorAmaury Denoyelle <adenoyelle@haproxy.com>
Thu, 28 Mar 2024 13:53:52 +0000 (14:53 +0100)
committerAmaury Denoyelle <adenoyelle@haproxy.com>
Fri, 26 Apr 2024 08:20:57 +0000 (10:20 +0200)
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
doc/management.txt
include/haproxy/stats-file.h [new file with mode: 0644]
src/stats-file.c [new file with mode: 0644]
src/stats.c

index 57be1b145b4b0adc1f9dc31ccb27cf0684d17a1f..fb41a047f71c0cc9c8d4741573ff325c1eb20c6b 100644 (file)
--- 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
index 893226ef7115d777e570d8fd9ab296efbfdb97e7..2fc8ea2e6b8d2f1cc0b2ce6d69256384d46eb766 100644 (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
 ----------------------------------------------
 
diff --git a/include/haproxy/stats-file.h b/include/haproxy/stats-file.h
new file mode 100644 (file)
index 0000000..9063487
--- /dev/null
@@ -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 */
diff --git a/src/stats-file.c b/src/stats-file.c
new file mode 100644 (file)
index 0000000..f3e0db9
--- /dev/null
@@ -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");
+}
index 991dc9f83fa3dbddf0853c9521ab7e080bd6d4ef..1280a1eeac3a2c3d6f1e91281b26d30932ee363e 100644 (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 },
        {{},}
 }};