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