1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3 * 4 * Copyright (c) 2004, 2008, 2009 Silicon Graphics International Corp. 5 * Copyright (c) 2017 Alexander Motin <mav@FreeBSD.org> 6 * All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 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. 19 * 20 * NO WARRANTY 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. 32 * 33 * $Id: //depot/users/kenm/FreeBSD-test2/usr.bin/ctlstat/ctlstat.c#4 $ 34 */ 35 /* 36 * CAM Target Layer statistics program 37 * 38 * Authors: Ken Merry <ken@FreeBSD.org>, Will Andrews <will@FreeBSD.org> 39 */ 40 41 #include <sys/cdefs.h> 42 __FBSDID("$FreeBSD$"); 43 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> 49 #include <sys/sbuf.h> 50 #include <sys/socket.h> 51 #include <sys/sysctl.h> 52 #include <sys/time.h> 53 #include <bsdxml.h> 54 #include <malloc_np.h> 55 #include <stdint.h> 56 #include <stdio.h> 57 #include <stdlib.h> 58 #include <unistd.h> 59 #include <fcntl.h> 60 #include <inttypes.h> 61 #include <getopt.h> 62 #include <string.h> 63 #include <errno.h> 64 #include <err.h> 65 #include <ctype.h> 66 #include <bitstring.h> 67 #include <cam/scsi/scsi_all.h> 68 #include <cam/ctl/ctl.h> 69 #include <cam/ctl/ctl_io.h> 70 #include <cam/ctl/ctl_scsi_all.h> 71 #include <cam/ctl/ctl_util.h> 72 #include <cam/ctl/ctl_backend.h> 73 #include <cam/ctl/ctl_ioctl.h> 74 75 /* 76 * The default amount of space we allocate for stats storage space. 77 * We dynamically allocate more if needed. 78 */ 79 #define CTL_STAT_NUM_ITEMS 256 80 81 static int ctl_stat_bits; 82 83 static const char *ctlstat_opts = "Cc:DPdhjl:n:p:tw:"; 84 static const char *ctlstat_usage = "Usage: ctlstat [-CDPdjht] [-l lunnum]" 85 "[-c count] [-n numdevs] [-w wait]\n"; 86 87 struct ctl_cpu_stats { 88 uint64_t user; 89 uint64_t nice; 90 uint64_t system; 91 uint64_t intr; 92 uint64_t idle; 93 }; 94 95 typedef enum { 96 CTLSTAT_MODE_STANDARD, 97 CTLSTAT_MODE_DUMP, 98 CTLSTAT_MODE_JSON, 99 CTLSTAT_MODE_PROMETHEUS, 100 } ctlstat_mode_types; 101 102 #define CTLSTAT_FLAG_CPU (1 << 0) 103 #define CTLSTAT_FLAG_HEADER (1 << 1) 104 #define CTLSTAT_FLAG_FIRST_RUN (1 << 2) 105 #define CTLSTAT_FLAG_TOTALS (1 << 3) 106 #define CTLSTAT_FLAG_DMA_TIME (1 << 4) 107 #define CTLSTAT_FLAG_TIME_VALID (1 << 5) 108 #define CTLSTAT_FLAG_MASK (1 << 6) 109 #define CTLSTAT_FLAG_LUNS (1 << 7) 110 #define CTLSTAT_FLAG_PORTS (1 << 8) 111 #define F_CPU(ctx) ((ctx)->flags & CTLSTAT_FLAG_CPU) 112 #define F_HDR(ctx) ((ctx)->flags & CTLSTAT_FLAG_HEADER) 113 #define F_FIRST(ctx) ((ctx)->flags & CTLSTAT_FLAG_FIRST_RUN) 114 #define F_TOTALS(ctx) ((ctx)->flags & CTLSTAT_FLAG_TOTALS) 115 #define F_DMA(ctx) ((ctx)->flags & CTLSTAT_FLAG_DMA_TIME) 116 #define F_TIMEVAL(ctx) ((ctx)->flags & CTLSTAT_FLAG_TIME_VALID) 117 #define F_MASK(ctx) ((ctx)->flags & CTLSTAT_FLAG_MASK) 118 #define F_LUNS(ctx) ((ctx)->flags & CTLSTAT_FLAG_LUNS) 119 #define F_PORTS(ctx) ((ctx)->flags & CTLSTAT_FLAG_PORTS) 120 121 struct ctlstat_context { 122 ctlstat_mode_types mode; 123 int flags; 124 struct ctl_io_stats *cur_stats, *prev_stats; 125 struct ctl_io_stats cur_total_stats[3], prev_total_stats[3]; 126 struct timespec cur_time, prev_time; 127 struct ctl_cpu_stats cur_cpu, prev_cpu; 128 uint64_t cur_total_jiffies, prev_total_jiffies; 129 uint64_t cur_idle, prev_idle; 130 bitstr_t *item_mask; 131 int cur_items, prev_items; 132 int cur_alloc, prev_alloc; 133 int numdevs; 134 int header_interval; 135 }; 136 137 struct cctl_portlist_data { 138 int level; 139 struct sbuf *cur_sb[32]; 140 int lun; 141 int ntargets; 142 char *target; 143 char **targets; 144 }; 145 146 #ifndef min 147 #define min(x,y) (((x) < (y)) ? (x) : (y)) 148 #endif 149 150 static void usage(int error); 151 static int getstats(int fd, int *alloc_items, int *num_items, 152 struct ctl_io_stats **xstats, struct timespec *cur_time, int *time_valid); 153 static int getcpu(struct ctl_cpu_stats *cpu_stats); 154 static void compute_stats(struct ctl_io_stats *cur_stats, 155 struct ctl_io_stats *prev_stats, 156 long double etime, long double *mbsec, 157 long double *kb_per_transfer, 158 long double *transfers_per_second, 159 long double *ms_per_transfer, 160 long double *ms_per_dma, 161 long double *dmas_per_second); 162 163 static void 164 usage(int error) 165 { 166 fputs(ctlstat_usage, error ? stderr : stdout); 167 } 168 169 static int 170 getstats(int fd, int *alloc_items, int *num_items, struct ctl_io_stats **stats, 171 struct timespec *cur_time, int *flags) 172 { 173 struct ctl_get_io_stats get_stats; 174 int more_space_count = 0; 175 176 if (*alloc_items == 0) 177 *alloc_items = CTL_STAT_NUM_ITEMS; 178 retry: 179 if (*stats == NULL) 180 *stats = malloc(sizeof(**stats) * *alloc_items); 181 182 memset(&get_stats, 0, sizeof(get_stats)); 183 get_stats.alloc_len = *alloc_items * sizeof(**stats); 184 memset(*stats, 0, get_stats.alloc_len); 185 get_stats.stats = *stats; 186 187 if (ioctl(fd, (*flags & CTLSTAT_FLAG_PORTS) ? CTL_GET_PORT_STATS : 188 CTL_GET_LUN_STATS, &get_stats) == -1) 189 err(1, "CTL_GET_*_STATS ioctl returned error"); 190 191 switch (get_stats.status) { 192 case CTL_SS_OK: 193 break; 194 case CTL_SS_ERROR: 195 err(1, "CTL_GET_*_STATS ioctl returned CTL_SS_ERROR"); 196 break; 197 case CTL_SS_NEED_MORE_SPACE: 198 if (more_space_count >= 2) 199 errx(1, "CTL_GET_*_STATS returned NEED_MORE_SPACE again"); 200 *alloc_items = get_stats.num_items * 5 / 4; 201 free(*stats); 202 *stats = NULL; 203 more_space_count++; 204 goto retry; 205 break; /* NOTREACHED */ 206 default: 207 errx(1, "CTL_GET_*_STATS ioctl returned unknown status %d", 208 get_stats.status); 209 break; 210 } 211 212 *num_items = get_stats.fill_len / sizeof(**stats); 213 cur_time->tv_sec = get_stats.timestamp.tv_sec; 214 cur_time->tv_nsec = get_stats.timestamp.tv_nsec; 215 if (get_stats.flags & CTL_STATS_FLAG_TIME_VALID) 216 *flags |= CTLSTAT_FLAG_TIME_VALID; 217 else 218 *flags &= ~CTLSTAT_FLAG_TIME_VALID; 219 220 return (0); 221 } 222 223 static int 224 getcpu(struct ctl_cpu_stats *cpu_stats) 225 { 226 long cp_time[CPUSTATES]; 227 size_t cplen; 228 229 cplen = sizeof(cp_time); 230 231 if (sysctlbyname("kern.cp_time", &cp_time, &cplen, NULL, 0) == -1) { 232 warn("sysctlbyname(kern.cp_time...) failed"); 233 return (1); 234 } 235 236 cpu_stats->user = cp_time[CP_USER]; 237 cpu_stats->nice = cp_time[CP_NICE]; 238 cpu_stats->system = cp_time[CP_SYS]; 239 cpu_stats->intr = cp_time[CP_INTR]; 240 cpu_stats->idle = cp_time[CP_IDLE]; 241 242 return (0); 243 } 244 245 static void 246 compute_stats(struct ctl_io_stats *cur_stats, 247 struct ctl_io_stats *prev_stats, long double etime, 248 long double *mbsec, long double *kb_per_transfer, 249 long double *transfers_per_second, long double *ms_per_transfer, 250 long double *ms_per_dma, long double *dmas_per_second) 251 { 252 uint64_t total_bytes = 0, total_operations = 0, total_dmas = 0; 253 struct bintime total_time_bt, total_dma_bt; 254 struct timespec total_time_ts, total_dma_ts; 255 int i; 256 257 bzero(&total_time_bt, sizeof(total_time_bt)); 258 bzero(&total_dma_bt, sizeof(total_dma_bt)); 259 bzero(&total_time_ts, sizeof(total_time_ts)); 260 bzero(&total_dma_ts, sizeof(total_dma_ts)); 261 for (i = 0; i < CTL_STATS_NUM_TYPES; i++) { 262 total_bytes += cur_stats->bytes[i]; 263 total_operations += cur_stats->operations[i]; 264 total_dmas += cur_stats->dmas[i]; 265 bintime_add(&total_time_bt, &cur_stats->time[i]); 266 bintime_add(&total_dma_bt, &cur_stats->dma_time[i]); 267 if (prev_stats != NULL) { 268 total_bytes -= prev_stats->bytes[i]; 269 total_operations -= prev_stats->operations[i]; 270 total_dmas -= prev_stats->dmas[i]; 271 bintime_sub(&total_time_bt, &prev_stats->time[i]); 272 bintime_sub(&total_dma_bt, &prev_stats->dma_time[i]); 273 } 274 } 275 276 *mbsec = total_bytes; 277 *mbsec /= 1024 * 1024; 278 if (etime > 0.0) 279 *mbsec /= etime; 280 else 281 *mbsec = 0; 282 *kb_per_transfer = total_bytes; 283 *kb_per_transfer /= 1024; 284 if (total_operations > 0) 285 *kb_per_transfer /= total_operations; 286 else 287 *kb_per_transfer = 0; 288 *transfers_per_second = total_operations; 289 *dmas_per_second = total_dmas; 290 if (etime > 0.0) { 291 *transfers_per_second /= etime; 292 *dmas_per_second /= etime; 293 } else { 294 *transfers_per_second = 0; 295 *dmas_per_second = 0; 296 } 297 298 bintime2timespec(&total_time_bt, &total_time_ts); 299 bintime2timespec(&total_dma_bt, &total_dma_ts); 300 if (total_operations > 0) { 301 /* 302 * Convert the timespec to milliseconds. 303 */ 304 *ms_per_transfer = total_time_ts.tv_sec * 1000; 305 *ms_per_transfer += total_time_ts.tv_nsec / 1000000; 306 *ms_per_transfer /= total_operations; 307 } else 308 *ms_per_transfer = 0; 309 310 if (total_dmas > 0) { 311 /* 312 * Convert the timespec to milliseconds. 313 */ 314 *ms_per_dma = total_dma_ts.tv_sec * 1000; 315 *ms_per_dma += total_dma_ts.tv_nsec / 1000000; 316 *ms_per_dma /= total_dmas; 317 } else 318 *ms_per_dma = 0; 319 } 320 321 /* The dump_stats() and json_stats() functions perform essentially the same 322 * purpose, but dump the statistics in different formats. JSON is more 323 * conducive to programming, however. 324 */ 325 326 #define PRINT_BINTIME(bt) \ 327 printf("%jd.%06ju", (intmax_t)(bt).sec, \ 328 (uintmax_t)(((bt).frac >> 32) * 1000000 >> 32)) 329 static const char *iotypes[] = {"NO IO", "READ", "WRITE"}; 330 331 static void 332 ctlstat_dump(struct ctlstat_context *ctx) 333 { 334 int iotype, i, n; 335 struct ctl_io_stats *stats = ctx->cur_stats; 336 337 for (i = n = 0; i < ctx->cur_items;i++) { 338 if (F_MASK(ctx) && bit_test(ctx->item_mask, 339 (int)stats[i].item) == 0) 340 continue; 341 printf("%s %d\n", F_PORTS(ctx) ? "port" : "lun", stats[i].item); 342 for (iotype = 0; iotype < CTL_STATS_NUM_TYPES; iotype++) { 343 printf(" io type %d (%s)\n", iotype, iotypes[iotype]); 344 printf(" bytes %ju\n", (uintmax_t) 345 stats[i].bytes[iotype]); 346 printf(" operations %ju\n", (uintmax_t) 347 stats[i].operations[iotype]); 348 printf(" dmas %ju\n", (uintmax_t) 349 stats[i].dmas[iotype]); 350 printf(" io time "); 351 PRINT_BINTIME(stats[i].time[iotype]); 352 printf("\n dma time "); 353 PRINT_BINTIME(stats[i].dma_time[iotype]); 354 printf("\n"); 355 } 356 if (++n >= ctx->numdevs) 357 break; 358 } 359 } 360 361 static void 362 ctlstat_json(struct ctlstat_context *ctx) { 363 int iotype, i, n; 364 struct ctl_io_stats *stats = ctx->cur_stats; 365 366 printf("{\"%s\":[", F_PORTS(ctx) ? "ports" : "luns"); 367 for (i = n = 0; i < ctx->cur_items; i++) { 368 if (F_MASK(ctx) && bit_test(ctx->item_mask, 369 (int)stats[i].item) == 0) 370 continue; 371 printf("{\"num\":%d,\"io\":[", 372 stats[i].item); 373 for (iotype = 0; iotype < CTL_STATS_NUM_TYPES; iotype++) { 374 printf("{\"type\":\"%s\",", iotypes[iotype]); 375 printf("\"bytes\":%ju,", (uintmax_t) 376 stats[i].bytes[iotype]); 377 printf("\"operations\":%ju,", (uintmax_t) 378 stats[i].operations[iotype]); 379 printf("\"dmas\":%ju,", (uintmax_t) 380 stats[i].dmas[iotype]); 381 printf("\"io time\":"); 382 PRINT_BINTIME(stats[i].time[iotype]); 383 printf(",\"dma time\":"); 384 PRINT_BINTIME(stats[i].dma_time[iotype]); 385 printf("}"); 386 if (iotype < (CTL_STATS_NUM_TYPES - 1)) 387 printf(","); /* continue io array */ 388 } 389 printf("]}"); 390 if (++n >= ctx->numdevs) 391 break; 392 if (i < (ctx->cur_items - 1)) 393 printf(","); /* continue lun array */ 394 } 395 printf("]}"); 396 } 397 398 #define CTLSTAT_PROMETHEUS_LOOP(field) \ 399 for (i = n = 0; i < ctx->cur_items; i++) { \ 400 if (F_MASK(ctx) && bit_test(ctx->item_mask, \ 401 (int)stats[i].item) == 0) \ 402 continue; \ 403 for (iotype = 0; iotype < CTL_STATS_NUM_TYPES; iotype++) { \ 404 int lun = stats[i].item; \ 405 if (lun >= targdata.ntargets) \ 406 errx(1, "LUN %u out of range", lun); \ 407 printf("iscsi_target_" #field "{" \ 408 "lun=\"%u\",target=\"%s\",type=\"%s\"} %" PRIu64 \ 409 "\n", \ 410 lun, targdata.targets[lun], iotypes[iotype], \ 411 stats[i].field[iotype]); \ 412 } \ 413 } \ 414 415 #define CTLSTAT_PROMETHEUS_TIMELOOP(field) \ 416 for (i = n = 0; i < ctx->cur_items; i++) { \ 417 if (F_MASK(ctx) && bit_test(ctx->item_mask, \ 418 (int)stats[i].item) == 0) \ 419 continue; \ 420 for (iotype = 0; iotype < CTL_STATS_NUM_TYPES; iotype++) { \ 421 uint64_t us; \ 422 struct timespec ts; \ 423 int lun = stats[i].item; \ 424 if (lun >= targdata.ntargets) \ 425 errx(1, "LUN %u out of range", lun); \ 426 bintime2timespec(&stats[i].field[iotype], &ts); \ 427 us = ts.tv_sec * 1000000 + ts.tv_nsec / 1000; \ 428 printf("iscsi_target_" #field "{" \ 429 "lun=\"%u\",target=\"%s\",type=\"%s\"} %" PRIu64 \ 430 "\n", \ 431 lun, targdata.targets[lun], iotypes[iotype], us); \ 432 } \ 433 } \ 434 435 static void 436 cctl_start_pelement(void *user_data, const char *name, const char **attr __unused) 437 { 438 struct cctl_portlist_data* targdata = user_data; 439 440 targdata->level++; 441 if ((u_int)targdata->level >= (sizeof(targdata->cur_sb) / 442 sizeof(targdata->cur_sb[0]))) 443 errx(1, "%s: too many nesting levels, %zd max", __func__, 444 sizeof(targdata->cur_sb) / sizeof(targdata->cur_sb[0])); 445 446 targdata->cur_sb[targdata->level] = sbuf_new_auto(); 447 if (targdata->cur_sb[targdata->level] == NULL) 448 err(1, "%s: Unable to allocate sbuf", __func__); 449 450 if (strcmp(name, "targ_port") == 0) { 451 targdata->lun = -1; 452 free(targdata->target); 453 targdata->target = NULL; 454 } 455 } 456 457 static void 458 cctl_char_phandler(void *user_data, const XML_Char *str, int len) 459 { 460 struct cctl_portlist_data *targdata = user_data; 461 462 sbuf_bcat(targdata->cur_sb[targdata->level], str, len); 463 } 464 465 static void 466 cctl_end_pelement(void *user_data, const char *name) 467 { 468 struct cctl_portlist_data* targdata = user_data; 469 char *str; 470 471 if (targdata->cur_sb[targdata->level] == NULL) 472 errx(1, "%s: no valid sbuf at level %d (name %s)", __func__, 473 targdata->level, name); 474 475 if (sbuf_finish(targdata->cur_sb[targdata->level]) != 0) 476 err(1, "%s: sbuf_finish", __func__); 477 str = strdup(sbuf_data(targdata->cur_sb[targdata->level])); 478 if (str == NULL) 479 err(1, "%s can't allocate %zd bytes for string", __func__, 480 sbuf_len(targdata->cur_sb[targdata->level])); 481 482 sbuf_delete(targdata->cur_sb[targdata->level]); 483 targdata->cur_sb[targdata->level] = NULL; 484 targdata->level--; 485 486 if (strcmp(name, "target") == 0) { 487 free(targdata->target); 488 targdata->target = str; 489 } else if (strcmp(name, "lun") == 0) { 490 targdata->lun = atoi(str); 491 free(str); 492 } else if (strcmp(name, "targ_port") == 0) { 493 if (targdata->lun >= 0 && targdata->target != NULL) { 494 if (targdata->lun >= targdata->ntargets) { 495 /* 496 * This can happen for example if there are 497 * holes in CTL's lunlist. 498 */ 499 targdata->ntargets = MAX(targdata->ntargets * 2, 500 targdata->lun + 1); 501 size_t newsize = targdata->ntargets * 502 sizeof(char*); 503 targdata->targets = rallocx(targdata->targets, 504 newsize, MALLOCX_ZERO); 505 } 506 free(targdata->targets[targdata->lun]); 507 targdata->targets[targdata->lun] = targdata->target; 508 targdata->target = NULL; 509 } 510 free(str); 511 } else { 512 free(str); 513 } 514 } 515 516 static void 517 ctlstat_prometheus(int fd, struct ctlstat_context *ctx) { 518 struct ctl_io_stats *stats = ctx->cur_stats; 519 struct ctl_lun_list list; 520 struct cctl_portlist_data targdata; 521 XML_Parser parser; 522 char *port_str = NULL; 523 int iotype, i, n, retval; 524 int port_len = 4096; 525 526 bzero(&targdata, sizeof(targdata)); 527 targdata.ntargets = ctx->cur_items; 528 targdata.targets = calloc(targdata.ntargets, sizeof(char*)); 529 retry: 530 port_str = (char *)realloc(port_str, port_len); 531 bzero(&list, sizeof(list)); 532 list.alloc_len = port_len; 533 list.status = CTL_LUN_LIST_NONE; 534 list.lun_xml = port_str; 535 if (ioctl(fd, CTL_PORT_LIST, &list) == -1) 536 err(1, "%s: error issuing CTL_PORT_LIST ioctl", __func__); 537 if (list.status == CTL_LUN_LIST_ERROR) { 538 warnx("%s: error returned from CTL_PORT_LIST ioctl:\n%s", 539 __func__, list.error_str); 540 } else if (list.status == CTL_LUN_LIST_NEED_MORE_SPACE) { 541 port_len <<= 1; 542 goto retry; 543 } 544 545 parser = XML_ParserCreate(NULL); 546 if (parser == NULL) 547 err(1, "%s: Unable to create XML parser", __func__); 548 XML_SetUserData(parser, &targdata); 549 XML_SetElementHandler(parser, cctl_start_pelement, cctl_end_pelement); 550 XML_SetCharacterDataHandler(parser, cctl_char_phandler); 551 552 retval = XML_Parse(parser, port_str, strlen(port_str), 1); 553 if (retval != 1) { 554 errx(1, "%s: Unable to parse XML: Error %d", __func__, 555 XML_GetErrorCode(parser)); 556 } 557 XML_ParserFree(parser); 558 559 /* 560 * NB: Some clients will print a warning if we don't set Content-Length, 561 * but they still work. And the data still gets into Prometheus. 562 */ 563 printf("HTTP/1.1 200 OK\r\n" 564 "Connection: close\r\n" 565 "Content-Type: text/plain; version=0.0.4\r\n" 566 "\r\n"); 567 568 printf("# HELP iscsi_target_bytes Number of bytes\n" 569 "# TYPE iscsi_target_bytes counter\n"); 570 CTLSTAT_PROMETHEUS_LOOP(bytes); 571 printf("# HELP iscsi_target_dmas Number of DMA\n" 572 "# TYPE iscsi_target_dmas counter\n"); 573 CTLSTAT_PROMETHEUS_LOOP(dmas); 574 printf("# HELP iscsi_target_operations Number of operations\n" 575 "# TYPE iscsi_target_operations counter\n"); 576 CTLSTAT_PROMETHEUS_LOOP(operations); 577 printf("# HELP iscsi_target_time Cumulative operation time in us\n" 578 "# TYPE iscsi_target_time counter\n"); 579 CTLSTAT_PROMETHEUS_TIMELOOP(time); 580 printf("# HELP iscsi_target_dma_time Cumulative DMA time in us\n" 581 "# TYPE iscsi_target_dma_time counter\n"); 582 CTLSTAT_PROMETHEUS_TIMELOOP(dma_time); 583 584 for (i = 0; i < targdata.ntargets; i++) 585 free(targdata.targets[i]); 586 free(targdata.target); 587 free(targdata.targets); 588 589 fflush(stdout); 590 } 591 592 static void 593 ctlstat_standard(struct ctlstat_context *ctx) { 594 long double etime; 595 uint64_t delta_jiffies, delta_idle; 596 long double cpu_percentage; 597 int i, j, n; 598 599 cpu_percentage = 0; 600 601 if (F_CPU(ctx) && (getcpu(&ctx->cur_cpu) != 0)) 602 errx(1, "error returned from getcpu()"); 603 604 etime = ctx->cur_time.tv_sec - ctx->prev_time.tv_sec + 605 (ctx->prev_time.tv_nsec - ctx->cur_time.tv_nsec) * 1e-9; 606 607 if (F_CPU(ctx)) { 608 ctx->prev_total_jiffies = ctx->cur_total_jiffies; 609 ctx->cur_total_jiffies = ctx->cur_cpu.user + 610 ctx->cur_cpu.nice + ctx->cur_cpu.system + 611 ctx->cur_cpu.intr + ctx->cur_cpu.idle; 612 delta_jiffies = ctx->cur_total_jiffies; 613 if (F_FIRST(ctx) == 0) 614 delta_jiffies -= ctx->prev_total_jiffies; 615 ctx->prev_idle = ctx->cur_idle; 616 ctx->cur_idle = ctx->cur_cpu.idle; 617 delta_idle = ctx->cur_idle - ctx->prev_idle; 618 619 cpu_percentage = delta_jiffies - delta_idle; 620 cpu_percentage /= delta_jiffies; 621 cpu_percentage *= 100; 622 } 623 624 if (F_HDR(ctx)) { 625 ctx->header_interval--; 626 if (ctx->header_interval <= 0) { 627 if (F_CPU(ctx)) 628 fprintf(stdout, " CPU"); 629 if (F_TOTALS(ctx)) { 630 fprintf(stdout, "%s Read %s" 631 " Write %s Total\n", 632 (F_TIMEVAL(ctx) != 0) ? " " : "", 633 (F_TIMEVAL(ctx) != 0) ? " " : "", 634 (F_TIMEVAL(ctx) != 0) ? " " : ""); 635 n = 3; 636 } else { 637 for (i = n = 0; i < min(ctl_stat_bits, 638 ctx->cur_items); i++) { 639 int item; 640 641 /* 642 * Obviously this won't work with 643 * LUN numbers greater than a signed 644 * integer. 645 */ 646 item = (int)ctx->cur_stats[i].item; 647 648 if (F_MASK(ctx) && 649 bit_test(ctx->item_mask, item) == 0) 650 continue; 651 fprintf(stdout, "%15.6s%d %s", 652 F_PORTS(ctx) ? "port" : "lun", item, 653 (F_TIMEVAL(ctx) != 0) ? " " : ""); 654 if (++n >= ctx->numdevs) 655 break; 656 } 657 fprintf(stdout, "\n"); 658 } 659 if (F_CPU(ctx)) 660 fprintf(stdout, " "); 661 for (i = 0; i < n; i++) 662 fprintf(stdout, "%s KB/t %s MB/s", 663 (F_TIMEVAL(ctx) != 0) ? " ms" : "", 664 (F_DMA(ctx) == 0) ? "tps" : "dps"); 665 fprintf(stdout, "\n"); 666 ctx->header_interval = 20; 667 } 668 } 669 670 if (F_CPU(ctx)) 671 fprintf(stdout, "%3.0Lf%%", cpu_percentage); 672 if (F_TOTALS(ctx) != 0) { 673 long double mbsec[3]; 674 long double kb_per_transfer[3]; 675 long double transfers_per_sec[3]; 676 long double ms_per_transfer[3]; 677 long double ms_per_dma[3]; 678 long double dmas_per_sec[3]; 679 680 for (i = 0; i < 3; i++) 681 ctx->prev_total_stats[i] = ctx->cur_total_stats[i]; 682 683 memset(&ctx->cur_total_stats, 0, sizeof(ctx->cur_total_stats)); 684 685 /* Use macros to make the next loop more readable. */ 686 #define ADD_STATS_BYTES(st, i, j) \ 687 ctx->cur_total_stats[st].bytes[j] += \ 688 ctx->cur_stats[i].bytes[j] 689 #define ADD_STATS_OPERATIONS(st, i, j) \ 690 ctx->cur_total_stats[st].operations[j] += \ 691 ctx->cur_stats[i].operations[j] 692 #define ADD_STATS_DMAS(st, i, j) \ 693 ctx->cur_total_stats[st].dmas[j] += \ 694 ctx->cur_stats[i].dmas[j] 695 #define ADD_STATS_TIME(st, i, j) \ 696 bintime_add(&ctx->cur_total_stats[st].time[j], \ 697 &ctx->cur_stats[i].time[j]) 698 #define ADD_STATS_DMA_TIME(st, i, j) \ 699 bintime_add(&ctx->cur_total_stats[st].dma_time[j], \ 700 &ctx->cur_stats[i].dma_time[j]) 701 702 for (i = 0; i < ctx->cur_items; i++) { 703 if (F_MASK(ctx) && bit_test(ctx->item_mask, 704 (int)ctx->cur_stats[i].item) == 0) 705 continue; 706 for (j = 0; j < CTL_STATS_NUM_TYPES; j++) { 707 ADD_STATS_BYTES(2, i, j); 708 ADD_STATS_OPERATIONS(2, i, j); 709 ADD_STATS_DMAS(2, i, j); 710 ADD_STATS_TIME(2, i, j); 711 ADD_STATS_DMA_TIME(2, i, j); 712 } 713 ADD_STATS_BYTES(0, i, CTL_STATS_READ); 714 ADD_STATS_OPERATIONS(0, i, CTL_STATS_READ); 715 ADD_STATS_DMAS(0, i, CTL_STATS_READ); 716 ADD_STATS_TIME(0, i, CTL_STATS_READ); 717 ADD_STATS_DMA_TIME(0, i, CTL_STATS_READ); 718 719 ADD_STATS_BYTES(1, i, CTL_STATS_WRITE); 720 ADD_STATS_OPERATIONS(1, i, CTL_STATS_WRITE); 721 ADD_STATS_DMAS(1, i, CTL_STATS_WRITE); 722 ADD_STATS_TIME(1, i, CTL_STATS_WRITE); 723 ADD_STATS_DMA_TIME(1, i, CTL_STATS_WRITE); 724 } 725 726 for (i = 0; i < 3; i++) { 727 compute_stats(&ctx->cur_total_stats[i], 728 F_FIRST(ctx) ? NULL : &ctx->prev_total_stats[i], 729 etime, &mbsec[i], &kb_per_transfer[i], 730 &transfers_per_sec[i], 731 &ms_per_transfer[i], &ms_per_dma[i], 732 &dmas_per_sec[i]); 733 if (F_DMA(ctx) != 0) 734 fprintf(stdout, " %5.1Lf", 735 ms_per_dma[i]); 736 else if (F_TIMEVAL(ctx) != 0) 737 fprintf(stdout, " %5.1Lf", 738 ms_per_transfer[i]); 739 fprintf(stdout, " %4.0Lf %5.0Lf %4.0Lf", 740 kb_per_transfer[i], 741 (F_DMA(ctx) == 0) ? transfers_per_sec[i] : 742 dmas_per_sec[i], mbsec[i]); 743 } 744 } else { 745 for (i = n = 0; i < min(ctl_stat_bits, ctx->cur_items); i++) { 746 long double mbsec, kb_per_transfer; 747 long double transfers_per_sec; 748 long double ms_per_transfer; 749 long double ms_per_dma; 750 long double dmas_per_sec; 751 752 if (F_MASK(ctx) && bit_test(ctx->item_mask, 753 (int)ctx->cur_stats[i].item) == 0) 754 continue; 755 for (j = 0; j < ctx->prev_items; j++) { 756 if (ctx->prev_stats[j].item == 757 ctx->cur_stats[i].item) 758 break; 759 } 760 if (j >= ctx->prev_items) 761 j = -1; 762 compute_stats(&ctx->cur_stats[i], 763 j >= 0 ? &ctx->prev_stats[j] : NULL, 764 etime, &mbsec, &kb_per_transfer, 765 &transfers_per_sec, &ms_per_transfer, 766 &ms_per_dma, &dmas_per_sec); 767 if (F_DMA(ctx)) 768 fprintf(stdout, " %5.1Lf", 769 ms_per_dma); 770 else if (F_TIMEVAL(ctx) != 0) 771 fprintf(stdout, " %5.1Lf", 772 ms_per_transfer); 773 fprintf(stdout, " %4.0Lf %5.0Lf %4.0Lf", 774 kb_per_transfer, (F_DMA(ctx) == 0) ? 775 transfers_per_sec : dmas_per_sec, mbsec); 776 if (++n >= ctx->numdevs) 777 break; 778 } 779 } 780 } 781 782 int 783 main(int argc, char **argv) 784 { 785 int c; 786 int count, waittime; 787 int fd, retval; 788 size_t size; 789 struct ctlstat_context ctx; 790 struct ctl_io_stats *tmp_stats; 791 792 /* default values */ 793 retval = 0; 794 waittime = 1; 795 count = -1; 796 memset(&ctx, 0, sizeof(ctx)); 797 ctx.numdevs = 3; 798 ctx.mode = CTLSTAT_MODE_STANDARD; 799 ctx.flags |= CTLSTAT_FLAG_CPU; 800 ctx.flags |= CTLSTAT_FLAG_FIRST_RUN; 801 ctx.flags |= CTLSTAT_FLAG_HEADER; 802 803 size = sizeof(ctl_stat_bits); 804 if (sysctlbyname("kern.cam.ctl.max_luns", &ctl_stat_bits, &size, NULL, 805 0) == -1) { 806 /* Backward compatibility for where the sysctl wasn't exposed */ 807 ctl_stat_bits = 1024; 808 } 809 ctx.item_mask = bit_alloc(ctl_stat_bits); 810 if (ctx.item_mask == NULL) 811 err(1, "bit_alloc() failed"); 812 813 while ((c = getopt(argc, argv, ctlstat_opts)) != -1) { 814 switch (c) { 815 case 'C': 816 ctx.flags &= ~CTLSTAT_FLAG_CPU; 817 break; 818 case 'c': 819 count = atoi(optarg); 820 break; 821 case 'd': 822 ctx.flags |= CTLSTAT_FLAG_DMA_TIME; 823 break; 824 case 'D': 825 ctx.mode = CTLSTAT_MODE_DUMP; 826 waittime = 30; 827 break; 828 case 'h': 829 ctx.flags &= ~CTLSTAT_FLAG_HEADER; 830 break; 831 case 'j': 832 ctx.mode = CTLSTAT_MODE_JSON; 833 waittime = 30; 834 break; 835 case 'l': { 836 int cur_lun; 837 838 cur_lun = atoi(optarg); 839 if (cur_lun > ctl_stat_bits) 840 errx(1, "Invalid LUN number %d", cur_lun); 841 842 if (!F_MASK(&ctx)) 843 ctx.numdevs = 1; 844 else 845 ctx.numdevs++; 846 bit_set(ctx.item_mask, cur_lun); 847 ctx.flags |= CTLSTAT_FLAG_MASK; 848 ctx.flags |= CTLSTAT_FLAG_LUNS; 849 break; 850 } 851 case 'n': 852 ctx.numdevs = atoi(optarg); 853 break; 854 case 'p': { 855 int cur_port; 856 857 cur_port = atoi(optarg); 858 if (cur_port > ctl_stat_bits) 859 errx(1, "Invalid port number %d", cur_port); 860 861 if (!F_MASK(&ctx)) 862 ctx.numdevs = 1; 863 else 864 ctx.numdevs++; 865 bit_set(ctx.item_mask, cur_port); 866 ctx.flags |= CTLSTAT_FLAG_MASK; 867 ctx.flags |= CTLSTAT_FLAG_PORTS; 868 break; 869 } 870 case 'P': 871 ctx.mode = CTLSTAT_MODE_PROMETHEUS; 872 break; 873 case 't': 874 ctx.flags |= CTLSTAT_FLAG_TOTALS; 875 break; 876 case 'w': 877 waittime = atoi(optarg); 878 break; 879 default: 880 retval = 1; 881 usage(retval); 882 exit(retval); 883 break; 884 } 885 } 886 887 if (F_LUNS(&ctx) && F_PORTS(&ctx)) 888 errx(1, "Options -p and -l are exclusive."); 889 890 if (ctx.mode == CTLSTAT_MODE_PROMETHEUS) { 891 if ((count != -1) || 892 (waittime != 1) || 893 /* NB: -P could be compatible with -t in the future */ 894 (ctx.flags & CTLSTAT_FLAG_TOTALS)) 895 { 896 errx(1, "Option -P is exclusive with -c, -w, and -t"); 897 } 898 count = 1; 899 } 900 901 if (!F_LUNS(&ctx) && !F_PORTS(&ctx)) { 902 if (F_TOTALS(&ctx)) 903 ctx.flags |= CTLSTAT_FLAG_PORTS; 904 else 905 ctx.flags |= CTLSTAT_FLAG_LUNS; 906 } 907 908 if ((fd = open(CTL_DEFAULT_DEV, O_RDWR)) == -1) 909 err(1, "cannot open %s", CTL_DEFAULT_DEV); 910 911 for (;count != 0;) { 912 tmp_stats = ctx.prev_stats; 913 ctx.prev_stats = ctx.cur_stats; 914 ctx.cur_stats = tmp_stats; 915 c = ctx.prev_alloc; 916 ctx.prev_alloc = ctx.cur_alloc; 917 ctx.cur_alloc = c; 918 c = ctx.prev_items; 919 ctx.prev_items = ctx.cur_items; 920 ctx.cur_items = c; 921 ctx.prev_time = ctx.cur_time; 922 ctx.prev_cpu = ctx.cur_cpu; 923 if (getstats(fd, &ctx.cur_alloc, &ctx.cur_items, 924 &ctx.cur_stats, &ctx.cur_time, &ctx.flags) != 0) 925 errx(1, "error returned from getstats()"); 926 927 switch(ctx.mode) { 928 case CTLSTAT_MODE_STANDARD: 929 ctlstat_standard(&ctx); 930 break; 931 case CTLSTAT_MODE_DUMP: 932 ctlstat_dump(&ctx); 933 break; 934 case CTLSTAT_MODE_JSON: 935 ctlstat_json(&ctx); 936 break; 937 case CTLSTAT_MODE_PROMETHEUS: 938 ctlstat_prometheus(fd, &ctx); 939 break; 940 default: 941 break; 942 } 943 944 fprintf(stdout, "\n"); 945 fflush(stdout); 946 ctx.flags &= ~CTLSTAT_FLAG_FIRST_RUN; 947 if (count != 1) 948 sleep(waittime); 949 if (count > 0) 950 count--; 951 } 952 953 exit (retval); 954 } 955 956 /* 957 * vim: ts=8 958 */ 959