MEDIUM: cli: Allow multiple filter entries for "show table"

For complex stick tables with many entries/columns, it can be beneficial
to filter using multiple criteria. The maximum number of filter entries
can be controlled by defining STKTABLE_FILTER_LEN during build time.

This patch can be backported to older releases.
This commit is contained in:
Adis Nezirovic 2020-01-16 15:19:29 +01:00 committed by Willy Tarreau
parent 016797f6ab
commit 1a693fc2fd
5 changed files with 76 additions and 57 deletions

View File

@ -2569,7 +2569,7 @@ show table
>>> # table: front_pub, type: ip, size:204800, used:171454 >>> # table: front_pub, type: ip, size:204800, used:171454
>>> # table: back_rdp, type: ip, size:204800, used:0 >>> # table: back_rdp, type: ip, size:204800, used:0
show table <name> [ data.<type> <operator> <value> ] | [ key <key> ] show table <name> [ data.<type> <operator> <value> [data.<type> ...]] | [ key <key> ]
Dump contents of stick-table <name>. In this mode, a first line of generic Dump contents of stick-table <name>. In this mode, a first line of generic
information about the table is reported as with "show table", then all information about the table is reported as with "show table", then all
entries are dumped. Since this can be quite heavy, it is possible to specify entries are dumped. Since this can be quite heavy, it is possible to specify
@ -2588,6 +2588,8 @@ show table <name> [ data.<type> <operator> <value> ] | [ key <key> ]
- lt : match entries whose data is less than this value - lt : match entries whose data is less than this value
- gt : match entries whose data is greater than this value - gt : match entries whose data is greater than this value
In this form, you can use multiple data filter entries, up to a maximum
defined during build time (4 by default).
When the key form is used the entry <key> is shown. The key must be of the When the key form is used the entry <key> is shown. The key must be of the
same type as the table, which currently is limited to IPv4, IPv6, integer, same type as the table, which currently is limited to IPv4, IPv6, integer,

View File

@ -116,6 +116,11 @@
#define STKTABLE_EXTRA_DATA_TYPES 0 #define STKTABLE_EXTRA_DATA_TYPES 0
#endif #endif
// max # of stick-table filter entries that can be used during dump
#ifndef STKTABLE_FILTER_LEN
#define STKTABLE_FILTER_LEN 4
#endif
// max # of loops we can perform around a read() which succeeds. // max # of loops we can perform around a read() which succeeds.
// It's very frequent that the system returns a few TCP segments at a time. // It's very frequent that the system returns a few TCP segments at a time.
#ifndef MAX_READ_POLL_LOOPS #ifndef MAX_READ_POLL_LOOPS

View File

