2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
4 * Copyright (c) 2004, 2008, 2009 Silicon Graphics International Corp.
5 * Copyright (c) 2017 Alexander Motin <mav@FreeBSD.org>
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions, and the following disclaimer,
13 * without modification.
14 * 2. Redistributions in binary form must reproduce at minimum a disclaimer
15 * substantially similar to the "NO WARRANTY" disclaimer below
16 * ("Disclaimer") and any redistribution must be conditioned upon
17 * including a substantially similar Disclaimer requirement for further
18 * binary redistribution.
21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
24 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25 * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
29 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
30 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 * POSSIBILITY OF SUCH DAMAGES.
33 * $Id: //depot/users/kenm/FreeBSD-test2/usr.bin/ctlstat/ctlstat.c#4 $
36 * CAM Target Layer statistics program
38 * Authors: Ken Merry <ken@FreeBSD.org>, Will Andrews <will@FreeBSD.org>
41 #include <sys/cdefs.h>
42 __FBSDID("$FreeBSD$");
44 #include <sys/param.h>
45 #include <sys/callout.h>
46 #include <sys/ioctl.h>
47 #include <sys/queue.h>
48 #include <sys/resource.h>
50 #include <sys/socket.h>
51 #include <sys/sysctl.h>
55 #include <malloc_np.h>
67 #include <bitstring.h>
68 #include <cam/scsi/scsi_all.h>
69 #include <cam/ctl/ctl.h>
70 #include <cam/ctl/ctl_io.h>
71 #include <cam/ctl/ctl_scsi_all.h>
72 #include <cam/ctl/ctl_util.h>
73 #include <cam/ctl/ctl_backend.h>
74 #include <cam/ctl/ctl_ioctl.h>
77 * The default amount of space we allocate for stats storage space.
78 * We dynamically allocate more if needed.
80 #define CTL_STAT_NUM_ITEMS 256
82 static int ctl_stat_bits;
84 static const char *ctlstat_opts = "Cc:DPdhjl:n:p:tw:";
85 static const char *ctlstat_usage = "Usage: ctlstat [-CDPdjht] [-l lunnum]"
86 "[-c count] [-n numdevs] [-w wait]\n";
88 struct ctl_cpu_stats {
97 CTLSTAT_MODE_STANDARD,
100 CTLSTAT_MODE_PROMETHEUS,
101 } ctlstat_mode_types;
103 #define CTLSTAT_FLAG_CPU (1 << 0)
104 #define CTLSTAT_FLAG_HEADER (1 << 1)
105 #define CTLSTAT_FLAG_FIRST_RUN (1 << 2)
106 #define CTLSTAT_FLAG_TOTALS (1 << 3)
107 #define CTLSTAT_FLAG_DMA_TIME (1 << 4)
108 #define CTLSTAT_FLAG_TIME_VALID (1 << 5)
109 #define CTLSTAT_FLAG_MASK (1 << 6)
110 #define CTLSTAT_FLAG_LUNS (1 << 7)
111 #define CTLSTAT_FLAG_PORTS (1 << 8)
112 #define F_CPU(ctx) ((ctx)->flags & CTLSTAT_FLAG_CPU)
113 #define F_HDR(ctx) ((ctx)->flags & CTLSTAT_FLAG_HEADER)
114 #define F_FIRST(ctx) ((ctx)->flags & CTLSTAT_FLAG_FIRST_RUN)
115 #define F_TOTALS(ctx) ((ctx)->flags & CTLSTAT_FLAG_TOTALS)
116 #define F_DMA(ctx) ((ctx)->flags & CTLSTAT_FLAG_DMA_TIME)
117 #define F_TIMEVAL(ctx) ((ctx)->flags & CTLSTAT_FLAG_TIME_VALID)
118 #define F_MASK(ctx) ((ctx)->flags & CTLSTAT_FLAG_MASK)
119 #define F_LUNS(ctx) ((ctx)->flags & CTLSTAT_FLAG_LUNS)
120 #define F_PORTS(ctx) ((ctx)->flags & CTLSTAT_FLAG_PORTS)
122 struct ctlstat_context {
123 ctlstat_mode_types mode;
125 struct ctl_io_stats *cur_stats, *prev_stats;
126 struct ctl_io_stats cur_total_stats[3], prev_total_stats[3];
127 struct timespec cur_time, prev_time;
128 struct ctl_cpu_stats cur_cpu, prev_cpu;
129 uint64_t cur_total_jiffies, prev_total_jiffies;
130 uint64_t cur_idle, prev_idle;
132 int cur_items, prev_items;
133 int cur_alloc, prev_alloc;
138 struct cctl_portlist_data {
140 struct sbuf *cur_sb[32];
149 #define min(x,y) (((x) < (y)) ? (x) : (y))
152 static void usage(int error);
153 static int getstats(int fd, int *alloc_items, int *num_items,
154 struct ctl_io_stats **xstats, struct timespec *cur_time, int *time_valid,
156 static int getcpu(struct ctl_cpu_stats *cpu_stats);
157 static void compute_stats(struct ctl_io_stats *cur_stats,
158 struct ctl_io_stats *prev_stats,
159 long double etime, long double *mbsec,
160 long double *kb_per_transfer,
161 long double *transfers_per_second,
162 long double *ms_per_transfer,
163 long double *ms_per_dma,
164 long double *dmas_per_second);
169 fputs(ctlstat_usage, error ? stderr : stdout);
173 getstats(int fd, int *alloc_items, int *num_items, struct ctl_io_stats **stats,
174 struct timespec *cur_time, int *flags, bool ports)
176 struct ctl_get_io_stats get_stats;
177 int more_space_count = 0;
179 if (*alloc_items == 0)
180 *alloc_items = CTL_STAT_NUM_ITEMS;
183 *stats = malloc(sizeof(**stats) * *alloc_items);
185 memset(&get_stats, 0, sizeof(get_stats));
186 get_stats.alloc_len = *alloc_items * sizeof(**stats);
187 memset(*stats, 0, get_stats.alloc_len);
188 get_stats.stats = *stats;
190 if (ioctl(fd, ports ? CTL_GET_PORT_STATS : CTL_GET_LUN_STATS,
192 err(1, "CTL_GET_*_STATS ioctl returned error");
194 switch (get_stats.status) {
198 err(1, "CTL_GET_*_STATS ioctl returned CTL_SS_ERROR");
200 case CTL_SS_NEED_MORE_SPACE:
201 if (more_space_count >= 2)
202 errx(1, "CTL_GET_*_STATS returned NEED_MORE_SPACE again");
203 *alloc_items = get_stats.num_items * 5 / 4;
208 break; /* NOTREACHED */
210 errx(1, "CTL_GET_*_STATS ioctl returned unknown status %d",
215 *num_items = get_stats.fill_len / sizeof(**stats);
216 cur_time->tv_sec = get_stats.timestamp.tv_sec;
217 cur_time->tv_nsec = get_stats.timestamp.tv_nsec;
218 if (get_stats.flags & CTL_STATS_FLAG_TIME_VALID)
219 *flags |= CTLSTAT_FLAG_TIME_VALID;
221 *flags &= ~CTLSTAT_FLAG_TIME_VALID;
227 getcpu(struct ctl_cpu_stats *cpu_stats)
229 long cp_time[CPUSTATES];
232 cplen = sizeof(cp_time);
234 if (sysctlbyname("kern.cp_time", &cp_time, &cplen, NULL, 0) == -1) {
235 warn("sysctlbyname(kern.cp_time...) failed");
239 cpu_stats->user = cp_time[CP_USER];
240 cpu_stats->nice = cp_time[CP_NICE];
241 cpu_stats->system = cp_time[CP_SYS];
242 cpu_stats->intr = cp_time[CP_INTR];
243 cpu_stats->idle = cp_time[CP_IDLE];
249 compute_stats(struct ctl_io_stats *cur_stats,
250 struct ctl_io_stats *prev_stats, long double etime,
251 long double *mbsec, long double *kb_per_transfer,
252 long double *transfers_per_second, long double *ms_per_transfer,
253 long double *ms_per_dma, long double *dmas_per_second)
255 uint64_t total_bytes = 0, total_operations = 0, total_dmas = 0;
256 struct bintime total_time_bt, total_dma_bt;
257 struct timespec total_time_ts, total_dma_ts;
260 bzero(&total_time_bt, sizeof(total_time_bt));
261 bzero(&total_dma_bt, sizeof(total_dma_bt));
262 bzero(&total_time_ts, sizeof(total_time_ts));
263 bzero(&total_dma_ts, sizeof(total_dma_ts));
264 for (i = 0; i < CTL_STATS_NUM_TYPES; i++) {
265 total_bytes += cur_stats->bytes[i];
266 total_operations += cur_stats->operations[i];
267 total_dmas += cur_stats->dmas[i];
268 bintime_add(&total_time_bt, &cur_stats->time[i]);
269 bintime_add(&total_dma_bt, &cur_stats->dma_time[i]);
270 if (prev_stats != NULL) {
271 total_bytes -= prev_stats->bytes[i];
272 total_operations -= prev_stats->operations[i];
273 total_dmas -= prev_stats->dmas[i];
274 bintime_sub(&total_time_bt, &prev_stats->time[i]);
275 bintime_sub(&total_dma_bt, &prev_stats->dma_time[i]);
279 *mbsec = total_bytes;
280 *mbsec /= 1024 * 1024;
285 *kb_per_transfer = total_bytes;
286 *kb_per_transfer /= 1024;
287 if (total_operations > 0)
288 *kb_per_transfer /= total_operations;
290 *kb_per_transfer = 0;
291 *transfers_per_second = total_operations;
292 *dmas_per_second = total_dmas;
294 *transfers_per_second /= etime;
295 *dmas_per_second /= etime;
297 *transfers_per_second = 0;
298 *dmas_per_second = 0;
301 bintime2timespec(&total_time_bt, &total_time_ts);
302 bintime2timespec(&total_dma_bt, &total_dma_ts);
303 if (total_operations > 0) {
305 * Convert the timespec to milliseconds.
307 *ms_per_transfer = total_time_ts.tv_sec * 1000;
308 *ms_per_transfer += total_time_ts.tv_nsec / 1000000;
309 *ms_per_transfer /= total_operations;
311 *ms_per_transfer = 0;
313 if (total_dmas > 0) {
315 * Convert the timespec to milliseconds.
317 *ms_per_dma = total_dma_ts.tv_sec * 1000;
318 *ms_per_dma += total_dma_ts.tv_nsec / 1000000;
319 *ms_per_dma /= total_dmas;
324 /* The dump_stats() and json_stats() functions perform essentially the same
325 * purpose, but dump the statistics in different formats. JSON is more
326 * conducive to programming, however.
329 #define PRINT_BINTIME(bt) \
330 printf("%jd.%06ju", (intmax_t)(bt).sec, \
331 (uintmax_t)(((bt).frac >> 32) * 1000000 >> 32))
332 static const char *iotypes[] = {"NO IO", "READ", "WRITE"};
335 ctlstat_dump(struct ctlstat_context *ctx)
338 struct ctl_io_stats *stats = ctx->cur_stats;
340 for (i = n = 0; i < ctx->cur_items;i++) {
341 if (F_MASK(ctx) && bit_test(ctx->item_mask,
342 (int)stats[i].item) == 0)
344 printf("%s %d\n", F_PORTS(ctx) ? "port" : "lun", stats[i].item);
345 for (iotype = 0; iotype < CTL_STATS_NUM_TYPES; iotype++) {
346 printf(" io type %d (%s)\n", iotype, iotypes[iotype]);
347 printf(" bytes %ju\n", (uintmax_t)
348 stats[i].bytes[iotype]);
349 printf(" operations %ju\n", (uintmax_t)
350 stats[i].operations[iotype]);
351 printf(" dmas %ju\n", (uintmax_t)
352 stats[i].dmas[iotype]);
354 PRINT_BINTIME(stats[i].time[iotype]);
355 printf("\n dma time ");
356 PRINT_BINTIME(stats[i].dma_time[iotype]);
359 if (++n >= ctx->numdevs)
365 ctlstat_json(struct ctlstat_context *ctx) {
367 struct ctl_io_stats *stats = ctx->cur_stats;
369 printf("{\"%s\":[", F_PORTS(ctx) ? "ports" : "luns");
370 for (i = n = 0; i < ctx->cur_items; i++) {
371 if (F_MASK(ctx) && bit_test(ctx->item_mask,
372 (int)stats[i].item) == 0)
374 printf("{\"num\":%d,\"io\":[",
376 for (iotype = 0; iotype < CTL_STATS_NUM_TYPES; iotype++) {
377 printf("{\"type\":\"%s\",", iotypes[iotype]);
378 printf("\"bytes\":%ju,", (uintmax_t)
379 stats[i].bytes[iotype]);
380 printf("\"operations\":%ju,", (uintmax_t)
381 stats[i].operations[iotype]);
382 printf("\"dmas\":%ju,", (uintmax_t)
383 stats[i].dmas[iotype]);
384 printf("\"io time\":");
385 PRINT_BINTIME(stats[i].time[iotype]);
386 printf(",\"dma time\":");
387 PRINT_BINTIME(stats[i].dma_time[iotype]);
389 if (iotype < (CTL_STATS_NUM_TYPES - 1))
390 printf(","); /* continue io array */
393 if (++n >= ctx->numdevs)
395 if (i < (ctx->cur_items - 1))
396 printf(","); /* continue lun array */
401 #define CTLSTAT_PROMETHEUS_LOOP(field, collector) \
402 for (i = n = 0; i < ctx->cur_items; i++) { \
403 if (F_MASK(ctx) && bit_test(ctx->item_mask, \
404 (int)stats[i].item) == 0) \
406 for (iotype = 0; iotype < CTL_STATS_NUM_TYPES; iotype++) { \
407 int idx = stats[i].item; \
409 * Note that Prometheus considers a label value of "" \
410 * to be the same as no label at all \
412 const char *target = ""; \
413 if (strcmp(collector, "port") == 0 && \
414 targdata.targets[idx] != NULL) \
416 target = targdata.targets[idx]; \
418 printf("iscsi_%s_" #field "{" \
419 "%s=\"%u\",target=\"%s\",type=\"%s\"} %" PRIu64 \
421 collector, collector, \
422 idx, target, iotypes[iotype], \
423 stats[i].field[iotype]); \
427 #define CTLSTAT_PROMETHEUS_TIMELOOP(field, collector) \
428 for (i = n = 0; i < ctx->cur_items; i++) { \
429 if (F_MASK(ctx) && bit_test(ctx->item_mask, \
430 (int)stats[i].item) == 0) \
432 for (iotype = 0; iotype < CTL_STATS_NUM_TYPES; iotype++) { \
434 struct timespec ts; \
435 int idx = stats[i].item; \
437 * Note that Prometheus considers a label value of "" \
438 * to be the same as no label at all \
440 const char *target = ""; \
441 if (strcmp(collector, "port") == 0 && \
442 targdata.targets[idx] != NULL) \
444 target = targdata.targets[idx]; \
446 bintime2timespec(&stats[i].field[iotype], &ts); \
447 us = ts.tv_sec * 1000000 + ts.tv_nsec / 1000; \
448 printf("iscsi_%s_" #field "{" \
449 "%s=\"%u\",target=\"%s\",type=\"%s\"} %" PRIu64 \
451 collector, collector, \
452 idx, target, iotypes[iotype], us); \
457 cctl_start_pelement(void *user_data, const char *name, const char **attr)
459 struct cctl_portlist_data* targdata = user_data;
462 if ((u_int)targdata->level >= (sizeof(targdata->cur_sb) /
463 sizeof(targdata->cur_sb[0])))
464 errx(1, "%s: too many nesting levels, %zd max", __func__,
465 sizeof(targdata->cur_sb) / sizeof(targdata->cur_sb[0]));
467 targdata->cur_sb[targdata->level] = sbuf_new_auto();
468 if (targdata->cur_sb[targdata->level] == NULL)
469 err(1, "%s: Unable to allocate sbuf", __func__);
471 if (strcmp(name, "targ_port") == 0) {
476 free(targdata->target);
477 targdata->target = NULL;
479 if (strcmp(attr[i], "id") == 0) {
481 * Well-formed XML always pairs keys with
485 targdata->id = atoi(attr[i + 1]);
494 cctl_char_phandler(void *user_data, const XML_Char *str, int len)
496 struct cctl_portlist_data *targdata = user_data;
498 sbuf_bcat(targdata->cur_sb[targdata->level], str, len);
502 cctl_end_pelement(void *user_data, const char *name)
504 struct cctl_portlist_data* targdata = user_data;
507 if (targdata->cur_sb[targdata->level] == NULL)
508 errx(1, "%s: no valid sbuf at level %d (name %s)", __func__,
509 targdata->level, name);
511 if (sbuf_finish(targdata->cur_sb[targdata->level]) != 0)
512 err(1, "%s: sbuf_finish", __func__);
513 str = strdup(sbuf_data(targdata->cur_sb[targdata->level]));
515 err(1, "%s can't allocate %zd bytes for string", __func__,
516 sbuf_len(targdata->cur_sb[targdata->level]));
518 sbuf_delete(targdata->cur_sb[targdata->level]);
519 targdata->cur_sb[targdata->level] = NULL;
522 if (strcmp(name, "target") == 0) {
523 free(targdata->target);
524 targdata->target = str;
525 } else if (strcmp(name, "targ_port") == 0) {
526 if (targdata->id >= 0 && targdata->target != NULL) {
527 if (targdata->id >= targdata->ntargets) {
529 * This can happen for example if there are
530 * targets with no LUNs.
532 targdata->ntargets = MAX(targdata->ntargets * 2,
534 size_t newsize = targdata->ntargets *
536 targdata->targets = rallocx(targdata->targets,
537 newsize, MALLOCX_ZERO);
539 free(targdata->targets[targdata->id]);
540 targdata->targets[targdata->id] = targdata->target;
541 targdata->target = NULL;
550 ctlstat_prometheus(int fd, struct ctlstat_context *ctx, bool ports) {
551 struct ctl_io_stats *stats = ctx->cur_stats;
552 struct ctl_lun_list list;
553 struct cctl_portlist_data targdata;
555 char *port_str = NULL;
556 int iotype, i, n, retval;
558 const char *collector;
560 bzero(&targdata, sizeof(targdata));
561 targdata.ntargets = ctx->cur_items;
562 targdata.targets = calloc(targdata.ntargets, sizeof(char*));
564 port_str = (char *)realloc(port_str, port_len);
565 bzero(&list, sizeof(list));
566 list.alloc_len = port_len;
567 list.status = CTL_LUN_LIST_NONE;
568 list.lun_xml = port_str;
569 if (ioctl(fd, CTL_PORT_LIST, &list) == -1)
570 err(1, "%s: error issuing CTL_PORT_LIST ioctl", __func__);
571 if (list.status == CTL_LUN_LIST_ERROR) {
572 warnx("%s: error returned from CTL_PORT_LIST ioctl:\n%s",
573 __func__, list.error_str);
574 } else if (list.status == CTL_LUN_LIST_NEED_MORE_SPACE) {
579 parser = XML_ParserCreate(NULL);
581 err(1, "%s: Unable to create XML parser", __func__);
582 XML_SetUserData(parser, &targdata);
583 XML_SetElementHandler(parser, cctl_start_pelement, cctl_end_pelement);
584 XML_SetCharacterDataHandler(parser, cctl_char_phandler);
586 retval = XML_Parse(parser, port_str, strlen(port_str), 1);
588 errx(1, "%s: Unable to parse XML: Error %d", __func__,
589 XML_GetErrorCode(parser));
591 XML_ParserFree(parser);
593 collector = ports ? "port" : "lun";
595 printf("# HELP iscsi_%s_bytes Number of bytes\n"
596 "# TYPE iscsi_%s_bytes counter\n", collector, collector);
597 CTLSTAT_PROMETHEUS_LOOP(bytes, collector);
598 printf("# HELP iscsi_%s_dmas Number of DMA\n"
599 "# TYPE iscsi_%s_dmas counter\n", collector, collector);
600 CTLSTAT_PROMETHEUS_LOOP(dmas, collector);
601 printf("# HELP iscsi_%s_operations Number of operations\n"
602 "# TYPE iscsi_%s_operations counter\n", collector, collector);
603 CTLSTAT_PROMETHEUS_LOOP(operations, collector);
604 printf("# HELP iscsi_%s_time Cumulative operation time in us\n"
605 "# TYPE iscsi_%s_time counter\n", collector, collector);
606 CTLSTAT_PROMETHEUS_TIMELOOP(time, collector);
607 printf("# HELP iscsi_%s_dma_time Cumulative DMA time in us\n"
608 "# TYPE iscsi_%s_dma_time counter\n", collector, collector);
609 CTLSTAT_PROMETHEUS_TIMELOOP(dma_time, collector);
611 for (i = 0; i < targdata.ntargets; i++)
612 free(targdata.targets[i]);
613 free(targdata.target);
614 free(targdata.targets);
620 ctlstat_standard(struct ctlstat_context *ctx) {
622 uint64_t delta_jiffies, delta_idle;
623 long double cpu_percentage;
628 if (F_CPU(ctx) && (getcpu(&ctx->cur_cpu) != 0))
629 errx(1, "error returned from getcpu()");
631 etime = ctx->cur_time.tv_sec - ctx->prev_time.tv_sec +
632 (ctx->prev_time.tv_nsec - ctx->cur_time.tv_nsec) * 1e-9;
635 ctx->prev_total_jiffies = ctx->cur_total_jiffies;
636 ctx->cur_total_jiffies = ctx->cur_cpu.user +
637 ctx->cur_cpu.nice + ctx->cur_cpu.system +
638 ctx->cur_cpu.intr + ctx->cur_cpu.idle;
639 delta_jiffies = ctx->cur_total_jiffies;
640 if (F_FIRST(ctx) == 0)
641 delta_jiffies -= ctx->prev_total_jiffies;
642 ctx->prev_idle = ctx->cur_idle;
643 ctx->cur_idle = ctx->cur_cpu.idle;
644 delta_idle = ctx->cur_idle - ctx->prev_idle;
646 cpu_percentage = delta_jiffies - delta_idle;
647 cpu_percentage /= delta_jiffies;
648 cpu_percentage *= 100;
652 ctx->header_interval--;
653 if (ctx->header_interval <= 0) {
655 fprintf(stdout, " CPU");
657 fprintf(stdout, "%s Read %s"
659 (F_TIMEVAL(ctx) != 0) ? " " : "",
660 (F_TIMEVAL(ctx) != 0) ? " " : "",
661 (F_TIMEVAL(ctx) != 0) ? " " : "");
664 for (i = n = 0; i < min(ctl_stat_bits,
665 ctx->cur_items); i++) {
669 * Obviously this won't work with
670 * LUN numbers greater than a signed
673 item = (int)ctx->cur_stats[i].item;
676 bit_test(ctx->item_mask, item) == 0)
678 fprintf(stdout, "%15.6s%d %s",
679 F_PORTS(ctx) ? "port" : "lun", item,
680 (F_TIMEVAL(ctx) != 0) ? " " : "");
681 if (++n >= ctx->numdevs)
684 fprintf(stdout, "\n");
687 fprintf(stdout, " ");
688 for (i = 0; i < n; i++)
689 fprintf(stdout, "%s KB/t %s MB/s",
690 (F_TIMEVAL(ctx) != 0) ? " ms" : "",
691 (F_DMA(ctx) == 0) ? "tps" : "dps");
692 fprintf(stdout, "\n");
693 ctx->header_interval = 20;
698 fprintf(stdout, "%3.0Lf%%", cpu_percentage);
699 if (F_TOTALS(ctx) != 0) {
700 long double mbsec[3];
701 long double kb_per_transfer[3];
702 long double transfers_per_sec[3];
703 long double ms_per_transfer[3];
704 long double ms_per_dma[3];
705 long double dmas_per_sec[3];
707 for (i = 0; i < 3; i++)
708 ctx->prev_total_stats[i] = ctx->cur_total_stats[i];
710 memset(&ctx->cur_total_stats, 0, sizeof(ctx->cur_total_stats));
712 /* Use macros to make the next loop more readable. */
713 #define ADD_STATS_BYTES(st, i, j) \
714 ctx->cur_total_stats[st].bytes[j] += \
715 ctx->cur_stats[i].bytes[j]
716 #define ADD_STATS_OPERATIONS(st, i, j) \
717 ctx->cur_total_stats[st].operations[j] += \
718 ctx->cur_stats[i].operations[j]
719 #define ADD_STATS_DMAS(st, i, j) \
720 ctx->cur_total_stats[st].dmas[j] += \
721 ctx->cur_stats[i].dmas[j]
722 #define ADD_STATS_TIME(st, i, j) \
723 bintime_add(&ctx->cur_total_stats[st].time[j], \
724 &ctx->cur_stats[i].time[j])
725 #define ADD_STATS_DMA_TIME(st, i, j) \
726 bintime_add(&ctx->cur_total_stats[st].dma_time[j], \
727 &ctx->cur_stats[i].dma_time[j])
729 for (i = 0; i < ctx->cur_items; i++) {
730 if (F_MASK(ctx) && bit_test(ctx->item_mask,
731 (int)ctx->cur_stats[i].item) == 0)
733 for (j = 0; j < CTL_STATS_NUM_TYPES; j++) {
734 ADD_STATS_BYTES(2, i, j);
735 ADD_STATS_OPERATIONS(2, i, j);
736 ADD_STATS_DMAS(2, i, j);
737 ADD_STATS_TIME(2, i, j);
738 ADD_STATS_DMA_TIME(2, i, j);
740 ADD_STATS_BYTES(0, i, CTL_STATS_READ);
741 ADD_STATS_OPERATIONS(0, i, CTL_STATS_READ);
742 ADD_STATS_DMAS(0, i, CTL_STATS_READ);
743 ADD_STATS_TIME(0, i, CTL_STATS_READ);
744 ADD_STATS_DMA_TIME(0, i, CTL_STATS_READ);
746 ADD_STATS_BYTES(1, i, CTL_STATS_WRITE);
747 ADD_STATS_OPERATIONS(1, i, CTL_STATS_WRITE);
748 ADD_STATS_DMAS(1, i, CTL_STATS_WRITE);
749 ADD_STATS_TIME(1, i, CTL_STATS_WRITE);
750 ADD_STATS_DMA_TIME(1, i, CTL_STATS_WRITE);
753 for (i = 0; i < 3; i++) {
754 compute_stats(&ctx->cur_total_stats[i],
755 F_FIRST(ctx) ? NULL : &ctx->prev_total_stats[i],
756 etime, &mbsec[i], &kb_per_transfer[i],
757 &transfers_per_sec[i],
758 &ms_per_transfer[i], &ms_per_dma[i],
761 fprintf(stdout, " %5.1Lf",
763 else if (F_TIMEVAL(ctx) != 0)
764 fprintf(stdout, " %5.1Lf",
766 fprintf(stdout, " %4.0Lf %5.0Lf %4.0Lf",
768 (F_DMA(ctx) == 0) ? transfers_per_sec[i] :
769 dmas_per_sec[i], mbsec[i]);
772 for (i = n = 0; i < min(ctl_stat_bits, ctx->cur_items); i++) {
773 long double mbsec, kb_per_transfer;
774 long double transfers_per_sec;
775 long double ms_per_transfer;
776 long double ms_per_dma;
777 long double dmas_per_sec;
779 if (F_MASK(ctx) && bit_test(ctx->item_mask,
780 (int)ctx->cur_stats[i].item) == 0)
782 for (j = 0; j < ctx->prev_items; j++) {
783 if (ctx->prev_stats[j].item ==
784 ctx->cur_stats[i].item)
787 if (j >= ctx->prev_items)
789 compute_stats(&ctx->cur_stats[i],
790 j >= 0 ? &ctx->prev_stats[j] : NULL,
791 etime, &mbsec, &kb_per_transfer,
792 &transfers_per_sec, &ms_per_transfer,
793 &ms_per_dma, &dmas_per_sec);
795 fprintf(stdout, " %5.1Lf",
797 else if (F_TIMEVAL(ctx) != 0)
798 fprintf(stdout, " %5.1Lf",
800 fprintf(stdout, " %4.0Lf %5.0Lf %4.0Lf",
801 kb_per_transfer, (F_DMA(ctx) == 0) ?
802 transfers_per_sec : dmas_per_sec, mbsec);
803 if (++n >= ctx->numdevs)
810 get_and_print_stats(int fd, struct ctlstat_context *ctx, bool ports)
812 struct ctl_io_stats *tmp_stats;
815 tmp_stats = ctx->prev_stats;
816 ctx->prev_stats = ctx->cur_stats;
817 ctx->cur_stats = tmp_stats;
819 ctx->prev_alloc = ctx->cur_alloc;
822 ctx->prev_items = ctx->cur_items;
824 ctx->prev_time = ctx->cur_time;
825 ctx->prev_cpu = ctx->cur_cpu;
826 if (getstats(fd, &ctx->cur_alloc, &ctx->cur_items,
827 &ctx->cur_stats, &ctx->cur_time, &ctx->flags, ports) != 0)
828 errx(1, "error returned from getstats()");
831 case CTLSTAT_MODE_STANDARD:
832 ctlstat_standard(ctx);
834 case CTLSTAT_MODE_DUMP:
837 case CTLSTAT_MODE_JSON:
840 case CTLSTAT_MODE_PROMETHEUS:
841 ctlstat_prometheus(fd, ctx, ports);
849 main(int argc, char **argv)
855 struct ctlstat_context ctx;
861 memset(&ctx, 0, sizeof(ctx));
863 ctx.mode = CTLSTAT_MODE_STANDARD;
864 ctx.flags |= CTLSTAT_FLAG_CPU;
865 ctx.flags |= CTLSTAT_FLAG_FIRST_RUN;
866 ctx.flags |= CTLSTAT_FLAG_HEADER;
868 size = sizeof(ctl_stat_bits);
869 if (sysctlbyname("kern.cam.ctl.max_luns", &ctl_stat_bits, &size, NULL,
871 /* Backward compatibility for where the sysctl wasn't exposed */
872 ctl_stat_bits = 1024;
874 ctx.item_mask = bit_alloc(ctl_stat_bits);
875 if (ctx.item_mask == NULL)
876 err(1, "bit_alloc() failed");
878 while ((c = getopt(argc, argv, ctlstat_opts)) != -1) {
881 ctx.flags &= ~CTLSTAT_FLAG_CPU;
884 count = atoi(optarg);
887 ctx.flags |= CTLSTAT_FLAG_DMA_TIME;
890 ctx.mode = CTLSTAT_MODE_DUMP;
894 ctx.flags &= ~CTLSTAT_FLAG_HEADER;
897 ctx.mode = CTLSTAT_MODE_JSON;
903 cur_lun = atoi(optarg);
904 if (cur_lun > ctl_stat_bits)
905 errx(1, "Invalid LUN number %d", cur_lun);
911 bit_set(ctx.item_mask, cur_lun);
912 ctx.flags |= CTLSTAT_FLAG_MASK;
913 ctx.flags |= CTLSTAT_FLAG_LUNS;
917 ctx.numdevs = atoi(optarg);
922 cur_port = atoi(optarg);
923 if (cur_port > ctl_stat_bits)
924 errx(1, "Invalid port number %d", cur_port);
930 bit_set(ctx.item_mask, cur_port);
931 ctx.flags |= CTLSTAT_FLAG_MASK;
932 ctx.flags |= CTLSTAT_FLAG_PORTS;
936 ctx.mode = CTLSTAT_MODE_PROMETHEUS;
939 ctx.flags |= CTLSTAT_FLAG_TOTALS;
942 waittime = atoi(optarg);
952 if (F_LUNS(&ctx) && F_PORTS(&ctx))
953 errx(1, "Options -p and -l are exclusive.");
955 if (ctx.mode == CTLSTAT_MODE_PROMETHEUS) {
959 /* NB: -P could be compatible with -t in the future */
960 (ctx.flags & CTLSTAT_FLAG_TOTALS))
962 errx(1, "Option -P is exclusive with -p, -c, -w, and -t");
967 if (!F_LUNS(&ctx) && !F_PORTS(&ctx)) {
969 ctx.flags |= CTLSTAT_FLAG_PORTS;
971 ctx.flags |= CTLSTAT_FLAG_LUNS;
974 if ((fd = open(CTL_DEFAULT_DEV, O_RDWR)) == -1)
975 err(1, "cannot open %s", CTL_DEFAULT_DEV);
977 if (ctx.mode == CTLSTAT_MODE_PROMETHEUS) {
979 * NB: Some clients will print a warning if we don't set
980 * Content-Length, but they still work. And the data still
981 * gets into Prometheus.
983 printf("HTTP/1.1 200 OK\r\n"
984 "Connection: close\r\n"
985 "Content-Type: text/plain; version=0.0.4\r\n"
992 if (ctx.mode == CTLSTAT_MODE_PROMETHEUS) {
993 get_and_print_stats(fd, &ctx, false);
994 get_and_print_stats(fd, &ctx, true);
996 ports = ctx.flags & CTLSTAT_FLAG_PORTS;
997 get_and_print_stats(fd, &ctx, ports);
1000 fprintf(stdout, "\n");
1002 ctx.flags &= ~CTLSTAT_FLAG_FIRST_RUN;