Hello,

Here is a patch to extend 'show table' with multiple filters. We already have this functionality in Lua (also adapted here to use the same definition for number of filters)

The current code is somewhat relaxed about the extra data (garbage) after the
  'data.<type> <operator> <value>'
part, and we've kept that behavior. For forward compatibility, it would be nice if we can introduce either additional 'haproxy -vv' info line, or CLI command (e.g. 'show cli filters'), which would return the supported number of filter entries.

Error handling could also be improved (we don't show which filter entry is wrong, if we have more than one), but I wanted to first check with you guys if we can add "snprintf" style error function to the cli, in addition to existing cli_dynerr()/cli_dynmsg()


Best regards,
--
Adis Nezirovic
Software Engineer
HAProxy Technologies - Powering your uptime!
375 Totten Pond Road, Suite 302 | Waltham, MA 02451, US
+1 (844) 222-4340 | https://www.haproxy.com
>From fdfcf4e124db1cffcce301116198bab9deec6583 Mon Sep 17 00:00:00 2001
From: Adis Nezirovic <[email protected]>
Date: Thu, 16 Jan 2020 15:19:29 +0100
Subject: [PATCH] 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.
---
 doc/management.txt        |   4 +-
 include/common/defaults.h |   5 ++
 include/types/applet.h    |   6 +--
 src/hlua_fcn.c            |   7 ++-
 src/stick_table.c         | 111 +++++++++++++++++++++-----------------
 5 files changed, 76 insertions(+), 57 deletions(-)

diff --git a/doc/management.txt b/doc/management.txt
index 521a67112..24969be88 100644
--- a/doc/management.txt
+++ b/doc/management.txt
@@ -2569,7 +2569,7 @@ show table
     >>> # table: front_pub, type: ip, size:204800, used:171454
     >>> # 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
   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
@@ -2588,6 +2588,8 @@ show table <name> [ data.<type> <operator> <value> ] | [ key <key> ]
     - lt : match entries whose data is less 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
   same type as the table, which currently is limited to IPv4, IPv6, integer,
diff --git a/include/common/defaults.h b/include/common/defaults.h
index fbacca481..e86c9bce1 100644
--- a/include/common/defaults.h
+++ b/include/common/defaults.h
@@ -116,6 +116,11 @@
 #define STKTABLE_EXTRA_DATA_TYPES 0
 #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.
 // It's very frequent that the system returns a few TCP segments at a time.
 #ifndef MAX_READ_POLL_LOOPS
diff --git a/include/types/applet.h b/include/types/applet.h
index dfb489c9f..76a598d21 100644
--- a/include/types/applet.h
+++ b/include/types/applet.h
@@ -150,9 +150,9 @@ struct appctx {
 			void *target;		/* table we want to dump, or NULL for all */
 			struct stktable *t;	/* table being currently dumped (first if NULL) */
 			struct stksess *entry;	/* last entry we were trying to dump (or first if NULL) */
-			long long value;	/* value to compare against */
-			signed char data_type;	/* type of data to compare, or -1 if none */
-			signed char data_op;	/* operator (STD_OP_*) when data_type set */
+			long long value[STKTABLE_FILTER_LEN];	     /* value to compare against */
+			signed char data_type[STKTABLE_FILTER_LEN];  /* type of data to compare, or -1 if none */
+			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_* */
 		} table;
 		struct {
diff --git a/src/hlua_fcn.c b/src/hlua_fcn.c
index 7ef708ccb..f8024aa84 100644
--- a/src/hlua_fcn.c
+++ b/src/hlua_fcn.c
@@ -39,7 +39,6 @@ static int class_listener_ref;
 static int class_regex_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))
 
 static THREAD_LOCAL struct field stats[STATS_LEN];
@@ -682,7 +681,7 @@ int hlua_stktable_dump(lua_State *L)
 	int op;
 	int dt;
 	long long val;
-	struct stk_filter filter[MAX_STK_FILTER_LEN];
+	struct stk_filter filter[STKTABLE_FILTER_LEN];
 	int filter_count = 0;
 	int i;
 	int skip_entry;
