Index | Thread | Search

From:
Rafael Sadowski <rafael@sizeofvoid.org>
Subject:
relayctl: rework output and add -v flag
To:
tech@openbsd.org
Date:
Sat, 1 Aug 2026 11:54:43 +0200

Download raw body.

Thread
  • Rafael Sadowski:

    relayctl: rework output and add -v flag

I find the current output: 1. Pointless by default, the keyword "imsg"
appears in the monitor mode. 2. Unreadable with long names. 3. Inconsistent.
The diff attempts to improve this.

"relayctl show sessions" is a different story. Even using "watch -s 0.2
'doas relayctl show sessions'" is of no use, as sessions are really
short-lived. In my view, this would need to be a monitor. But that would
involve more work.

See the commit message below for impl details.


Current:

$ doas relayctl monitor
sync: imsg type 38 len 0 peerid 4294967295 pid 1
	timestamp: 1785577221, Sat Aug  1 11:40:21 2026
host_status: imsg type 37 len 32 peerid 4294967295 pid 1
	timestamp: 1785577221, Sat Aug  1 11:40:21 2026
	id: 3
	state: down
host_status: imsg type 37 len 32 peerid 4294967295 pid 1
	timestamp: 1785577221, Sat Aug  1 11:40:21 2026
	id: 1
	state: up

(Why someone should be interested in imsg?).

$ doas relayctl show summary
Id  	Type    	Name                    	Avlblty	Status
1   	redirect	db-redirect             	       	down
6   	table   	db:5432                 	       	empty
8   	host    	10.0.23.9               	0.00%  	down
9   	host    	10.0.23.10              	0.00%  	down
2   	redirect	cache-redirect          	       	down
7   	table   	cache2:11211            	       	empty
10  	host    	10.0.23.11              	0.00%  	down
3   	redirect	api-redirect            	       	down
8   	table   	api:9000                	       	empty
11  	host    	10.0.23.8               	0.00%  	down
1   	relay   	https-gateway-way-to-long-name-for-testing	       	active
1   	table   	ap:6443                 	       	active (1 hosts)
1   	host    	10.0.23.6               	100.00%	up
2   	relay   	https-api-gateway       	       	active

$ doas relayctl show hosts
Id  	Type    	Name                    	Avlblty	Status
6   	table   	db:5432                 	       	empty
8   	host    	10.0.23.9               	0.00%  	down
	        	total: 0/3 checks, error: tcp connect timeout
9   	host    	10.0.23.10              	0.00%  	down
	        	total: 0/3 checks, error: tcp connect timeout
7   	table   	cache2:11211            	       	empty
10  	host    	10.0.23.11              	0.00%  	down
	        	total: 0/3 checks, error: tcp connect timeout
8   	table   	api:9000                	       	empty
11  	host    	10.0.23.8               	0.00%  	down
	        	total: 0/3 checks, error: tcp connect timeout
1   	table   	ap:6443                 	       	active (1 hosts)
1   	host    	10.0.23.6               	100.00%	up
	        	total: 3/3 checks
2   	table   	api:8443                	       	empty
2   	host    	10.0.23.8               	0.00%  	down
	        	total: 0/3 checks, error: tcp connect timeout

$ doas relayctl show relays
Id  	Type    	Name                    	Avlblty	Status
1   	relay   	https-gateway-way-to-long-name-for-testing	       	active

$ doas relayctl show hosts
Id  	Type    	Name                    	Avlblty	Status
6   	table   	db:5432                 	       	empty
8   	host    	10.0.23.9               	0.00%  	down
	        	total: 0/10 checks, error: tcp connect timeout
9   	host    	10.0.23.10              	0.00%  	down
	        	total: 0/10 checks, error: tcp connect timeout
7   	table   	cache2:11211            	       	empty
10  	host    	10.0.23.11              	0.00%  	down
	        	total: 0/10 checks, error: tcp connect timeout
8   	table   	api:9000                	       	empty
11  	host    	10.0.23.8               	0.00%  	down
	        	total: 0/10 checks, error: tcp connect timeout
1   	table   	ap:6443                 	       	active (1 hosts)
1   	host    	10.0.23.6               	100.00%	up
	        	total: 10/10 checksdoas relayctl show hosts
Id  	Type    	Name                    	Avlblty	Status
6   	table   	db:5432                 	       	empty
8   	host    	10.0.23.9               	0.00%  	down
	        	total: 0/10 checks, error: tcp connect timeout
9   	host    	10.0.23.10              	0.00%  	down
	        	total: 0/10 checks, error: tcp connect timeout