@ -150,9 +150,9 @@ struct appctx {
void *target; /* table we want to dump, or NULL for all */ void *target; /* table we want to dump, or NULL for all */
struct stktable *t; /* table being currently dumped (first if NULL) */ struct stktable *t; /* table being currently dumped (first if NULL) */
struct stksess *entry; /* last entry we were trying to dump (or first if NULL) */ struct stksess *entry; /* last entry we were trying to dump (or first if NULL) */
long long value; /* value to compare against */ long long value[STKTABLE_FILTER_LEN]; /* value to compare against */
signed char data_type; /* type of data to compare, or -1 if none */ signed char data_type[STKTABLE_FILTER_LEN]; /* type of data to compare, or -1 if none */
signed char data_op; /* operator (STD_OP_*) when data_type set */ signed char data_op[STKTABLE_FILTER_LEN]; /* operator (STD_OP_*) when data_type set */
char action; /* action on the table : one of STK_CLI_ACT_* */ char action; /* action on the table : one of STK_CLI_ACT_* */
} table; } table;
struct { struct {

View File

@ -39,7 +39,6 @@ static int class_listener_ref;
static int class_regex_ref; static int class_regex_ref;
static int class_stktable_ref; static int class_stktable_ref;
#define MAX_STK_FILTER_LEN 4
#define STATS_LEN (MAX((int)ST_F_TOTAL_FIELDS, (int)INF_TOTAL_FIELDS)) #define STATS_LEN (MAX((int)ST_F_TOTAL_FIELDS, (int)INF_TOTAL_FIELDS))
static THREAD_LOCAL struct field stats[STATS_LEN]; static THREAD_LOCAL struct field stats[STATS_LEN];
@ -682,7 +681,7 @@ int hlua_stktable_dump(lua_State *L)
int op; int op;
int dt; int dt;
long long val; long long val;
struct stk_filter filter[MAX_STK_FILTER_LEN]; struct stk_filter filter[STKTABLE_FILTER_LEN];
int filter_count = 0; int filter_count = 0;
int i; int i;
int skip_entry; int skip_entry;
@ -700,8 +699,8 @@ int hlua_stktable_dump(lua_State *L)
while (lua_next(L, 2) != 0) { while (lua_next(L, 2) != 0) {
int entry_idx = 0; int entry_idx = 0;
if (filter_count >= MAX_STK_FILTER_LEN) if (filter_count >= STKTABLE_FILTER_LEN)
return hlua_error(L, "Filter table too large (len > %d)", MAX_STK_FILTER_LEN); return hlua_error(L, "Filter table too large (len > %d)", STKTABLE_FILTER_LEN);
if (lua_type(L, -1) != LUA_TTABLE || lua_rawlen(L, -1) != 3) if (lua_type(L, -1) != LUA_TTABLE || lua_rawlen(L, -1) != 3)
return hlua_error(L, "Filter table entry must be a triplet: {\"data_col\", \"op\", val} (entry #%d)", filter_count + 1); return hlua_error(L, "Filter table entry must be a triplet: {\"data_col\", \"op\", val} (entry #%d)", filter_count + 1);

View File

@ -3600,23 +3600,29 @@ static int table_process_entry_per_key(struct appctx *appctx, char **args)
*/ */
static int table_prepare_data_request(struct appctx *appctx, char **args) static int table_prepare_data_request(struct appctx *appctx, char **args)
{ {
int i;
if (appctx->ctx.table.action != STK_CLI_ACT_SHOW && appctx->ctx.table.action != STK_CLI_ACT_CLR) if (appctx->ctx.table.action != STK_CLI_ACT_SHOW && appctx->ctx.table.action != STK_CLI_ACT_CLR)
return cli_err(appctx, "content-based lookup is only supported with the \"show\" and \"clear\" actions\n"); return cli_err(appctx, "content-based lookup is only supported with the \"show\" and \"clear\" actions\n");
for (i = 0; i < STKTABLE_FILTER_LEN; i++) {
if (i > 0 && !*args[3+3*i]) // number of filter entries can be less than STKTABLE_FILTER_LEN
break;
/* condition on stored data value */ /* condition on stored data value */
appctx->ctx.table.data_type = stktable_get_data_type(args[3] + 5); appctx->ctx.table.data_type[i] = stktable_get_data_type(args[3+3*i] + 5);
if (appctx->ctx.table.data_type < 0) if (appctx->ctx.table.data_type[i] < 0)
return cli_err(appctx, "Unknown data type\n"); return cli_err(appctx, "Unknown data type\n");
if (!((struct stktable *)appctx->ctx.table.target)->data_ofs[appctx->ctx.table.data_type]) if (!((struct stktable *)appctx->ctx.table.target)->data_ofs[appctx->ctx.table.data_type[i]])
return cli_err(appctx, "Data type not stored in this table\n"); return cli_err(appctx, "Data type not stored in this table\n");
appctx->ctx.table.data_op = get_std_op(args[4]); appctx->ctx.table.data_op[i] = get_std_op(args[4+3*i]);
if (appctx->ctx.table.data_op < 0) if (appctx->ctx.table.data_op < 0)
return cli_err(appctx, "Require and operator among \"eq\", \"ne\", \"le\", \"ge\", \"lt\", \"gt\"\n"); return cli_err(appctx, "Require and operator among \"eq\", \"ne\", \"le\", \"ge\", \"lt\", \"gt\"\n");
if (!*args[5] || strl2llrc(args[5], strlen(args[5]), &appctx->ctx.table.value) != 0) if (!*args[5] || strl2llrc(args[5+3*i], strlen(args[5+3*i]), &appctx->ctx.table.value[i]) != 0)
return cli_err(appctx, "Require a valid integer value to compare against\n"); return cli_err(appctx, "Require a valid integer value to compare against\n");
}
/* OK we're done, all the fields are set */ /* OK we're done, all the fields are set */
return 0; return 0;
@ -3625,7 +3631,10 @@ static int table_prepare_data_request(struct appctx *appctx, char **args)
/* returns 0 if wants to be called, 1 if has ended processing */ /* returns 0 if wants to be called, 1 if has ended processing */
static int cli_parse_table_req(char **args, char *payload, struct appctx *appctx, void *private) static int cli_parse_table_req(char **args, char *payload, struct appctx *appctx, void *private)
{ {
appctx->ctx.table.data_type = -1; int i;
for (i = 0; i < STKTABLE_FILTER_LEN; i++)
appctx->ctx.table.data_type[i] = -1;
appctx->ctx.table.target = NULL; appctx->ctx.table.target = NULL;
appctx->ctx.table.entry = NULL; appctx->ctx.table.entry = NULL;
appctx->ctx.table.action = (long)private; // keyword argument, one of STK_CLI_ACT_* appctx->ctx.table.action = (long)private; // keyword argument, one of STK_CLI_ACT_*
@ -3672,7 +3681,6 @@ static int cli_io_handler_table(struct appctx *appctx)
struct stream_interface *si = appctx->owner; struct stream_interface *si = appctx->owner;
struct stream *s = si_strm(si); struct stream *s = si_strm(si);
struct ebmb_node *eb; struct ebmb_node *eb;
int dt;
int skip_entry; int skip_entry;
int show = appctx->ctx.table.action == STK_CLI_ACT_SHOW; int show = appctx->ctx.table.action == STK_CLI_ACT_SHOW;
@ -3744,13 +3752,18 @@ static int cli_io_handler_table(struct appctx *appctx)
HA_RWLOCK_RDLOCK(STK_SESS_LOCK, &appctx->ctx.table.entry->lock); HA_RWLOCK_RDLOCK(STK_SESS_LOCK, &appctx->ctx.table.entry->lock);
if (appctx->ctx.table.data_type >= 0) { if (appctx->ctx.table.data_type[0] >= 0) {
/* we're filtering on some data contents */ /* we're filtering on some data contents */
void *ptr; void *ptr;
long long data; int dt;
signed char op;
long long data, value;
dt = appctx->ctx.table.data_type; for (int i = 0; i < STKTABLE_FILTER_LEN; i++) {
if (appctx->ctx.table.data_type[i] == -1)
break;
dt = appctx->ctx.table.data_type[i];
ptr = stktable_data_ptr(appctx->ctx.table.t, ptr = stktable_data_ptr(appctx->ctx.table.t,
appctx->ctx.table.entry, appctx->ctx.table.entry,
dt); dt);
@ -3772,20 +3785,20 @@ static int cli_io_handler_table(struct appctx *appctx)
break; break;
} }
op = appctx->ctx.table.data_op[i];
value = appctx->ctx.table.value[i];
/* skip the entry if the data does not match the test and the value */ /* skip the entry if the data does not match the test and the value */
if ((data < appctx->ctx.table.value && if ((data < value &&
(appctx->ctx.table.data_op == STD_OP_EQ || (op == STD_OP_EQ || op == STD_OP_GT || op == STD_OP_GE)) ||
appctx->ctx.table.data_op == STD_OP_GT || (data == value &&
appctx->ctx.table.data_op == STD_OP_GE)) || (op == STD_OP_NE || op == STD_OP_GT || op == STD_OP_LT)) ||
(data == appctx->ctx.table.value && (data > value &&
(appctx->ctx.table.data_op == STD_OP_NE || (op == STD_OP_EQ || op == STD_OP_LT || op == STD_OP_LE))) {
appctx->ctx.table.data_op == STD_OP_GT ||
appctx->ctx.table.data_op == STD_OP_LT)) ||
(data > appctx->ctx.table.value &&
(appctx->ctx.table.data_op == STD_OP_EQ ||
appctx->ctx.table.data_op == STD_OP_LT ||
appctx->ctx.table.data_op == STD_OP_LE)))
skip_entry = 1; skip_entry = 1;
break;
}
}
} }
if (show && !skip_entry && if (show && !skip_entry &&