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 (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved.
23 */
24
25 #include "statcommon.h"
26 #include "dsr.h"
27
28 #include <sys/dklabel.h>
29 #include <sys/dktp/fdisk.h>
30 #include <stdlib.h>
31 #include <stdarg.h>
32 #include <stddef.h>
33 #include <unistd.h>
34 #include <strings.h>
35 #include <errno.h>
36 #include <limits.h>
37
38 static void insert_iodev(struct snapshot *ss, struct iodev_snapshot *iodev);
39
40 static struct iodev_snapshot *
41 make_controller(int cid)
42 {
43 struct iodev_snapshot *new;
44
45 new = safe_alloc(sizeof (struct iodev_snapshot));
46 (void) memset(new, 0, sizeof (struct iodev_snapshot));
47 new->is_type = IODEV_CONTROLLER;
48 new->is_id.id = cid;
49 new->is_parent_id.id = IODEV_NO_ID;
50
51 (void) snprintf(new->is_name, sizeof (new->is_name), "c%d", cid);
52
53 return (new);
54 }
55
56 static struct iodev_snapshot *
57 find_iodev_by_name(struct iodev_snapshot *list, const char *name)
58 {
59 struct iodev_snapshot *pos;
60 struct iodev_snapshot *pos2;
61
62 for (pos = list; pos; pos = pos->is_next) {
63 if (strcmp(pos->is_name, name) == 0)
64 return (pos);
65
66 pos2 = find_iodev_by_name(pos->is_children, name);
67 if (pos2 != NULL)
68 return (pos2);
69 }
70
71 return (NULL);
72 }
73
74 static enum iodev_type
75 parent_iodev_type(enum iodev_type type)
76 {
77 switch (type) {
78 case IODEV_CONTROLLER: return (0);
79 case IODEV_IOPATH_LT: return (0);
80 case IODEV_IOPATH_LI: return (0);
81 case IODEV_NFS: return (0);
82 case IODEV_TAPE: return (0);
83 case IODEV_ZFS: return (0);
84 case IODEV_IOPATH_LTI: 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 /*
148 * Introduce an index into the list to speed up insert_into looking for the
149 * right position in the list. This index is an AVL tree of all the
150 * iodev_snapshot in the list.
151 */
152 static int
153 avl_iodev_cmp(const void* is1, const void* is2)
154 {
155 int c = iodev_cmp((struct iodev_snapshot *)is1,
156 (struct iodev_snapshot *)is2);
157
158 if (c > 0)
159 return (1);
160
161 if (c < 0)
162 return (-1);
163
164 return (0);
165 }
166
167 static void
168 ix_new_list(struct iodev_snapshot *elem)
169 {
170 avl_tree_t *l = malloc(sizeof (avl_tree_t));
171
172 elem->avl_list = l;
173 if (l == NULL)
174 return;
175
176 avl_create(l, avl_iodev_cmp, sizeof (struct iodev_snapshot),
177 offsetof(struct iodev_snapshot, avl_link));
178
179 avl_add(l, elem);
180 }
181
182 static void
183 ix_list_del(struct iodev_snapshot *elem)
184 {
185 avl_tree_t *l = elem->avl_list;
186
187 if (l == NULL)
188 return;
189
190 elem->avl_list = NULL;
191
192 avl_remove(l, elem);
193 if (avl_numnodes(l) == 0) {
194 avl_destroy(l);
195 free(l);
196 }
197 }
198
199 static void
200 ix_insert_here(struct iodev_snapshot *pos, struct iodev_snapshot *elem, int ba)
201 {
202 avl_tree_t *l = pos->avl_list;
203 elem->avl_list = l;
204
205 if (l == NULL)
206 return;
207
208 avl_insert_here(l, elem, pos, ba);
209 }
210
211 static void
212 list_del(struct iodev_snapshot **list, struct iodev_snapshot *pos)
213 {
214 ix_list_del(pos);
215
216 if (*list == pos)
217 *list = pos->is_next;
218 if (pos->is_next)
219 pos->is_next->is_prev = pos->is_prev;
220 if (pos->is_prev)
221 pos->is_prev->is_next = pos->is_next;
222 pos->is_prev = pos->is_next = NULL;
223 }
224
225 static void
226 insert_before(struct iodev_snapshot **list, struct iodev_snapshot *pos,
227 struct iodev_snapshot *new)
228 {
229 if (pos == NULL) {
230 new->is_prev = new->is_next = NULL;
231 *list = new;
232 ix_new_list(new);
233 return;
234 }
235
236 new->is_next = pos;
237 new->is_prev = pos->is_prev;
238 if (pos->is_prev)
239 pos->is_prev->is_next = new;
240 else
241 *list = new;
242 pos->is_prev = new;
243
244 ix_insert_here(pos, new, AVL_BEFORE);
245 }
246
247 static void
248 insert_after(struct iodev_snapshot **list, struct iodev_snapshot *pos,
249 struct iodev_snapshot *new)
250 {
251 if (pos == NULL) {
252 new->is_prev = new->is_next = NULL;
253 *list = new;
254 ix_new_list(new);
255 return;
256 }
257
258 new->is_next = pos->is_next;
259 new->is_prev = pos;
260 if (pos->is_next)
261 pos->is_next->is_prev = new;
262 pos->is_next = new;
263
264 ix_insert_here(pos, new, AVL_AFTER);
265 }
266
267 static void
268 insert_into(struct iodev_snapshot **list, struct iodev_snapshot *iodev)
269 {
270 struct iodev_snapshot *tmp = *list;
271 avl_tree_t *l;
272 void *p;
273 avl_index_t where;
274
275 if (*list == NULL) {
276 *list = iodev;
277 ix_new_list(iodev);
278 return;
279 }
280
281 /*
282 * Optimize the search: instead of walking the entire list
283 * (which can contain thousands of nodes), search in the AVL
284 * tree the nearest node and reposition the startup point to
285 * this node rather than always starting from the beginning
286 * of the list.
287 */
288 l = tmp->avl_list;
289 if (l != NULL) {
290 p = avl_find(l, iodev, &where);
291 if (p == NULL) {
292 p = avl_nearest(l, where, AVL_BEFORE);
293 }
294 if (p != NULL) {
295 tmp = (struct iodev_snapshot *)p;
296 }
297 }
298
299 for (;;) {
300 if (iodev_cmp(tmp, iodev) > 0) {
301 insert_before(list, tmp, iodev);
302 return;
303 }
304
305 if (tmp->is_next == NULL)
306 break;
307
308 tmp = tmp->is_next;
309 }
310
311 insert_after(list, tmp, iodev);
312 }
313
314 static int
315 disk_or_partition(enum iodev_type type)
316 {
317 return (type == IODEV_DISK || type == IODEV_PARTITION);
318 }
319
320 static int
321 disk_or_partition_or_iopath(enum iodev_type type)
322 {
323 return (type == IODEV_DISK || type == IODEV_PARTITION ||
324 type == IODEV_IOPATH_LTI);
325 }
326
327 static void
328 insert_iodev(struct snapshot *ss, struct iodev_snapshot *iodev)
329 {
330 struct iodev_snapshot *parent = find_parent(ss, iodev);
331 struct iodev_snapshot **list;
332
333 if (parent != NULL) {
334 list = &parent->is_children;
335 parent->is_nr_children++;
336 } else {
337 list = &ss->s_iodevs;
338 ss->s_nr_iodevs++;
339 }
340
341 insert_into(list, iodev);
342 }
343
344 /* return 1 if dev passes filter */
345 static int
346 iodev_match(struct iodev_snapshot *dev, struct iodev_filter *df)
347 {
348 int is_floppy = (strncmp(dev->is_name, "fd", 2) == 0);
349 char *isn, *ispn, *ifn;
350 char *path;
351 int ifnl;
352 size_t i;
353
354 /* no filter, pass */
355 if (df == NULL)
356 return (1); /* pass */
357
358 /* no filtered names, pass if not floppy and skipped */
359 if (df->if_nr_names == NULL)
360 return (!(df->if_skip_floppy && is_floppy));
361
362 isn = dev->is_name;
363 ispn = dev->is_pretty;
364 for (i = 0; i < df->if_nr_names; i++) {
365 ifn = df->if_names[i];
366 ifnl = strlen(ifn);
367 path = strchr(ifn, '.');
368
369 if ((strcmp(isn, ifn) == 0) ||
370 (ispn && (strcmp(ispn, ifn) == 0)))
371 return (1); /* pass */
372
373 /* if filter is a path allow partial match */
374 if (path &&
375 ((strncmp(isn, ifn, ifnl) == 0) ||
376 (ispn && (strncmp(ispn, ifn, ifnl) == 0))))
377 return (1); /* pass */
378 }
379
380 return (0); /* fail */
381 }
382
383 /* return 1 if path is an mpxio path associated with dev */
384 static int
385 iodev_path_match(struct iodev_snapshot *dev, struct iodev_snapshot *path)
386 {
387 char *dn, *pn;
388 int dnl;
389
390 dn = dev->is_name;
391 pn = path->is_name;
392 dnl = strlen(dn);
393
394 if ((strncmp(pn, dn, dnl) == 0) && (pn[dnl] == '.'))
395 return (1); /* yes */
396
397 return (0); /* no */
398 }
399
400 /* select which I/O devices to collect stats for */
401 static void
402 choose_iodevs(struct snapshot *ss, struct iodev_snapshot *iodevs,
403 struct iodev_filter *df)
404 {
405 struct iodev_snapshot *pos, *ppos, *tmp, *ptmp;
406 int nr_iodevs;
407 int nr_iodevs_orig;
408
409 nr_iodevs = df ? df->if_max_iodevs : UNLIMITED_IODEVS;
410 nr_iodevs_orig = nr_iodevs;
411
412 if (nr_iodevs == UNLIMITED_IODEVS)
413 nr_iodevs = INT_MAX;
414
415 /* add the full matches */
416 pos = iodevs;
417 while (pos && nr_iodevs) {
418 tmp = pos;
419 pos = pos->is_next;
420
421 if (!iodev_match(tmp, df))
422 continue; /* failed full match */
423
424 list_del(&iodevs, tmp);
425 insert_iodev(ss, tmp);
426
427 /*
428 * Add all mpxio paths associated with match above. Added
429 * paths don't count against nr_iodevs.
430 */
431 if (strchr(tmp->is_name, '.') == NULL) {
432 ppos = iodevs;
433 while (ppos) {
434 ptmp = ppos;
435 ppos = ppos->is_next;
436
437 if (!iodev_path_match(tmp, ptmp))
438 continue; /* not an mpxio path */
439
440 list_del(&iodevs, ptmp);
441 insert_iodev(ss, ptmp);
442 if (pos == ptmp)
443 pos = ppos;
444 }
445 }
446
447 nr_iodevs--;
448 }
449
450 /*
451 * If we had a filter, and *nothing* passed the filter then we
452 * don't want to fill the remaining slots - it is just confusing
453 * if we don that, it makes it look like the filter code is broken.
454 */
455 if ((df->if_nr_names == NULL) || (nr_iodevs != nr_iodevs_orig)) {
456 /* now insert any iodevs into the remaining slots */
457 pos = iodevs;
458 while (pos && nr_iodevs) {
459 tmp = pos;
460 pos = pos->is_next;
461
462 if (df && df->if_skip_floppy &&
463 strncmp(tmp->is_name, "fd", 2) == 0)
464 continue;
465
466 list_del(&iodevs, tmp);
467 insert_iodev(ss, tmp);
468
469 --nr_iodevs;
470 }
471 }
472
473 /* clear the unwanted ones */
474 pos = iodevs;
475 while (pos) {
476 struct iodev_snapshot *tmp = pos;
477 pos = pos->is_next;
478 free_iodev(tmp);
479 }
480 }
481
482 static int
483 collate_controller(struct iodev_snapshot *controller,
484 struct iodev_snapshot *disk)
485 {
486 controller->is_stats.nread += disk->is_stats.nread;
487 controller->is_stats.nwritten += disk->is_stats.nwritten;
488 controller->is_stats.reads += disk->is_stats.reads;
489 controller->is_stats.writes += disk->is_stats.writes;
490 controller->is_stats.wtime += disk->is_stats.wtime;
491 controller->is_stats.wlentime += disk->is_stats.wlentime;
492 controller->is_stats.rtime += disk->is_stats.rtime;
493 controller->is_stats.rlentime += disk->is_stats.rlentime;
494 controller->is_crtime += disk->is_crtime;
495 controller->is_snaptime += disk->is_snaptime;
496 if (kstat_add(&disk->is_errors, &controller->is_errors))
497 return (errno);
498 return (0);
499 }
500
501 static int
502 acquire_iodev_stats(struct iodev_snapshot *list, kstat_ctl_t *kc)
503 {
504 struct iodev_snapshot *pos;
505 int err = 0;
506
507 for (pos = list; pos; pos = pos->is_next) {
508 /* controllers don't have stats (yet) */
509 if (pos->is_ksp != NULL) {
510 if (kstat_read(kc, pos->is_ksp, &pos->is_stats) == -1)
511 return (errno);
512 /* make sure crtime/snaptime is updated */
513 pos->is_crtime = pos->is_ksp->ks_crtime;
514 pos->is_snaptime = pos->is_ksp->ks_snaptime;
515 }
516
517 if ((err = acquire_iodev_stats(pos->is_children, kc)))
518 return (err);
519
520 if (pos->is_type == IODEV_CONTROLLER) {
521 struct iodev_snapshot *pos2 = pos->is_children;
522
523 for (; pos2; pos2 = pos2->is_next) {
524 if ((err = collate_controller(pos, pos2)))
525 return (err);
526 }
527 }
528 }
529
530 return (0);
531 }
532
533 static int
534 acquire_iodev_errors(struct snapshot *ss, kstat_ctl_t *kc)
535 {
536 kstat_t *ksp;
537
538 for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) {
539 char kstat_name[KSTAT_STRLEN];
540 char *dname = kstat_name;
541 char *ename = ksp->ks_name;
542 struct iodev_snapshot *iodev;
543
544 if (ksp->ks_type != KSTAT_TYPE_NAMED)
545 continue;
546 if (strncmp(ksp->ks_class, "device_error", 12) != 0 &&
547 strncmp(ksp->ks_class, "iopath_error", 12) != 0)
548 continue;
549
550 /*
551 * Some drivers may not follow the naming convention
552 * for error kstats (i.e., drivername,err) so
553 * be sure we don't walk off the end.
554 */
555 while (*ename && *ename != ',') {
556 *dname = *ename;
557 dname++;
558 ename++;
559 }
560 *dname = '\0';
561
562 iodev = find_iodev_by_name(ss->s_iodevs, kstat_name);
563
564 if (iodev == NULL)
565 continue;
566
567 if (kstat_read(kc, ksp, NULL) == -1)
568 return (errno);
569 if (kstat_copy(ksp, &iodev->is_errors) == -1)
570 return (errno);
571 }
572
573 return (0);
574 }
575
576 static void
577 get_ids(struct iodev_snapshot *iodev, const char *pretty)
578 {
579 int ctr, disk, slice, ret;
580 char *target;
581 const char *p1;
582 const char *p2;
583
584 if (pretty == NULL)
585 return;
586
587 if (sscanf(pretty, "c%d", &ctr) != 1)
588 return;
589
590 p1 = pretty;
591 while (*p1 && *p1 != 't')
592 ++p1;
593
594 if (!*p1)
595 return;
596 ++p1;
597
598 p2 = p1;
599 while (*p2 && *p2 != 'd')
600 ++p2;
601
602 if (!*p2 || p2 == p1)
603 return;
604
605 target = safe_alloc(1 + p2 - p1);
606 (void) strlcpy(target, p1, 1 + p2 - p1);
607
608 ret = sscanf(p2, "d%d%*[sp]%d", &disk, &slice);
609
610 if (ret == 2 && iodev->is_type == IODEV_PARTITION) {
611 iodev->is_id.id = slice;
612 iodev->is_parent_id.id = disk;
613 (void) strlcpy(iodev->is_parent_id.tid, target, KSTAT_STRLEN);
614 } else if (ret == 1) {
615 if (iodev->is_type == IODEV_DISK) {
616 iodev->is_id.id = disk;
617 (void) strlcpy(iodev->is_id.tid, target, KSTAT_STRLEN);
618 iodev->is_parent_id.id = ctr;
619 } else if (iodev->is_type == IODEV_IOPATH_LTI) {
620 iodev->is_parent_id.id = disk;
621 (void) strlcpy(iodev->is_parent_id.tid,
622 target, KSTAT_STRLEN);
623 }
624 }
625
626 free(target);
627 }
628
629 static void
630 get_pretty_name(enum snapshot_types types, struct iodev_snapshot *iodev,
631 kstat_ctl_t *kc)
632 {
633 disk_list_t *dl;
634 char *pretty = NULL;
635
636 if (iodev->is_type == IODEV_NFS) {
637 if (!(types & SNAP_IODEV_PRETTY))
638 return;
639
640 iodev->is_pretty = lookup_nfs_name(iodev->is_name, kc);
641 return;
642 }
643
644 /* lookup/translate the kstat name */
645 dl = lookup_ks_name(iodev->is_name, (types & SNAP_IODEV_DEVID) ? 1 : 0);
646 if (dl == NULL)
647 return;
648
649 if (dl->dsk)
650 pretty = safe_strdup(dl->dsk);
651
652 if (types & SNAP_IODEV_PRETTY) {
653 if (dl->dname)
654 iodev->is_dname = safe_strdup(dl->dname);
655 }
656
657 if (dl->devidstr)
658 iodev->is_devid = safe_strdup(dl->devidstr);
659
660 get_ids(iodev, pretty);
661
662 /*
663 * we fill in pretty name wether it is asked for or not because
664 * it could be used in a filter by match_iodevs.
665 */
666 iodev->is_pretty = pretty;
667 }
668
669 static enum iodev_type
670 get_iodev_type(kstat_t *ksp)
671 {
672 if (strcmp(ksp->ks_class, "disk") == 0)
673 return (IODEV_DISK);
674 if (strcmp(ksp->ks_class, "partition") == 0)
675 return (IODEV_PARTITION);
676 if (strcmp(ksp->ks_class, "nfs") == 0)
677 return (IODEV_NFS);
678 if (strcmp(ksp->ks_class, "iopath") == 0)
679 return (IODEV_IOPATH_LTI);
680 if (strcmp(ksp->ks_class, "tape") == 0)
681 return (IODEV_TAPE);
682 if (strcmp(ksp->ks_class, "zfs") == 0)
683 return (IODEV_ZFS);
684 return (IODEV_UNKNOWN);
685 }
686
687 /* get the lun/target/initiator from the name, return 1 on success */
688 static int
689 get_lti(char *s,
690 char *lname, int *l, char *tname, int *t, char *iname, int *i)
691 {
692 int num = 0;
693
694 num = sscanf(s, "%[a-z]%d%*[.]%[a-z]%d%*[.]%[a-z_]%d", lname, l,
695 tname, t, iname, i);
696 return ((num == 6) ? 1 : 0);
697 }
698
699
700 /* get the lun, target, and initiator name and instance */
701 static void
702 get_path_info(struct iodev_snapshot *io, char *mod, size_t modlen, int *type,
703 int *inst, char *name, size_t size)
704 {
705
706 /*
707 * If it is iopath or ssd then pad the name with i/t/l so we can sort
708 * by alpha order and set type for IOPATH to DISK since we want to
709 * have it grouped with its ssd parent. The lun can be 5 digits,
710 * the target can be 4 digits, and the initiator can be 3 digits and
711 * the padding is done appropriately for string comparisons.
712 */
713 if (disk_or_partition_or_iopath(io->is_type)) {
714 int i1, t1, l1;
715 char tname[KSTAT_STRLEN], iname[KSTAT_STRLEN];
716 char *ptr, lname[KSTAT_STRLEN];
717
718 i1 = t1 = l1 = 0;
719 (void) get_lti(io->is_name, lname, &l1, tname, &t1, iname, &i1);
720 *type = io->is_type;
721 if (io->is_type == IODEV_DISK) {
722 (void) snprintf(name, size, "%s%05d", lname, l1);
723 } else if (io->is_type == IODEV_PARTITION) {
724 ptr = strchr(io->is_name, ',');
725 (void) snprintf(name, size, "%s%05d%s", lname, l1, ptr);
726 } else {
727 (void) snprintf(name, size, "%s%05d.%s%04d.%s%03d",
728 lname, l1, tname, t1, iname, i1);
729 /* set to disk so we sort with disks */
730 *type = IODEV_DISK;
731 }
732 (void) strlcpy(mod, lname, modlen);
733 *inst = l1;
734 } else {
735 (void) strlcpy(mod, io->is_module, modlen);
736 (void) strlcpy(name, io->is_name, size);
737 *type = io->is_type;
738 *inst = io->is_instance;
739 }
740 }
741
742 int
743 iodev_cmp(struct iodev_snapshot *io1, struct iodev_snapshot *io2)
744 {
745 int type1, type2;
746 int inst1, inst2;
747 char name1[KSTAT_STRLEN], name2[KSTAT_STRLEN];
748 char mod1[KSTAT_STRLEN], mod2[KSTAT_STRLEN];
749
750 get_path_info(io1, mod1, sizeof (mod1), &type1, &inst1, name1,
751 sizeof (name1));
752 get_path_info(io2, mod2, sizeof (mod2), &type2, &inst2, name2,
753 sizeof (name2));
754 if ((!disk_or_partition(type1)) ||
755 (!disk_or_partition(type2))) {
756 /* neutral sort order between disk and part */
757 if (type1 < type2) {
758 return (-1);
759 }
760 if (type1 > type2) {
761 return (1);
762 }
763 }
764
765 /* controller doesn't have ksp */
766 if (io1->is_ksp && io2->is_ksp) {
767 if (strcmp(mod1, mod2) != 0) {
768 return (strcmp(mod1, mod2));
769 }
770 if (inst1 < inst2) {
771 return (-1);
772 }
773 if (inst1 > inst2) {
774 return (1);
775 }
776 } else {
777 if (io1->is_id.id < io2->is_id.id) {
778 return (-1);
779 }
780 if (io1->is_id.id > io2->is_id.id) {
781 return (1);
782 }
783 }
784
785 return (strcmp(name1, name2));
786 }
787
788 /* update the target reads and writes */
789 static void
790 update_target(struct iodev_snapshot *tgt, struct iodev_snapshot *path)
791 {
792 tgt->is_stats.reads += path->is_stats.reads;
793 tgt->is_stats.writes += path->is_stats.writes;
794 tgt->is_stats.nread += path->is_stats.nread;
795 tgt->is_stats.nwritten += path->is_stats.nwritten;
796 tgt->is_stats.wcnt += path->is_stats.wcnt;
797 tgt->is_stats.rcnt += path->is_stats.rcnt;
798
799 /*
800 * Stash the t_delta in the crtime for use in show_disk
801 * NOTE: this can't be done in show_disk because the
802 * itl entry is removed for the old format
803 */
804 tgt->is_crtime += hrtime_delta(path->is_crtime, path->is_snaptime);
805 tgt->is_snaptime += path->is_snaptime;
806 tgt->is_nr_children += 1;
807 }
808
809 /*
810 * Create a new synthetic device entry of the specified type. The supported
811 * synthetic types are IODEV_IOPATH_LT and IODEV_IOPATH_LI.
812 */
813 static struct iodev_snapshot *
814 make_extended_device(int type, struct iodev_snapshot *old)
815 {
816 struct iodev_snapshot *tptr = NULL;
817 char *ptr;
818 int lun, tgt, initiator;
819 char lun_name[KSTAT_STRLEN];
820 char tgt_name[KSTAT_STRLEN];
821 char initiator_name[KSTAT_STRLEN];
822
823 if (old == NULL)
824 return (NULL);
825 if (get_lti(old->is_name,
826 lun_name, &lun, tgt_name, &tgt, initiator_name, &initiator) != 1) {
827 return (NULL);
828 }
829 tptr = safe_alloc(sizeof (*old));
830 bzero(tptr, sizeof (*old));
831 if (old->is_pretty != NULL) {
832 tptr->is_pretty = safe_alloc(strlen(old->is_pretty) + 1);
833 (void) strcpy(tptr->is_pretty, old->is_pretty);
834 }
835 bcopy(&old->is_parent_id, &tptr->is_parent_id,
836 sizeof (old->is_parent_id));
837
838 tptr->is_type = type;
839
840 if (type == IODEV_IOPATH_LT) {
841 /* make new synthetic entry that is the LT */
842 /* set the id to the target id */
843 tptr->is_id.id = tgt;
844 (void) snprintf(tptr->is_id.tid, sizeof (tptr->is_id.tid),
845 "%s%d", tgt_name, tgt);
846 (void) snprintf(tptr->is_name, sizeof (tptr->is_name),
847 "%s%d.%s%d", lun_name, lun, tgt_name, tgt);
848
849 if (old->is_pretty) {
850 ptr = strrchr(tptr->is_pretty, '.');
851 if (ptr)
852 *ptr = '\0';
853 }
854 } else if (type == IODEV_IOPATH_LI) {
855 /* make new synthetic entry that is the LI */
856 /* set the id to the initiator number */
857 tptr->is_id.id = initiator;
858 (void) snprintf(tptr->is_id.tid, sizeof (tptr->is_id.tid),
859 "%s%d", initiator_name, initiator);
860 (void) snprintf(tptr->is_name, sizeof (tptr->is_name),
861 "%s%d.%s%d", lun_name, lun, initiator_name, initiator);
862
863 if (old->is_pretty) {
864 ptr = strchr(tptr->is_pretty, '.');
865 if (ptr)
866 (void) snprintf(ptr + 1,
867 strlen(tptr->is_pretty) + 1,
868 "%s%d", initiator_name, initiator);
869 }
870 }
871 return (tptr);
872 }
873
874 /*
875 * This is to get the original -X LI format (e.g. ssd1.fp0). When an LTI kstat
876 * is found - traverse the children looking for the same initiator and sum
877 * them up. Add an LI entry and delete all of the LTI entries with the same
878 * initiator.
879 */
880 static int
881 create_li_delete_lti(struct snapshot *ss, struct iodev_snapshot *list)
882 {
883 struct iodev_snapshot *pos, *entry, *parent;
884 int lun, tgt, initiator;
885 char lun_name[KSTAT_STRLEN];
886 char tgt_name[KSTAT_STRLEN];
887 char initiator_name[KSTAT_STRLEN];
888 int err;
889
890 for (entry = list; entry; entry = entry->is_next) {
891 if ((err = create_li_delete_lti(ss, entry->is_children)) != 0)
892 return (err);
893
894 if (entry->is_type == IODEV_IOPATH_LTI) {
895 parent = find_parent(ss, entry);
896 if (get_lti(entry->is_name, lun_name, &lun,
897 tgt_name, &tgt, initiator_name, &initiator) != 1) {
898 return (1);
899 }
900
901 pos = (parent == NULL) ? NULL : parent->is_children;
902 for (; pos; pos = pos->is_next) {
903 if (pos->is_id.id != -1 &&
904 pos->is_id.id == initiator &&
905 pos->is_type == IODEV_IOPATH_LI) {
906 /* found the same initiator */
907 update_target(pos, entry);
908 list_del(&parent->is_children, entry);
909 free_iodev(entry);
910 parent->is_nr_children--;
911 entry = pos;
912 break;
913 }
914 }
915
916 if (!pos) {
917 /* make the first LI entry */
918 pos = make_extended_device(
919 IODEV_IOPATH_LI, entry);
920 update_target(pos, entry);
921
922 if (parent) {
923 insert_before(&parent->is_children,
924 entry, pos);
925 list_del(&parent->is_children, entry);
926 free_iodev(entry);
927 } else {
928 insert_before(&ss->s_iodevs, entry,
929 pos);
930 list_del(&ss->s_iodevs, entry);
931 free_iodev(entry);
932 }
933 entry = pos;
934 }
935 }
936 }
937 return (0);
938 }
939
940 /*
941 * We have the LTI kstat, now add an entry for the LT that sums up all of
942 * the LTI's with the same target(t).
943 */
944 static int
945 create_lt(struct snapshot *ss, struct iodev_snapshot *list)
946 {
947 struct iodev_snapshot *entry, *parent, *pos;
948 int lun, tgt, initiator;
949 char lun_name[KSTAT_STRLEN];
950 char tgt_name[KSTAT_STRLEN];
951 char initiator_name[KSTAT_STRLEN];
952 int err;
953
954 for (entry = list; entry; entry = entry->is_next) {
955 if ((err = create_lt(ss, entry->is_children)) != 0)
956 return (err);
957
958 if (entry->is_type == IODEV_IOPATH_LTI) {
959 parent = find_parent(ss, entry);
960 if (get_lti(entry->is_name, lun_name, &lun,
961 tgt_name, &tgt, initiator_name, &initiator) != 1) {
962 return (1);
963 }
964
965 pos = (parent == NULL) ? NULL : parent->is_children;
966 for (; pos; pos = pos->is_next) {
967 if (pos->is_id.id != -1 &&
968 pos->is_id.id == tgt &&
969 pos->is_type == IODEV_IOPATH_LT) {
970 /* found the same target */
971 update_target(pos, entry);
972 break;
973 }
974 }
975
976 if (!pos) {
977 pos = make_extended_device(
978 IODEV_IOPATH_LT, entry);
979 update_target(pos, entry);
980
981 if (parent) {
982 insert_before(&parent->is_children,
983 entry, pos);
984 parent->is_nr_children++;
985 } else {
986 insert_before(&ss->s_iodevs,
987 entry, pos);
988 }
989 }
990 }
991 }
992 return (0);
993 }
994
995 /* Find the longest is_name field to aid formatting of output */
996 static int
997 iodevs_is_name_maxlen(struct iodev_snapshot *list)
998 {
999 struct iodev_snapshot *entry;
1000 int max = 0, cmax, len;
1001
1002 for (entry = list; entry; entry = entry->is_next) {
1003 cmax = iodevs_is_name_maxlen(entry->is_children);
1004 max = (cmax > max) ? cmax : max;
1005 len = strlen(entry->is_name);
1006 max = (len > max) ? len : max;
1007 }
1008 return (max);
1009 }
1010
1011 int
1012 acquire_iodevs(struct snapshot *ss, kstat_ctl_t *kc, struct iodev_filter *df)
1013 {
1014 kstat_t *ksp;
1015 struct iodev_snapshot *pos;
1016 struct iodev_snapshot *list = NULL;
1017 int err = 0;
1018
1019 ss->s_nr_iodevs = 0;
1020 ss->s_iodevs_is_name_maxlen = 0;
1021
1022 /*
1023 * Call cleanup_iodevs_snapshot() so that a cache miss in
1024 * lookup_ks_name() will result in a fresh snapshot.
1025 */
1026 cleanup_iodevs_snapshot();
1027
1028 for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) {
1029 enum iodev_type type;
1030
1031 if (ksp->ks_type != KSTAT_TYPE_IO)
1032 continue;
1033
1034 /* e.g. "usb_byte_count" is not handled */
1035 if ((type = get_iodev_type(ksp)) == IODEV_UNKNOWN)
1036 continue;
1037
1038 if (df && !(type & df->if_allowed_types))
1039 continue;
1040
1041 if ((pos = malloc(sizeof (struct iodev_snapshot))) == NULL) {
1042 err = errno;
1043 goto out;
1044 }
1045
1046 (void) memset(pos, 0, sizeof (struct iodev_snapshot));
1047
1048 pos->is_type = type;
1049 pos->is_crtime = ksp->ks_crtime;
1050 pos->is_snaptime = ksp->ks_snaptime;
1051 pos->is_id.id = IODEV_NO_ID;
1052 pos->is_parent_id.id = IODEV_NO_ID;
1053 pos->is_ksp = ksp;
1054 pos->is_instance = ksp->ks_instance;
1055
1056 (void) strlcpy(pos->is_module, ksp->ks_module, KSTAT_STRLEN);
1057 (void) strlcpy(pos->is_name, ksp->ks_name, KSTAT_STRLEN);
1058 get_pretty_name(ss->s_types, pos, kc);
1059
1060 /*
1061 * We must insert in sort order so e.g. vmstat -l
1062 * chooses in order.
1063 */
1064 insert_into(&list, pos);
1065 }
1066
1067 choose_iodevs(ss, list, df);
1068
1069 /* before acquire_stats for collate_controller()'s benefit */
1070 if (ss->s_types & SNAP_IODEV_ERRORS) {
1071 if ((err = acquire_iodev_errors(ss, kc)) != 0)
1072 goto out;
1073 }
1074
1075 if ((err = acquire_iodev_stats(ss->s_iodevs, kc)) != 0)
1076 goto out;
1077
1078 if (ss->s_types & SNAP_IOPATHS_LTI) {
1079 /*
1080 * -Y: kstats are LTI, need to create a synthetic LT
1081 * for -Y output.
1082 */
1083 if ((err = create_lt(ss, ss->s_iodevs)) != 0) {
1084 return (err);
1085 }
1086 }
1087 if (ss->s_types & SNAP_IOPATHS_LI) {
1088 /*
1089 * -X: kstats are LTI, need to create a synthetic LI and
1090 * delete the LTI for -X output
1091 */
1092 if ((err = create_li_delete_lti(ss, ss->s_iodevs)) != 0) {
1093 return (err);
1094 }
1095 }
1096
1097 /* determine width of longest is_name */
1098 ss->s_iodevs_is_name_maxlen = iodevs_is_name_maxlen(ss->s_iodevs);
1099
1100 err = 0;
1101 out:
1102 return (err);
1103 }
1104
1105 void
1106 free_iodev(struct iodev_snapshot *iodev)
1107 {
1108 while (iodev->is_children) {
1109 struct iodev_snapshot *tmp = iodev->is_children;
1110 iodev->is_children = iodev->is_children->is_next;
1111 free_iodev(tmp);
1112 }
1113
1114 if (iodev->avl_list) {
1115 avl_remove(iodev->avl_list, iodev);
1116 if (avl_numnodes(iodev->avl_list) == 0) {
1117 avl_destroy(iodev->avl_list);
1118 free(iodev->avl_list);
1119 }
1120 }
1121
1122 free(iodev->is_errors.ks_data);
1123 free(iodev->is_pretty);
1124 free(iodev->is_dname);
1125 free(iodev->is_devid);
1126 free(iodev);
1127 }