REORG: cli: move 'show sess' to stream.c

Move 'show sess' CLI functions to stream.c and use the cli keyword API
to register it on the CLI.

[wt: the choice of stream vs session makes sense because since 1.6 these
 really are streams that we're dumping and not sessions anymore]
diff --git a/src/cli.c b/src/cli.c
index 606ccc7..11f7e44 100644
--- a/src/cli.c
+++ b/src/cli.c
@@ -136,8 +136,6 @@
 static int stats_dump_backend_to_buffer(struct stream_interface *si);
 static int stats_dump_env_to_buffer(struct stream_interface *si);
 static int stats_dump_info_to_buffer(struct stream_interface *si);
-static int stats_dump_full_sess_to_buffer(struct stream_interface *si, struct stream *sess);
-static int stats_dump_sess_to_buffer(struct stream_interface *si);
 static int stats_dump_errors_to_buffer(struct stream_interface *si);
 static int stats_table_request(struct stream_interface *si, int show);
 
@@ -156,7 +154,6 @@
 	"  show info      : report information about the running process\n"
 	"  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"
@@ -1110,23 +1107,6 @@
 			appctx->st2 = STAT_ST_INIT;
 			appctx->st0 = STAT_CLI_O_INFO; // stats_dump_info_to_buffer
 		}
