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