]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: cli: add a new "wait" command to wait for a certain delay
authorWilly Tarreau <w@1wt.eu>
Thu, 8 Feb 2024 20:45:22 +0000 (21:45 +0100)
committerWilly Tarreau <w@1wt.eu>
Thu, 8 Feb 2024 20:54:54 +0000 (21:54 +0100)
This allows to insert delays between commands, i.e. to collect a same
set of metrics at a fixed interval. E.g:

  $ socat -t20 /path/to/socket <<< "show activity; wait 10s; show activity"

The goal will be to extend the feature to optionally support waiting on
certain conditions. For this reason the struct definitions and enums were
placed into cli-t.h.

doc/management.txt
include/haproxy/cli-t.h
src/cli.c

index 63ba742c1fb3ac021468692ed6faf21f4cb5679e..c5e5579d37b676916498c882f04710bf9af1b3ca 100644 (file)
@@ -3997,6 +3997,15 @@ update ssl ocsp-response <certfile>
   local tree, its contents will be displayed on the standard output. The format
   is the same as the one described in "show ssl ocsp-response".
 
+wait <delay>
+  This simply waits for the requested delay before continuing. This can be used
+  to collect metrics around a specific interval. The default unit for the delay
+  is milliseconds, though other units are accepted if suffixed with the usual
+  timer units (s, m, h, d). When used with the 'socat' utility, do not forget
+  to extend socat's close timeout to cover the wait time.
+  Example:
+    $ socat -t20 /path/to/socket <<< "show activity; wait 10s; show activity"
+
 
 9.4. Master CLI
 ---------------
index c155df33873b582f254544a7a5332a8e8b390953..9334fcd9f01c160c54d5bfbb87797300c23a5abd 100644 (file)
@@ -81,6 +81,27 @@ struct cli_print_ctx {
        int severity;           /* severity of the message to be returned according to (syslog) rfc5424 */
 };
 