-		else if (strcmp(args[1], "sess") == 0) {
-			appctx->st2 = STAT_ST_INIT;
-			if (strm_li(s)->bind_conf->level < ACCESS_LVL_OPER) {
-				appctx->ctx.cli.msg = stats_permission_denied_msg;
-				appctx->st0 = STAT_CLI_PRINT;
-				return 1;
-			}
-			if (*args[2] && strcmp(args[2], "all") == 0)
-				appctx->ctx.sess.target = (void *)-1;
-			else if (*args[2])
-				appctx->ctx.sess.target = (void *)strtoul(args[2], NULL, 0);
-			else
-				appctx->ctx.sess.target = NULL;
-			appctx->ctx.sess.section = 0; /* start with stream status */
-			appctx->ctx.sess.pos = 0;
-			appctx->st0 = STAT_CLI_O_SESS; // stats_dump_sess_to_buffer
-		}
 		else if (strcmp(args[1], "errors") == 0) {
 			if (strm_li(s)->bind_conf->level < ACCESS_LVL_OPER) {
 				appctx->ctx.cli.msg = stats_permission_denied_msg;
@@ -1914,10 +1894,6 @@
 				if (stats_dump_stat_to_buffer(si, NULL))
 					appctx->st0 = STAT_CLI_PROMPT;
 				break;
-			case STAT_CLI_O_SESS:
-				if (stats_dump_sess_to_buffer(si))
-					appctx->st0 = STAT_CLI_PROMPT;
-				break;
 			case STAT_CLI_O_ERR:	/* errors dump */
 				if (stats_dump_errors_to_buffer(si))
 					appctx->st0 = STAT_CLI_PROMPT;
@@ -2195,594 +2171,11 @@
 			si_applet_cant_put(si);
 			return 0;
 		}
-	}
-
-	return 1;
-}
-
-static inline const char *get_conn_ctrl_name(const struct connection *conn)
-{
-	if (!conn_ctrl_ready(conn))
-		return "NONE";
-	return conn->ctrl->name;
-}
-
-static inline const char *get_conn_xprt_name(const struct connection *conn)
-{
-	static char ptr[19];
-
-	if (!conn_xprt_ready(conn))
-		return "NONE";
-
-	if (conn->xprt == &raw_sock)
-		return "RAW";
-
-#ifdef USE_OPENSSL
-	if (conn->xprt == &ssl_sock)
-		return "SSL";
-#endif
-	snprintf(ptr, sizeof(ptr), "%p", conn->xprt);
-	return ptr;
-}
-
-static inline const char *get_conn_data_name(const struct connection *conn)
-{
-	static char ptr[19];
-
-	if (!conn->data)
-		return "NONE";
-
-	if (conn->data == &sess_conn_cb)
-		return "SESS";
-
-	if (conn->data == &si_conn_cb)
-		return "STRM";
-
-	if (conn->data == &check_conn_cb)
-		return "CHCK";
-
-	snprintf(ptr, sizeof(ptr), "%p", conn->data);
-	return ptr;
-}
-
-/* This function dumps a complete stream state onto the stream interface's
- * read buffer. The stream has to be set in sess->target. It returns
- * 0 if the output buffer is full and it needs to be called again, otherwise
- * non-zero. It is designed to be called from stats_dump_sess_to_buffer() below.
- */
-static int stats_dump_full_sess_to_buffer(struct stream_interface *si, struct stream *sess)
-{
-	struct appctx *appctx = __objt_appctx(si->end);
-	struct tm tm;
-	extern const char *monthname[12];
-	char pn[INET6_ADDRSTRLEN];
-	struct connection *conn;
-	struct appctx *tmpctx;
-
-	chunk_reset(&trash);
-
-	if (appctx->ctx.sess.section > 0 && appctx->ctx.sess.uid != sess->uniq_id) {
-		/* stream changed, no need to go any further */
-		chunk_appendf(&trash, "  *** session terminated while we were watching it ***\n");
-		if (bi_putchk(si_ic(si), &trash) == -1) {
-			si_applet_cant_put(si);
-			return 0;
-		}
-		appctx->ctx.sess.uid = 0;
-		appctx->ctx.sess.section = 0;
-		return 1;
 	}
 
-	switch (appctx->ctx.sess.section) {
-	case 0: /* main status of the stream */
-		appctx->ctx.sess.uid = sess->uniq_id;
-		appctx->ctx.sess.section = 1;
-		/* fall through */
-
-	case 1:
-		get_localtime(sess->logs.accept_date.tv_sec, &tm);
-		chunk_appendf(&trash,
-			     "%p: [%02d/%s/%04d:%02d:%02d:%02d.%06d] id=%u proto=%s",
-			     sess,
-			     tm.tm_mday, monthname[tm.tm_mon], tm.tm_year+1900,
-			     tm.tm_hour, tm.tm_min, tm.tm_sec, (int)(sess->logs.accept_date.tv_usec),
-			     sess->uniq_id,
-			     strm_li(sess) ? strm_li(sess)->proto->name : "?");
-
-		conn = objt_conn(strm_orig(sess));
-		switch (conn ? addr_to_str(&conn->addr.from, pn, sizeof(pn)) : AF_UNSPEC) {
-		case AF_INET:
-		case AF_INET6:
-			chunk_appendf(&trash, " source=%s:%d\n",
-			              pn, get_host_port(&conn->addr.from));
-			break;
-		case AF_UNIX:
-			chunk_appendf(&trash, " source=unix:%d\n", strm_li(sess)->luid);
-			break;
-		default:
-			/* no more information to print right now */
-			chunk_appendf(&trash, "\n");
-			break;
-		}
-
-		chunk_appendf(&trash,
-			     "  flags=0x%x, conn_retries=%d, srv_conn=%p, pend_pos=%p\n",
-			     sess->flags, sess->si[1].conn_retries, sess->srv_conn, sess->pend_pos);
-
-		chunk_appendf(&trash,
-			     "  frontend=%s (id=%u mode=%s), listener=%s (id=%u)",
-			     strm_fe(sess)->id, strm_fe(sess)->uuid, strm_fe(sess)->mode ? "http" : "tcp",
-			     strm_li(sess) ? strm_li(sess)->name ? strm_li(sess)->name : "?" : "?",
-			     strm_li(sess) ? strm_li(sess)->luid : 0);
-
-		if (conn)
-			conn_get_to_addr(conn);
-
-		switch (conn ? addr_to_str(&conn->addr.to, pn, sizeof(pn)) : AF_UNSPEC) {
-		case AF_INET:
-		case AF_INET6:
-			chunk_appendf(&trash, " addr=%s:%d\n",
-				     pn, get_host_port(&conn->addr.to));
-			break;
-		case AF_UNIX:
-			chunk_appendf(&trash, " addr=unix:%d\n", strm_li(sess)->luid);
-			break;
-		default:
-			/* no more information to print right now */
-			chunk_appendf(&trash, "\n");
-			break;
-		}
-
-		if (sess->be->cap & PR_CAP_BE)
-			chunk_appendf(&trash,
-				     "  backend=%s (id=%u mode=%s)",
-				     sess->be->id,
-				     sess->be->uuid, sess->be->mode ? "http" : "tcp");
-		else
-			chunk_appendf(&trash, "  backend=<NONE> (id=-1 mode=-)");
-
-		conn = objt_conn(sess->si[1].end);
-		if (conn)
-			conn_get_from_addr(conn);
-
-		switch (conn ? addr_to_str(&conn->addr.from, pn, sizeof(pn)) : AF_UNSPEC) {
-		case AF_INET:
-		case AF_INET6:
-			chunk_appendf(&trash, " addr=%s:%d\n",
-				     pn, get_host_port(&conn->addr.from));
-			break;
-		case AF_UNIX:
-			chunk_appendf(&trash, " addr=unix\n");
-			break;
-		default:
-			/* no more information to print right now */
-			chunk_appendf(&trash, "\n");
-			break;
-		}
-
-		if (sess->be->cap & PR_CAP_BE)
-			chunk_appendf(&trash,
-				     "  server=%s (id=%u)",
-				     objt_server(sess->target) ? objt_server(sess->target)->id : "<none>",
-				     objt_server(sess->target) ? objt_server(sess->target)->puid : 0);
-		else
-			chunk_appendf(&trash, "  server=<NONE> (id=-1)");
-
-		if (conn)
-			conn_get_to_addr(conn);
-
-		switch (conn ? addr_to_str(&conn->addr.to, pn, sizeof(pn)) : AF_UNSPEC) {
-		case AF_INET:
-		case AF_INET6:
-			chunk_appendf(&trash, " addr=%s:%d\n",
-				     pn, get_host_port(&conn->addr.to));
-			break;
-		case AF_UNIX:
-			chunk_appendf(&trash, " addr=unix\n");
-			break;
-		default:
-			/* no more information to print right now */
-			chunk_appendf(&trash, "\n");
-			break;
-		}
-
-		chunk_appendf(&trash,
-			     "  task=%p (state=0x%02x nice=%d calls=%d exp=%s%s",
-			     sess->task,
-			     sess->task->state,
-			     sess->task->nice, sess->task->calls,
-			     sess->task->expire ?
-			             tick_is_expired(sess->task->expire, now_ms) ? "<PAST>" :
-			                     human_time(TICKS_TO_MS(sess->task->expire - now_ms),
-			                     TICKS_TO_MS(1000)) : "<NEVER>",
-			     task_in_rq(sess->task) ? ", running" : "");
-
-		chunk_appendf(&trash,
-			     " age=%s)\n",
-			     human_time(now.tv_sec - sess->logs.accept_date.tv_sec, 1));
-
-		if (sess->txn)
-			chunk_appendf(&trash,
-			     "  txn=%p flags=0x%x meth=%d status=%d req.st=%s rsp.st=%s waiting=%d\n",
-			      sess->txn, sess->txn->flags, sess->txn->meth, sess->txn->status,
-			      http_msg_state_str(sess->txn->req.msg_state), http_msg_state_str(sess->txn->rsp.msg_state), !LIST_ISEMPTY(&sess->buffer_wait));
-
-		chunk_appendf(&trash,
-			     "  si[0]=%p (state=%s flags=0x%02x endp0=%s:%p exp=%s, et=0x%03x)\n",
-			     &sess->si[0],
-			     si_state_str(sess->si[0].state),
-			     sess->si[0].flags,
-			     obj_type_name(sess->si[0].end),
-			     obj_base_ptr(sess->si[0].end),
-			     sess->si[0].exp ?
-			             tick_is_expired(sess->si[0].exp, now_ms) ? "<PAST>" :
-			                     human_time(TICKS_TO_MS(sess->si[0].exp - now_ms),
-			                     TICKS_TO_MS(1000)) : "<NEVER>",
-			     sess->si[0].err_type);
-
-		chunk_appendf(&trash,
-			     "  si[1]=%p (state=%s flags=0x%02x endp1=%s:%p exp=%s, et=0x%03x)\n",
-			     &sess->si[1],
-			     si_state_str(sess->si[1].state),
-			     sess->si[1].flags,
-			     obj_type_name(sess->si[1].end),
-			     obj_base_ptr(sess->si[1].end),
-			     sess->si[1].exp ?
-			             tick_is_expired(sess->si[1].exp, now_ms) ? "<PAST>" :
-			                     human_time(TICKS_TO_MS(sess->si[1].exp - now_ms),
-			                     TICKS_TO_MS(1000)) : "<NEVER>",
-			     sess->si[1].err_type);
-
-		if ((conn = objt_conn(sess->si[0].end)) != NULL) {
-			chunk_appendf(&trash,
-			              "  co0=%p ctrl=%s xprt=%s data=%s target=%s:%p\n",
-				      conn,
-				      get_conn_ctrl_name(conn),
-				      get_conn_xprt_name(conn),
-				      get_conn_data_name(conn),
-			              obj_type_name(conn->target),
-			              obj_base_ptr(conn->target));
-
-			chunk_appendf(&trash, "      flags=0x%08x", conn->flags);
-
-			if (conn->t.sock.fd >= 0) {
-				chunk_appendf(&trash, " fd=%d fd.state=%02x fd.cache=%d updt=%d\n",
-				              conn->t.sock.fd, fdtab[conn->t.sock.fd].state,
-				              fdtab[conn->t.sock.fd].cache, fdtab[conn->t.sock.fd].updated);
-			}
-			else
-				chunk_appendf(&trash, " fd=<dead>\n");
-		}
-		else if ((tmpctx = objt_appctx(sess->si[0].end)) != NULL) {
-			chunk_appendf(&trash,
-			              "  app0=%p st0=%d st1=%d st2=%d applet=%s\n",
-				      tmpctx,
-				      tmpctx->st0,
-				      tmpctx->st1,
-				      tmpctx->st2,
-			              tmpctx->applet->name);
-		}
-
-		if ((conn = objt_conn(sess->si[1].end)) != NULL) {
-			chunk_appendf(&trash,
-			              "  co1=%p ctrl=%s xprt=%s data=%s target=%s:%p\n",
-				      conn,
-				      get_conn_ctrl_name(conn),
-				      get_conn_xprt_name(conn),
-				      get_conn_data_name(conn),
-			              obj_type_name(conn->target),
-			              obj_base_ptr(conn->target));
-
-			chunk_appendf(&trash, "      flags=0x%08x", conn->flags);
-
-			if (conn->t.sock.fd >= 0) {
-				chunk_appendf(&trash, " fd=%d fd.state=%02x fd.cache=%d updt=%d\n",
-				              conn->t.sock.fd, fdtab[conn->t.sock.fd].state,
-				              fdtab[conn->t.sock.fd].cache, fdtab[conn->t.sock.fd].updated);
-			}
-			else
-				chunk_appendf(&trash, " fd=<dead>\n");
-		}
-		else if ((tmpctx = objt_appctx(sess->si[1].end)) != NULL) {
-			chunk_appendf(&trash,
-			              "  app1=%p st0=%d st1=%d st2=%d applet=%s\n",
-				      tmpctx,
-				      tmpctx->st0,
-				      tmpctx->st1,
-				      tmpctx->st2,
-			              tmpctx->applet->name);
-		}
-
-		chunk_appendf(&trash,
-			     "  req=%p (f=0x%06x an=0x%x pipe=%d tofwd=%d total=%lld)\n"
-			     "      an_exp=%s",
-			     &sess->req,
-			     sess->req.flags, sess->req.analysers,
-			     sess->req.pipe ? sess->req.pipe->data : 0,
-			     sess->req.to_forward, sess->req.total,
-			     sess->req.analyse_exp ?
-			     human_time(TICKS_TO_MS(sess->req.analyse_exp - now_ms),
-					TICKS_TO_MS(1000)) : "<NEVER>");
-
-		chunk_appendf(&trash,
-			     " rex=%s",
-			     sess->req.rex ?
-			     human_time(TICKS_TO_MS(sess->req.rex - now_ms),
-					TICKS_TO_MS(1000)) : "<NEVER>");
-
-		chunk_appendf(&trash,
-			     " wex=%s\n"
-			     "      buf=%p data=%p o=%d p=%d req.next=%d i=%d size=%d\n",
-			     sess->req.wex ?
-			     human_time(TICKS_TO_MS(sess->req.wex - now_ms),
-					TICKS_TO_MS(1000)) : "<NEVER>",
-			     sess->req.buf,
-			     sess->req.buf->data, sess->req.buf->o,
-			     (int)(sess->req.buf->p - sess->req.buf->data),
-			     sess->txn ? sess->txn->req.next : 0, sess->req.buf->i,
-			     sess->req.buf->size);
-
-		chunk_appendf(&trash,
-			     "  res=%p (f=0x%06x an=0x%x pipe=%d tofwd=%d total=%lld)\n"
-			     "      an_exp=%s",
-			     &sess->res,
-			     sess->res.flags, sess->res.analysers,
-			     sess->res.pipe ? sess->res.pipe->data : 0,
-			     sess->res.to_forward, sess->res.total,
-			     sess->res.analyse_exp ?
-			     human_time(TICKS_TO_MS(sess->res.analyse_exp - now_ms),
-					TICKS_TO_MS(1000)) : "<NEVER>");
-
-		chunk_appendf(&trash,
-			     " rex=%s",
-			     sess->res.rex ?
-			     human_time(TICKS_TO_MS(sess->res.rex - now_ms),
-					TICKS_TO_MS(1000)) : "<NEVER>");
-
-		chunk_appendf(&trash,
-			     " wex=%s\n"
-			     "      buf=%p data=%p o=%d p=%d rsp.next=%d i=%d size=%d\n",
-			     sess->res.wex ?
-			     human_time(TICKS_TO_MS(sess->res.wex - now_ms),
-					TICKS_TO_MS(1000)) : "<NEVER>",
-			     sess->res.buf,
-			     sess->res.buf->data, sess->res.buf->o,
-			     (int)(sess->res.buf->p - sess->res.buf->data),
-			     sess->txn ? sess->txn->rsp.next : 0, sess->res.buf->i,
-			     sess->res.buf->size);
-
-		if (bi_putchk(si_ic(si), &trash) == -1) {
-			si_applet_cant_put(si);
-			return 0;
-		}
-
-		/* use other states to dump the contents */
-	}
-	/* end of dump */
-	appctx->ctx.sess.uid = 0;
-	appctx->ctx.sess.section = 0;
 	return 1;
 }
 