7   	table   	cache2:11211            	       	empty
10  	host    	10.0.23.11              	0.00%  	down
	        	total: 0/10 checks, error: tcp connect timeout
8   	table   	api:9000                	       	empty
11  	host    	10.0.23.8               	0.00%  	down
	        	total: 0/10 checks, error: tcp connect timeout
1   	table   	ap:6443                 	       	active (1 hosts)
1   	host    	10.0.23.6               	100.00%	up
	        	total: 10/10 checks

With diff:

$ doas relayctl show summary
Id   Type   Name                     Avlblty Checks   Status
1    rdr    db-redirect                               down
6    table  db:5432                                   empty
8    host   10.0.23.9                0.00%   0/2      down
9    host   10.0.23.10               0.00%   0/2      down
2    rdr    cache-redirect                            down
7    table  cache2:11211                              empty
10   host   10.0.23.11               0.00%   0/2      down
3    rdr    api-redirect                              down
8    table  api:9000                                  empty
11   host   10.0.23.8                0.00%   0/2      down
1    relay  https-gateway-way-to-lon                  active
1    table  ap:6443                                   active
1    host   10.0.23.6                100.00% 2/2      up

$ doas relayctl -v show summary
Id   Type   Name                     Avlblty Checks   Status
1    rdr    db-redirect                               down
6    table  db:5432                                   empty
8    host   10.0.23.9                0.00%   0/3      down (tcp connect timeout)
9    host   10.0.23.10               0.00%   0/3      down (tcp connect timeout)
2    rdr    cache-redirect                            down
7    table  cache2:11211                              empty
10   host   10.0.23.11               0.00%   0/3      down (tcp connect timeout)
3    rdr    api-redirect                              down
8    table  api:9000                                  empty
11   host   10.0.23.8                0.00%   0/3      down (tcp connect timeout)
1    relay  https-gateway-way-to-lon                  active
1    table  ap:6443                                   active (1 hosts)
1    host   10.0.23.6                100.00% 3/3      up

$ doas relayctl show hosts
Id   Type   Name                     Avlblty Checks   Status
8    host   10.0.23.9                0.00%   0/5      down
9    host   10.0.23.10               0.00%   0/5      down
10   host   10.0.23.11               0.00%   0/5      down
11   host   10.0.23.8                0.00%   0/5      down
1    host   10.0.23.6                100.00% 5/5      up
2    host   10.0.23.8                0.00%   0/5      down
3    host   10.0.23.6                0.00%   0/5      down
4    host   10.0.23.7                0.00%   0/5      down
5    host   10.0.23.9                0.00%   0/5      down
6    host   10.0.23.10               0.00%   0/5      down
7    host   10.0.23.11               0.00%   0/5      down

$ doas relayctl -v show hosts
Id   Type   Name                     Avlblty Checks   Status
8    host   10.0.23.9                0.00%   0/6      down (tcp connect timeout)
9    host   10.0.23.10               0.00%   0/6      down (tcp connect timeout)
10   host   10.0.23.11               0.00%   0/6      down (tcp connect timeout)
11   host   10.0.23.8                0.00%   0/6      down (tcp connect timeout)
1    host   10.0.23.6                100.00% 6/6      up
2    host   10.0.23.8                0.00%   0/6      down (tcp connect timeout)
3    host   10.0.23.6                0.00%   0/6      down (tls connect failed)
4    host   10.0.23.7                0.00%   0/6      down (tcp connect timeout)
5    host   10.0.23.9                0.00%   0/6      down (tcp connect timeout)
6    host   10.0.23.10               0.00%   0/6      down (tcp connect timeout)
7    host   10.0.23.11               0.00%   0/6      down (tcp connect timeout)

$ doas relayctl show relays
Id   Type   Name                     Avlblty Checks   Status
1    relay  https-gateway-way-to-lon                  active
2    relay  https-api-gateway                         active
3    relay  wiki-frontend                             active
4    relay  ssh-tunnel                                active
5    relay  cache-service                             active

$ doas relayctl monitor
host_status: id: 3 status: down
host_status: id: 1 status: up
host_status: id: 5 status: down
host_status: id: 10 status: down
host_status: id: 9 status: down

$ doas relayctl -v monitor
sync: imsg type 38 len 0 peerid 4294967295 pid 1
sync: timestamp: 1785577623, Sat Aug  1 11:47:03 2026
host_status: imsg type 37 len 32 peerid 4294967295 pid 1
host_status: timestamp: 1785577623, Sat Aug  1 11:47:03 2026
host_status: id: 3 status: down
host_status: imsg type 37 len 32 peerid 4294967295 pid 1
host_status: timestamp: 1785577623, Sat Aug  1 11:47:03 2026
host_status: id: 1 status: up


