xref: /freebsd/usr.bin/systat/iolat.c (revision 22054f88)
122054f88SWarner Losh /*
222054f88SWarner Losh  * Copyright (c) 2021 Netflix, Inc
322054f88SWarner Losh  *
422054f88SWarner Losh  * SPDX-License-Identifier: BSD-2-Clause
522054f88SWarner Losh  */
622054f88SWarner Losh 
722054f88SWarner Losh 
822054f88SWarner Losh #include <sys/param.h>
922054f88SWarner Losh #include <sys/sysctl.h>
1022054f88SWarner Losh #include <sys/resource.h>
1122054f88SWarner Losh 
1222054f88SWarner Losh #include <devstat.h>
1322054f88SWarner Losh #include <err.h>
1422054f88SWarner Losh #include <errno.h>
1522054f88SWarner Losh #include <math.h>
1622054f88SWarner Losh #include <stdbool.h>
1722054f88SWarner Losh #include <stdlib.h>
1822054f88SWarner Losh #include <string.h>
1922054f88SWarner Losh 
2022054f88SWarner Losh #include <sys/queue.h>
2122054f88SWarner Losh #include <sys/sysctl.h>
2222054f88SWarner Losh 
2322054f88SWarner Losh #include "systat.h"
2422054f88SWarner Losh #include "extern.h"
2522054f88SWarner Losh #include "devs.h"
2622054f88SWarner Losh 
2722054f88SWarner Losh #define CAM_BASE "kern.cam"
2822054f88SWarner Losh #define LATENCY ".latencies"
2922054f88SWarner Losh #define CAM_IOSCHED_BASE "kern.cam.iosched.bucket_base_us"
3022054f88SWarner Losh 
3122054f88SWarner Losh #define DEV_NAMSIZE	32
3222054f88SWarner Losh #define OP_NAMSIZE	16
3322054f88SWarner Losh #define MAX_LATS	32
3422054f88SWarner Losh 
3522054f88SWarner Losh static double high_thresh = 500;
3622054f88SWarner Losh static double med_thresh = 300;
3722054f88SWarner Losh static bool docolor = true;
3822054f88SWarner Losh 
3922054f88SWarner Losh static int ndevs;
4022054f88SWarner Losh static SLIST_HEAD(, iosched_stat)	curlist;
4122054f88SWarner Losh 
4222054f88SWarner Losh struct iosched_op_stat {
4322054f88SWarner Losh 	int		nlats;
4422054f88SWarner Losh 	uint64_t	lats[MAX_LATS];
4522054f88SWarner Losh 	uint64_t	prev_lats[MAX_LATS];
4622054f88SWarner Losh };
4722054f88SWarner Losh 
4822054f88SWarner Losh enum { OP_READ = 0, OP_WRITE, OP_TRIM, NUM_OPS };
4922054f88SWarner Losh static const char *ops[NUM_OPS] = { "read", "write", "trim" };
5022054f88SWarner Losh #define OP_READ_MASK (1 << OP_READ)
5122054f88SWarner Losh #define OP_WRITE_MASK (1 << OP_WRITE)
5222054f88SWarner Losh #define OP_TRIM_MASK (1 << OP_TRIM)
5322054f88SWarner Losh 
5422054f88SWarner Losh static uint32_t flags = OP_READ_MASK | OP_WRITE_MASK | OP_TRIM_MASK;
5522054f88SWarner Losh 
5622054f88SWarner Losh struct iosched_stat {
5722054f88SWarner Losh 	SLIST_ENTRY(iosched_stat)	 link;
5822054f88SWarner Losh 	char		dev_name[DEV_NAMSIZE];
5922054f88SWarner Losh 	int		unit;
6022054f88SWarner Losh 	struct iosched_op_stat op_stats[NUM_OPS];
6122054f88SWarner Losh };
6222054f88SWarner Losh 
6322054f88SWarner Losh static int	name2oid(const char *, int *);
6422054f88SWarner Losh static int	walk_sysctl(int *, size_t);
6522054f88SWarner Losh 
6622054f88SWarner Losh static int
name2oid(const char * name,int * oidp)6722054f88SWarner Losh name2oid(const char *name, int *oidp)
6822054f88SWarner Losh {
6922054f88SWarner Losh 	int oid[2];
7022054f88SWarner Losh 	int i;
7122054f88SWarner Losh 	size_t j;
7222054f88SWarner Losh 
7322054f88SWarner Losh 	oid[0] = CTL_SYSCTL;
7422054f88SWarner Losh 	oid[1] = CTL_SYSCTL_NAME2OID;
7522054f88SWarner Losh 
7622054f88SWarner Losh 	j = CTL_MAXNAME * sizeof(int);
7722054f88SWarner Losh 	i = sysctl(oid, 2, oidp, &j, name, strlen(name));
7822054f88SWarner Losh 	if (i < 0)
7922054f88SWarner Losh 		return (i);
8022054f88SWarner Losh 	j /= sizeof(int);
8122054f88SWarner Losh 	return (j);
8222054f88SWarner Losh }
8322054f88SWarner Losh 
8422054f88SWarner Losh static size_t /* Includes the trailing NUL */
oid2name(int * oid,size_t nlen,char * name,size_t namlen)8522054f88SWarner Losh oid2name(int *oid, size_t nlen, char *name, size_t namlen)
8622054f88SWarner Losh {
8722054f88SWarner Losh 	int qoid[CTL_MAXNAME + 2];
8822054f88SWarner Losh 	int i;
8922054f88SWarner Losh 	size_t j;
9022054f88SWarner Losh 
9122054f88SWarner Losh 	bzero(name, namlen);
9222054f88SWarner Losh 	qoid[0] = CTL_SYSCTL;
9322054f88SWarner Losh 	qoid[1] = CTL_SYSCTL_NAME;
9422054f88SWarner Losh 	memcpy(qoid + 2, oid, nlen * sizeof(int));
9522054f88SWarner Losh 	j = namlen;
9622054f88SWarner Losh 	i = sysctl(qoid, nlen + 2, name, &j, 0, 0);
9722054f88SWarner Losh 	if (i || !j)
9822054f88SWarner Losh 		err(1, "sysctl name %d %zu %d", i, j, errno);
9922054f88SWarner Losh 	return (j);
10022054f88SWarner Losh }
10122054f88SWarner Losh 
10222054f88SWarner Losh static int
oidfmt(int * oid,int len,u_int * kind)10322054f88SWarner Losh oidfmt(int *oid, int len, u_int *kind)
10422054f88SWarner Losh {
10522054f88SWarner Losh 	int qoid[CTL_MAXNAME+2];
10622054f88SWarner Losh 	u_char buf[BUFSIZ];
10722054f88SWarner Losh 	int i;
10822054f88SWarner Losh 	size_t j;
10922054f88SWarner Losh 
11022054f88SWarner Losh 	qoid[0] = CTL_SYSCTL;
11122054f88SWarner Losh 	qoid[1] = CTL_SYSCTL_OIDFMT;
11222054f88SWarner Losh 	memcpy(qoid + 2, oid, len * sizeof(int));
11322054f88SWarner Losh 
11422054f88SWarner Losh 	j = sizeof(buf);
11522054f88SWarner Losh 	i = sysctl(qoid, len + 2, buf, &j, 0, 0);
11622054f88SWarner Losh 	if (i)
11722054f88SWarner Losh 		err(1, "sysctl fmt %d %zu %d", i, j, errno);
11822054f88SWarner Losh 	*kind = *(u_int *)buf;
11922054f88SWarner Losh 	return (0);
12022054f88SWarner Losh }
12122054f88SWarner Losh 
12222054f88SWarner Losh static int
split_u64(char * str,const char * delim,uint64_t * buckets,int * nbuckets)12322054f88SWarner Losh split_u64(char *str, const char *delim, uint64_t *buckets, int *nbuckets)
12422054f88SWarner Losh {
12522054f88SWarner Losh 	int n = *nbuckets, i;
12622054f88SWarner Losh 	char *v;
12722054f88SWarner Losh 
12822054f88SWarner Losh 	memset(buckets, 0, n * sizeof(buckets[0]));
12922054f88SWarner Losh 	for (i = 0; (v = strsep(&str, delim)) != NULL && i < n; i++) {
13022054f88SWarner Losh 		buckets[i] = strtoull(v, NULL, 10);
13122054f88SWarner Losh 	}
13222054f88SWarner Losh 	if (i < n)
13322054f88SWarner Losh 		*nbuckets = i;
13422054f88SWarner Losh 	return (i < n);
13522054f88SWarner Losh }
13622054f88SWarner Losh 
13722054f88SWarner Losh static double baselat = 0.000020;
13822054f88SWarner Losh 
13922054f88SWarner Losh static float
pest(int permill,uint64_t * lats,int nlat)14022054f88SWarner Losh pest(int permill, uint64_t *lats, int nlat)
14122054f88SWarner Losh {
14222054f88SWarner Losh 	uint64_t tot, samp;
14322054f88SWarner Losh 	int i;
14422054f88SWarner Losh 	float b1, b2;
14522054f88SWarner Losh 
14622054f88SWarner Losh 	for (tot = 0, i = 0; i < nlat; i++)
14722054f88SWarner Losh 		tot += lats[i];
14822054f88SWarner Losh 	if (tot == 0)
14922054f88SWarner Losh 		return -nanf("");
15022054f88SWarner Losh 	if (tot < (uint64_t)2000 / (1000 - permill))
15122054f88SWarner Losh 		return nanf("");
15222054f88SWarner Losh 	samp = tot * permill / 1000;
15322054f88SWarner Losh 	if (samp < lats[0])
15422054f88SWarner Losh 		return baselat * (float)samp / lats[0]; /* linear interpolation 0 and baselat */
15522054f88SWarner Losh 	for (tot = 0, i = 0; samp >= tot && i < nlat; i++)
15622054f88SWarner Losh 		tot += lats[i];
15722054f88SWarner Losh 	i--;
15822054f88SWarner Losh 	b1 = baselat * (1 << (i - 1));
15922054f88SWarner Losh 	b2 = baselat * (1 << i);
16022054f88SWarner Losh 	/* Should expoentially interpolate between buckets -- doing linear instead */
16122054f88SWarner Losh 	return b1 + (b2 - b1) * (float)(lats[i] - (tot - samp)) / lats[i];
16222054f88SWarner Losh }
16322054f88SWarner Losh 
16422054f88SWarner Losh static int
op2num(const char * op)16522054f88SWarner Losh op2num(const char *op)
16622054f88SWarner Losh {
16722054f88SWarner Losh 	for (int i = 0; i < NUM_OPS; i++)
16822054f88SWarner Losh 		if (strcmp(op, ops[i]) == 0)
16922054f88SWarner Losh 			return i;
17022054f88SWarner Losh 	return -1;
17122054f88SWarner Losh }
17222054f88SWarner Losh 
17322054f88SWarner Losh static struct iosched_op_stat *
find_dev(const char * dev,int unit,int op)17422054f88SWarner Losh find_dev(const char *dev, int unit, int op)
17522054f88SWarner Losh {
17622054f88SWarner Losh 	struct iosched_stat *isp;
17722054f88SWarner Losh 	struct iosched_op_stat *iosp;
17822054f88SWarner Losh 
17922054f88SWarner Losh 	SLIST_FOREACH(isp, &curlist, link) {
18022054f88SWarner Losh 		if (strcmp(isp->dev_name, dev) != 0 || isp->unit != unit)
18122054f88SWarner Losh 			continue;
18222054f88SWarner Losh 		iosp = &isp->op_stats[op];
18322054f88SWarner Losh 		return iosp;
18422054f88SWarner Losh 	}
18522054f88SWarner Losh 	return NULL;
18622054f88SWarner Losh }
18722054f88SWarner Losh 
18822054f88SWarner Losh static struct iosched_op_stat *
alloc_dev(const char * dev,int unit,int op)18922054f88SWarner Losh alloc_dev(const char *dev, int unit, int op)
19022054f88SWarner Losh {
19122054f88SWarner Losh 	struct iosched_stat *isp;
19222054f88SWarner Losh 	struct iosched_op_stat *iosp;
19322054f88SWarner Losh 
19422054f88SWarner Losh 	isp = malloc(sizeof(*isp));
19522054f88SWarner Losh 	if (isp == NULL)
19622054f88SWarner Losh 		return NULL;
19722054f88SWarner Losh 	strlcpy(isp->dev_name, dev, sizeof(isp->dev_name));
19822054f88SWarner Losh 	isp->unit = unit;
19922054f88SWarner Losh 	SLIST_INSERT_HEAD(&curlist, isp, link);
20022054f88SWarner Losh 	ndevs++;
20122054f88SWarner Losh 	iosp = &isp->op_stats[op];
20222054f88SWarner Losh 	return iosp;
20322054f88SWarner Losh }
20422054f88SWarner Losh 
20522054f88SWarner Losh #define E3 1000.0
20622054f88SWarner Losh static void
update_dev(const char * dev,int unit,int op,uint64_t * lats,int nlat)20722054f88SWarner Losh update_dev(const char *dev, int unit, int op, uint64_t *lats, int nlat)
20822054f88SWarner Losh {
20922054f88SWarner Losh 	struct iosched_op_stat *iosp;
21022054f88SWarner Losh 
21122054f88SWarner Losh 	iosp = find_dev(dev, unit, op);
21222054f88SWarner Losh 	if (iosp == NULL)
21322054f88SWarner Losh 		iosp = alloc_dev(dev, unit, op);
21422054f88SWarner Losh 	if (iosp == NULL)
21522054f88SWarner Losh 		return;
21622054f88SWarner Losh 	iosp->nlats = nlat;
21722054f88SWarner Losh 	memcpy(iosp->prev_lats, iosp->lats, iosp->nlats * sizeof(uint64_t));
21822054f88SWarner Losh 	memcpy(iosp->lats, lats, iosp->nlats * sizeof(uint64_t));
21922054f88SWarner Losh //	printf("%s%d: %-6s %.3f %.3f %.3f %.3f\r\n",
22022054f88SWarner Losh //	    dev, unit, operation, E3 * pest(500, lats, nlat), E3 * pest(900, lats, nlat),
22122054f88SWarner Losh //	    E3 * pest(990, lats, nlat), E3 * pest(999, lats, nlat));
22222054f88SWarner Losh }
22322054f88SWarner Losh 
22422054f88SWarner Losh static int
walk_sysctl(int * base_oid,size_t len)22522054f88SWarner Losh walk_sysctl(int *base_oid, size_t len)
22622054f88SWarner Losh {
22722054f88SWarner Losh 	int qoid[CTL_MAXNAME + 2], oid[CTL_MAXNAME];
22822054f88SWarner Losh 	size_t l1, l2;
22922054f88SWarner Losh 	char name[BUFSIZ];
23022054f88SWarner Losh 
23122054f88SWarner Losh 	if (len > CTL_MAXNAME)
23222054f88SWarner Losh 		err(1, "Length %zd too long", len);
23322054f88SWarner Losh 
23422054f88SWarner Losh 	qoid[0] = CTL_SYSCTL;
23522054f88SWarner Losh 	qoid[1] = CTL_SYSCTL_NEXT;
23622054f88SWarner Losh 	l1 = 2;
23722054f88SWarner Losh 	memcpy(qoid + 2, base_oid, len * sizeof(int));
23822054f88SWarner Losh 	l1 += len;
23922054f88SWarner Losh 	for (;;) {
24022054f88SWarner Losh 		/*
24122054f88SWarner Losh 		 * Get the next one or return when we get to the end of the
24222054f88SWarner Losh 		 * sysctls in the kernel.
24322054f88SWarner Losh 		 */
24422054f88SWarner Losh 		l2 = sizeof(oid);
24522054f88SWarner Losh 		if (sysctl(qoid, l1, oid, &l2, 0, 0) != 0) {
24622054f88SWarner Losh 			if (errno == ENOENT)
24722054f88SWarner Losh 				return (0);
24822054f88SWarner Losh 			err(1, "sysctl(getnext) %zu", l2);
24922054f88SWarner Losh 		}
25022054f88SWarner Losh 
25122054f88SWarner Losh 		l2 /= sizeof(int);
25222054f88SWarner Losh 
25322054f88SWarner Losh 		/*
25422054f88SWarner Losh 		 * Bail if we're seeing OIDs that don't have the
25522054f88SWarner Losh 		 * same prefix or can't have the same prefix.
25622054f88SWarner Losh 		 */
25722054f88SWarner Losh 		if (l2 < len ||
25822054f88SWarner Losh 		    memcmp(oid, base_oid, len * sizeof(int)) != 0)
25922054f88SWarner Losh 			return (0);
26022054f88SWarner Losh 
26122054f88SWarner Losh 		/*
26222054f88SWarner Losh 		 * Get the name, validate it's one we're looking for,
26322054f88SWarner Losh 		 * parse the latency and add to list.
26422054f88SWarner Losh 		 */
26522054f88SWarner Losh 		do {
26622054f88SWarner Losh 			int nlat;
26722054f88SWarner Losh 			size_t l3;
26822054f88SWarner Losh 			char val[BUFSIZ];
26922054f88SWarner Losh 			char *walker, *dev, *opstr;
27022054f88SWarner Losh 			uint64_t latvals[MAX_LATS];
27122054f88SWarner Losh 			u_int kind;
27222054f88SWarner Losh 			int unit, op;
27322054f88SWarner Losh 
27422054f88SWarner Losh 			l1 = oid2name(oid, l2, name, sizeof(name));
27522054f88SWarner Losh 			if (strcmp(name + l1 - strlen(LATENCY) - 1, LATENCY) != 0)
27622054f88SWarner Losh 				break;
27722054f88SWarner Losh 			if (oidfmt(oid, l2, &kind) != 0)
27822054f88SWarner Losh 				err(1, "oidfmt");
27922054f88SWarner Losh 			if ((kind & CTLTYPE) != CTLTYPE_STRING)
28022054f88SWarner Losh 				errx(1, "string");
28122054f88SWarner Losh 			l3 = sizeof(val);
28222054f88SWarner Losh 			if (sysctl(oid, l2, val, &l3, 0, 0) != 0)
28322054f88SWarner Losh 				err(1, "sysctl");
28422054f88SWarner Losh 			val[l3] = '\0';
28522054f88SWarner Losh 			nlat = nitems(latvals);
28622054f88SWarner Losh 			if (split_u64(val, ",", latvals, &nlat) == 0)
28722054f88SWarner Losh 				break;
28822054f88SWarner Losh 			walker = name + strlen(CAM_BASE) + 1;
28922054f88SWarner Losh 			dev = strsep(&walker, ".");
29022054f88SWarner Losh 			unit = (int)strtol(strsep(&walker, "."), NULL, 10);
29122054f88SWarner Losh 			strsep(&walker, ".");
29222054f88SWarner Losh 			opstr = strsep(&walker, ".");
29322054f88SWarner Losh 			op = op2num(opstr);
29422054f88SWarner Losh 			if (op < 0)
29522054f88SWarner Losh 				break;
29622054f88SWarner Losh 			update_dev(dev, unit, op, latvals, nlat);
29722054f88SWarner Losh 		} while (false);
29822054f88SWarner Losh 
29922054f88SWarner Losh 		memcpy(qoid + 2, oid, l2 * sizeof(int));
30022054f88SWarner Losh 		l1 = 2 + l2;
30122054f88SWarner Losh 	}
30222054f88SWarner Losh }
30322054f88SWarner Losh 
30422054f88SWarner Losh void
closeiolat(WINDOW * w)30522054f88SWarner Losh closeiolat(WINDOW *w)
30622054f88SWarner Losh {
30722054f88SWarner Losh 	if (w == NULL)
30822054f88SWarner Losh 		return;
30922054f88SWarner Losh 	wclear(w);
31022054f88SWarner Losh 	wrefresh(w);
31122054f88SWarner Losh 	delwin(w);
31222054f88SWarner Losh }
31322054f88SWarner Losh 
31422054f88SWarner Losh static void
doublecmd(const char * cmd,double * v)31522054f88SWarner Losh doublecmd(const char *cmd, double *v)
31622054f88SWarner Losh {
31722054f88SWarner Losh 	const char *p;
31822054f88SWarner Losh 	double tv;
31922054f88SWarner Losh 
32022054f88SWarner Losh 	p = strchr(cmd, '=');
32122054f88SWarner Losh 	if (p == NULL)
32222054f88SWarner Losh 		return;	/* XXX Tell the user something? */
32322054f88SWarner Losh 	if (sscanf(p + 1, "%lf", &tv) != 1)
32422054f88SWarner Losh 		return;	/* XXX Tell the user something? */
32522054f88SWarner Losh 	*v = tv;
32622054f88SWarner Losh }
32722054f88SWarner Losh 
32822054f88SWarner Losh int
cmdiolat(const char * cmd __unused,const char * args __unused)32922054f88SWarner Losh cmdiolat(const char *cmd __unused, const char *args __unused)
33022054f88SWarner Losh {
33122054f88SWarner Losh 	fprintf(stderr, "CMD IS '%s'\n\n", cmd);
33222054f88SWarner Losh 	if (prefix(cmd, "trim"))
33322054f88SWarner Losh 		flags ^= OP_TRIM_MASK;
33422054f88SWarner Losh 	else if (prefix(cmd, "read"))
33522054f88SWarner Losh 		flags ^= OP_READ_MASK;
33622054f88SWarner Losh 	else if (prefix(cmd, "write"))
33722054f88SWarner Losh 		flags ^= OP_WRITE_MASK;
33822054f88SWarner Losh 	else if (prefix(cmd, "color"))
33922054f88SWarner Losh 		docolor = !docolor;
34022054f88SWarner Losh 	else if (prefix("high", cmd))
34122054f88SWarner Losh 		doublecmd(cmd, &high_thresh);
34222054f88SWarner Losh 	else if (prefix("med", cmd))
34322054f88SWarner Losh 		doublecmd(cmd, &med_thresh);
34422054f88SWarner Losh 	else
34522054f88SWarner Losh 		return (0);
34622054f88SWarner Losh 	wclear(wnd);
34722054f88SWarner Losh 	labeliolat();
34822054f88SWarner Losh 	refresh();
34922054f88SWarner Losh 	return (1);
35022054f88SWarner Losh }
35122054f88SWarner Losh 
35222054f88SWarner Losh int
initiolat(void)35322054f88SWarner Losh initiolat(void)
35422054f88SWarner Losh {
35522054f88SWarner Losh 	int cam[CTL_MAXNAME];
35622054f88SWarner Losh 	uint64_t sbt_base;
35722054f88SWarner Losh 	size_t len = sizeof(sbt_base);
35822054f88SWarner Losh 
35922054f88SWarner Losh 	SLIST_INIT(&curlist);
36022054f88SWarner Losh 
36122054f88SWarner Losh 	baselat = 1e-3;		/* old default */
36222054f88SWarner Losh 	if (sysctlbyname(CAM_IOSCHED_BASE, &sbt_base, &len, NULL, 0) == 0)
36322054f88SWarner Losh 		baselat = sbt_base * 1e-6;	/* Convert to microseconds */
36422054f88SWarner Losh 
36522054f88SWarner Losh 	name2oid(CAM_BASE, cam);
36622054f88SWarner Losh 	walk_sysctl(cam, 2);
36722054f88SWarner Losh 	return (1);
36822054f88SWarner Losh }
36922054f88SWarner Losh 
37022054f88SWarner Losh void
fetchiolat(void)37122054f88SWarner Losh fetchiolat(void)
37222054f88SWarner Losh {
37322054f88SWarner Losh 	int cam[CTL_MAXNAME];
37422054f88SWarner Losh 
37522054f88SWarner Losh 	name2oid(CAM_BASE, cam);
37622054f88SWarner Losh 	walk_sysctl(cam, 2);
37722054f88SWarner Losh }
37822054f88SWarner Losh 
37922054f88SWarner Losh #define	INSET	10
38022054f88SWarner Losh 
38122054f88SWarner Losh void
labeliolat(void)38222054f88SWarner Losh labeliolat(void)
38322054f88SWarner Losh {
38422054f88SWarner Losh 	int _col, ndrives, lpr, row, j;
38522054f88SWarner Losh 	int regions __unused;
38622054f88SWarner Losh 	struct iosched_stat *isp;
38722054f88SWarner Losh 	char tmpstr[32];
38822054f88SWarner Losh #define COLWIDTH	29
38922054f88SWarner Losh #define DRIVESPERLINE	((getmaxx(wnd) - 1 - INSET) / COLWIDTH)
39022054f88SWarner Losh 	ndrives = ndevs; // XXX FILTER XXX
39122054f88SWarner Losh 	regions = howmany(ndrives, DRIVESPERLINE);
39222054f88SWarner Losh 	lpr = 2; /* for headers */
39322054f88SWarner Losh 	for (int i = 0; i < NUM_OPS; i++) {
39422054f88SWarner Losh 		if (flags & (1 << i))
39522054f88SWarner Losh 			lpr++;
39622054f88SWarner Losh 	}
39722054f88SWarner Losh 	row = 0;
39822054f88SWarner Losh 	_col = INSET;
39922054f88SWarner Losh 	j = 2;
40022054f88SWarner Losh 	if (flags & OP_READ_MASK)
40122054f88SWarner Losh 		mvwaddstr(wnd, row + j++, 1, "read");
40222054f88SWarner Losh 	if (flags & OP_WRITE_MASK)
40322054f88SWarner Losh 		mvwaddstr(wnd, row + j++, 1, "write");
40422054f88SWarner Losh 	if (flags & OP_TRIM_MASK)
40522054f88SWarner Losh 		mvwaddstr(wnd, row + j++, 1, "trim");
40622054f88SWarner Losh 	SLIST_FOREACH(isp, &curlist, link) {
40722054f88SWarner Losh 		if (_col + COLWIDTH >= getmaxx(wnd) - 1 - INSET) {
40822054f88SWarner Losh 			_col = INSET;
40922054f88SWarner Losh 			row += lpr + 1;
41022054f88SWarner Losh 			if (row > getmaxy(wnd) - 1 - (lpr + 1))
41122054f88SWarner Losh 				break;
41222054f88SWarner Losh 			j = 2;
41322054f88SWarner Losh 			if (flags & OP_READ_MASK)
41422054f88SWarner Losh 				mvwaddstr(wnd, row + j++, 1, "read");
41522054f88SWarner Losh 			if (flags & OP_WRITE_MASK)
41622054f88SWarner Losh 				mvwaddstr(wnd, row + j++, 1, "write");
41722054f88SWarner Losh 			if (flags & OP_TRIM_MASK)
41822054f88SWarner Losh 				mvwaddstr(wnd, row + j++, 1, "trim");
41922054f88SWarner Losh 		}
42022054f88SWarner Losh 		snprintf(tmpstr, sizeof(tmpstr), "%s%d", isp->dev_name, isp->unit);
42122054f88SWarner Losh 		mvwaddstr(wnd, row, _col + (COLWIDTH - strlen(tmpstr)) / 2, tmpstr);
42222054f88SWarner Losh 		mvwaddstr(wnd, row + 1, _col, "   p50    p90    p99  p99.9");
42322054f88SWarner Losh 		_col += COLWIDTH;
42422054f88SWarner Losh 	}
42522054f88SWarner Losh }
42622054f88SWarner Losh 
42722054f88SWarner Losh WINDOW *
openiolat(void)42822054f88SWarner Losh openiolat(void)
42922054f88SWarner Losh {
43022054f88SWarner Losh 	return (subwin(stdscr, LINES-3-1, 0, MAINWIN_ROW, 0));
43122054f88SWarner Losh }
43222054f88SWarner Losh 
43322054f88SWarner Losh static void
fmt(float f,char * buf,size_t len)43422054f88SWarner Losh fmt(float f, char *buf, size_t len)
43522054f88SWarner Losh {
43622054f88SWarner Losh 	if (isnan(f))
43722054f88SWarner Losh 		strlcpy(buf, "   -  ", len);
43822054f88SWarner Losh 	else if (f >= 1000.0)
43922054f88SWarner Losh 		snprintf(buf, len, "%6d", (int)f);
44022054f88SWarner Losh 	else if (f >= 100.0)
44122054f88SWarner Losh 		snprintf(buf, len, "%6.1f", f);
44222054f88SWarner Losh 	else if (f >= 10.0)
44322054f88SWarner Losh 		snprintf(buf, len, "%6.2f", f);
44422054f88SWarner Losh 	else
44522054f88SWarner Losh 		snprintf(buf, len, "%6.3f", f);
44622054f88SWarner Losh }
44722054f88SWarner Losh 
44822054f88SWarner Losh static void
latout(double lat,int y,int x)44922054f88SWarner Losh latout(double lat, int y, int x)
45022054f88SWarner Losh {
45122054f88SWarner Losh 	int i;
45222054f88SWarner Losh 	char tmpstr[32];
45322054f88SWarner Losh 
45422054f88SWarner Losh 	fmt(lat, tmpstr, sizeof(tmpstr));
45522054f88SWarner Losh 	if (isnan(lat))
45622054f88SWarner Losh 		i = 4;
45722054f88SWarner Losh 	else if (lat > high_thresh)
45822054f88SWarner Losh 		i = 3;
45922054f88SWarner Losh 	else if (lat > med_thresh)
46022054f88SWarner Losh 		i = 2;
46122054f88SWarner Losh 	else
46222054f88SWarner Losh 		i = 1;
46322054f88SWarner Losh 	if (docolor)
46422054f88SWarner Losh 		wattron(wnd, COLOR_PAIR(i));
46522054f88SWarner Losh 	mvwaddstr(wnd, y, x, tmpstr);
46622054f88SWarner Losh 	if (docolor)
46722054f88SWarner Losh 		wattroff(wnd, COLOR_PAIR(i));
46822054f88SWarner Losh }
46922054f88SWarner Losh 
47022054f88SWarner Losh void
showiolat(void)47122054f88SWarner Losh showiolat(void)
47222054f88SWarner Losh {
47322054f88SWarner Losh 	int _col, ndrives, lpr, row, k;
47422054f88SWarner Losh 	int regions __unused;
47522054f88SWarner Losh 	struct iosched_stat *isp;
47622054f88SWarner Losh 	struct iosched_op_stat *iosp;
47722054f88SWarner Losh #define COLWIDTH	29
47822054f88SWarner Losh #define DRIVESPERLINE	((getmaxx(wnd) - 1 - INSET) / COLWIDTH)
47922054f88SWarner Losh 	ndrives = ndevs; // XXX FILTER XXX
48022054f88SWarner Losh 	regions = howmany(ndrives, DRIVESPERLINE);
48122054f88SWarner Losh 	lpr = 2; /* XXX */
48222054f88SWarner Losh 	for (int i = 0; i < NUM_OPS; i++) {
48322054f88SWarner Losh 		if (flags & (1 << i))
48422054f88SWarner Losh 			lpr++;
48522054f88SWarner Losh 	}
48622054f88SWarner Losh 	row = 0;
48722054f88SWarner Losh 	_col = INSET;
48822054f88SWarner Losh 	SLIST_FOREACH(isp, &curlist, link) {
48922054f88SWarner Losh 		if (_col + COLWIDTH >= getmaxx(wnd) - 1 - INSET) {
49022054f88SWarner Losh 			_col = INSET;
49122054f88SWarner Losh 			row += lpr + 1;
49222054f88SWarner Losh 			if (row > getmaxy(wnd) - 1 - (lpr + 1))
49322054f88SWarner Losh 				break;
49422054f88SWarner Losh 		}
49522054f88SWarner Losh 		k = 2;
49622054f88SWarner Losh 		for (int i = 0; i < NUM_OPS; i++) {
49722054f88SWarner Losh 			uint64_t lats[MAX_LATS];
49822054f88SWarner Losh 			int nlats;
49922054f88SWarner Losh 			float p50, p90, p99, p999;
50022054f88SWarner Losh 
50122054f88SWarner Losh 			if ((flags & (1 << i)) == 0)
50222054f88SWarner Losh 				continue;
50322054f88SWarner Losh 			iosp = &isp->op_stats[i];
50422054f88SWarner Losh 			nlats = iosp->nlats;
50522054f88SWarner Losh 			memset(lats, 0, sizeof(lats));
50622054f88SWarner Losh 			for (int j = 0; j < iosp->nlats; j++)
50722054f88SWarner Losh 				lats[j] = iosp->lats[j] - iosp->prev_lats[j];
50822054f88SWarner Losh 			p50 = pest(500, lats, nlats) * E3;
50922054f88SWarner Losh 			p90 = pest(900, lats, nlats) * E3;
51022054f88SWarner Losh 			p99 = pest(990, lats, nlats) * E3;
51122054f88SWarner Losh 			p999 = pest(999, lats, nlats) * E3;
51222054f88SWarner Losh 			latout(p50, row + k, _col);
51322054f88SWarner Losh 			latout(p90, row + k, _col + 7);
51422054f88SWarner Losh 			latout(p99, row + k, _col + 14);
51522054f88SWarner Losh 			latout(p999, row + k, _col + 21);
51622054f88SWarner Losh 			k++;
51722054f88SWarner Losh 		}
51822054f88SWarner Losh 		_col += COLWIDTH;
51922054f88SWarner Losh 	}
52022054f88SWarner Losh }
521