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