2 * Copyright (c) 2021 Netflix, Inc
4 * SPDX-License-Identifier: BSD-2-Clause
9 #include <sys/sysctl.h>
10 #include <sys/resource.h>
20 #include <sys/queue.h>
21 #include <sys/sysctl.h>
27 #define CAM_BASE "kern.cam"
28 #define LATENCY ".latencies"
29 #define CAM_IOSCHED_BASE "kern.cam.iosched.bucket_base_us"
31 #define DEV_NAMSIZE 32
35 static double high_thresh = 500;
36 static double med_thresh = 300;
37 static bool docolor = true;
40 static SLIST_HEAD(, iosched_stat) curlist;
42 struct iosched_op_stat {
44 uint64_t lats[MAX_LATS];
45 uint64_t prev_lats[MAX_LATS];
48 enum { OP_READ = 0, OP_WRITE, OP_TRIM, NUM_OPS };
49 static const char *ops[NUM_OPS] = { "read", "write", "trim" };
50 #define OP_READ_MASK (1 << OP_READ)
51 #define OP_WRITE_MASK (1 << OP_WRITE)
52 #define OP_TRIM_MASK (1 << OP_TRIM)
54 static uint32_t flags = OP_READ_MASK | OP_WRITE_MASK | OP_TRIM_MASK;
57 SLIST_ENTRY(iosched_stat) link;
58 char dev_name[DEV_NAMSIZE];
60 struct iosched_op_stat op_stats[NUM_OPS];
63 static int name2oid(const char *, int *);
64 static int walk_sysctl(int *, size_t);
67 name2oid(const char *name, int *oidp)
74 oid[1] = CTL_SYSCTL_NAME2OID;
76 j = CTL_MAXNAME * sizeof(int);
77 i = sysctl(oid, 2, oidp, &j, name, strlen(name));
84 static size_t /* Includes the trailing NUL */
85 oid2name(int *oid, size_t nlen, char *name, size_t namlen)
87 int qoid[CTL_MAXNAME + 2];
93 qoid[1] = CTL_SYSCTL_NAME;
94 memcpy(qoid + 2, oid, nlen * sizeof(int));
96 i = sysctl(qoid, nlen + 2, name, &j, 0, 0);
98 err(1, "sysctl name %d %zu %d", i, j, errno);
103 oidfmt(int *oid, int len, u_int *kind)
105 int qoid[CTL_MAXNAME+2];
110 qoid[0] = CTL_SYSCTL;
111 qoid[1] = CTL_SYSCTL_OIDFMT;
112 memcpy(qoid + 2, oid, len * sizeof(int));
115 i = sysctl(qoid, len + 2, buf, &j, 0, 0);
117 err(1, "sysctl fmt %d %zu %d", i, j, errno);
118 *kind = *(u_int *)buf;
123 split_u64(char *str, const char *delim, uint64_t *buckets, int *nbuckets)
125 int n = *nbuckets, i;
128 memset(buckets, 0, n * sizeof(buckets[0]));
129 for (i = 0; (v = strsep(&str, delim)) != NULL && i < n; i++) {
130 buckets[i] = strtoull(v, NULL, 10);
137 static double baselat = 0.000020;
140 pest(int permill, uint64_t *lats, int nlat)
146 for (tot = 0, i = 0; i < nlat; i++)
150 if (tot < (uint64_t)2000 / (1000 - permill))
152 samp = tot * permill / 1000;
154 return baselat * (float)samp / lats[0]; /* linear interpolation 0 and baselat */
155 for (tot = 0, i = 0; samp >= tot && i < nlat; i++)
158 b1 = baselat * (1 << (i - 1));
159 b2 = baselat * (1 << i);
160 /* Should expoentially interpolate between buckets -- doing linear instead */
161 return b1 + (b2 - b1) * (float)(lats[i] - (tot - samp)) / lats[i];
165 op2num(const char *op)
167 for (int i = 0; i < NUM_OPS; i++)
168 if (strcmp(op, ops[i]) == 0)
173 static struct iosched_op_stat *
174 find_dev(const char *dev, int unit, int op)
176 struct iosched_stat *isp;
177 struct iosched_op_stat *iosp;
179 SLIST_FOREACH(isp, &curlist, link) {
180 if (strcmp(isp->dev_name, dev) != 0 || isp->unit != unit)
182 iosp = &isp->op_stats[op];
188 static struct iosched_op_stat *
189 alloc_dev(const char *dev, int unit, int op)
191 struct iosched_stat *isp;
192 struct iosched_op_stat *iosp;
194 isp = malloc(sizeof(*isp));
197 strlcpy(isp->dev_name, dev, sizeof(isp->dev_name));
199 SLIST_INSERT_HEAD(&curlist, isp, link);
201 iosp = &isp->op_stats[op];
207 update_dev(const char *dev, int unit, int op, uint64_t *lats, int nlat)
209 struct iosched_op_stat *iosp;
211 iosp = find_dev(dev, unit, op);
213 iosp = alloc_dev(dev, unit, op);
217 memcpy(iosp->prev_lats, iosp->lats, iosp->nlats * sizeof(uint64_t));
218 memcpy(iosp->lats, lats, iosp->nlats * sizeof(uint64_t));
219 // printf("%s%d: %-6s %.3f %.3f %.3f %.3f\r\n",
220 // dev, unit, operation, E3 * pest(500, lats, nlat), E3 * pest(900, lats, nlat),
221 // E3 * pest(990, lats, nlat), E3 * pest(999, lats, nlat));
225 walk_sysctl(int *base_oid, size_t len)
227 int qoid[CTL_MAXNAME + 2], oid[CTL_MAXNAME];
231 if (len > CTL_MAXNAME)
232 err(1, "Length %zd too long", len);
234 qoid[0] = CTL_SYSCTL;
235 qoid[1] = CTL_SYSCTL_NEXT;
237 memcpy(qoid + 2, base_oid, len * sizeof(int));
241 * Get the next one or return when we get to the end of the
242 * sysctls in the kernel.
245 if (sysctl(qoid, l1, oid, &l2, 0, 0) != 0) {
248 err(1, "sysctl(getnext) %zu", l2);
254 * Bail if we're seeing OIDs that don't have the
255 * same prefix or can't have the same prefix.
258 memcmp(oid, base_oid, len * sizeof(int)) != 0)
262 * Get the name, validate it's one we're looking for,
263 * parse the latency and add to list.
269 char *walker, *dev, *opstr;
270 uint64_t latvals[MAX_LATS];
274 l1 = oid2name(oid, l2, name, sizeof(name));
275 if (strcmp(name + l1 - strlen(LATENCY) - 1, LATENCY) != 0)
277 if (oidfmt(oid, l2, &kind) != 0)
279 if ((kind & CTLTYPE) != CTLTYPE_STRING)
282 if (sysctl(oid, l2, val, &l3, 0, 0) != 0)
285 nlat = nitems(latvals);
286 if (split_u64(val, ",", latvals, &nlat) == 0)
288 walker = name + strlen(CAM_BASE) + 1;
289 dev = strsep(&walker, ".");
290 unit = (int)strtol(strsep(&walker, "."), NULL, 10);
291 strsep(&walker, ".");
292 opstr = strsep(&walker, ".");
296 update_dev(dev, unit, op, latvals, nlat);
299 memcpy(qoid + 2, oid, l2 * sizeof(int));
305 closeiolat(WINDOW *w)
315 doublecmd(const char *cmd, double *v)
320 p = strchr(cmd, '=');
322 return; /* XXX Tell the user something? */
323 if (sscanf(p + 1, "%lf", &tv) != 1)
324 return; /* XXX Tell the user something? */
329 cmdiolat(const char *cmd __unused, const char *args __unused)
331 fprintf(stderr, "CMD IS '%s'\n\n", cmd);
332 if (prefix(cmd, "trim"))
333 flags ^= OP_TRIM_MASK;
334 else if (prefix(cmd, "read"))
335 flags ^= OP_READ_MASK;
336 else if (prefix(cmd, "write"))
337 flags ^= OP_WRITE_MASK;
338 else if (prefix(cmd, "color"))
340 else if (prefix("high", cmd))
341 doublecmd(cmd, &high_thresh);
342 else if (prefix("med", cmd))
343 doublecmd(cmd, &med_thresh);
355 int cam[CTL_MAXNAME];
357 size_t len = sizeof(sbt_base);
359 SLIST_INIT(&curlist);
361 baselat = 1e-3; /* old default */
362 if (sysctlbyname(CAM_IOSCHED_BASE, &sbt_base, &len, NULL, 0) == 0)
363 baselat = sbt_base * 1e-6; /* Convert to microseconds */
365 name2oid(CAM_BASE, cam);
373 int cam[CTL_MAXNAME];
375 name2oid(CAM_BASE, cam);
384 int _col, ndrives, lpr, row, j;
385 int regions __unused;
386 struct iosched_stat *isp;
389 #define DRIVESPERLINE ((getmaxx(wnd) - 1 - INSET) / COLWIDTH)
390 ndrives = ndevs; // XXX FILTER XXX
391 regions = howmany(ndrives, DRIVESPERLINE);
392 lpr = 2; /* for headers */
393 for (int i = 0; i < NUM_OPS; i++) {
394 if (flags & (1 << i))
400 if (flags & OP_READ_MASK)
401 mvwaddstr(wnd, row + j++, 1, "read");
402 if (flags & OP_WRITE_MASK)
403 mvwaddstr(wnd, row + j++, 1, "write");
404 if (flags & OP_TRIM_MASK)
405 mvwaddstr(wnd, row + j++, 1, "trim");
406 SLIST_FOREACH(isp, &curlist, link) {
407 if (_col + COLWIDTH >= getmaxx(wnd) - 1 - INSET) {
410 if (row > getmaxy(wnd) - 1 - (lpr + 1))
413 if (flags & OP_READ_MASK)
414 mvwaddstr(wnd, row + j++, 1, "read");
415 if (flags & OP_WRITE_MASK)
416 mvwaddstr(wnd, row + j++, 1, "write");
417 if (flags & OP_TRIM_MASK)
418 mvwaddstr(wnd, row + j++, 1, "trim");
420 snprintf(tmpstr, sizeof(tmpstr), "%s%d", isp->dev_name, isp->unit);
421 mvwaddstr(wnd, row, _col + (COLWIDTH - strlen(tmpstr)) / 2, tmpstr);
422 mvwaddstr(wnd, row + 1, _col, " p50 p90 p99 p99.9");
430 return (subwin(stdscr, LINES-3-1, 0, MAINWIN_ROW, 0));
434 fmt(float f, char *buf, size_t len)
437 strlcpy(buf, " - ", len);
438 else if (f >= 1000.0)
439 snprintf(buf, len, "%6d", (int)f);
441 snprintf(buf, len, "%6.1f", f);
443 snprintf(buf, len, "%6.2f", f);
445 snprintf(buf, len, "%6.3f", f);
449 latout(double lat, int y, int x)
454 fmt(lat, tmpstr, sizeof(tmpstr));
457 else if (lat > high_thresh)
459 else if (lat > med_thresh)
464 wattron(wnd, COLOR_PAIR(i));
465 mvwaddstr(wnd, y, x, tmpstr);
467 wattroff(wnd, COLOR_PAIR(i));
473 int _col, ndrives, lpr, row, k;
474 int regions __unused;
475 struct iosched_stat *isp;
476 struct iosched_op_stat *iosp;
478 #define DRIVESPERLINE ((getmaxx(wnd) - 1 - INSET) / COLWIDTH)
479 ndrives = ndevs; // XXX FILTER XXX
480 regions = howmany(ndrives, DRIVESPERLINE);
482 for (int i = 0; i < NUM_OPS; i++) {
483 if (flags & (1 << i))
488 SLIST_FOREACH(isp, &curlist, link) {
489 if (_col + COLWIDTH >= getmaxx(wnd) - 1 - INSET) {
492 if (row > getmaxy(wnd) - 1 - (lpr + 1))
496 for (int i = 0; i < NUM_OPS; i++) {
497 uint64_t lats[MAX_LATS];
499 float p50, p90, p99, p999;
501 if ((flags & (1 << i)) == 0)
503 iosp = &isp->op_stats[i];
505 memset(lats, 0, sizeof(lats));
506 for (int j = 0; j < iosp->nlats; j++)
507 lats[j] = iosp->lats[j] - iosp->prev_lats[j];
508 p50 = pest(500, lats, nlats) * E3;
509 p90 = pest(900, lats, nlats) * E3;
510 p99 = pest(990, lats, nlats) * E3;
511 p999 = pest(999, lats, nlats) * E3;
512 latout(p50, row + k, _col);
513 latout(p90, row + k, _col + 7);
514 latout(p99, row + k, _col + 14);
515 latout(p999, row + k, _col + 21);