@@ -700,8 +699,8 @@ int hlua_stktable_dump(lua_State *L)
 		while (lua_next(L, 2) != 0) {
 			int entry_idx = 0;
 
-			if (filter_count >= MAX_STK_FILTER_LEN)
-				return hlua_error(L, "Filter table too large (len > %d)", MAX_STK_FILTER_LEN);
+			if (filter_count >= STKTABLE_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)
 				return hlua_error(L, "Filter table entry must be a triplet: {\"data_col\", \"op\", val} (entry #%d)", filter_count + 1);
diff --git a/src/stick_table.c b/src/stick_table.c
index 7b648475b..1393b1ff3 100644
--- a/src/stick_table.c
+++ b/src/stick_table.c
@@ -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)
 {
+	int i;
+
 	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");
 
-	/* condition on stored data value */
-	appctx->ctx.table.data_type = stktable_get_data_type(args[3] + 5);
-	if (appctx->ctx.table.data_type < 0)
-		return cli_err(appctx, "Unknown data type\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 */
+		appctx->ctx.table.data_type[i] = stktable_get_data_type(args[3+3*i] + 5);
+		if (appctx->ctx.table.data_type[i] < 0)
+			return cli_err(appctx, "Unknown data type\n");
 
-	if (!((struct stktable *)appctx->ctx.table.target)->data_ofs[appctx->ctx.table.data_type])
-		return cli_err(appctx, "Data type not stored in this table\n");
+		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");
 
-	appctx->ctx.table.data_op = get_std_op(args[4]);
-	if (appctx->ctx.table.data_op < 0)
-		return cli_err(appctx, "Require and operator among \"eq\", \"ne\", \"le\", \"ge\", \"lt\", \"gt\"\n");
+		appctx->ctx.table.data_op[i] = get_std_op(args[4+3*i]);
+		if (appctx->ctx.table.data_op < 0)
+			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)
-		return cli_err(appctx, "Require a valid integer value to compare against\n");
+		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");
+	}
 
 	/* OK we're done, all the fields are set */
 	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 */
 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.entry = NULL;
 	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 *s = si_strm(si);
 	struct ebmb_node *eb;
-	int dt;
 	int skip_entry;
 	int show = appctx->ctx.table.action == STK_CLI_ACT_SHOW;
 
@@ -3744,48 +3752,53 @@ static int cli_io_handler_table(struct appctx *appctx)
 
 			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 */
 				void *ptr;
-				long long data;
+				int dt;
+				signed char op;
+				long long data, value;
 
 
-				dt = appctx->ctx.table.data_type;
-				ptr = stktable_data_ptr(appctx->ctx.table.t,
-							appctx->ctx.table.entry,
-							dt);
+				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,
+								appctx->ctx.table.entry,
+								dt);
+
+					data = 0;
+					switch (stktable_data_types[dt].std_type) {
+					case STD_T_SINT:
+						data = stktable_data_cast(ptr, std_t_sint);
+						break;
+					case STD_T_UINT:
+						data = stktable_data_cast(ptr, std_t_uint);
+						break;
+					case STD_T_ULL:
+						data = stktable_data_cast(ptr, std_t_ull);
+						break;
+					case STD_T_FRQP:
+						data = read_freq_ctr_period(&stktable_data_cast(ptr, std_t_frqp),
+									    appctx->ctx.table.t->data_arg[dt].u);
+						break;
+					}
 
-				data = 0;
-				switch (stktable_data_types[dt].std_type) {
-				case STD_T_SINT:
-					data = stktable_data_cast(ptr, std_t_sint);
-					break;
-				case STD_T_UINT:
-					data = stktable_data_cast(ptr, std_t_uint);
-					break;
-				case STD_T_ULL:
-					data = stktable_data_cast(ptr, std_t_ull);
-					break;
-				case STD_T_FRQP:
-					data = read_freq_ctr_period(&stktable_data_cast(ptr, std_t_frqp),
-								    appctx->ctx.table.t->data_arg[dt].u);
-					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 */
+					if ((data < value &&
+					     (op == STD_OP_EQ || op == STD_OP_GT || op == STD_OP_GE)) ||
+					    (data == value &&
+					     (op == STD_OP_NE || op == STD_OP_GT || op == STD_OP_LT)) ||
+					    (data > value &&
+					     (op == STD_OP_EQ || op == STD_OP_LT || op == STD_OP_LE))) {
+						skip_entry = 1;
+						break;
+					}
 				}
-
-				/* skip the entry if the data does not match the test and the value */
-				if ((data < appctx->ctx.table.value &&
-				     (appctx->ctx.table.data_op == STD_OP_EQ ||
-				      appctx->ctx.table.data_op == STD_OP_GT ||
-				      appctx->ctx.table.data_op == STD_OP_GE)) ||
-				    (data == appctx->ctx.table.value &&
-				     (appctx->ctx.table.data_op == STD_OP_NE ||
-				      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;
 			}
 
 			if (show && !skip_entry &&
-- 
2.25.0

Reply via email to