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