+/* context for the "wait" command that's used to wait for some time on a
+ * condition. We store the start date and the expiration date. The error
+ * value is set by the I/O handler to be printed by the release handler at
+ * the end.
+ */
+enum cli_wait_err {
+       CLI_WAIT_ERR_DONE,       // condition satisfied
+       CLI_WAIT_ERR_INTR,       // interrupted
+       CLI_WAIT_ERR_EXP,        // finished on wait expiration
+};
+
+enum cli_wait_cond {
+       CLI_WAIT_COND_NONE,      // no condition to wait on
+};
+
+struct cli_wait_ctx {
+       uint start, deadline;    // both are in ticks.
+       enum cli_wait_cond cond; // CLI_WAIT_COND_*
+       enum cli_wait_err error; // CLI_WAIT_ERR_*
+};
+
 struct cli_kw {
        const char *str_kw[CLI_PREFIX_KW_NB]; /* keywords ended by NULL, limited to CLI_PREFIX_KW_NB
                                 separated keywords combination */
index 1546c0d7cd3bc010948776ba766a300d7f9d3979..acd5b8729ef0828a5d38c56bcb9b43e06fdcc9ed 100644 (file)
--- a/src/cli.c
+++ b/src/cli.c
@@ -2010,6 +2010,105 @@ static int cli_parse_set_ratelimit(char **args, char *payload, struct appctx *ap
        return 1;
 }
 
+/* Parse a "wait <time>" command.
+ * It uses a "cli_wait_ctx" struct for its context.
+ * Returns 0 if the server deletion has been successfully scheduled, 1 on failure.
+ */
+static int cli_parse_wait(char **args, char *payload, struct appctx *appctx, void *private)
+{
+       struct cli_wait_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
+       uint wait_ms;
+       const char *err;
+
+       if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
+               return 1;
+
+       if (!*args[1])
+               return cli_err(appctx, "Expects a duration in milliseconds.\n");
+
+       err = parse_time_err(args[1], &wait_ms, TIME_UNIT_MS);
+       if (err || wait_ms < 1)
+               return cli_err(appctx, "Invalid duration.\n");
+
+       ctx->start = now_ms;
+       ctx->deadline = tick_add(now_ms, wait_ms);
+
+       /* proceed with the I/O handler */
+       return 0;
+}
+
+/* Execute a "wait" condition. The delay is exponentially incremented between
+ * now_ms and ctx->deadline in powers of 1.5 and with a bound set to 10% of the
+ * programmed wait time, so that in a few wakeups we can later check a condition
+ * with reasonable accuracy. Shutdowns and other errors are handled as well and
+ * terminate the operation, but not new inputs so that it remains possible to
+ * chain other commands after it. Returns 0 if not finished, 1 if finished.
+ */
+static int cli_io_handler_wait(struct appctx *appctx)
+{
+       struct cli_wait_ctx *ctx = appctx->svcctx;
+       struct stconn *sc = appctx_sc(appctx);
+       uint total, elapsed, left, wait;
+
+       /* note: upon first invocation, the timeout is not set */
+       if (tick_isset(appctx->t->expire) &&
+           !tick_is_expired(appctx->t->expire, now_ms))
+               goto wait;
+
+       /* here we should evaluate our waiting conditions, if any */
+
+       /* and here we recalculate the new wait time or abort */
+       left  = tick_remain(now_ms, ctx->deadline);
+       if (!left) {
+               /* let the release handler know we've expired. When there is no
+                * wait condition, it's a simple sleep so we declare we're done.
+                */
+               if (ctx->cond == CLI_WAIT_COND_NONE)
+                       ctx->error = CLI_WAIT_ERR_DONE;
+               else
+                       ctx->error = CLI_WAIT_ERR_EXP;
+               return 1;
+       }
+
+       total = tick_remain(ctx->start, ctx->deadline);
+       elapsed = total - left;
+       wait = elapsed / 2 + 1;
+       if (wait > left)
+               wait = left;
+       else if (wait > total / 10)
+               wait = total / 10;
+
+       appctx->t->expire = tick_add(now_ms, wait);
+
+ wait:
+       /* Stop waiting upon close/abort/error */
+       if (unlikely((sc->flags & SC_FL_SHUT_DONE) ||
+                    se_fl_test(appctx->sedesc, (SE_FL_EOS|SE_FL_ERROR|SE_FL_SHR|SE_FL_SHW)))) {
+               co_skip(sc_oc(sc), sc_oc(sc)->output);
+               ctx->error = CLI_WAIT_ERR_INTR;
+               return 1;
+       }
+       return 0;
+}
+
+/* release structs allocated by "delete server" */
+static void cli_release_wait(struct appctx *appctx)
+{
+       struct cli_wait_ctx *ctx = appctx->svcctx;
+       const char *msg;
+
+       switch (ctx->error) {
+       case CLI_WAIT_ERR_EXP:      msg = "Wait delay expired.\n"; break;
+       case CLI_WAIT_ERR_INTR:     msg = "Interrupted.\n"; break;
+       default:                    msg = "Done.\n"; break;
+       }
+
+       if (ctx->error == CLI_WAIT_ERR_DONE)
+               cli_msg(appctx, LOG_INFO, msg);
+       else
+               cli_err(appctx, msg);
+}
+
 /* parse the "expose-fd" argument on the bind lines */
 static int bind_parse_expose_fd(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err)
 {
@@ -3406,6 +3505,7 @@ static struct cli_kw_list cli_kws = {{ },{
        { { "show", "version", NULL },           "show version                            : show version of the current process",                     cli_parse_show_version, NULL, NULL, NULL, ACCESS_MASTER },
        { { "operator", NULL },                  "operator                                : lower the level of the current CLI session to operator",  cli_parse_set_lvl, NULL, NULL, NULL, ACCESS_MASTER},
        { { "user", NULL },                      "user                                    : lower the level of the current CLI session to user",      cli_parse_set_lvl, NULL, NULL, NULL, ACCESS_MASTER},
+       { { "wait", NULL },                      "wait <ms>                               : wait the specified delay",                                cli_parse_wait, cli_io_handler_wait, cli_release_wait, NULL },
        {{},}
 }};