-/* This function dumps all streams' states onto the stream interface's
- * read buffer. It returns 0 if the output buffer is full and it needs
- * to be called again, otherwise non-zero. It is designed to be called
- * from stats_dump_sess_to_buffer() below.
- */
-static int stats_dump_sess_to_buffer(struct stream_interface *si)
-{
-	struct appctx *appctx = __objt_appctx(si->end);
-	struct connection *conn;
-
-	if (unlikely(si_ic(si)->flags & (CF_WRITE_ERROR|CF_SHUTW))) {
-		/* If we're forced to shut down, we might have to remove our
-		 * reference to the last stream being dumped.
-		 */
-		if (appctx->st2 == STAT_ST_LIST) {
-			if (!LIST_ISEMPTY(&appctx->ctx.sess.bref.users)) {
-				LIST_DEL(&appctx->ctx.sess.bref.users);
-				LIST_INIT(&appctx->ctx.sess.bref.users);
-			}
-		}
-		return 1;
-	}
-
-	chunk_reset(&trash);
-
-	switch (appctx->st2) {
-	case STAT_ST_INIT:
-		/* the function had not been called yet, let's prepare the
-		 * buffer for a response. We initialize the current stream
-		 * pointer to the first in the global list. When a target
-		 * stream is being destroyed, it is responsible for updating
-		 * this pointer. We know we have reached the end when this
-		 * pointer points back to the head of the streams list.
-		 */
-		LIST_INIT(&appctx->ctx.sess.bref.users);
-		appctx->ctx.sess.bref.ref = streams.n;
-		appctx->st2 = STAT_ST_LIST;
-		/* fall through */
-
-	case STAT_ST_LIST:
-		/* first, let's detach the back-ref from a possible previous stream */
-		if (!LIST_ISEMPTY(&appctx->ctx.sess.bref.users)) {
-			LIST_DEL(&appctx->ctx.sess.bref.users);
-			LIST_INIT(&appctx->ctx.sess.bref.users);
-		}
-
-		/* and start from where we stopped */
-		while (appctx->ctx.sess.bref.ref != &streams) {
-			char pn[INET6_ADDRSTRLEN];
-			struct stream *curr_sess;
-
-			curr_sess = LIST_ELEM(appctx->ctx.sess.bref.ref, struct stream *, list);
-
-			if (appctx->ctx.sess.target) {
-				if (appctx->ctx.sess.target != (void *)-1 && appctx->ctx.sess.target != curr_sess)
-					goto next_sess;
-
-				LIST_ADDQ(&curr_sess->back_refs, &appctx->ctx.sess.bref.users);
-				/* call the proper dump() function and return if we're missing space */
-				if (!stats_dump_full_sess_to_buffer(si, curr_sess))
-					return 0;
-
-				/* stream dump complete */
-				LIST_DEL(&appctx->ctx.sess.bref.users);
-				LIST_INIT(&appctx->ctx.sess.bref.users);
-				if (appctx->ctx.sess.target != (void *)-1) {
-					appctx->ctx.sess.target = NULL;
-					break;
-				}
-				else
-					goto next_sess;
-			}
-
-			chunk_appendf(&trash,
-				     "%p: proto=%s",
-				     curr_sess,
-				     strm_li(curr_sess) ? strm_li(curr_sess)->proto->name : "?");
-
-			conn = objt_conn(strm_orig(curr_sess));
-			switch (conn ? addr_to_str(&conn->addr.from, pn, sizeof(pn)) : AF_UNSPEC) {
-			case AF_INET:
-			case AF_INET6:
-				chunk_appendf(&trash,
-					     " src=%s:%d fe=%s be=%s srv=%s",
-					     pn,
-					     get_host_port(&conn->addr.from),
-					     strm_fe(curr_sess)->id,
-					     (curr_sess->be->cap & PR_CAP_BE) ? curr_sess->be->id : "<NONE>",
-					     objt_server(curr_sess->target) ? objt_server(curr_sess->target)->id : "<none>"
-					     );
-				break;
-			case AF_UNIX:
-				chunk_appendf(&trash,
-					     " src=unix:%d fe=%s be=%s srv=%s",
-					     strm_li(curr_sess)->luid,
-					     strm_fe(curr_sess)->id,
-					     (curr_sess->be->cap & PR_CAP_BE) ? curr_sess->be->id : "<NONE>",
-					     objt_server(curr_sess->target) ? objt_server(curr_sess->target)->id : "<none>"
-					     );
-				break;
-			}
-
-			chunk_appendf(&trash,
-				     " ts=%02x age=%s calls=%d",
-				     curr_sess->task->state,
-				     human_time(now.tv_sec - curr_sess->logs.tv_accept.tv_sec, 1),
-				     curr_sess->task->calls);
-
-			chunk_appendf(&trash,
-				     " rq[f=%06xh,i=%d,an=%02xh,rx=%s",
-				     curr_sess->req.flags,
-				     curr_sess->req.buf->i,
-				     curr_sess->req.analysers,
-				     curr_sess->req.rex ?
-				     human_time(TICKS_TO_MS(curr_sess->req.rex - now_ms),
-						TICKS_TO_MS(1000)) : "");
-
-			chunk_appendf(&trash,
-				     ",wx=%s",
-				     curr_sess->req.wex ?
-				     human_time(TICKS_TO_MS(curr_sess->req.wex - now_ms),
-						TICKS_TO_MS(1000)) : "");
-
-			chunk_appendf(&trash,
-				     ",ax=%s]",
-				     curr_sess->req.analyse_exp ?
-				     human_time(TICKS_TO_MS(curr_sess->req.analyse_exp - now_ms),
-						TICKS_TO_MS(1000)) : "");
-
-			chunk_appendf(&trash,
-				     " rp[f=%06xh,i=%d,an=%02xh,rx=%s",
-				     curr_sess->res.flags,
-				     curr_sess->res.buf->i,
-				     curr_sess->res.analysers,
-				     curr_sess->res.rex ?
-				     human_time(TICKS_TO_MS(curr_sess->res.rex - now_ms),
-						TICKS_TO_MS(1000)) : "");
-
-			chunk_appendf(&trash,
-				     ",wx=%s",
-				     curr_sess->res.wex ?
-				     human_time(TICKS_TO_MS(curr_sess->res.wex - now_ms),
-						TICKS_TO_MS(1000)) : "");
-
-			chunk_appendf(&trash,
-				     ",ax=%s]",
-				     curr_sess->res.analyse_exp ?
-				     human_time(TICKS_TO_MS(curr_sess->res.analyse_exp - now_ms),
-						TICKS_TO_MS(1000)) : "");
-
-			conn = objt_conn(curr_sess->si[0].end);
-			chunk_appendf(&trash,
-				     " s0=[%d,%1xh,fd=%d,ex=%s]",
-				     curr_sess->si[0].state,
-				     curr_sess->si[0].flags,
-				     (conn && conn->t.sock.fd >= 0) ? conn->t.sock.fd : -1,
-				     curr_sess->si[0].exp ?
-				     human_time(TICKS_TO_MS(curr_sess->si[0].exp - now_ms),
-						TICKS_TO_MS(1000)) : "");
-
-			conn = objt_conn(curr_sess->si[1].end);
-			chunk_appendf(&trash,
-				     " s1=[%d,%1xh,fd=%d,ex=%s]",
-				     curr_sess->si[1].state,
-				     curr_sess->si[1].flags,
-				     (conn && conn->t.sock.fd >= 0) ? conn->t.sock.fd : -1,
-				     curr_sess->si[1].exp ?
-				     human_time(TICKS_TO_MS(curr_sess->si[1].exp - now_ms),
-						TICKS_TO_MS(1000)) : "");
-
-			chunk_appendf(&trash,
-				     " exp=%s",
-				     curr_sess->task->expire ?
-				     human_time(TICKS_TO_MS(curr_sess->task->expire - now_ms),
-						TICKS_TO_MS(1000)) : "");
-			if (task_in_rq(curr_sess->task))
-				chunk_appendf(&trash, " run(nice=%d)", curr_sess->task->nice);
-
-			chunk_appendf(&trash, "\n");
-
-			if (bi_putchk(si_ic(si), &trash) == -1) {
-				/* let's try again later from this stream. We add ourselves into
-				 * this stream's users so that it can remove us upon termination.
-				 */
-				si_applet_cant_put(si);
-				LIST_ADDQ(&curr_sess->back_refs, &appctx->ctx.sess.bref.users);
-				return 0;
-			}
-
-		next_sess:
-			appctx->ctx.sess.bref.ref = curr_sess->list.n;
-		}
-
-		if (appctx->ctx.sess.target && appctx->ctx.sess.target != (void *)-1) {
-			/* specified stream not found */
-			if (appctx->ctx.sess.section > 0)
-				chunk_appendf(&trash, "  *** session terminated while we were watching it ***\n");
-			else
-				chunk_appendf(&trash, "Session not found.\n");
-
-			if (bi_putchk(si_ic(si), &trash) == -1) {
-				si_applet_cant_put(si);
-				return 0;
-			}
-
-			appctx->ctx.sess.target = NULL;
-			appctx->ctx.sess.uid = 0;
-			return 1;
-		}
-
-		appctx->st2 = STAT_ST_FIN;
-		/* fall through */
-
-	default:
-		appctx->st2 = STAT_ST_FIN;
-		return 1;
-	}
-}
-
 /* This is called when the stream interface is closed. For instance, upon an
  * external abort, we won't call the i/o handler anymore so we may need to
  * remove back references to the stream currently being dumped.
@@ -2793,10 +2186,6 @@
 		appctx->io_release(appctx);
 		appctx->io_release = NULL;
 	}
-	if (appctx->st0 == STAT_CLI_O_SESS && appctx->st2 == STAT_ST_LIST) {
-		if (!LIST_ISEMPTY(&appctx->ctx.sess.bref.users))
-			LIST_DEL(&appctx->ctx.sess.bref.users);
-	}
 	else if ((appctx->st0 == STAT_CLI_O_TAB || appctx->st0 == STAT_CLI_O_CLR) &&
 		 appctx->st2 == STAT_ST_LIST) {
 		appctx->ctx.table.entry->ref_cnt--;
diff --git a/src/stream.c b/src/stream.c
index 346e7b6..0d98745 100644
--- a/src/stream.c
+++ b/src/stream.c
@@ -22,6 +22,7 @@
 
 #include <types/applet.h>
 #include <types/capture.h>
+#include <types/cli.h>
 #include <types/filters.h>
 #include <types/global.h>
 #include <types/stats.h>
@@ -32,6 +33,7 @@
 #include <proto/backend.h>
 #include <proto/channel.h>
 #include <proto/checks.h>
+#include <proto/cli.h>
 #include <proto/connection.h>
 #include <proto/stats.h>
 #include <proto/fd.h>
@@ -3350,6 +3352,574 @@
 	LIST_ADDQ(&service_keywords, &kw_list->list);
 }
 
+
+/* This function dumps a complete stream state onto the stream interface's
+ * read buffer. The stream has to be set in strm. It returns 0 if the output
+ * buffer is full and it needs to be called again, otherwise non-zero. It is
+ * designed to be called from stats_dump_strm_to_buffer() below.
+ */
+static int stats_dump_full_strm_to_buffer(struct stream_interface *si, struct stream *strm)
+{
+	struct appctx *appctx = __objt_appctx(si->end);
+	struct tm tm;
+	extern const char *monthname[12];
+	char pn[INET6_ADDRSTRLEN];
+	struct connection *conn;
+	struct appctx *tmpctx;
+
+	chunk_reset(&trash);
+
+	if (appctx->ctx.sess.section > 0 && appctx->ctx.sess.uid != strm->uniq_id) {
+		/* stream changed, no need to go any further */
+		chunk_appendf(&trash, "  *** session terminated while we were watching it ***\n");
+		if (bi_putchk(si_ic(si), &trash) == -1) {
+			si_applet_cant_put(si);
+			return 0;
+		}
+		appctx->ctx.sess.uid = 0;
+		appctx->ctx.sess.section = 0;
+		return 1;
+	}
+
+	switch (appctx->ctx.sess.section) {
+	case 0: /* main status of the stream */
+		appctx->ctx.sess.uid = strm->uniq_id;
+		appctx->ctx.sess.section = 1;
+		/* fall through */
+
+	case 1:
+		get_localtime(strm->logs.accept_date.tv_sec, &tm);
+		chunk_appendf(&trash,
+			     "%p: [%02d/%s/%04d:%02d:%02d:%02d.%06d] id=%u proto=%s",
+			     strm,
+			     tm.tm_mday, monthname[tm.tm_mon], tm.tm_year+1900,
+			     tm.tm_hour, tm.tm_min, tm.tm_sec, (int)(strm->logs.accept_date.tv_usec),
+			     strm->uniq_id,
+			     strm_li(strm) ? strm_li(strm)->proto->name : "?");
+
+		conn = objt_conn(strm_orig(strm));
+		switch (conn ? addr_to_str(&conn->addr.from, pn, sizeof(pn)) : AF_UNSPEC) {
+		case AF_INET:
+		case AF_INET6:
+			chunk_appendf(&trash, " source=%s:%d\n",
+			              pn, get_host_port(&conn->addr.from));
+			break;
+		case AF_UNIX:
+			chunk_appendf(&trash, " source=unix:%d\n", strm_li(strm)->luid);
+			break;
+		default:
+			/* no more information to print right now */
+			chunk_appendf(&trash, "\n");
+			break;
+		}
+
+		chunk_appendf(&trash,
+			     "  flags=0x%x, conn_retries=%d, srv_conn=%p, pend_pos=%p\n",
+			     strm->flags, strm->si[1].conn_retries, strm->srv_conn, strm->pend_pos);
+
+		chunk_appendf(&trash,
+			     "  frontend=%s (id=%u mode=%s), listener=%s (id=%u)",
+			     strm_fe(strm)->id, strm_fe(strm)->uuid, strm_fe(strm)->mode ? "http" : "tcp",
+			     strm_li(strm) ? strm_li(strm)->name ? strm_li(strm)->name : "?" : "?",
+			     strm_li(strm) ? strm_li(strm)->luid : 0);
+
+		if (conn)
+			conn_get_to_addr(conn);
+
+		switch (conn ? addr_to_str(&conn->addr.to, pn, sizeof(pn)) : AF_UNSPEC) {
+		case AF_INET:
+		case AF_INET6:
+			chunk_appendf(&trash, " addr=%s:%d\n",
+				     pn, get_host_port(&conn->addr.to));
+			break;
+		case AF_UNIX:
+			chunk_appendf(&trash, " addr=unix:%d\n", strm_li(strm)->luid);
+			break;
+		default:
+			/* no more information to print right now */
+			chunk_appendf(&trash, "\n");
+			break;
+		}
+
+		if (strm->be->cap & PR_CAP_BE)
+			chunk_appendf(&trash,
+				     "  backend=%s (id=%u mode=%s)",
+				     strm->be->id,
+				     strm->be->uuid, strm->be->mode ? "http" : "tcp");
+		else
+			chunk_appendf(&trash, "  backend=<NONE> (id=-1 mode=-)");
+
+		conn = objt_conn(strm->si[1].end);
+		if (conn)
+			conn_get_from_addr(conn);
+
+		switch (conn ? addr_to_str(&conn->addr.from, pn, sizeof(pn)) : AF_UNSPEC) {
+		case AF_INET:
+		case AF_INET6:
+			chunk_appendf(&trash, " addr=%s:%d\n",
+				     pn, get_host_port(&conn->addr.from));
+			break;
+		case AF_UNIX:
+			chunk_appendf(&trash, " addr=unix\n");
+			break;
+		default:
+			/* no more information to print right now */
+			chunk_appendf(&trash, "\n");
+			break;
+		}
+
+		if (strm->be->cap & PR_CAP_BE)
+			chunk_appendf(&trash,
+				     "  server=%s (id=%u)",
+				     objt_server(strm->target) ? objt_server(strm->target)->id : "<none>",
+				     objt_server(strm->target) ? objt_server(strm->target)->puid : 0);
+		else
+			chunk_appendf(&trash, "  server=<NONE> (id=-1)");
+
+		if (conn)
+			conn_get_to_addr(conn);
+
+		switch (conn ? addr_to_str(&conn->addr.to, pn, sizeof(pn)) : AF_UNSPEC) {
+		case AF_INET:
+		case AF_INET6:
+			chunk_appendf(&trash, " addr=%s:%d\n",
+				     pn, get_host_port(&conn->addr.to));
+			break;
+		case AF_UNIX:
+			chunk_appendf(&trash, " addr=unix\n");
+			break;
+		default:
+			/* no more information to print right now */
+			chunk_appendf(&trash, "\n");
+			break;
+		}
+
+		chunk_appendf(&trash,
+			     "  task=%p (state=0x%02x nice=%d calls=%d exp=%s%s",
+			     strm->task,
+			     strm->task->state,
+			     strm->task->nice, strm->task->calls,
+			     strm->task->expire ?
+			             tick_is_expired(strm->task->expire, now_ms) ? "<PAST>" :
+			                     human_time(TICKS_TO_MS(strm->task->expire - now_ms),
+			                     TICKS_TO_MS(1000)) : "<NEVER>",
+			     task_in_rq(strm->task) ? ", running" : "");
+
+		chunk_appendf(&trash,
+			     " age=%s)\n",
+			     human_time(now.tv_sec - strm->logs.accept_date.tv_sec, 1));
+
+		if (strm->txn)
+			chunk_appendf(&trash,
+			     "  txn=%p flags=0x%x meth=%d status=%d req.st=%s rsp.st=%s waiting=%d\n",
+			      strm->txn, strm->txn->flags, strm->txn->meth, strm->txn->status,
+			      http_msg_state_str(strm->txn->req.msg_state), http_msg_state_str(strm->txn->rsp.msg_state), !LIST_ISEMPTY(&strm->buffer_wait));
+
+		chunk_appendf(&trash,
+			     "  si[0]=%p (state=%s flags=0x%02x endp0=%s:%p exp=%s, et=0x%03x)\n",
+			     &strm->si[0],
+			     si_state_str(strm->si[0].state),
+			     strm->si[0].flags,
+			     obj_type_name(strm->si[0].end),
+			     obj_base_ptr(strm->si[0].end),
+			     strm->si[0].exp ?
+			             tick_is_expired(strm->si[0].exp, now_ms) ? "<PAST>" :
+			                     human_time(TICKS_TO_MS(strm->si[0].exp - now_ms),
+			                     TICKS_TO_MS(1000)) : "<NEVER>",
+			     strm->si[0].err_type);
+
+		chunk_appendf(&trash,
+			     "  si[1]=%p (state=%s flags=0x%02x endp1=%s:%p exp=%s, et=0x%03x)\n",
+			     &strm->si[1],
+			     si_state_str(strm->si[1].state),
+			     strm->si[1].flags,
+			     obj_type_name(strm->si[1].end),
+			     obj_base_ptr(strm->si[1].end),
+			     strm->si[1].exp ?
+			             tick_is_expired(strm->si[1].exp, now_ms) ? "<PAST>" :
+			                     human_time(TICKS_TO_MS(strm->si[1].exp - now_ms),
+			                     TICKS_TO_MS(1000)) : "<NEVER>",
+			     strm->si[1].err_type);
+
+		if ((conn = objt_conn(strm->si[0].end)) != NULL) {
+			chunk_appendf(&trash,
+			              "  co0=%p ctrl=%s xprt=%s data=%s target=%s:%p\n",
+				      conn,
+				      conn_get_ctrl_name(conn),
+				      conn_get_xprt_name(conn),
+				      conn_get_data_name(conn),
+			              obj_type_name(conn->target),
+			              obj_base_ptr(conn->target));
+
+			chunk_appendf(&trash,
+			              "      flags=0x%08x fd=%d fd.state=%02x fd.cache=%d updt=%d\n",
+			              conn->flags,
+			              conn->t.sock.fd,
+			              conn->t.sock.fd >= 0 ? fdtab[conn->t.sock.fd].state : 0,
+			              conn->t.sock.fd >= 0 ? fdtab[conn->t.sock.fd].cache : 0,
+			              conn->t.sock.fd >= 0 ? fdtab[conn->t.sock.fd].updated : 0);
+		}
+		else if ((tmpctx = objt_appctx(strm->si[0].end)) != NULL) {
+			chunk_appendf(&trash,
+			              "  app0=%p st0=%d st1=%d st2=%d applet=%s\n",
+				      tmpctx,
+				      tmpctx->st0,
+				      tmpctx->st1,
+				      tmpctx->st2,
+			              tmpctx->applet->name);
+		}
+
+		if ((conn = objt_conn(strm->si[1].end)) != NULL) {
+			chunk_appendf(&trash,
+			              "  co1=%p ctrl=%s xprt=%s data=%s target=%s:%p\n",
+				      conn,
+				      conn_get_ctrl_name(conn),
+				      conn_get_xprt_name(conn),
+				      conn_get_data_name(conn),
+			              obj_type_name(conn->target),
+			              obj_base_ptr(conn->target));
+
+			chunk_appendf(&trash,
+			              "      flags=0x%08x fd=%d fd.state=%02x fd.cache=%d updt=%d\n",
+			              conn->flags,
+			              conn->t.sock.fd,
+			              conn->t.sock.fd >= 0 ? fdtab[conn->t.sock.fd].state : 0,
+			              conn->t.sock.fd >= 0 ? fdtab[conn->t.sock.fd].cache : 0,
+			              conn->t.sock.fd >= 0 ? fdtab[conn->t.sock.fd].updated : 0);
+		}
+		else if ((tmpctx = objt_appctx(strm->si[1].end)) != NULL) {
+			chunk_appendf(&trash,
+			              "  app1=%p st0=%d st1=%d st2=%d applet=%s\n",
+				      tmpctx,
+				      tmpctx->st0,
+				      tmpctx->st1,
+				      tmpctx->st2,
+			              tmpctx->applet->name);
+		}
+
+		chunk_appendf(&trash,
+			     "  req=%p (f=0x%06x an=0x%x pipe=%d tofwd=%d total=%lld)\n"
+			     "      an_exp=%s",
+			     &strm->req,
+			     strm->req.flags, strm->req.analysers,
+			     strm->req.pipe ? strm->req.pipe->data : 0,
+			     strm->req.to_forward, strm->req.total,
+			     strm->req.analyse_exp ?
+			     human_time(TICKS_TO_MS(strm->req.analyse_exp - now_ms),
+					TICKS_TO_MS(1000)) : "<NEVER>");
+
+		chunk_appendf(&trash,
+			     " rex=%s",
+			     strm->req.rex ?
+			     human_time(TICKS_TO_MS(strm->req.rex - now_ms),
+					TICKS_TO_MS(1000)) : "<NEVER>");
+
+		chunk_appendf(&trash,
+			     " wex=%s\n"
+			     "      buf=%p data=%p o=%d p=%d req.next=%d i=%d size=%d\n",
+			     strm->req.wex ?
+			     human_time(TICKS_TO_MS(strm->req.wex - now_ms),
+					TICKS_TO_MS(1000)) : "<NEVER>",
+			     strm->req.buf,
+			     strm->req.buf->data, strm->req.buf->o,
+			     (int)(strm->req.buf->p - strm->req.buf->data),
+			     strm->txn ? strm->txn->req.next : 0, strm->req.buf->i,
+			     strm->req.buf->size);
+
+		chunk_appendf(&trash,
+			     "  res=%p (f=0x%06x an=0x%x pipe=%d tofwd=%d total=%lld)\n"
+			     "      an_exp=%s",
+			     &strm->res,
+			     strm->res.flags, strm->res.analysers,
+			     strm->res.pipe ? strm->res.pipe->data : 0,
+			     strm->res.to_forward, strm->res.total,
+			     strm->res.analyse_exp ?
+			     human_time(TICKS_TO_MS(strm->res.analyse_exp - now_ms),
+					TICKS_TO_MS(1000)) : "<NEVER>");
+
+		chunk_appendf(&trash,
+			     " rex=%s",
+			     strm->res.rex ?
+			     human_time(TICKS_TO_MS(strm->res.rex - now_ms),
+					TICKS_TO_MS(1000)) : "<NEVER>");
+
+		chunk_appendf(&trash,
+			     " wex=%s\n"
+			     "      buf=%p data=%p o=%d p=%d rsp.next=%d i=%d size=%d\n",
+			     strm->res.wex ?
+			     human_time(TICKS_TO_MS(strm->res.wex - now_ms),
+					TICKS_TO_MS(1000)) : "<NEVER>",
+			     strm->res.buf,
+			     strm->res.buf->data, strm->res.buf->o,
+			     (int)(strm->res.buf->p - strm->res.buf->data),
+			     strm->txn ? strm->txn->rsp.next : 0, strm->res.buf->i,
+			     strm->res.buf->size);
+
+		if (bi_putchk(si_ic(si), &trash) == -1) {
+			si_applet_cant_put(si);
+			return 0;
+		}
+
+		/* use other states to dump the contents */
+	}
+	/* end of dump */
+	appctx->ctx.sess.uid = 0;
+	appctx->ctx.sess.section = 0;
+	return 1;
+}
+
+
+static int cli_parse_show_sess(char **args, struct appctx *appctx, void *private)
+{
+	appctx->st2 = STAT_ST_INIT;
+	if (!cli_has_level(appctx, ACCESS_LVL_OPER))
+		return 1;
+
+	if (*args[2] && strcmp(args[2], "all") == 0)
+		appctx->ctx.sess.target = (void *)-1;
+	else if (*args[2])
+		appctx->ctx.sess.target = (void *)strtoul(args[2], NULL, 0);
+	else
+		appctx->ctx.sess.target = NULL;
+	appctx->ctx.sess.section = 0; /* start with stream status */
+	appctx->ctx.sess.pos = 0;
+
+	return 0;
+}
+
+/* This function dumps all streams' states onto the stream interface's
+ * read buffer. It returns 0 if the output buffer is full and it needs
+ * to be called again, otherwise non-zero. It is designed to be called
+ * from stats_dump_sess_to_buffer() below.
+ */
+static int cli_io_handler_dump_sess(struct appctx *appctx)
+{
+	struct stream_interface *si = appctx->owner;
+	struct connection *conn;
+
+	if (unlikely(si_ic(si)->flags & (CF_WRITE_ERROR|CF_SHUTW))) {
+		/* If we're forced to shut down, we might have to remove our
+		 * reference to the last stream being dumped.
+		 */
+		if (appctx->st2 == STAT_ST_LIST) {
+			if (!LIST_ISEMPTY(&appctx->ctx.sess.bref.users)) {
+				LIST_DEL(&appctx->ctx.sess.bref.users);
+				LIST_INIT(&appctx->ctx.sess.bref.users);
+			}
+		}
+		return 1;
+	}
+
+	chunk_reset(&trash);
+
+	switch (appctx->st2) {
+	case STAT_ST_INIT:
+		/* the function had not been called yet, let's prepare the
+		 * buffer for a response. We initialize the current stream
+		 * pointer to the first in the global list. When a target
+		 * stream is being destroyed, it is responsible for updating
+		 * this pointer. We know we have reached the end when this
+		 * pointer points back to the head of the streams list.
+		 */
+		LIST_INIT(&appctx->ctx.sess.bref.users);
+		appctx->ctx.sess.bref.ref = streams.n;
+		appctx->st2 = STAT_ST_LIST;
+		/* fall through */
+
+	case STAT_ST_LIST:
+		/* first, let's detach the back-ref from a possible previous stream */
+		if (!LIST_ISEMPTY(&appctx->ctx.sess.bref.users)) {
+			LIST_DEL(&appctx->ctx.sess.bref.users);
+			LIST_INIT(&appctx->ctx.sess.bref.users);
+		}
+
+		/* and start from where we stopped */
+		while (appctx->ctx.sess.bref.ref != &streams) {
+			char pn[INET6_ADDRSTRLEN];
+			struct stream *curr_strm;
+
+			curr_strm = LIST_ELEM(appctx->ctx.sess.bref.ref, struct stream *, list);
+
+			if (appctx->ctx.sess.target) {
+				if (appctx->ctx.sess.target != (void *)-1 && appctx->ctx.sess.target != curr_strm)
+					goto next_sess;
+
+				LIST_ADDQ(&curr_strm->back_refs, &appctx->ctx.sess.bref.users);
+				/* call the proper dump() function and return if we're missing space */
+				if (!stats_dump_full_strm_to_buffer(si, curr_strm))
+					return 0;
+
+				/* stream dump complete */
+				LIST_DEL(&appctx->ctx.sess.bref.users);
+				LIST_INIT(&appctx->ctx.sess.bref.users);
+				if (appctx->ctx.sess.target != (void *)-1) {
+					appctx->ctx.sess.target = NULL;
+					break;
+				}
+				else
+					goto next_sess;
+			}
+
+			chunk_appendf(&trash,
+				     "%p: proto=%s",
+				     curr_strm,
+				     strm_li(curr_strm) ? strm_li(curr_strm)->proto->name : "?");
+
+			conn = objt_conn(strm_orig(curr_strm));
+			switch (conn ? addr_to_str(&conn->addr.from, pn, sizeof(pn)) : AF_UNSPEC) {
+			case AF_INET:
+			case AF_INET6:
+				chunk_appendf(&trash,
+					     " src=%s:%d fe=%s be=%s srv=%s",
+					     pn,
+					     get_host_port(&conn->addr.from),
+					     strm_fe(curr_strm)->id,
+					     (curr_strm->be->cap & PR_CAP_BE) ? curr_strm->be->id : "<NONE>",
+					     objt_server(curr_strm->target) ? objt_server(curr_strm->target)->id : "<none>"
+					     );
+				break;
+			case AF_UNIX:
+				chunk_appendf(&trash,
+					     " src=unix:%d fe=%s be=%s srv=%s",
+					     strm_li(curr_strm)->luid,
+					     strm_fe(curr_strm)->id,
+					     (curr_strm->be->cap & PR_CAP_BE) ? curr_strm->be->id : "<NONE>",
+					     objt_server(curr_strm->target) ? objt_server(curr_strm->target)->id : "<none>"
+					     );
+				break;
+			}
+
+			chunk_appendf(&trash,
+				     " ts=%02x age=%s calls=%d",
+				     curr_strm->task->state,
+				     human_time(now.tv_sec - curr_strm->logs.tv_accept.tv_sec, 1),
+				     curr_strm->task->calls);
+
+			chunk_appendf(&trash,
+				     " rq[f=%06xh,i=%d,an=%02xh,rx=%s",
+				     curr_strm->req.flags,
+				     curr_strm->req.buf->i,
+				     curr_strm->req.analysers,
+				     curr_strm->req.rex ?
+				     human_time(TICKS_TO_MS(curr_strm->req.rex - now_ms),
+						TICKS_TO_MS(1000)) : "");
+
+			chunk_appendf(&trash,
+				     ",wx=%s",
+				     curr_strm->req.wex ?
+				     human_time(TICKS_TO_MS(curr_strm->req.wex - now_ms),
+						TICKS_TO_MS(1000)) : "");
+
+			chunk_appendf(&trash,
+				     ",ax=%s]",
+				     curr_strm->req.analyse_exp ?
+				     human_time(TICKS_TO_MS(curr_strm->req.analyse_exp - now_ms),
+						TICKS_TO_MS(1000)) : "");
+
+			chunk_appendf(&trash,
+				     " rp[f=%06xh,i=%d,an=%02xh,rx=%s",
+				     curr_strm->res.flags,
+				     curr_strm->res.buf->i,
+				     curr_strm->res.analysers,
+				     curr_strm->res.rex ?
+				     human_time(TICKS_TO_MS(curr_strm->res.rex - now_ms),
+						TICKS_TO_MS(1000)) : "");
+
+			chunk_appendf(&trash,
+				     ",wx=%s",
+				     curr_strm->res.wex ?
+				     human_time(TICKS_TO_MS(curr_strm->res.wex - now_ms),
+						TICKS_TO_MS(1000)) : "");
+
+			chunk_appendf(&trash,
+				     ",ax=%s]",
+				     curr_strm->res.analyse_exp ?
+				     human_time(TICKS_TO_MS(curr_strm->res.analyse_exp - now_ms),
+						TICKS_TO_MS(1000)) : "");
+
+			conn = objt_conn(curr_strm->si[0].end);
+			chunk_appendf(&trash,
+				     " s0=[%d,%1xh,fd=%d,ex=%s]",
+				     curr_strm->si[0].state,
+				     curr_strm->si[0].flags,
+				     conn ? conn->t.sock.fd : -1,
+				     curr_strm->si[0].exp ?
+				     human_time(TICKS_TO_MS(curr_strm->si[0].exp - now_ms),
+						TICKS_TO_MS(1000)) : "");
+
+			conn = objt_conn(curr_strm->si[1].end);
+			chunk_appendf(&trash,
+				     " s1=[%d,%1xh,fd=%d,ex=%s]",
+				     curr_strm->si[1].state,
+				     curr_strm->si[1].flags,
+				     conn ? conn->t.sock.fd : -1,
+				     curr_strm->si[1].exp ?
+				     human_time(TICKS_TO_MS(curr_strm->si[1].exp - now_ms),
+						TICKS_TO_MS(1000)) : "");
+
+			chunk_appendf(&trash,
+				     " exp=%s",
+				     curr_strm->task->expire ?
+				     human_time(TICKS_TO_MS(curr_strm->task->expire - now_ms),
+						TICKS_TO_MS(1000)) : "");
+			if (task_in_rq(curr_strm->task))
+				chunk_appendf(&trash, " run(nice=%d)", curr_strm->task->nice);
+
+			chunk_appendf(&trash, "\n");
+
+			if (bi_putchk(si_ic(si), &trash) == -1) {
+				/* let's try again later from this stream. We add ourselves into
+				 * this stream's users so that it can remove us upon termination.
+				 */
+				si_applet_cant_put(si);
+				LIST_ADDQ(&curr_strm->back_refs, &appctx->ctx.sess.bref.users);
+				return 0;
+			}
+
+		next_sess:
+			appctx->ctx.sess.bref.ref = curr_strm->list.n;
+		}
+
+		if (appctx->ctx.sess.target && appctx->ctx.sess.target != (void *)-1) {
+			/* specified stream not found */
+			if (appctx->ctx.sess.section > 0)
+				chunk_appendf(&trash, "  *** session terminated while we were watching it ***\n");
+			else
+				chunk_appendf(&trash, "Session not found.\n");
+
+			if (bi_putchk(si_ic(si), &trash) == -1) {
+				si_applet_cant_put(si);
+				return 0;
+			}
+
+			appctx->ctx.sess.target = NULL;
+			appctx->ctx.sess.uid = 0;
+			return 1;
+		}
+
+		appctx->st2 = STAT_ST_FIN;
+		/* fall through */
+
+	default:
+		appctx->st2 = STAT_ST_FIN;
+		return 1;
+	}
+}
+
+static void cli_release_show_sess(struct appctx *appctx)
+{
+	if (appctx->st2 == STAT_ST_LIST) {
+		if (!LIST_ISEMPTY(&appctx->ctx.sess.bref.users))
+			LIST_DEL(&appctx->ctx.sess.bref.users);
+	}
+}
+
+/* register cli keywords */
+static struct cli_kw_list cli_kws = {{ },{
+	{ { "show", "sess",  NULL }, "show sess [id] : report the list of current sessions or dump this session", cli_parse_show_sess, cli_io_handler_dump_sess, cli_release_show_sess },
+	{{},}
+}};
+
 /* main configuration keyword registration. */
 static struct action_kw_list stream_tcp_keywords = { ILH, {
 	{ "use-service", stream_parse_use_service },
@@ -3483,6 +4053,7 @@
 	acl_register_keywords(&acl_kws);
 	tcp_req_cont_keywords_register(&stream_tcp_keywords);
 	http_req_keywords_register(&stream_http_keywords);
+	cli_register_kw(&cli_kws);
 }
 
 /*