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