commit 900841a571028d4dd0dce801a3abcfdbfa95141c
Author: Rafael Sadowski <rafael@sizeofvoid.org>
Date:   Sat Aug 1 09:52:26 2026 +0200

    relayctl: rework show output and add -v flag
    
    - Added -v flag for more detailed output.
    - Replaced tab separators with space-padded, truncated columns (%.Ns) for
      stable alignment.
    - Added a new "Checks" column for host checks (N/N), replacing the total: N/N
      checks sub-line.
    - Shortened redirect to rdr in the Type column to stay under 80 columns.
    - Without -v: compact status (down); with -v: error inlined (down (tcp connect
      timeout)).
    - Table status shows host count (N hosts) only with -v.
    - retries sub-line only appears with show hosts -v.
    - show hosts no longer lists tables; use show summary for that.

diff --git a/relayctl.8 b/relayctl.8
index 96e68a5..fcdf043 100644
--- a/relayctl.8
+++ b/relayctl.8
@@ -23,6 +23,7 @@
 .Nd control the relay daemon
 .Sh SYNOPSIS
 .Nm
+.Op Fl v
 .Op Fl s Ar socket
 .Ar command
 .Op Ar argument ...
@@ -42,6 +43,8 @@ instead of the default
 .Pa /var/run/relayd.sock
 to communicate with
 .Xr relayd 8 .
+.It Fl v
+Verbose output.
 .El
 .Pp
 The following commands are available:
@@ -78,7 +81,7 @@ the backup table as well.
 .It Cm reload
 Reload the configuration file.
 .It Cm show hosts
-Show detailed status of hosts and tables.
+Show detailed status of hosts.
 It will also print the last error for failed host checks;
 see the
 .Sx DIAGNOSTICS
diff --git a/relayctl.c b/relayctl.c
index b76d9fc..a47bf7a 100644
--- a/relayctl.c
+++ b/relayctl.c
@@ -50,6 +50,7 @@ char		*print_host_status(int, int);
 char		*print_table_status(int, int);
 char		*print_relay_status(int);
 void		 print_statistics(struct ctl_stats [PROC_MAX_INSTANCES+1]);
