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 2006 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #pragma ident "%Z%%M% %I% %E% SMI" 27 28 #include "statcommon.h" 29 #include "dsr.h" 30 31 #include <sys/dklabel.h> 32 #include <sys/dktp/fdisk.h> 33 #include <stdlib.h> 34 #include <stdarg.h> 35 #include <unistd.h> 36 #include <strings.h> 37 #include <errno.h> 38 #include <limits.h> 39 40 static void insert_iodev(struct snapshot *ss, struct iodev_snapshot *iodev); 41 42 static struct iodev_snapshot * 43 make_controller(int cid) 44 { 45 struct iodev_snapshot *new; 46 47 new = safe_alloc(sizeof (struct iodev_snapshot)); 48 (void) memset(new, 0, sizeof (struct iodev_snapshot)); 49 new->is_type = IODEV_CONTROLLER; 50 new->is_id.id = cid; 51 new->is_parent_id.id = IODEV_NO_ID; 52 53 (void) snprintf(new->is_name, sizeof (new->is_name), "c%d", cid); 54 55 return (new); 56 } 57 58 static struct iodev_snapshot * 59 find_iodev_by_name(struct iodev_snapshot *list, const char *name) 60 { 61 struct iodev_snapshot *pos; 62 struct iodev_snapshot *pos2; 63 64 for (pos = list; pos; pos = pos->is_next) { 65 if (strcmp(pos->is_name, name) == 0) 66 return (pos); 67 68 pos2 = find_iodev_by_name(pos->is_children, name); 69 if (pos2 != NULL) 70 return (pos2); 71 } 72 73 return (NULL); 74 } 75 76 static enum iodev_type 77 parent_iodev_type(enum iodev_type type) 78 { 79 switch (type) { 80 case IODEV_CONTROLLER: return (0); 81 case IODEV_NFS: return (0); 82 case IODEV_TAPE: return (0); 83 case IODEV_IOPATH: return (IODEV_DISK); 84 case IODEV_DISK: return (IODEV_CONTROLLER); 85 case IODEV_PARTITION: return (IODEV_DISK); 86 } 87 return (IODEV_UNKNOWN); 88 } 89 90 static int 91 id_match(struct iodev_id *id1, struct iodev_id *id2) 92 { 93 return (id1->id == id2->id && 94 strcmp(id1->tid, id2->tid) == 0); 95 } 96 97 static struct iodev_snapshot * 98 find_parent(struct snapshot *ss, struct iodev_snapshot *iodev) 99 { 100 enum iodev_type parent_type = parent_iodev_type(iodev->is_type); 101 struct iodev_snapshot *pos; 102 struct iodev_snapshot *pos2; 103 104 if (parent_type == 0 || parent_type == IODEV_UNKNOWN) 105 return (NULL); 106 107 if (iodev->is_parent_id.id == IODEV_NO_ID && 108 iodev->is_parent_id.tid[0] == '\0') 109 return (NULL); 110 111 if (parent_type == IODEV_CONTROLLER) { 112 for (pos = ss->s_iodevs; pos; pos = pos->is_next) { 113 if (pos->is_type != IODEV_CONTROLLER) 114 continue; 115 if (pos->is_id.id != iodev->is_parent_id.id) 116 continue; 117 return (pos); 118 } 119 120 if (!(ss->s_types & SNAP_CONTROLLERS)) 121 return (NULL); 122 123 pos = make_controller(iodev->is_parent_id.id); 124 insert_iodev(ss, pos); 125 return (pos); 126 } 127 128 /* IODEV_DISK parent */ 129 for (pos = ss->s_iodevs; pos; pos = pos->is_next) { 130 if (id_match(&iodev->is_parent_id, &pos->is_id) && 131 pos->is_type == IODEV_DISK) 132 return (pos); 133 if (pos->is_type != IODEV_CONTROLLER) 134 continue; 135 for (pos2 = pos->is_children; pos2; pos2 = pos2->is_next) { 136 if (pos2->is_type != IODEV_DISK) 137 continue; 138 if (id_match(&iodev->is_parent_id, &pos2->is_id)) 139 return (pos2); 140 } 141 } 142 143 return (NULL); 144 } 145 146 static void 147 list_del(struct iodev_snapshot **list, struct iodev_snapshot *pos) 148 { 149 if (*list == pos) 150 *list = pos->is_next; 151 if (pos->is_next) 152 pos->is_next->is_prev = pos->is_prev; 153 if (pos->is_prev) 154 pos->is_prev->is_next = pos->is_next; 155 pos->is_prev = pos->is_next = NULL; 156 } 157 158 static void 159 insert_before(struct iodev_snapshot **list, struct iodev_snapshot *pos, 160 struct iodev_snapshot *new) 161 { 162 if (pos == NULL) { 163 new->is_prev = new->is_next = NULL; 164 *list = new; 165 return; 166 } 167 168 new->is_next = pos; 169 new->is_prev = pos->is_prev; 170 if (pos->is_prev) 171 pos->is_prev->is_next = new; 172 else 173 *list = new; 174 pos->is_prev = new; 175 } 176 177 static void 178 insert_after(struct iodev_snapshot **list, struct iodev_snapshot *pos, 179 struct iodev_snapshot *new) 180 { 181 if (pos == NULL) { 182 new->is_prev = new->is_next = NULL; 183 *list = new; 184 return; 185 } 186 187 new->is_next = pos->is_next; 188 new->is_prev = pos; 189 if (pos->is_next) 190 pos->is_next->is_prev = new; 191 pos->is_next = new; 192 } 193 194 static void 195 insert_into(struct iodev_snapshot **list, struct iodev_snapshot *iodev) 196 { 197 struct iodev_snapshot *tmp = *list; 198 if (*list == NULL) { 199 *list = iodev; 200 return; 201 } 202 203 for (;;) { 204 if (iodev_cmp(tmp, iodev) > 0) { 205 insert_before(list, tmp, iodev); 206 return; 207 } 208 209 if (tmp->is_next == NULL) 210 break; 211 212 tmp = tmp->is_next; 213 } 214 215 insert_after(list, tmp, iodev); 216 } 217 218 static int 219 disk_or_partition(enum iodev_type type) 220 { 221 return (type == IODEV_DISK || type == IODEV_PARTITION); 222 } 223 224 static void 225 insert_iodev(struct snapshot *ss, struct iodev_snapshot *iodev) 226 { 227 struct iodev_snapshot *parent = find_parent(ss, iodev); 228 struct iodev_snapshot **list; 229 230 if (parent != NULL) { 231 list = &parent->is_children; 232 parent->is_nr_children++; 233 } else { 234 list = &ss->s_iodevs; 235 ss->s_nr_iodevs++; 236 } 237 238 insert_into(list, iodev); 239 } 240 241 static int 242 iodev_match(struct iodev_snapshot *dev, struct iodev_filter *df) 243 { 244 size_t i; 245 int is_floppy = (strncmp(dev->is_name, "fd", 2) == 0); 246 247 /* no filter, pass */ 248 if (df == NULL) 249 return (1); 250 251 /* no filtered names, pass if not floppy and skipped */ 252 if (df->if_nr_names == NULL) 253 return (!(df->if_skip_floppy && is_floppy)); 254 255 for (i = 0; i < df->if_nr_names; i++) { 256 if (strcmp(dev->is_name, df->if_names[i]) == 0) 257 return (1); 258 if (dev->is_pretty != NULL && 259 strcmp(dev->is_pretty, df->if_names[i]) == 0) 260 return (1); 261 } 262 263 /* not found in specified names, fail match */ 264 return (0); 265 } 266 267 /* select which I/O devices to collect stats for */ 268 static void 269 choose_iodevs(struct snapshot *ss, struct iodev_snapshot *iodevs, 270 struct iodev_filter *df) 271 { 272 struct iodev_snapshot *pos = iodevs; 273 int nr_iodevs = df ? df->if_max_iodevs : UNLIMITED_IODEVS; 274 275 if (nr_iodevs == UNLIMITED_IODEVS) 276 nr_iodevs = INT_MAX; 277 278 while (pos && nr_iodevs) { 279 struct iodev_snapshot *tmp = pos; 280 pos = pos->is_next; 281 282 if (!iodev_match(tmp, df)) 283 continue; 284 285 list_del(&iodevs, tmp); 286 insert_iodev(ss, tmp); 287 288 --nr_iodevs; 289 } 290 291 pos = iodevs; 292 293 /* now insert any iodevs into the remaining slots */ 294 while (pos && nr_iodevs) { 295 struct iodev_snapshot *tmp = pos; 296 pos = pos->is_next; 297 298 if (df && df->if_skip_floppy && 299 strncmp(tmp->is_name, "fd", 2) == 0) 300 continue; 301 302 list_del(&iodevs, tmp); 303 insert_iodev(ss, tmp); 304 305 --nr_iodevs; 306 } 307 308 /* clear the unwanted ones */ 309 pos = iodevs; 310 while (pos) { 311 struct iodev_snapshot *tmp = pos; 312 pos = pos->is_next; 313 free_iodev(tmp); 314 } 315 } 316 317 static int 318 collate_controller(struct iodev_snapshot *controller, 319 struct iodev_snapshot *disk) 320 { 321 controller->is_stats.nread += disk->is_stats.nread; 322 controller->is_stats.nwritten += disk->is_stats.nwritten; 323 controller->is_stats.reads += disk->is_stats.reads; 324 controller->is_stats.writes += disk->is_stats.writes; 325 controller->is_stats.wtime += disk->is_stats.wtime; 326 controller->is_stats.wlentime += disk->is_stats.wlentime; 327 controller->is_stats.rtime += disk->is_stats.rtime; 328 controller->is_stats.rlentime += disk->is_stats.rlentime; 329 controller->is_crtime += disk->is_crtime; 330 controller->is_snaptime += disk->is_snaptime; 331 if (kstat_add(&disk->is_errors, &controller->is_errors)) 332 return (errno); 333 return (0); 334 } 335 336 static int 337 acquire_iodev_stats(struct iodev_snapshot *list, kstat_ctl_t *kc) 338 { 339 struct iodev_snapshot *pos; 340 int err = 0; 341 342 for (pos = list; pos; pos = pos->is_next) { 343 /* controllers don't have stats (yet) */ 344 if (pos->is_ksp != NULL) { 345 if (kstat_read(kc, pos->is_ksp, &pos->is_stats) == -1) 346 return (errno); 347 /* make sure crtime/snaptime is updated */ 348 pos->is_crtime = pos->is_ksp->ks_crtime; 349 pos->is_snaptime = pos->is_ksp->ks_snaptime; 350 } 351 352 if ((err = acquire_iodev_stats(pos->is_children, kc))) 353 return (err); 354 355 if (pos->is_type == IODEV_CONTROLLER) { 356 struct iodev_snapshot *pos2 = pos->is_children; 357 358 for (; pos2; pos2 = pos2->is_next) { 359 if ((err = collate_controller(pos, pos2))) 360 return (err); 361 } 362 } 363 } 364 365 return (0); 366 } 367 368 static int 369 acquire_iodev_errors(struct snapshot *ss, kstat_ctl_t *kc) 370 { 371 kstat_t *ksp; 372 373 if (!(ss->s_types && SNAP_IODEV_ERRORS)) 374 return (0); 375 376 for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) { 377 char kstat_name[KSTAT_STRLEN]; 378 char *dname = kstat_name; 379 char *ename = ksp->ks_name; 380 struct iodev_snapshot *iodev; 381 382 if (ksp->ks_type != KSTAT_TYPE_NAMED) 383 continue; 384 if (strncmp(ksp->ks_class, "device_error", 12) != 0 && 385 strncmp(ksp->ks_class, "iopath_error", 12) != 0) 386 continue; 387 388 /* 389 * Some drivers may not follow the naming convention 390 * for error kstats (i.e., drivername,err) so 391 * be sure we don't walk off the end. 392 */ 393 while (*ename && *ename != ',') { 394 *dname = *ename; 395 dname++; 396 ename++; 397 } 398 *dname = '\0'; 399 400 iodev = find_iodev_by_name(ss->s_iodevs, kstat_name); 401 402 if (iodev == NULL) 403 continue; 404 405 if (kstat_read(kc, ksp, NULL) == -1) 406 return (errno); 407 if (kstat_copy(ksp, &iodev->is_errors) == -1) 408 return (errno); 409 } 410 411 return (0); 412 } 413 414 static void 415 get_ids(struct iodev_snapshot *iodev, const char *pretty) 416 { 417 int ctr, disk, slice, ret; 418 char *target; 419 const char *p1; 420 const char *p2; 421 422 if (pretty == NULL) 423 return; 424 425 if (sscanf(pretty, "c%d", &ctr) != 1) 426 return; 427 428 p1 = pretty; 429 while (*p1 && *p1 != 't') 430 ++p1; 431 432 if (!*p1) 433 return; 434 ++p1; 435 436 p2 = p1; 437 while (*p2 && *p2 != 'd') 438 ++p2; 439 440 if (!*p2 || p2 == p1) 441 return; 442 443 target = safe_alloc(1 + p2 - p1); 444 (void) strlcpy(target, p1, 1 + p2 - p1); 445 446 ret = sscanf(p2, "d%d%*[sp]%d", &disk, &slice); 447 448 if (ret == 2 && iodev->is_type == IODEV_PARTITION) { 449 iodev->is_id.id = slice; 450 iodev->is_parent_id.id = disk; 451 (void) strlcpy(iodev->is_parent_id.tid, target, KSTAT_STRLEN); 452 } else if (ret == 1) { 453 if (iodev->is_type == IODEV_DISK) { 454 iodev->is_id.id = disk; 455 (void) strlcpy(iodev->is_id.tid, target, KSTAT_STRLEN); 456 iodev->is_parent_id.id = ctr; 457 } else if (iodev->is_type == IODEV_IOPATH) { 458 iodev->is_parent_id.id = disk; 459 (void) strlcpy(iodev->is_parent_id.tid, 460 target, KSTAT_STRLEN); 461 } 462 } 463 464 free(target); 465 } 466 467 static char * 468 get_slice(int partition, disk_list_t *dl) 469 { 470 char *tmpbuf; 471 size_t tmplen; 472 473 if (!(dl->flags & SLICES_OK)) 474 return (NULL); 475 if (partition < 0 || partition >= NDKMAP) 476 return (NULL); 477 478 /* space for 's', and integer < NDKMAP (16) */ 479 tmplen = strlen(dl->dsk) + strlen("sXX") + 1; 480 tmpbuf = safe_alloc(tmplen); 481 482 /* 483 * This is a regular slice. Create the name and 484 * copy it for use by the calling routine. 485 */ 486 (void) snprintf(tmpbuf, tmplen, "%ss%d", dl->dsk, partition); 487 return (tmpbuf); 488 } 489 490 static char * 491 get_intel_partition(int partition, disk_list_t *dl) 492 { 493 char *tmpbuf; 494 size_t tmplen; 495 496 if (partition <= 0 || !(dl->flags & PARTITIONS_OK)) 497 return (NULL); 498 499 /* 500 * See if it falls in the range of allowable partitions. The 501 * fdisk partitions show up after the traditional slices so we 502 * determine which partition we're in and return that. 503 * The NUMPART + 1 is not a mistake. There are currently 504 * FD_NUMPART + 1 partitions that show up in the device directory. 505 */ 506 partition -= NDKMAP; 507 if (partition < 0 || partition >= (FD_NUMPART + 1)) 508 return (NULL); 509 510 /* space for 'p', and integer < NDKMAP (16) */ 511 tmplen = strlen(dl->dsk) + strlen("pXX") + 1; 512 tmpbuf = safe_alloc(tmplen); 513 514 (void) snprintf(tmpbuf, tmplen, "%sp%d", dl->dsk, partition); 515 return (tmpbuf); 516 } 517 518 static void 519 get_pretty_name(enum snapshot_types types, struct iodev_snapshot *iodev, 520 kstat_ctl_t *kc) 521 { 522 disk_list_t *dl; 523 char *pretty = NULL; 524 char *tmp; 525 int partition; 526 527 if (iodev->is_type == IODEV_NFS) { 528 if (!(types & SNAP_IODEV_PRETTY)) 529 return; 530 531 iodev->is_pretty = lookup_nfs_name(iodev->is_name, kc); 532 return; 533 } 534 535 if (iodev->is_type == IODEV_IOPATH) { 536 char buf[KSTAT_STRLEN]; 537 size_t len; 538 539 tmp = iodev->is_name; 540 while (*tmp && *tmp != '.') 541 tmp++; 542 if (!*tmp) 543 return; 544 (void) strlcpy(buf, iodev->is_name, 1 + tmp - iodev->is_name); 545 dl = lookup_ks_name(buf, (types & SNAP_IODEV_DEVID) ? 1 : 0); 546 if (dl == NULL || dl->dsk == NULL) 547 return; 548 len = strlen(dl->dsk) + strlen(tmp) + 1; 549 pretty = safe_alloc(len); 550 (void) strlcpy(pretty, dl->dsk, len); 551 (void) strlcat(pretty, tmp, len); 552 goto out; 553 } 554 555 dl = lookup_ks_name(iodev->is_name, (types & SNAP_IODEV_DEVID) ? 1 : 0); 556 if (dl == NULL) 557 return; 558 559 if (dl->dsk) 560 pretty = safe_strdup(dl->dsk); 561 562 if (types & SNAP_IODEV_PRETTY) { 563 if (dl->dname) 564 iodev->is_dname = safe_strdup(dl->dname); 565 } 566 567 if (dl->devidstr) 568 iodev->is_devid = safe_strdup(dl->devidstr); 569 570 /* look for a possible partition number */ 571 tmp = iodev->is_name; 572 while (*tmp && *tmp != ',') 573 tmp++; 574 if (*tmp != ',') 575 goto out; 576 577 tmp++; 578 partition = (int)(*tmp - 'a'); 579 580 if (iodev->is_type == IODEV_PARTITION) { 581 char *part; 582 if ((part = get_slice(partition, dl)) == NULL) 583 part = get_intel_partition(partition, dl); 584 if (part != NULL) { 585 free(pretty); 586 pretty = part; 587 } 588 } 589 590 out: 591 get_ids(iodev, pretty); 592 593 /* only fill in the pretty name if specifically asked for */ 594 if (types & SNAP_IODEV_PRETTY) { 595 iodev->is_pretty = pretty; 596 } else { 597 free(pretty); 598 } 599 } 600 601 static enum iodev_type 602 get_iodev_type(kstat_t *ksp) 603 { 604 if (strcmp(ksp->ks_class, "disk") == 0) 605 return (IODEV_DISK); 606 if (strcmp(ksp->ks_class, "partition") == 0) 607 return (IODEV_PARTITION); 608 if (strcmp(ksp->ks_class, "nfs") == 0) 609 return (IODEV_NFS); 610 if (strcmp(ksp->ks_class, "iopath") == 0) 611 return (IODEV_IOPATH); 612 if (strcmp(ksp->ks_class, "tape") == 0) 613 return (IODEV_TAPE); 614 return (IODEV_UNKNOWN); 615 } 616 617 int 618 iodev_cmp(struct iodev_snapshot *io1, struct iodev_snapshot *io2) 619 { 620 /* neutral sort order between disk and part */ 621 if (!disk_or_partition(io1->is_type) || 622 !disk_or_partition(io2->is_type)) { 623 if (io1->is_type < io2->is_type) 624 return (-1); 625 if (io1->is_type > io2->is_type) 626 return (1); 627 } 628 629 /* controller doesn't have ksp */ 630 if (io1->is_ksp && io2->is_ksp) { 631 if (strcmp(io1->is_module, io2->is_module) != 0) 632 return (strcmp(io1->is_module, io2->is_module)); 633 if (io1->is_instance < io2->is_instance) 634 return (-1); 635 if (io1->is_instance > io2->is_instance) 636 return (1); 637 } else { 638 if (io1->is_id.id < io2->is_id.id) 639 return (-1); 640 if (io1->is_id.id > io2->is_id.id) 641 return (1); 642 } 643 644 return (strcmp(io1->is_name, io2->is_name)); 645 } 646 647 int 648 acquire_iodevs(struct snapshot *ss, kstat_ctl_t *kc, struct iodev_filter *df) 649 { 650 kstat_t *ksp; 651 int err = 0; 652 struct iodev_snapshot *pos; 653 struct iodev_snapshot *list = NULL; 654 655 ss->s_nr_iodevs = 0; 656 657 /* 658 * Call cleanup_iodevs_snapshot() so that a cache miss in 659 * lookup_ks_name() will result in a fresh snapshot. 660 */ 661 cleanup_iodevs_snapshot(); 662 663 for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) { 664 enum iodev_type type; 665 666 if (ksp->ks_type != KSTAT_TYPE_IO) 667 continue; 668 669 /* e.g. "usb_byte_count" is not handled */ 670 if ((type = get_iodev_type(ksp)) == IODEV_UNKNOWN) 671 continue; 672 673 if (df && !(type & df->if_allowed_types)) 674 continue; 675 676 if ((pos = malloc(sizeof (struct iodev_snapshot))) == NULL) { 677 err = errno; 678 goto out; 679 } 680 681 (void) memset(pos, 0, sizeof (struct iodev_snapshot)); 682 683 pos->is_type = type; 684 pos->is_crtime = ksp->ks_crtime; 685 pos->is_snaptime = ksp->ks_snaptime; 686 pos->is_id.id = IODEV_NO_ID; 687 pos->is_parent_id.id = IODEV_NO_ID; 688 pos->is_ksp = ksp; 689 pos->is_instance = ksp->ks_instance; 690 691 (void) strlcpy(pos->is_module, ksp->ks_module, KSTAT_STRLEN); 692 (void) strlcpy(pos->is_name, ksp->ks_name, KSTAT_STRLEN); 693 get_pretty_name(ss->s_types, pos, kc); 694 695 /* 696 * We must insert in sort order so e.g. vmstat -l 697 * chooses in order. 698 */ 699 insert_into(&list, pos); 700 } 701 702 choose_iodevs(ss, list, df); 703 704 /* before acquire_stats for collate_controller()'s benefit */ 705 if (ss->s_types & SNAP_IODEV_ERRORS) { 706 if ((err = acquire_iodev_errors(ss, kc)) != 0) 707 goto out; 708 } 709 710 if ((err = acquire_iodev_stats(ss->s_iodevs, kc)) != 0) 711 goto out; 712 713 err = 0; 714 out: 715 return (err); 716 } 717 718 void 719 free_iodev(struct iodev_snapshot *iodev) 720 { 721 while (iodev->is_children) { 722 struct iodev_snapshot *tmp = iodev->is_children; 723 iodev->is_children = iodev->is_children->is_next; 724 free_iodev(tmp); 725 } 726 727 free(iodev->is_errors.ks_data); 728 free(iodev->is_pretty); 729 free(iodev->is_dname); 730 free(iodev->is_devid); 731 free(iodev); 732 } 733