Changeset View
Changeset View
Standalone View
Standalone View
usr.bin/ctlstat/ctlstat.c
Show All 35 Lines | |||||
* CAM Target Layer statistics program | * CAM Target Layer statistics program | ||||
* | * | ||||
* Authors: Ken Merry <ken@FreeBSD.org>, Will Andrews <will@FreeBSD.org> | * Authors: Ken Merry <ken@FreeBSD.org>, Will Andrews <will@FreeBSD.org> | ||||
*/ | */ | ||||
#include <sys/cdefs.h> | #include <sys/cdefs.h> | ||||
__FBSDID("$FreeBSD$"); | __FBSDID("$FreeBSD$"); | ||||
#include <sys/ioctl.h> | |||||
#include <sys/types.h> | |||||
#include <sys/param.h> | #include <sys/param.h> | ||||
#include <sys/time.h> | |||||
#include <sys/sysctl.h> | |||||
#include <sys/resource.h> | |||||
#include <sys/queue.h> | |||||
#include <sys/callout.h> | #include <sys/callout.h> | ||||
#include <sys/ioctl.h> | |||||
#include <sys/queue.h> | |||||
#include <sys/resource.h> | |||||
#include <sys/sbuf.h> | |||||
#include <sys/socket.h> | |||||
#include <sys/sysctl.h> | |||||
#include <sys/time.h> | |||||
#include <bsdxml.h> | |||||
#include <malloc_np.h> | |||||
#include <stdint.h> | #include <stdint.h> | ||||
#include <stdio.h> | #include <stdio.h> | ||||
#include <stdlib.h> | #include <stdlib.h> | ||||
#include <unistd.h> | #include <unistd.h> | ||||
#include <fcntl.h> | #include <fcntl.h> | ||||
#include <inttypes.h> | |||||
#include <getopt.h> | #include <getopt.h> | ||||
#include <string.h> | #include <string.h> | ||||
#include <errno.h> | #include <errno.h> | ||||
#include <err.h> | #include <err.h> | ||||
#include <ctype.h> | #include <ctype.h> | ||||
#include <bitstring.h> | #include <bitstring.h> | ||||
#include <cam/scsi/scsi_all.h> | #include <cam/scsi/scsi_all.h> | ||||
#include <cam/ctl/ctl.h> | #include <cam/ctl/ctl.h> | ||||
#include <cam/ctl/ctl_io.h> | #include <cam/ctl/ctl_io.h> | ||||
#include <cam/ctl/ctl_scsi_all.h> | #include <cam/ctl/ctl_scsi_all.h> | ||||
#include <cam/ctl/ctl_util.h> | #include <cam/ctl/ctl_util.h> | ||||
#include <cam/ctl/ctl_backend.h> | #include <cam/ctl/ctl_backend.h> | ||||
#include <cam/ctl/ctl_ioctl.h> | #include <cam/ctl/ctl_ioctl.h> | ||||
/* | /* | ||||
* The default amount of space we allocate for stats storage space. | * The default amount of space we allocate for stats storage space. | ||||
* We dynamically allocate more if needed. | * We dynamically allocate more if needed. | ||||
*/ | */ | ||||
#define CTL_STAT_NUM_ITEMS 256 | #define CTL_STAT_NUM_ITEMS 256 | ||||
static int ctl_stat_bits; | static int ctl_stat_bits; | ||||
static const char *ctlstat_opts = "Cc:Ddhjl:n:p:tw:"; | static const char *ctlstat_opts = "Cc:DPdhjl:n:p:tw:"; | ||||
static const char *ctlstat_usage = "Usage: ctlstat [-CDdjht] [-l lunnum]" | static const char *ctlstat_usage = "Usage: ctlstat [-CDPdjht] [-l lunnum]" | ||||
"[-c count] [-n numdevs] [-w wait]\n"; | "[-c count] [-n numdevs] [-w wait]\n"; | ||||
struct ctl_cpu_stats { | struct ctl_cpu_stats { | ||||
uint64_t user; | uint64_t user; | ||||
uint64_t nice; | uint64_t nice; | ||||
uint64_t system; | uint64_t system; | ||||
uint64_t intr; | uint64_t intr; | ||||
uint64_t idle; | uint64_t idle; | ||||
}; | }; | ||||
typedef enum { | typedef enum { | ||||
CTLSTAT_MODE_STANDARD, | CTLSTAT_MODE_STANDARD, | ||||
CTLSTAT_MODE_DUMP, | CTLSTAT_MODE_DUMP, | ||||
CTLSTAT_MODE_JSON, | CTLSTAT_MODE_JSON, | ||||
CTLSTAT_MODE_PROMETHEUS, | |||||
} ctlstat_mode_types; | } ctlstat_mode_types; | ||||
#define CTLSTAT_FLAG_CPU (1 << 0) | #define CTLSTAT_FLAG_CPU (1 << 0) | ||||
#define CTLSTAT_FLAG_HEADER (1 << 1) | #define CTLSTAT_FLAG_HEADER (1 << 1) | ||||
#define CTLSTAT_FLAG_FIRST_RUN (1 << 2) | #define CTLSTAT_FLAG_FIRST_RUN (1 << 2) | ||||
#define CTLSTAT_FLAG_TOTALS (1 << 3) | #define CTLSTAT_FLAG_TOTALS (1 << 3) | ||||
#define CTLSTAT_FLAG_DMA_TIME (1 << 4) | #define CTLSTAT_FLAG_DMA_TIME (1 << 4) | ||||
#define CTLSTAT_FLAG_TIME_VALID (1 << 5) | #define CTLSTAT_FLAG_TIME_VALID (1 << 5) | ||||
Show All 21 Lines | struct ctlstat_context { | ||||
uint64_t cur_idle, prev_idle; | uint64_t cur_idle, prev_idle; | ||||
bitstr_t *item_mask; | bitstr_t *item_mask; | ||||
int cur_items, prev_items; | int cur_items, prev_items; | ||||
int cur_alloc, prev_alloc; | int cur_alloc, prev_alloc; | ||||
int numdevs; | int numdevs; | ||||
int header_interval; | int header_interval; | ||||
}; | }; | ||||
struct cctl_portlist_data { | |||||
int level; | |||||
struct sbuf *cur_sb[32]; | |||||
int lun; | |||||
int ntargets; | |||||
char *target; | |||||
char **targets; | |||||
}; | |||||
#ifndef min | #ifndef min | ||||
#define min(x,y) (((x) < (y)) ? (x) : (y)) | #define min(x,y) (((x) < (y)) ? (x) : (y)) | ||||
#endif | #endif | ||||
static void usage(int error); | static void usage(int error); | ||||
static int getstats(int fd, int *alloc_items, int *num_items, | static int getstats(int fd, int *alloc_items, int *num_items, | ||||
struct ctl_io_stats **xstats, struct timespec *cur_time, int *time_valid); | struct ctl_io_stats **xstats, struct timespec *cur_time, int *time_valid); | ||||
static int getcpu(struct ctl_cpu_stats *cpu_stats); | static int getcpu(struct ctl_cpu_stats *cpu_stats); | ||||
▲ Show 20 Lines • Show All 236 Lines • ▼ Show 20 Lines | for (i = n = 0; i < ctx->cur_items; i++) { | ||||
if (++n >= ctx->numdevs) | if (++n >= ctx->numdevs) | ||||
break; | break; | ||||
if (i < (ctx->cur_items - 1)) | if (i < (ctx->cur_items - 1)) | ||||
printf(","); /* continue lun array */ | printf(","); /* continue lun array */ | ||||
} | } | ||||
printf("]}"); | printf("]}"); | ||||
} | } | ||||
#define CTLSTAT_PROMETHEUS_LOOP(field) \ | |||||
for (i = n = 0; i < ctx->cur_items; i++) { \ | |||||
if (F_MASK(ctx) && bit_test(ctx->item_mask, \ | |||||
(int)stats[i].item) == 0) \ | |||||
continue; \ | |||||
for (iotype = 0; iotype < CTL_STATS_NUM_TYPES; iotype++) { \ | |||||
int lun = stats[i].item; \ | |||||
if (lun >= targdata.ntargets) \ | |||||
errx(1, "LUN %u out of range", lun); \ | |||||
printf("iscsi_target_" #field "{" \ | |||||
"lun=\"%u\",target=\"%s\",type=\"%s\"} %" PRIu64 \ | |||||
"\n", \ | |||||
lun, targdata.targets[lun], iotypes[iotype], \ | |||||
stats[i].field[iotype]); \ | |||||
} \ | |||||
} \ | |||||
#define CTLSTAT_PROMETHEUS_TIMELOOP(field) \ | |||||
for (i = n = 0; i < ctx->cur_items; i++) { \ | |||||
if (F_MASK(ctx) && bit_test(ctx->item_mask, \ | |||||
(int)stats[i].item) == 0) \ | |||||
continue; \ | |||||
for (iotype = 0; iotype < CTL_STATS_NUM_TYPES; iotype++) { \ | |||||
uint64_t us; \ | |||||
struct timespec ts; \ | |||||
int lun = stats[i].item; \ | |||||
if (lun >= targdata.ntargets) \ | |||||
errx(1, "LUN %u out of range", lun); \ | |||||
bintime2timespec(&stats[i].field[iotype], &ts); \ | |||||
us = ts.tv_sec * 1000000 + ts.tv_nsec / 1000; \ | |||||
printf("iscsi_target_" #field "{" \ | |||||
"lun=\"%u\",target=\"%s\",type=\"%s\"} %" PRIu64 \ | |||||
"\n", \ | |||||
lun, targdata.targets[lun], iotypes[iotype], us); \ | |||||
} \ | |||||
} \ | |||||
static void | static void | ||||
cctl_start_pelement(void *user_data, const char *name, const char **attr __unused) | |||||
{ | |||||
struct cctl_portlist_data* targdata = user_data; | |||||
targdata->level++; | |||||
if ((u_int)targdata->level >= (sizeof(targdata->cur_sb) / | |||||
sizeof(targdata->cur_sb[0]))) | |||||
errx(1, "%s: too many nesting levels, %zd max", __func__, | |||||
sizeof(targdata->cur_sb) / sizeof(targdata->cur_sb[0])); | |||||
targdata->cur_sb[targdata->level] = sbuf_new_auto(); | |||||
if (targdata->cur_sb[targdata->level] == NULL) | |||||
err(1, "%s: Unable to allocate sbuf", __func__); | |||||
if (strcmp(name, "targ_port") == 0) { | |||||
targdata->lun = -1; | |||||
free(targdata->target); | |||||
targdata->target = NULL; | |||||
} | |||||
} | |||||
static void | |||||
cctl_char_phandler(void *user_data, const XML_Char *str, int len) | |||||
{ | |||||
struct cctl_portlist_data *targdata = user_data; | |||||
sbuf_bcat(targdata->cur_sb[targdata->level], str, len); | |||||
} | |||||
static void | |||||
cctl_end_pelement(void *user_data, const char *name) | |||||
{ | |||||
struct cctl_portlist_data* targdata = user_data; | |||||
char *str; | |||||
if (targdata->cur_sb[targdata->level] == NULL) | |||||
errx(1, "%s: no valid sbuf at level %d (name %s)", __func__, | |||||
targdata->level, name); | |||||
if (sbuf_finish(targdata->cur_sb[targdata->level]) != 0) | |||||
err(1, "%s: sbuf_finish", __func__); | |||||
str = strdup(sbuf_data(targdata->cur_sb[targdata->level])); | |||||
if (str == NULL) | |||||
err(1, "%s can't allocate %zd bytes for string", __func__, | |||||
sbuf_len(targdata->cur_sb[targdata->level])); | |||||
sbuf_delete(targdata->cur_sb[targdata->level]); | |||||
targdata->cur_sb[targdata->level] = NULL; | |||||
targdata->level--; | |||||
if (strcmp(name, "target") == 0) { | |||||
free(targdata->target); | |||||
targdata->target = str; | |||||
} else if (strcmp(name, "lun") == 0) { | |||||
targdata->lun = atoi(str); | |||||
free(str); | |||||
} else if (strcmp(name, "targ_port") == 0) { | |||||
if (targdata->lun >= 0 && targdata->target != NULL) { | |||||
if (targdata->lun >= targdata->ntargets) { | |||||
/* | |||||
* This can happen for example if there are | |||||
* holes in CTL's lunlist. | |||||
*/ | |||||
targdata->ntargets = MAX(targdata->ntargets * 2, | |||||
targdata->lun + 1); | |||||
size_t newsize = targdata->ntargets * | |||||
sizeof(char*); | |||||
targdata->targets = rallocx(targdata->targets, | |||||
newsize, MALLOCX_ZERO); | |||||
} | |||||
free(targdata->targets[targdata->lun]); | |||||
targdata->targets[targdata->lun] = targdata->target; | |||||
targdata->target = NULL; | |||||
} | |||||
free(str); | |||||
} else { | |||||
free(str); | |||||
} | |||||
} | |||||
static void | |||||
ctlstat_prometheus(int fd, struct ctlstat_context *ctx) { | |||||
struct ctl_io_stats *stats = ctx->cur_stats; | |||||
struct ctl_lun_list list; | |||||
struct cctl_portlist_data targdata; | |||||
XML_Parser parser; | |||||
char *port_str = NULL; | |||||
int iotype, i, n, retval; | |||||
int port_len = 4096; | |||||
bzero(&targdata, sizeof(targdata)); | |||||
targdata.ntargets = ctx->cur_items; | |||||
targdata.targets = calloc(targdata.ntargets, sizeof(char*)); | |||||
retry: | |||||
port_str = (char *)realloc(port_str, port_len); | |||||
bzero(&list, sizeof(list)); | |||||
list.alloc_len = port_len; | |||||
list.status = CTL_LUN_LIST_NONE; | |||||
list.lun_xml = port_str; | |||||
if (ioctl(fd, CTL_PORT_LIST, &list) == -1) | |||||
err(1, "%s: error issuing CTL_PORT_LIST ioctl", __func__); | |||||
if (list.status == CTL_LUN_LIST_ERROR) { | |||||
warnx("%s: error returned from CTL_PORT_LIST ioctl:\n%s", | |||||
__func__, list.error_str); | |||||
} else if (list.status == CTL_LUN_LIST_NEED_MORE_SPACE) { | |||||
port_len <<= 1; | |||||
goto retry; | |||||
} | |||||
parser = XML_ParserCreate(NULL); | |||||
if (parser == NULL) | |||||
err(1, "%s: Unable to create XML parser", __func__); | |||||
XML_SetUserData(parser, &targdata); | |||||
XML_SetElementHandler(parser, cctl_start_pelement, cctl_end_pelement); | |||||
XML_SetCharacterDataHandler(parser, cctl_char_phandler); | |||||
retval = XML_Parse(parser, port_str, strlen(port_str), 1); | |||||
if (retval != 1) { | |||||
errx(1, "%s: Unable to parse XML: Error %d", __func__, | |||||
XML_GetErrorCode(parser)); | |||||
} | |||||
XML_ParserFree(parser); | |||||
/* | |||||
* NB: Some clients will print a warning if we don't set Content-Length, | |||||
* but they still work. And the data still gets into Prometheus. | |||||
*/ | |||||
printf("HTTP/1.1 200 OK\r\n" | |||||
"Connection: close\r\n" | |||||
"Content-Type: text/plain; version=0.0.4\r\n" | |||||
"\r\n"); | |||||
printf("# HELP iscsi_target_bytes Number of bytes\n" | |||||
"# TYPE iscsi_target_bytes counter\n"); | |||||
CTLSTAT_PROMETHEUS_LOOP(bytes); | |||||
printf("# HELP iscsi_target_dmas Number of DMA\n" | |||||
"# TYPE iscsi_target_dmas counter\n"); | |||||
CTLSTAT_PROMETHEUS_LOOP(dmas); | |||||
printf("# HELP iscsi_target_operations Number of operations\n" | |||||
"# TYPE iscsi_target_operations counter\n"); | |||||
CTLSTAT_PROMETHEUS_LOOP(operations); | |||||
printf("# HELP iscsi_target_time Cumulative operation time in us\n" | |||||
"# TYPE iscsi_target_time counter\n"); | |||||
CTLSTAT_PROMETHEUS_TIMELOOP(time); | |||||
printf("# HELP iscsi_target_dma_time Cumulative DMA time in us\n" | |||||
"# TYPE iscsi_target_dma_time counter\n"); | |||||
CTLSTAT_PROMETHEUS_TIMELOOP(dma_time); | |||||
for (i = 0; i < targdata.ntargets; i++) | |||||
free(targdata.targets[i]); | |||||
free(targdata.target); | |||||
free(targdata.targets); | |||||
fflush(stdout); | |||||
} | |||||
static void | |||||
ctlstat_standard(struct ctlstat_context *ctx) { | ctlstat_standard(struct ctlstat_context *ctx) { | ||||
long double etime; | long double etime; | ||||
uint64_t delta_jiffies, delta_idle; | uint64_t delta_jiffies, delta_idle; | ||||
long double cpu_percentage; | long double cpu_percentage; | ||||
int i, j, n; | int i, j, n; | ||||
cpu_percentage = 0; | cpu_percentage = 0; | ||||
▲ Show 20 Lines • Show All 261 Lines • ▼ Show 20 Lines | case 'p': { | ||||
ctx.numdevs = 1; | ctx.numdevs = 1; | ||||
else | else | ||||
ctx.numdevs++; | ctx.numdevs++; | ||||
bit_set(ctx.item_mask, cur_port); | bit_set(ctx.item_mask, cur_port); | ||||
ctx.flags |= CTLSTAT_FLAG_MASK; | ctx.flags |= CTLSTAT_FLAG_MASK; | ||||
ctx.flags |= CTLSTAT_FLAG_PORTS; | ctx.flags |= CTLSTAT_FLAG_PORTS; | ||||
break; | break; | ||||
} | } | ||||
case 'P': | |||||
ctx.mode = CTLSTAT_MODE_PROMETHEUS; | |||||
break; | |||||
case 't': | case 't': | ||||
ctx.flags |= CTLSTAT_FLAG_TOTALS; | ctx.flags |= CTLSTAT_FLAG_TOTALS; | ||||
break; | break; | ||||
case 'w': | case 'w': | ||||
waittime = atoi(optarg); | waittime = atoi(optarg); | ||||
break; | break; | ||||
default: | default: | ||||
retval = 1; | retval = 1; | ||||
usage(retval); | usage(retval); | ||||
exit(retval); | exit(retval); | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
if (F_LUNS(&ctx) && F_PORTS(&ctx)) | if (F_LUNS(&ctx) && F_PORTS(&ctx)) | ||||
errx(1, "Options -p and -l are exclusive."); | errx(1, "Options -p and -l are exclusive."); | ||||
if (ctx.mode == CTLSTAT_MODE_PROMETHEUS) { | |||||
if ((count != -1) || | |||||
(waittime != 1) || | |||||
/* NB: -P could be compatible with -t in the future */ | |||||
(ctx.flags & CTLSTAT_FLAG_TOTALS)) | |||||
{ | |||||
errx(1, "Option -P is exclusive with -c, -w, and -t"); | |||||
} | |||||
count = 1; | |||||
} | |||||
if (!F_LUNS(&ctx) && !F_PORTS(&ctx)) { | if (!F_LUNS(&ctx) && !F_PORTS(&ctx)) { | ||||
if (F_TOTALS(&ctx)) | if (F_TOTALS(&ctx)) | ||||
ctx.flags |= CTLSTAT_FLAG_PORTS; | ctx.flags |= CTLSTAT_FLAG_PORTS; | ||||
else | else | ||||
ctx.flags |= CTLSTAT_FLAG_LUNS; | ctx.flags |= CTLSTAT_FLAG_LUNS; | ||||
} | } | ||||
if ((fd = open(CTL_DEFAULT_DEV, O_RDWR)) == -1) | if ((fd = open(CTL_DEFAULT_DEV, O_RDWR)) == -1) | ||||
Show All 19 Lines | for (;count != 0;) { | ||||
case CTLSTAT_MODE_STANDARD: | case CTLSTAT_MODE_STANDARD: | ||||
ctlstat_standard(&ctx); | ctlstat_standard(&ctx); | ||||
break; | break; | ||||
case CTLSTAT_MODE_DUMP: | case CTLSTAT_MODE_DUMP: | ||||
ctlstat_dump(&ctx); | ctlstat_dump(&ctx); | ||||
break; | break; | ||||
case CTLSTAT_MODE_JSON: | case CTLSTAT_MODE_JSON: | ||||
ctlstat_json(&ctx); | ctlstat_json(&ctx); | ||||
break; | |||||
case CTLSTAT_MODE_PROMETHEUS: | |||||
ctlstat_prometheus(fd, &ctx); | |||||
break; | break; | ||||
default: | default: | ||||
break; | break; | ||||
} | } | ||||
fprintf(stdout, "\n"); | fprintf(stdout, "\n"); | ||||
fflush(stdout); | fflush(stdout); | ||||
ctx.flags &= ~CTLSTAT_FLAG_FIRST_RUN; | ctx.flags &= ~CTLSTAT_FLAG_FIRST_RUN; | ||||
Show All 12 Lines |