+void		 print_imsg(struct imsg *, const char *);
 
 struct imsgname {
 	int type;
@@ -82,13 +83,14 @@ struct imsgname imsgunknown = {
 
 struct imsgbuf	*ibuf;
 int		 error = 0;
+int		 verbose = 0;
 
 __dead void
 usage(void)
 {
 	extern char *__progname;
 
-	fprintf(stderr, "usage: %s [-s socket] command [argument ...]\n",
+	fprintf(stderr, "usage: %s [-v] [-s socket] command [argument ...]\n",
 	    __progname);
 	exit(1);
 }
@@ -101,16 +103,19 @@ main(int argc, char *argv[])
 	struct imsg		 imsg;
 	int			 ctl_sock;
 	int			 done = 0;
-	int			 n, verbose = 0;
+	int			 n, relayd_log_level = 0;
 	int			 ch;
 	const char		*sockname;
 
 	sockname = RELAYD_SOCKET;
-	while ((ch = getopt(argc, argv, "s:")) != -1) {
+	while ((ch = getopt(argc, argv, "vs:")) != -1) {
 		switch (ch) {
 		case 's':
 			sockname = optarg;
 			break;
+		case 'v':
+			verbose++;
+			break;
 		default:
 			usage();
 			/* NOTREACHED */
@@ -163,8 +168,8 @@ main(int argc, char *argv[])
 	case SHOW_RELAYS:
 	case SHOW_ROUTERS:
 		imsg_compose(ibuf, IMSG_CTL_SHOW_SUM, 0, 0, -1, NULL, 0);
-		printf("%-4s\t%-8s\t%-24s\t%-7s\tStatus\n",
-		    "Id", "Type", "Name", "Avlblty");
+		printf("%-4s %-6s %-24.24s %-7.7s %-8.8s %s\n",
+		    "Id", "Type", "Name", "Avlblty", "Checks", "Status");
 		break;
 	case SHOW_SESSIONS:
 		imsg_compose(ibuf, IMSG_CTL_SESSION, 0, 0, -1, NULL, 0);
@@ -212,11 +217,11 @@ main(int argc, char *argv[])
 		imsg_compose(ibuf, IMSG_CTL_NOTIFY, 0, 0, -1, NULL, 0);
 		break;
 	case LOG_VERBOSE:
-		verbose = 2;
+		relayd_log_level = 2;
 		/* FALLTHROUGH */
 	case LOG_BRIEF:
 		imsg_compose(ibuf, IMSG_CTL_VERBOSE, 0, 0, -1,
-		    &verbose, sizeof(verbose));
+		    &relayd_log_level, sizeof(relayd_log_level));
 		printf("logging request sent.\n");
 		done = 1;
 		break;
@@ -294,8 +299,7 @@ monitor_host_status(struct imsg *imsg)
 
 	if (imsg_get_data(imsg, &cs, sizeof(cs)) == -1)
 		errx(1, "imsg_get_data ctl status error");
-	printf("\tid: %u\n", cs.id);
-	printf("\tstate: ");
+	printf("host_status: id: %u status: ", cs.id);
 	switch (cs.up) {
 	case HOST_UP:
 		printf("up\n");
@@ -316,25 +320,19 @@ monitor_id(struct imsg *imsg)
 
 	if (imsg_get_data(imsg, &id, sizeof(id)) == -1)
 		errx(1, "imsg_get_data ctl id error");
-	printf("\tid: %u\n", id.id);
+	printf("monitor_id: id: %u\n", id.id);
 	if (strlen(id.name))
-		printf("\tname: %s\n", id.name);
+		printf("name: %s\n", id.name);
 }
 
 int
 monitor(struct imsg *imsg)
 {
-	time_t			 now;
 	int			 done = 0;
 	struct imsgname		*imn;
 
-	now = time(NULL);
-
 	imn = monitor_lookup(imsg_get_type(imsg));
-	printf("%s: imsg type %u len %zu peerid %u pid %d\n", imn->name,
-	    imsg_get_type(imsg), imsg_get_len(imsg), imsg_get_id(imsg),
-	    imsg_get_pid(imsg));
-	printf("\ttimestamp: %lld, %s", (long long)now, ctime(&now));
+	print_imsg(imsg, imn->name);
 	if (imn->type == -1)
 		done = 1;
 	if (imn->func != NULL)
@@ -352,8 +350,10 @@ show_summary_msg(struct imsg *imsg, int type)
 	struct relay		 rlay;
 	struct router		 rt;
 	struct netroute		 nr;
-	struct ctl_stats	 stats[PROC_MAX_INSTANCES];
+	struct ctl_stats	 stats[PROC_MAX_INSTANCES + 1];
 	char			 name[HOST_NAME_MAX + 1];
+	char			 checks[16];
+	char			 status[128];
 
 	switch (imsg_get_type(imsg)) {
 	case IMSG_CTL_RDR:
@@ -362,18 +362,18 @@ show_summary_msg(struct imsg *imsg, int type)
 		if (imsg_get_data(imsg, &rdr, sizeof(rdr)) == -1)
 			errx(1, "imsg_get_data rdr error");
 		rdr.conf.name[sizeof(rdr.conf.name) - 1] = '\0';
-		printf("%-4u\t%-8s\t%-24s\t%-7s\t%s\n",
-		    rdr.conf.id, "redirect", rdr.conf.name, "",
+		printf("%-4u %-6s %-24.24s %-7.7s %-8.8s %s\n",
+		    rdr.conf.id, "rdr", rdr.conf.name, "", "",
 		    print_rdr_status(rdr.conf.flags));
 		break;
 	case IMSG_CTL_TABLE:
-		if (!(type == SHOW_SUM || type == SHOW_HOSTS))
+		if (type != SHOW_SUM)
 			break;
 		if (imsg_get_data(imsg, &table, sizeof(table)) == -1)
 			errx(1, "imsg_get_data table error");
 		table.conf.name[sizeof(table.conf.name) - 1] = '\0';
-		printf("%-4u\t%-8s\t%-24s\t%-7s\t%s\n",
-		    table.conf.id, "table", table.conf.name, "",
+		printf("%-4u %-6s %-24.24s %-7.7s %-8.8s %s\n",
+		    table.conf.id, "table", table.conf.name, "", "",
 		    print_table_status(table.up, table.conf.flags));
 		break;
 	case IMSG_CTL_HOST:
@@ -387,19 +387,24 @@ show_summary_msg(struct imsg *imsg, int type)
 			    host.conf.name, host.conf.parentid);
 		else
 			strlcpy(name, host.conf.name, sizeof(name));
-		printf("%-4u\t%-8s\t%-24s\t%-7s\t%s\n",
+		if (host.check_cnt)
+			snprintf(checks, sizeof(checks), "%lu/%lu",
+			    host.up_cnt, host.check_cnt);
+		else
+			checks[0] = '\0';
+		if (host.he && host.up == HOST_DOWN && verbose)
+			snprintf(status, sizeof(status), "%s (%s)",
+			    print_host_status(host.up, host.flags),
+			    host_error(host.he));
+		else
+			strlcpy(status, print_host_status(host.up, host.flags),
+			    sizeof(status));
+		printf("%-4u %-6s %-24.24s %-7.7s %-8.8s %s\n",
 		    host.conf.id, "host", name,
 		    print_availability(host.check_cnt, host.up_cnt),
-		    print_host_status(host.up, host.flags));
-		if (type == SHOW_HOSTS && host.check_cnt) {
-			printf("\t%8s\ttotal: %lu/%lu checks",
-			    "", host.up_cnt, host.check_cnt);
-			if (host.retry_cnt)
-				printf(", %d retries", host.retry_cnt);
-			if (host.he && host.up == HOST_DOWN)
-				printf(", error: %s", host_error(host.he));
-			printf("\n");
-		}
+		    checks, status);
+		if (type == SHOW_HOSTS && verbose && host.retry_cnt)
+			printf("\t%8s\t%d retries\n", "", host.retry_cnt);
 		break;
 	case IMSG_CTL_RELAY:
 		if (!(type == SHOW_SUM || type == SHOW_RELAYS))
@@ -407,8 +412,8 @@ show_summary_msg(struct imsg *imsg, int type)
 		if (imsg_get_data(imsg, &rlay, sizeof(rlay)) == -1)
 			errx(1, "imsg_get_data relay error");
 		rlay.rl_conf.name[sizeof(rlay.rl_conf.name) - 1] = '\0';
-		printf("%-4u\t%-8s\t%-24s\t%-7s\t%s\n",
-		    rlay.rl_conf.id, "relay", rlay.rl_conf.name, "",
+		printf("%-4u %-6s %-24.24s %-7.7s %-8.8s %s\n",
+		    rlay.rl_conf.id, "relay", rlay.rl_conf.name, "", "",
 		    print_relay_status(rlay.rl_conf.flags));
 		break;
 	case IMSG_CTL_RDR_STATS:
@@ -433,8 +438,8 @@ show_summary_msg(struct imsg *imsg, int type)
 			errx(1, "imsg_get_data router error");
 		rt.rt_conf.name[sizeof(rt.rt_conf.name) - 1] = '\0';
 		rt.rt_conf.label[sizeof(rt.rt_conf.label) - 1] = '\0';
-		printf("%-4u\t%-8s\t%-24s\t%-7s\t%s\n",
-		    rt.rt_conf.id, "router", rt.rt_conf.name, "",
+		printf("%-4u %-6s %-24.24s %-7.7s %-8.8s %s\n",
+		    rt.rt_conf.id, "router", rt.rt_conf.name, "", "",
 		    print_relay_status(rt.rt_conf.flags));
 		if (type != SHOW_ROUTERS)
 			break;
@@ -483,7 +488,7 @@ show_session_msg(struct imsg *imsg)
 		getmonotime(&tv_now);
 		print_time(&tv_now, &con.se_tv_start, a, sizeof(a));
 		print_time(&tv_now, &con.se_tv_last, b, sizeof(b));
-		printf("\tage %s, idle %s, relay %u, pid %u",
+		printf("age %s, idle %s, relay %u, pid %u",
 		    a, b, con.se_relayid, con.se_pid);
 		/* XXX grab tagname instead of tag id */
 		if (con.se_tag)
@@ -540,8 +545,10 @@ print_table_status(int up, int fl)
 		snprintf(buf, sizeof(buf) - 1, "disabled");
 	} else if (!up) {
 		snprintf(buf, sizeof(buf) - 1, "empty");
-	} else
+	} else if (verbose)
 		snprintf(buf, sizeof(buf) - 1, "active (%d hosts)", up);
+	else
+		snprintf(buf, sizeof(buf) - 1, "active");
 	return (buf);
 }
 
@@ -600,3 +607,19 @@ print_statistics(struct ctl_stats stats[PROC_MAX_INSTANCES+1])
 	    "", crs.avg, crs.interval,
 	    crs.avg_hour, crs.avg_day);
 }
+
+void
+print_imsg(struct imsg *imsg, const char *name)
+{
+	time_t			 now;
+
+	if (!verbose)
+		return;
+
+	now = time(NULL);
+
+	printf("%s: imsg type %u len %zu peerid %u pid %d\n", name,
+	    imsg_get_type(imsg), imsg_get_len(imsg), imsg_get_id(imsg),
+	    imsg_get_pid(imsg));
+	printf("%s: timestamp: %lld, %s", name, (long long)now, ctime(&now));
+}