1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright 2009 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #include <stdio.h> 27 #include <stdarg.h> 28 #include <dtrace.h> 29 #include <errno.h> 30 #include <string.h> 31 #include <stdlib.h> 32 #include <unistd.h> 33 #include <limits.h> 34 #include <strings.h> 35 #include <termio.h> 36 #include <signal.h> 37 #include <locale.h> 38 39 #include "statcommon.h" 40 41 #define INTRSTAT_COLUMN_OFFS 14 42 #define INTRSTAT_COLUMNS_PER_CPU 15 43 #define INTRSTAT_CPUS_PER_LINE(w) \ 44 (((w) - INTRSTAT_COLUMN_OFFS) / INTRSTAT_COLUMNS_PER_CPU) 45 #define INTRSTAT_OPTSTR "x:c:C:T:" 46 47 static uint_t timestamp_fmt = NODATE; 48 49 #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */ 50 #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it isn't */ 51 #endif 52 53 static dtrace_hdl_t *g_dtp; 54 static int *g_present; 55 static int g_max_cpus; 56 static int g_start, g_end; 57 static int g_header; 58 static long g_sleeptime = 1; 59 static hrtime_t g_interval = NANOSEC; 60 static int g_intr; 61 static psetid_t g_pset = PS_NONE; 62 static processorid_t *g_pset_cpus; 63 static uint_t g_pset_ncpus; 64 static int g_cpus_per_line = INTRSTAT_CPUS_PER_LINE(80); 65 66 static const char *g_pname = "intrstat"; 67 static const char *g_prog = 68 "interrupt-start" 69 "/arg0 != NULL/" 70 "{" 71 " self->ts = vtimestamp;" 72 "}" 73 "" 74 "interrupt-complete" 75 "/self->ts/" 76 "{" 77 " this->devi = (struct dev_info *)arg0;" 78 " @counts[stringof(`devnamesp[this->devi->devi_major].dn_name)," 79 " this->devi->devi_instance] = count();" 80 " @times[stringof(`devnamesp[this->devi->devi_major].dn_name)," 81 " this->devi->devi_instance] = sum(vtimestamp - self->ts);" 82 " self->ts = 0;" 83 "}"; 84 85 static void 86 usage(void) 87 { 88 (void) fprintf(stderr, 89 "usage: intrstat [ -C psrset | -c cpulist ] [-x opt[=val]] " 90 "[-T d|u] [interval [ count]]\n"); 91 92 exit(EXIT_FAILURE); 93 } 94 95 static void 96 fatal(const char *fmt, ...) 97 { 98 va_list ap; 99 100 va_start(ap, fmt); 101 102 (void) fprintf(stderr, "%s: ", g_pname); 103 (void) vfprintf(stderr, fmt, ap); 104 105 if (fmt[strlen(fmt) - 1] != '\n') 106 (void) fprintf(stderr, ": %s\n", 107 dtrace_errmsg(g_dtp, dtrace_errno(g_dtp))); 108 109 exit(EXIT_FAILURE); 110 } 111 112 /*ARGSUSED*/ 113 static void 114 intr(int signo) 115 { 116 g_intr++; 117 } 118 119 static void 120 status(void) 121 {} 122 123 static void 124 set_width(void) 125 { 126 struct winsize win; 127 128 if (!isatty(fileno(stdout))) 129 return; 130 131 if (ioctl(fileno(stdout), TIOCGWINSZ, &win) == -1) 132 return; 133 134 if (win.ws_col == 0) { 135 /* 136 * If TIOCGWINSZ returned 0 for the columns, just return -- 137 * thereby using the default value of g_cpus_per_line. (This 138 * happens, e.g., when running over a tip line.) 139 */ 140 return; 141 } 142 143 g_cpus_per_line = INTRSTAT_CPUS_PER_LINE(win.ws_col); 144 145 if (g_cpus_per_line < 1) 146 g_cpus_per_line = 1; 147 } 148 149 static void 150 print_header() 151 { 152 int i, j; 153 char c[256]; 154 155 if (!g_header) 156 return; 157 158 (void) printf("\n%12s |", "device"); 159 for (i = g_start, j = 0; i < g_max_cpus; i++) { 160 if (!g_present[i]) 161 continue; 162 163 (void) sprintf(c, "cpu%d", i); 164 (void) printf(" %9s %%tim", c); 165 166 if (++j >= g_cpus_per_line) 167 break; 168 } 169 170 (void) printf("\n-------------+"); 171 172 while (j--) 173 (void) printf("---------------"); 174 175 (void) printf("\n"); 176 g_header = 0; 177 } 178 179 /*ARGSUSED*/ 180 static int 181 walk(const dtrace_aggdata_t *data, void *arg) 182 { 183 dtrace_aggdesc_t *aggdesc = data->dtada_desc; 184 dtrace_recdesc_t *nrec, *irec; 185 char *name, c[256]; 186 int32_t *instance; 187 static const dtrace_aggdata_t *count; 188 int i, j; 189 190 if (count == NULL) { 191 count = data; 192 return (DTRACE_AGGWALK_NEXT); 193 } 194 195 nrec = &aggdesc->dtagd_rec[1]; 196 irec = &aggdesc->dtagd_rec[2]; 197 198 name = data->dtada_data + nrec->dtrd_offset; 199 /* LINTED - alignment */ 200 instance = (int32_t *)(data->dtada_data + irec->dtrd_offset); 201 202 for (i = g_start, j = 0; i < g_max_cpus && j < g_cpus_per_line; i++) { 203 /* LINTED - alignment */ 204 uint64_t time = *((uint64_t *)(data->dtada_percpu[i])); 205 /* LINTED - alignment */ 206 uint64_t n = *((uint64_t *)(count->dtada_percpu[i])); 207 208 if (!g_present[i]) 209 continue; 210 211 if (j++ == 0) { 212 print_header(); 213 (void) snprintf(c, sizeof (c), "%s#%d", 214 name, *instance); 215 (void) printf("%12s |", c); 216 } 217 218 (void) printf(" %9lld %4.1f", 219 (unsigned long long)((double)n / 220 ((double)g_interval / (double)NANOSEC)), 221 ((double)time * (double)100.0) / (double)g_interval); 222 } 223 224 (void) printf(j ? "\n" : ""); 225 g_end = i; 226 count = NULL; 227 return (DTRACE_AGGWALK_NEXT); 228 } 229 230 static void 231 select_cpu(processorid_t cpu) 232 { 233 if (g_pset != PS_NONE) 234 fatal("cannot specify both a processor set and a processor\n"); 235 236 if (cpu < 0 || cpu >= g_max_cpus) 237 fatal("cpu %d out of range\n", cpu); 238 239 if (p_online(cpu, P_STATUS) == -1) { 240 if (errno != EINVAL) 241 fatal("could not get status for cpu %d", cpu); 242 fatal("cpu %d not present\n", cpu); 243 } 244 245 g_present[cpu] = 1; 246 } 247 248 static void 249 select_cpus(processorid_t low, processorid_t high) 250 { 251 if (g_pset != PS_NONE) 252 fatal("cannot specify both a processor set and processors\n"); 253 254 if (low < 0 || low >= g_max_cpus) 255 fatal("invalid cpu '%d'\n", low); 256 257 if (high < 0 || high >= g_max_cpus) 258 fatal("invalid cpu '%d'\n", high); 259 260 if (low >= high) 261 fatal("invalid range '%d' to '%d'\n", low, high); 262 263 do { 264 if (p_online(low, P_STATUS) != -1) 265 g_present[low] = 1; 266 } while (++low <= high); 267 } 268 269 static void 270 select_pset(psetid_t pset) 271 { 272 processorid_t i; 273 274 if (pset < 0) 275 fatal("processor set %d is out of range\n", pset); 276 277 /* 278 * Only one processor set can be specified. 279 */ 280 if (g_pset != PS_NONE) 281 fatal("at most one processor set may be specified\n"); 282 283 /* 284 * One cannot select processors _and_ a processor set. 285 */ 286 for (i = 0; i < g_max_cpus; i++) 287 if (g_present[i]) 288 break; 289 290 if (i != g_max_cpus) 291 fatal("cannot specify both a processor and a processor set\n"); 292 293 g_pset = pset; 294 g_pset_ncpus = g_max_cpus; 295 296 if (pset_info(g_pset, NULL, &g_pset_ncpus, g_pset_cpus) == -1) 297 fatal("invalid processor set: %d\n", g_pset); 298 299 if (g_pset_ncpus == 0) 300 fatal("processor set %d empty\n", g_pset); 301 302 for (i = 0; i < g_pset_ncpus; i++) 303 g_present[g_pset_cpus[i]] = 1; 304 } 305 306 static void 307 check_pset(void) 308 { 309 uint_t ncpus = g_max_cpus; 310 processorid_t i; 311 312 if (g_pset == PS_NONE) 313 return; 314 315 if (pset_info(g_pset, NULL, &ncpus, g_pset_cpus) == -1) { 316 if (errno == EINVAL) 317 fatal("processor set %d destroyed\n", g_pset); 318 319 fatal("couldn't get info for processor set %d", g_pset); 320 } 321 322 if (ncpus == 0) 323 fatal("processor set %d empty\n", g_pset); 324 325 if (ncpus == g_pset_ncpus) { 326 for (i = 0; i < g_pset_ncpus; i++) { 327 if (!g_present[g_pset_cpus[i]]) 328 break; 329 } 330 331 /* 332 * If the number of CPUs hasn't changed, and every CPU 333 * in the processor set is also selected, we know that the 334 * processor set itself hasn't changed. 335 */ 336 if (i == g_pset_ncpus) 337 return; 338 } 339 340 /* 341 * If we're here, we have a new processor set. First, we need 342 * to zero out the present array. 343 */ 344 bzero(g_present, sizeof (processorid_t) * g_max_cpus); 345 346 g_pset_ncpus = ncpus; 347 348 for (i = 0; i < g_pset_ncpus; i++) 349 g_present[g_pset_cpus[i]] = 1; 350 } 351 352 int 353 main(int argc, char **argv) 354 { 355 dtrace_prog_t *prog; 356 dtrace_proginfo_t info; 357 int err, i, indefinite = 1; 358 long iter; 359 processorid_t id; 360 struct sigaction act; 361 struct itimerspec ts; 362 struct sigevent ev; 363 sigset_t set; 364 timer_t tid; 365 char *end, *p; 366 char c; 367 hrtime_t last, now; 368 dtrace_optval_t statustime; 369 370 (void) setlocale(LC_ALL, ""); 371 (void) textdomain(TEXT_DOMAIN); 372 373 (void) sigemptyset(&act.sa_mask); 374 act.sa_flags = 0; 375 act.sa_handler = set_width; 376 (void) sigaction(SIGWINCH, &act, NULL); 377 378 (void) sigemptyset(&act.sa_mask); 379 act.sa_flags = 0; 380 act.sa_handler = intr; 381 (void) sigaction(SIGUSR1, &act, NULL); 382 383 (void) sigemptyset(&act.sa_mask); 384 act.sa_flags = 0; 385 act.sa_handler = status; 386 (void) sigaction(SIGUSR2, &act, NULL); 387 388 act.sa_handler = set_width; 389 (void) sigaction(SIGWINCH, &act, NULL); 390 set_width(); 391 392 (void) sigemptyset(&set); 393 (void) sigaddset(&set, SIGUSR1); 394 (void) sigaddset(&set, SIGWINCH); 395 (void) sigprocmask(SIG_BLOCK, &set, NULL); 396 397 ev.sigev_notify = SIGEV_SIGNAL; 398 ev.sigev_signo = SIGUSR1; 399 400 if (timer_create(CLOCK_REALTIME, &ev, &tid) == -1) 401 fatal("cannot create CLOCK_HIGHRES timer"); 402 403 g_max_cpus = sysconf(_SC_CPUID_MAX) + 1; 404 405 if ((g_present = malloc(sizeof (processorid_t) * g_max_cpus)) == NULL) 406 fatal("could not allocate g_present array\n"); 407 408 bzero(g_present, sizeof (processorid_t) * g_max_cpus); 409 410 g_pset_cpus = malloc(sizeof (processorid_t) * g_max_cpus); 411 if (g_pset_cpus == NULL) 412 fatal("could not allocate g_pset_cpus"); 413 414 bzero(g_pset_cpus, sizeof (processorid_t) * g_max_cpus); 415 416 while ((c = getopt(argc, argv, INTRSTAT_OPTSTR)) != EOF) { 417 switch (c) { 418 case 'c': { 419 /* 420 * We allow CPUs to be specified as an optionally 421 * comma separated list of either CPU IDs or ranges 422 * of CPU IDs. 423 */ 424 char *s = strtok(optarg, ","); 425 426 while (s != NULL) { 427 id = strtoul(s, &end, 0); 428 429 if (id == ULONG_MAX && errno == ERANGE) { 430 *end = '\0'; 431 fatal("invalid cpu '%s'\n", s); 432 } 433 434 if (*(s = end) != '\0') { 435 processorid_t p; 436 437 if (*s != '-') 438 fatal("invalid cpu '%s'\n", s); 439 p = strtoul(++s, &end, 0); 440 441 if (*end != '\0' || 442 (p == ULONG_MAX && errno == ERANGE)) 443 fatal("invalid cpu '%s'\n", s); 444 445 select_cpus(id, p); 446 } else { 447 select_cpu(id); 448 } 449 450 s = strtok(NULL, ","); 451 } 452 453 break; 454 } 455 456 case 'C': { 457 psetid_t pset = strtoul(optarg, &end, 0); 458 459 if (*end != '\0' || 460 (pset == ULONG_MAX && errno == ERANGE)) 461 fatal("invalid processor set '%s'\n", optarg); 462 463 select_pset(pset); 464 break; 465 } 466 467 case 'T': 468 if (optarg) { 469 if (*optarg == 'u') 470 timestamp_fmt = UDATE; 471 else if (*optarg == 'd') 472 timestamp_fmt = DDATE; 473 else 474 usage(); 475 } else { 476 usage(); 477 } 478 break; 479 480 default: 481 if (strchr(INTRSTAT_OPTSTR, c) == NULL) 482 usage(); 483 } 484 } 485 486 if ((g_dtp = dtrace_open(DTRACE_VERSION, 0, &err)) == NULL) { 487 fatal("cannot open dtrace library: %s\n", 488 dtrace_errmsg(NULL, err)); 489 } 490 491 if ((prog = dtrace_program_strcompile(g_dtp, g_prog, 492 DTRACE_PROBESPEC_NAME, 0, 0, NULL)) == NULL) 493 fatal("invalid program"); 494 495 if (dtrace_program_exec(g_dtp, prog, &info) == -1) 496 fatal("failed to enable probes"); 497 498 if (dtrace_setopt(g_dtp, "aggsize", "128k") == -1) 499 fatal("failed to set 'aggsize'"); 500 501 if (dtrace_setopt(g_dtp, "aggrate", "0") == -1) 502 fatal("failed to set 'aggrate'"); 503 504 if (dtrace_setopt(g_dtp, "aggpercpu", 0) == -1) 505 fatal("failed to set 'aggpercpu'"); 506 507 optind = 1; 508 while ((c = getopt(argc, argv, INTRSTAT_OPTSTR)) != EOF) { 509 switch (c) { 510 case 'x': 511 if ((p = strchr(optarg, '=')) != NULL) 512 *p++ = '\0'; 513 514 if (dtrace_setopt(g_dtp, optarg, p) != 0) 515 fatal("failed to set -x %s", optarg); 516 break; 517 } 518 } 519 520 if (optind != argc) { 521 g_sleeptime = strtol(argv[optind], &end, 10); 522 523 if (*end != '\0' || g_sleeptime == 0) 524 fatal("invalid interval '%s'\n", argv[1]); 525 526 if (g_sleeptime <= 0) 527 fatal("interval must be greater than zero.\n"); 528 529 if (g_sleeptime == LONG_MAX && errno == ERANGE) 530 fatal("invalid interval '%s'\n", argv[optind]); 531 532 if (++optind != argc) { 533 char *s = argv[optind]; 534 535 iter = strtol(s, &end, 0); 536 indefinite = 0; 537 538 if (*end != '\0' || iter <= 0 || 539 (iter == LONG_MAX && errno == ERANGE)) 540 fatal("invalid count '%s'\n", s); 541 } 542 } 543 544 ts.it_value.tv_sec = g_sleeptime; 545 ts.it_value.tv_nsec = 0; 546 ts.it_interval.tv_sec = g_sleeptime; 547 ts.it_interval.tv_nsec = 0; 548 549 if (timer_settime(tid, TIMER_RELTIME, &ts, NULL) == -1) 550 fatal("cannot set time on CLOCK_REALTIME timer"); 551 552 for (i = 0; i < g_max_cpus && !g_present[i]; i++) 553 continue; 554 555 if (i == g_max_cpus) { 556 for (i = 0; i < g_max_cpus; i++) 557 g_present[i] = p_online(i, P_STATUS) == -1 ? 0 : 1; 558 } 559 560 if (dtrace_go(g_dtp) != 0) 561 fatal("dtrace_go()"); 562 563 last = gethrtime(); 564 565 if (dtrace_getopt(g_dtp, "statusrate", &statustime) == -1) 566 fatal("failed to get 'statusrate'"); 567 568 if (statustime < ((dtrace_optval_t)g_sleeptime * NANOSEC)) { 569 ev.sigev_notify = SIGEV_SIGNAL; 570 ev.sigev_signo = SIGUSR2; 571 572 if (timer_create(CLOCK_REALTIME, &ev, &tid) == -1) 573 fatal("cannot create status timer"); 574 575 ts.it_value.tv_sec = statustime / NANOSEC; 576 ts.it_value.tv_nsec = statustime % NANOSEC; 577 ts.it_interval = ts.it_value; 578 579 if (timer_settime(tid, TIMER_RELTIME, &ts, NULL) == -1) 580 fatal("cannot set time on status timer"); 581 } 582 583 (void) sigemptyset(&set); 584 585 while (indefinite || iter) { 586 587 (void) sigsuspend(&set); 588 589 if (dtrace_status(g_dtp) == -1) 590 fatal("dtrace_status()"); 591 592 if (g_intr == 0) 593 continue; 594 595 iter--; 596 g_intr--; 597 check_pset(); 598 599 now = gethrtime(); 600 g_interval = now - last; 601 last = now; 602 603 if (dtrace_aggregate_snap(g_dtp) != 0) 604 fatal("failed to add to aggregate"); 605 606 g_start = g_end = 0; 607 608 if (timestamp_fmt != NODATE) 609 print_timestamp(timestamp_fmt); 610 611 do { 612 g_header = 1; 613 614 if (dtrace_aggregate_walk_keyvarsorted(g_dtp, 615 walk, NULL) != 0) 616 fatal("failed to sort aggregate"); 617 618 if (g_start == g_end) 619 break; 620 } while ((g_start = g_end) < g_max_cpus); 621 622 dtrace_aggregate_clear(g_dtp); 623 } 624 625 return (0); 626 } 627