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