MINOR: cli: add a new "wait" command to wait for a certain delay

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.
This commit is contained in:
Willy Tarreau 2024-02-08 21:45:22 +01:00
parent 02b31fa003
commit 1d2255a78a
3 changed files with 130 additions and 0 deletions

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

View 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 */

100
src/cli.c
View File

@ -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 },
{{},}
}};