[MEDIUM] stats: add "show table [<name>]" to dump a stick-table

It is now possible to dump a table's contents with keys, expire,
use count, and various data using the command above on the stats
socket.

"show table" only shows main table stats, while "show table <name>"
dumps table contents, only if the socket level is admin.
diff --git a/src/dumpstats.c b/src/dumpstats.c
index 1d1e0ff..13e2dd0 100644
--- a/src/dumpstats.c
+++ b/src/dumpstats.c
@@ -64,6 +64,7 @@
 	"  show stat      : report counters for each proxy and server\n"
 	"  show errors    : report last request and response errors for each proxy\n"
 	"  show sess [id] : report the list of current sessions or dump this session\n"
+	"  show table [id]: report table usage stats or dump this table's contents\n"
 	"  get weight     : report a server's current weight\n"
 	"  set weight     : change a server's weight\n"
 	"  set timeout    : change a timeout setting\n"
@@ -377,6 +378,16 @@
 			s->data_state = DATA_ST_INIT;
 			si->st0 = STAT_CLI_O_ERR; // stats_dump_errors_to_buffer
 		}
+		else if (strcmp(args[1], "table") == 0) {
+			s->data_state = DATA_ST_INIT;
+			if (*args[2])
+				s->data_ctx.table.target = findproxy(args[2], 0);
+			else
+				s->data_ctx.table.target = NULL;
+			s->data_ctx.table.proxy = NULL;
+			s->data_ctx.table.entry = NULL;
+			si->st0 = STAT_CLI_O_TAB; // stats_dump_table_to_buffer
+		}
 		else { /* neither "stat" nor "info" nor "sess" nor "errors"*/
 			return 0;
 		}
@@ -807,6 +818,10 @@
 				if (stats_dump_errors_to_buffer(s, res))
 					si->st0 = STAT_CLI_PROMPT;
 				break;
+			case STAT_CLI_O_TAB:
+				if (stats_dump_table_to_buffer(s, res))
+					si->st0 = STAT_CLI_PROMPT;
+				break;
 			default: /* abnormal state */
 				si->st0 = STAT_CLI_PROMPT;
 				break;
@@ -2767,6 +2782,174 @@
 		s->data_state = DATA_ST_FIN;
 		return 1;
 	}
