mirror of
https://github.com/freebsd/freebsd-src
synced 2024-10-15 12:54:27 +00:00
521 lines
11 KiB
C
521 lines
11 KiB
C
|
/*
|
||
|
* Copyright (c) 2021 Netflix, Inc
|
||
|
*
|
||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||
|
*/
|
||
|
|
||
|
|
||
|
#include <sys/param.h>
|
||
|
#include <sys/sysctl.h>
|
||
|
#include <sys/resource.h>
|
||
|
|
||
|
#include <devstat.h>
|
||
|
#include <err.h>
|
||
|
#include <errno.h>
|
||
|
#include <math.h>
|
||
|
#include <stdbool.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
|
||
|
#include <sys/queue.h>
|
||
|
#include <sys/sysctl.h>
|
||
|
|
||
|
#include "systat.h"
|
||
|
#include "extern.h"
|
||
|
#include "devs.h"
|
||
|
|
||
|
#define CAM_BASE "kern.cam"
|
||
|
#define LATENCY ".latencies"
|
||
|
#define CAM_IOSCHED_BASE "kern.cam.iosched.bucket_base_us"
|
||
|
|
||
|
#define DEV_NAMSIZE 32
|
||
|
#define OP_NAMSIZE 16
|
||
|
#define MAX_LATS 32
|
||
|
|
||
|
static double high_thresh = 500;
|
||
|
static double med_thresh = 300;
|
||
|
static bool docolor = true;
|
||
|
|
||
|
static int ndevs;
|
||
|
static SLIST_HEAD(, iosched_stat) curlist;
|
||
|
|
||
|
struct iosched_op_stat {
|
||
|
int nlats;
|
||
|
uint64_t lats[MAX_LATS];
|
||
|
uint64_t prev_lats[MAX_LATS];
|
||
|
};
|
||
|
|
||
|
enum { OP_READ = 0, OP_WRITE, OP_TRIM, NUM_OPS };
|
||
|
static const char *ops[NUM_OPS] = { "read", "write", "trim" };
|
||
|
#define OP_READ_MASK (1 << OP_READ)
|
||
|
#define OP_WRITE_MASK (1 << OP_WRITE)
|
||
|
#define OP_TRIM_MASK (1 << OP_TRIM)
|
||
|
|
||
|
static uint32_t flags = OP_READ_MASK | OP_WRITE_MASK | OP_TRIM_MASK;
|
||
|
|
||
|
struct iosched_stat {
|
||
|
SLIST_ENTRY(iosched_stat) link;
|
||
|
char dev_name[DEV_NAMSIZE];
|
||
|
int unit;
|
||
|
struct iosched_op_stat op_stats[NUM_OPS];
|
||
|
};
|
||
|
|
||
|
static int name2oid(const char *, int *);
|
||
|
static int walk_sysctl(int *, size_t);
|
||
|
|
||
|
static int
|
||
|
name2oid(const char *name, int *oidp)
|
||
|
{
|
||
|
int oid[2];
|
||
|
int i;
|
||
|
size_t j;
|
||
|
|
||
|
oid[0] = CTL_SYSCTL;
|
||
|
oid[1] = CTL_SYSCTL_NAME2OID;
|
||
|
|
||
|
j = CTL_MAXNAME * sizeof(int);
|
||
|
i = sysctl(oid, 2, oidp, &j, name, strlen(name));
|
||
|
if (i < 0)
|
||
|
return (i);
|
||
|
j /= sizeof(int);
|
||
|
return (j);
|
||
|
}
|
||
|
|
||
|
static size_t /* Includes the trailing NUL */
|
||
|
oid2name(int *oid, size_t nlen, char *name, size_t namlen)
|
||
|
{
|
||
|
int qoid[CTL_MAXNAME + 2];
|
||
|
int i;
|
||
|
size_t j;
|
||
|
|
||
|
bzero(name, namlen);
|
||
|
qoid[0] = CTL_SYSCTL;
|
||
|
qoid[1] = CTL_SYSCTL_NAME;
|
||
|
memcpy(qoid + 2, oid, nlen * sizeof(int));
|
||
|
j = namlen;
|
||
|
i = sysctl(qoid, nlen + 2, name, &j, 0, 0);
|
||
|
if (i || !j)
|
||
|
err(1, "sysctl name %d %zu %d", i, j, errno);
|
||
|
return (j);
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
oidfmt(int *oid, int len, u_int *kind)
|
||
|
{
|
||
|
int qoid[CTL_MAXNAME+2];
|
||
|
u_char buf[BUFSIZ];
|
||
|
int i;
|
||
|
size_t j;
|
||
|
|
||
|
qoid[0] = CTL_SYSCTL;
|
||
|
qoid[1] = CTL_SYSCTL_OIDFMT;
|
||
|
memcpy(qoid + 2, oid, len * sizeof(int));
|
||
|
|
||
|
j = sizeof(buf);
|
||
|
i = sysctl(qoid, len + 2, buf, &j, 0, 0);
|
||
|
if (i)
|
||
|
err(1, "sysctl fmt %d %zu %d", i, j, errno);
|
||
|
*kind = *(u_int *)buf;
|
||
|
return (0);
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
split_u64(char *str, const char *delim, uint64_t *buckets, int *nbuckets)
|
||
|
{
|
||
|
int n = *nbuckets, i;
|
||
|
char *v;
|
||
|
|
||
|
memset(buckets, 0, n * sizeof(buckets[0]));
|
||
|
for (i = 0; (v = strsep(&str, delim)) != NULL && i < n; i++) {
|
||
|
buckets[i] = strtoull(v, NULL, 10);
|
||
|
}
|
||
|
if (i < n)
|
||
|
*nbuckets = i;
|
||
|
return (i < n);
|
||
|
}
|
||
|
|
||
|
static double baselat = 0.000020;
|
||
|
|
||
|
static float
|
||
|
pest(int permill, uint64_t *lats, int nlat)
|
||
|
{
|
||
|
uint64_t tot, samp;
|
||
|
int i;
|
||
|
float b1, b2;
|
||
|
|
||
|
for (tot = 0, i = 0; i < nlat; i++)
|
||
|
tot += lats[i];
|
||
|
if (tot == 0)
|
||
|
return -nanf("");
|
||
|
if (tot < (uint64_t)2000 / (1000 - permill))
|
||
|
return nanf("");
|
||
|
samp = tot * permill / 1000;
|
||
|
if (samp < lats[0])
|
||
|
return baselat * (float)samp / lats[0]; /* linear interpolation 0 and baselat */
|
||
|
for (tot = 0, i = 0; samp >= tot && i < nlat; i++)
|
||
|
tot += lats[i];
|
||
|
i--;
|
||
|
b1 = baselat * (1 << (i - 1));
|
||
|
b2 = baselat * (1 << i);
|
||
|
/* Should expoentially interpolate between buckets -- doing linear instead */
|
||
|
return b1 + (b2 - b1) * (float)(lats[i] - (tot - samp)) / lats[i];
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
op2num(const char *op)
|
||
|
{
|
||
|
for (int i = 0; i < NUM_OPS; i++)
|
||
|
if (strcmp(op, ops[i]) == 0)
|
||
|
return i;
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
static struct iosched_op_stat *
|
||
|
find_dev(const char *dev, int unit, int op)
|
||
|
{
|
||
|
struct iosched_stat *isp;
|
||
|
struct iosched_op_stat *iosp;
|
||
|
|
||
|
SLIST_FOREACH(isp, &curlist, link) {
|
||
|
if (strcmp(isp->dev_name, dev) != 0 || isp->unit != unit)
|
||
|
continue;
|
||
|
iosp = &isp->op_stats[op];
|
||
|
return iosp;
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
static struct iosched_op_stat *
|
||
|
alloc_dev(const char *dev, int unit, int op)
|
||
|
{
|
||
|
struct iosched_stat *isp;
|
||
|
struct iosched_op_stat *iosp;
|
||
|
|
||
|
isp = malloc(sizeof(*isp));
|
||
|
if (isp == NULL)
|
||
|
return NULL;
|
||
|
strlcpy(isp->dev_name, dev, sizeof(isp->dev_name));
|
||
|
isp->unit = unit;
|
||
|
SLIST_INSERT_HEAD(&curlist, isp, link);
|
||
|
ndevs++;
|
||
|
iosp = &isp->op_stats[op];
|
||
|
return iosp;
|
||
|
}
|
||
|
|
||
|
#define E3 1000.0
|
||
|
static void
|
||
|
update_dev(const char *dev, int unit, int op, uint64_t *lats, int nlat)
|
||
|
{
|
||
|
struct iosched_op_stat *iosp;
|
||
|
|
||
|
iosp = find_dev(dev, unit, op);
|
||
|
if (iosp == NULL)
|
||
|
iosp = alloc_dev(dev, unit, op);
|
||
|
if (iosp == NULL)
|
||
|
return;
|
||
|
iosp->nlats = nlat;
|
||
|
memcpy(iosp->prev_lats, iosp->lats, iosp->nlats * sizeof(uint64_t));
|
||
|
memcpy(iosp->lats, lats, iosp->nlats * sizeof(uint64_t));
|
||
|
// printf("%s%d: %-6s %.3f %.3f %.3f %.3f\r\n",
|
||
|
// dev, unit, operation, E3 * pest(500, lats, nlat), E3 * pest(900, lats, nlat),
|
||
|
// E3 * pest(990, lats, nlat), E3 * pest(999, lats, nlat));
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
walk_sysctl(int *base_oid, size_t len)
|
||
|
{
|
||
|
int qoid[CTL_MAXNAME + 2], oid[CTL_MAXNAME];
|
||
|
size_t l1, l2;
|
||
|
char name[BUFSIZ];
|
||
|
|
||
|
if (len > CTL_MAXNAME)
|
||
|
err(1, "Length %zd too long", len);
|
||
|
|
||
|
qoid[0] = CTL_SYSCTL;
|
||
|
qoid[1] = CTL_SYSCTL_NEXT;
|
||
|
l1 = 2;
|
||
|
memcpy(qoid + 2, base_oid, len * sizeof(int));
|
||
|
l1 += len;
|
||
|
for (;;) {
|
||
|
/*
|
||
|
* Get the next one or return when we get to the end of the
|
||
|
* sysctls in the kernel.
|
||
|
*/
|
||
|
l2 = sizeof(oid);
|
||
|
if (sysctl(qoid, l1, oid, &l2, 0, 0) != 0) {
|
||
|
if (errno == ENOENT)
|
||
|
return (0);
|
||
|
err(1, "sysctl(getnext) %zu", l2);
|
||
|
}
|
||
|
|
||
|
l2 /= sizeof(int);
|
||
|
|
||
|
/*
|
||
|
* Bail if we're seeing OIDs that don't have the
|
||
|
* same prefix or can't have the same prefix.
|
||
|
*/
|
||
|
if (l2 < len ||
|
||
|
memcmp(oid, base_oid, len * sizeof(int)) != 0)
|
||
|
return (0);
|
||
|
|
||
|
/*
|
||
|
* Get the name, validate it's one we're looking for,
|
||
|
* parse the latency and add to list.
|
||
|
*/
|
||
|
do {
|
||
|
int nlat;
|
||
|
size_t l3;
|
||
|
char val[BUFSIZ];
|
||
|
char *walker, *dev, *opstr;
|
||
|
uint64_t latvals[MAX_LATS];
|
||
|
u_int kind;
|
||
|
int unit, op;
|
||
|
|
||
|
l1 = oid2name(oid, l2, name, sizeof(name));
|
||
|
if (strcmp(name + l1 - strlen(LATENCY) - 1, LATENCY) != 0)
|
||
|
break;
|
||
|
if (oidfmt(oid, l2, &kind) != 0)
|
||
|
err(1, "oidfmt");
|
||
|
if ((kind & CTLTYPE) != CTLTYPE_STRING)
|
||
|
errx(1, "string");
|
||
|
l3 = sizeof(val);
|
||
|
if (sysctl(oid, l2, val, &l3, 0, 0) != 0)
|
||
|
err(1, "sysctl");
|
||
|
val[l3] = '\0';
|
||
|
nlat = nitems(latvals);
|
||
|
if (split_u64(val, ",", latvals, &nlat) == 0)
|
||
|
break;
|
||
|
walker = name + strlen(CAM_BASE) + 1;
|
||
|
dev = strsep(&walker, ".");
|
||
|
unit = (int)strtol(strsep(&walker, "."), NULL, 10);
|
||
|
strsep(&walker, ".");
|
||
|
opstr = strsep(&walker, ".");
|
||
|
op = op2num(opstr);
|
||
|
if (op < 0)
|
||
|
break;
|
||
|
update_dev(dev, unit, op, latvals, nlat);
|
||
|
} while (false);
|
||
|
|
||
|
memcpy(qoid + 2, oid, l2 * sizeof(int));
|
||
|
l1 = 2 + l2;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void
|
||
|
closeiolat(WINDOW *w)
|
||
|
{
|
||
|
if (w == NULL)
|
||
|
return;
|
||
|
wclear(w);
|
||
|
wrefresh(w);
|
||
|
delwin(w);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
doublecmd(const char *cmd, double *v)
|
||
|
{
|
||
|
const char *p;
|
||
|
double tv;
|
||
|
|
||
|
p = strchr(cmd, '=');
|
||
|
if (p == NULL)
|
||
|
return; /* XXX Tell the user something? */
|
||
|
if (sscanf(p + 1, "%lf", &tv) != 1)
|
||
|
return; /* XXX Tell the user something? */
|
||
|
*v = tv;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
cmdiolat(const char *cmd __unused, const char *args __unused)
|
||
|
{
|
||
|
fprintf(stderr, "CMD IS '%s'\n\n", cmd);
|
||
|
if (prefix(cmd, "trim"))
|
||
|
flags ^= OP_TRIM_MASK;
|
||
|
else if (prefix(cmd, "read"))
|
||
|
flags ^= OP_READ_MASK;
|
||
|
else if (prefix(cmd, "write"))
|
||
|
flags ^= OP_WRITE_MASK;
|
||
|
else if (prefix(cmd, "color"))
|
||
|
docolor = !docolor;
|
||
|
else if (prefix("high", cmd))
|
||
|
doublecmd(cmd, &high_thresh);
|
||
|
else if (prefix("med", cmd))
|
||
|
doublecmd(cmd, &med_thresh);
|
||
|
else
|
||
|
return (0);
|
||
|
wclear(wnd);
|
||
|
labeliolat();
|
||
|
refresh();
|
||
|
return (1);
|
||
|
}
|
||
|
|
||
|
int
|
||
|
initiolat(void)
|
||
|
{
|
||
|
int cam[CTL_MAXNAME];
|
||
|
uint64_t sbt_base;
|
||
|
size_t len = sizeof(sbt_base);
|
||
|
|
||
|
SLIST_INIT(&curlist);
|
||
|
|
||
|
baselat = 1e-3; /* old default */
|
||
|
if (sysctlbyname(CAM_IOSCHED_BASE, &sbt_base, &len, NULL, 0) == 0)
|
||
|
baselat = sbt_base * 1e-6; /* Convert to microseconds */
|
||
|
|
||
|
name2oid(CAM_BASE, cam);
|
||
|
walk_sysctl(cam, 2);
|
||
|
return (1);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
fetchiolat(void)
|
||
|
{
|
||
|
int cam[CTL_MAXNAME];
|
||
|
|
||
|
name2oid(CAM_BASE, cam);
|
||
|
walk_sysctl(cam, 2);
|
||
|
}
|
||
|
|
||
|
#define INSET 10
|
||
|
|
||
|
void
|
||
|
labeliolat(void)
|
||
|
{
|
||
|
int _col, ndrives, lpr, row, j;
|
||
|
int regions __unused;
|
||
|
struct iosched_stat *isp;
|
||
|
char tmpstr[32];
|
||
|
#define COLWIDTH 29
|
||
|
#define DRIVESPERLINE ((getmaxx(wnd) - 1 - INSET) / COLWIDTH)
|
||
|
ndrives = ndevs; // XXX FILTER XXX
|
||
|
regions = howmany(ndrives, DRIVESPERLINE);
|
||
|
lpr = 2; /* for headers */
|
||
|
for (int i = 0; i < NUM_OPS; i++) {
|
||
|
if (flags & (1 << i))
|
||
|
lpr++;
|
||
|
}
|
||
|
row = 0;
|
||
|
_col = INSET;
|
||
|
j = 2;
|
||
|
if (flags & OP_READ_MASK)
|
||
|
mvwaddstr(wnd, row + j++, 1, "read");
|
||
|
if (flags & OP_WRITE_MASK)
|
||
|
mvwaddstr(wnd, row + j++, 1, "write");
|
||
|
if (flags & OP_TRIM_MASK)
|
||
|
mvwaddstr(wnd, row + j++, 1, "trim");
|
||
|
SLIST_FOREACH(isp, &curlist, link) {
|
||
|
if (_col + COLWIDTH >= getmaxx(wnd) - 1 - INSET) {
|
||
|
_col = INSET;
|
||
|
row += lpr + 1;
|
||
|
if (row > getmaxy(wnd) - 1 - (lpr + 1))
|
||
|
break;
|
||
|
j = 2;
|
||
|
if (flags & OP_READ_MASK)
|
||
|
mvwaddstr(wnd, row + j++, 1, "read");
|
||
|
if (flags & OP_WRITE_MASK)
|
||
|
mvwaddstr(wnd, row + j++, 1, "write");
|
||
|
if (flags & OP_TRIM_MASK)
|
||
|
mvwaddstr(wnd, row + j++, 1, "trim");
|
||
|
}
|
||
|
snprintf(tmpstr, sizeof(tmpstr), "%s%d", isp->dev_name, isp->unit);
|
||
|
mvwaddstr(wnd, row, _col + (COLWIDTH - strlen(tmpstr)) / 2, tmpstr);
|
||
|
mvwaddstr(wnd, row + 1, _col, " p50 p90 p99 p99.9");
|
||
|
_col += COLWIDTH;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
WINDOW *
|
||
|
openiolat(void)
|
||
|
{
|
||
|
return (subwin(stdscr, LINES-3-1, 0, MAINWIN_ROW, 0));
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
fmt(float f, char *buf, size_t len)
|
||
|
{
|
||
|
if (isnan(f))
|
||
|
strlcpy(buf, " - ", len);
|
||
|
else if (f >= 1000.0)
|
||
|
snprintf(buf, len, "%6d", (int)f);
|
||
|
else if (f >= 100.0)
|
||
|
snprintf(buf, len, "%6.1f", f);
|
||
|
else if (f >= 10.0)
|
||
|
snprintf(buf, len, "%6.2f", f);
|
||
|
else
|
||
|
snprintf(buf, len, "%6.3f", f);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
latout(double lat, int y, int x)
|
||
|
{
|
||
|
int i;
|
||
|
char tmpstr[32];
|
||
|
|
||
|
fmt(lat, tmpstr, sizeof(tmpstr));
|
||
|
if (isnan(lat))
|
||
|
i = 4;
|
||
|
else if (lat > high_thresh)
|
||
|
i = 3;
|
||
|
else if (lat > med_thresh)
|
||
|
i = 2;
|
||
|
else
|
||
|
i = 1;
|
||
|
if (docolor)
|
||
|
wattron(wnd, COLOR_PAIR(i));
|
||
|
mvwaddstr(wnd, y, x, tmpstr);
|
||
|
if (docolor)
|
||
|
wattroff(wnd, COLOR_PAIR(i));
|
||
|
}
|
||
|
|
||
|
void
|
||
|
showiolat(void)
|
||
|
{
|
||
|
int _col, ndrives, lpr, row, k;
|
||
|
int regions __unused;
|
||
|
struct iosched_stat *isp;
|
||
|
struct iosched_op_stat *iosp;
|
||
|
#define COLWIDTH 29
|
||
|
#define DRIVESPERLINE ((getmaxx(wnd) - 1 - INSET) / COLWIDTH)
|
||
|
ndrives = ndevs; // XXX FILTER XXX
|
||
|
regions = howmany(ndrives, DRIVESPERLINE);
|
||
|
lpr = 2; /* XXX */
|
||
|
for (int i = 0; i < NUM_OPS; i++) {
|
||
|
if (flags & (1 << i))
|
||
|
lpr++;
|
||
|
}
|
||
|
row = 0;
|
||
|
_col = INSET;
|
||
|
SLIST_FOREACH(isp, &curlist, link) {
|
||
|
if (_col + COLWIDTH >= getmaxx(wnd) - 1 - INSET) {
|
||
|
_col = INSET;
|
||
|
row += lpr + 1;
|
||
|
if (row > getmaxy(wnd) - 1 - (lpr + 1))
|
||
|
break;
|
||
|
}
|
||
|
k = 2;
|
||
|
for (int i = 0; i < NUM_OPS; i++) {
|
||
|
uint64_t lats[MAX_LATS];
|
||
|
int nlats;
|
||
|
float p50, p90, p99, p999;
|
||
|
|
||
|
if ((flags & (1 << i)) == 0)
|
||
|
continue;
|
||
|
iosp = &isp->op_stats[i];
|
||
|
nlats = iosp->nlats;
|
||
|
memset(lats, 0, sizeof(lats));
|
||
|
for (int j = 0; j < iosp->nlats; j++)
|
||
|
lats[j] = iosp->lats[j] - iosp->prev_lats[j];
|
||
|
p50 = pest(500, lats, nlats) * E3;
|
||
|
p90 = pest(900, lats, nlats) * E3;
|
||
|
p99 = pest(990, lats, nlats) * E3;
|
||
|
p999 = pest(999, lats, nlats) * E3;
|
||
|
latout(p50, row + k, _col);
|
||
|
latout(p90, row + k, _col + 7);
|
||
|
latout(p99, row + k, _col + 14);
|
||
|
latout(p999, row + k, _col + 21);
|
||
|
k++;
|
||
|
}
|
||
|
_col += COLWIDTH;
|
||
|
}
|
||
|
}
|