+}
+
+/* This function is called to send output to the response buffer.
+ * It dumps the tables states onto the output buffer <rep>.
+ * Expects to be called with client socket shut down on input.
+ * s->data_ctx must have been zeroed first, and the flags properly set.
+ * It returns 0 as long as it does not complete, non-zero upon completion.
+ */
+int stats_dump_table_to_buffer(struct session *s, struct buffer *rep)
+{
+	struct chunk msg;
+	struct ebmb_node *eb;
+	int dt;
+
+	/*
+	 * We have 3 possible states in s->data_state :
+	 *   - DATA_ST_INIT : the first call
+	 *   - DATA_ST_INFO : the proxy pointer points to the next table to
+	 *     dump, the entry pointer is NULL ;
+	 *   - DATA_ST_LIST : the proxy pointer points to the current table
+	 *     and the entry pointer points to the next entry to be dumped,
+	 *     and the refcount on the next entry is held ;
+	 *   - DATA_ST_END : nothing left to dump, the buffer may contain some
+	 *     data though.
+	 */
+
+	if (unlikely(rep->flags & (BF_WRITE_ERROR|BF_SHUTW))) {
+		/* in case of abort, remove any refcount we might have set on an entry */
+		if (s->data_state == DATA_ST_LIST)
+			s->data_ctx.table.entry->ref_cnt--;
+		return 1;
+	}
+
+	chunk_init(&msg, trash, sizeof(trash));
+
+	while (s->data_state != DATA_ST_FIN) {
+		switch (s->data_state) {
+		case DATA_ST_INIT:
+			s->data_ctx.table.proxy = s->data_ctx.table.target;
+			if (!s->data_ctx.table.proxy)
+				s->data_ctx.table.proxy = proxy;
+
+			s->data_ctx.table.entry = NULL;
+			s->data_state = DATA_ST_INFO;
+			break;
+
+		case DATA_ST_INFO:
+			if (!s->data_ctx.table.proxy ||
+			    (s->data_ctx.table.target &&
+			     s->data_ctx.table.proxy != s->data_ctx.table.target)) {
+				s->data_state = DATA_ST_END;
+				break;
+			}
+
+			if (s->data_ctx.table.proxy->table.size) {
+				chunk_printf(&msg, "# table: %s, type: %ld, size:%d, used:%d\n",
+					     s->data_ctx.table.proxy->id,
+					     s->data_ctx.table.proxy->table.type,
+					     s->data_ctx.table.proxy->table.size,
+					     s->data_ctx.table.proxy->table.current);
+
+				/* any other information should be dumped here */
+
+				if (s->data_ctx.table.target &&
+				    s->listener->perm.ux.level < ACCESS_LVL_OPER)
+					chunk_printf(&msg, "# contents not dumped due to insufficient privileges\n");
+
+				if (buffer_feed_chunk(rep, &msg) >= 0)
+					return 0;
+
+				if (s->data_ctx.table.target &&
+				    s->listener->perm.ux.level >= ACCESS_LVL_OPER) {
+					/* dump entries only if table explicitly requested */
+					eb = ebmb_first(&s->data_ctx.table.proxy->table.keys);
+					if (eb) {
+						s->data_ctx.table.entry = ebmb_entry(eb, struct stksess, key);
+						s->data_ctx.table.entry->ref_cnt++;
+						s->data_state = DATA_ST_LIST;
+						break;
+					}
+				}
+			}
+			s->data_ctx.table.proxy = s->data_ctx.table.proxy->next;
+			break;
+
+		case DATA_ST_LIST:
+			chunk_printf(&msg, "%p:", s->data_ctx.table.entry);
+
+			if (s->data_ctx.table.proxy->table.type == STKTABLE_TYPE_IP) {
+				char addr[16];
+				inet_ntop(AF_INET,
+					  (const void *)&s->data_ctx.table.entry->key.key,
+					  addr, sizeof(addr));
+				chunk_printf(&msg, " key=%s", addr);
+			}
+			else
+				chunk_printf(&msg, " key=?");
+
+			chunk_printf(&msg, " use=%d exp=%d",
+				     s->data_ctx.table.entry->ref_cnt - 1,
+				     tick_remain(now_ms, s->data_ctx.table.entry->expire));
+
+			for (dt = 0; dt < STKTABLE_DATA_TYPES; dt++) {
+				void *ptr;
+
+				if (s->data_ctx.table.proxy->table.data_ofs[dt] == 0)
+					continue;
+				if (stktable_data_types[dt].arg_type == ARG_T_DELAY)
+					chunk_printf(&msg, " %s(%d)=",
+						     stktable_data_types[dt].name,
+						     s->data_ctx.table.proxy->table.data_arg[dt].u);
+				else
+					chunk_printf(&msg, " %s=", stktable_data_types[dt].name);
+
+				ptr = stktable_data_ptr(&s->data_ctx.table.proxy->table,
+							s->data_ctx.table.entry,
+							dt);
+				switch (dt) {
+					/* all entries using the same type can be folded */
+				case STKTABLE_DT_SERVER_ID:
+				case STKTABLE_DT_GPC0:
+				case STKTABLE_DT_CONN_CNT:
+				case STKTABLE_DT_CONN_CUR:
+				case STKTABLE_DT_SESS_CNT:
+				case STKTABLE_DT_HTTP_REQ_CNT:
+				case STKTABLE_DT_HTTP_ERR_CNT:
+					chunk_printf(&msg, "%u", stktable_data_cast(ptr, server_id));
+					break;
+				case STKTABLE_DT_CONN_RATE:
+				case STKTABLE_DT_SESS_RATE:
+				case STKTABLE_DT_HTTP_REQ_RATE:
+				case STKTABLE_DT_HTTP_ERR_RATE:
+				case STKTABLE_DT_BYTES_IN_RATE:
+				case STKTABLE_DT_BYTES_OUT_RATE:
+					chunk_printf(&msg, "%d",
+						     read_freq_ctr_period(&stktable_data_cast(ptr, conn_rate),
+									  s->data_ctx.table.proxy->table.data_arg[dt].u));
+					break;
+				case STKTABLE_DT_BYTES_IN_CNT:
+				case STKTABLE_DT_BYTES_OUT_CNT:
+					chunk_printf(&msg, "%lld", stktable_data_cast(ptr, bytes_in_cnt));
+					break;
+				}
+			}
+			chunk_printf(&msg, "\n");
+
+			if (buffer_feed_chunk(rep, &msg) >= 0)
+				return 0;
+
+			s->data_ctx.table.entry->ref_cnt--;
+
+			eb = ebmb_next(&s->data_ctx.table.entry->key);
+			if (eb) {
+				s->data_ctx.table.entry = ebmb_entry(eb, struct stksess, key);
+				s->data_ctx.table.entry->ref_cnt++;
+				break;
+			}
+
+			s->data_ctx.table.proxy = s->data_ctx.table.proxy->next;
+			s->data_state = DATA_ST_INFO;
+			break;
+
+		case DATA_ST_END:
+			s->data_state = DATA_ST_FIN;
+			break;
+		}
+	}
+	return 1;
 }
 
 /* print a line of text buffer (limited to 70 bytes) to <out>. The format is :