acquire_iodevs.c revision 4be70790cfe8d92fa6249ff02f1e5f1268ae2b8e
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include "statcommon.h"
#include "dsr.h"
#include <sys/dklabel.h>
#include <sys/dktp/fdisk.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <strings.h>
#include <errno.h>
#include <limits.h>
static void insert_iodev(struct snapshot *ss, struct iodev_snapshot *iodev);
static struct iodev_snapshot *
make_controller(int cid)
{
struct iodev_snapshot *new;
new = safe_alloc(sizeof (struct iodev_snapshot));
(void) memset(new, 0, sizeof (struct iodev_snapshot));
new->is_type = IODEV_CONTROLLER;
new->is_id.id = cid;
new->is_parent_id.id = IODEV_NO_ID;
(void) snprintf(new->is_name, sizeof (new->is_name), "c%d", cid);
return (new);
}
static struct iodev_snapshot *
find_iodev_by_name(struct iodev_snapshot *list, const char *name)
{
struct iodev_snapshot *pos;
struct iodev_snapshot *pos2;
for (pos = list; pos; pos = pos->is_next) {
if (strcmp(pos->is_name, name) == 0)
return (pos);
pos2 = find_iodev_by_name(pos->is_children, name);
if (pos2 != NULL)
return (pos2);
}
return (NULL);
}
static enum iodev_type
parent_iodev_type(enum iodev_type type)
{
switch (type) {
case IODEV_CONTROLLER: return (0);
case IODEV_IOPATH_LT: return (0);
case IODEV_IOPATH_LI: return (0);
case IODEV_NFS: return (0);
case IODEV_TAPE: return (0);
case IODEV_IOPATH_LTI: return (IODEV_DISK);
case IODEV_DISK: return (IODEV_CONTROLLER);
case IODEV_PARTITION: return (IODEV_DISK);
}
return (IODEV_UNKNOWN);
}
static int
id_match(struct iodev_id *id1, struct iodev_id *id2)
{
return (id1->id == id2->id &&
strcmp(id1->tid, id2->tid) == 0);
}
static struct iodev_snapshot *
find_parent(struct snapshot *ss, struct iodev_snapshot *iodev)
{
enum iodev_type parent_type = parent_iodev_type(iodev->is_type);
struct iodev_snapshot *pos;
struct iodev_snapshot *pos2;
if (parent_type == 0 || parent_type == IODEV_UNKNOWN)
return (NULL);
if (iodev->is_parent_id.id == IODEV_NO_ID &&
iodev->is_parent_id.tid[0] == '\0')
return (NULL);
if (parent_type == IODEV_CONTROLLER) {
for (pos = ss->s_iodevs; pos; pos = pos->is_next) {
if (pos->is_type != IODEV_CONTROLLER)
continue;
if (pos->is_id.id != iodev->is_parent_id.id)
continue;
return (pos);
}
if (!(ss->s_types & SNAP_CONTROLLERS))
return (NULL);
pos = make_controller(iodev->is_parent_id.id);
insert_iodev(ss, pos);
return (pos);
}
/* IODEV_DISK parent */
for (pos = ss->s_iodevs; pos; pos = pos->is_next) {
if (id_match(&iodev->is_parent_id, &pos->is_id) &&
pos->is_type == IODEV_DISK)
return (pos);
if (pos->is_type != IODEV_CONTROLLER)
continue;
for (pos2 = pos->is_children; pos2; pos2 = pos2->is_next) {
if (pos2->is_type != IODEV_DISK)
continue;
if (id_match(&iodev->is_parent_id, &pos2->is_id))
return (pos2);
}
}
return (NULL);
}
/*
* Introduce an index into the list to speed up insert_into looking for the
* right position in the list. This index is an AVL tree of all the
* iodev_snapshot in the list.
*/
#define offsetof(s, m) (size_t)(&(((s *)0)->m)) /* for avl_create */
static int
avl_iodev_cmp(const void* is1, const void* is2)
{
int c = iodev_cmp((struct iodev_snapshot *)is1,
(struct iodev_snapshot *)is2);
if (c > 0)
return (1);
if (c < 0)
return (-1);
return (0);
}
static void
ix_new_list(struct iodev_snapshot *elem)
{
avl_tree_t *l = malloc(sizeof (avl_tree_t));
elem->avl_list = l;
if (l == NULL)
return;
avl_create(l, avl_iodev_cmp, sizeof (struct iodev_snapshot),
offsetof(struct iodev_snapshot, avl_link));
avl_add(l, elem);
}
static void
ix_list_del(struct iodev_snapshot *elem)
{
avl_tree_t *l = elem->avl_list;
if (l == NULL)
return;
elem->avl_list = NULL;
avl_remove(l, elem);
if (avl_numnodes(l) == 0) {
avl_destroy(l);
free(l);
}
}
static void
ix_insert_here(struct iodev_snapshot *pos, struct iodev_snapshot *elem, int ba)
{
avl_tree_t *l = pos->avl_list;
elem->avl_list = l;
if (l == NULL)
return;
avl_insert_here(l, elem, pos, ba);
}
static void
list_del(struct iodev_snapshot **list, struct iodev_snapshot *pos)
{
ix_list_del(pos);
if (*list == pos)
*list = pos->is_next;
if (pos->is_next)
pos->is_next->is_prev = pos->is_prev;
if (pos->is_prev)
pos->is_prev->is_next = pos->is_next;
pos->is_prev = pos->is_next = NULL;
}
static void
insert_before(struct iodev_snapshot **list, struct iodev_snapshot *pos,
struct iodev_snapshot *new)
{
if (pos == NULL) {
new->is_prev = new->is_next = NULL;
*list = new;
ix_new_list(new);
return;
}
new->is_next = pos;
new->is_prev = pos->is_prev;
if (pos->is_prev)
pos->is_prev->is_next = new;
else
*list = new;
pos->is_prev = new;
ix_insert_here(pos, new, AVL_BEFORE);
}
static void
insert_after(struct iodev_snapshot **list, struct iodev_snapshot *pos,
struct iodev_snapshot *new)
{
if (pos == NULL) {
new->is_prev = new->is_next = NULL;
*list = new;
ix_new_list(new);
return;
}
new->is_next = pos->is_next;
new->is_prev = pos;
if (pos->is_next)
pos->is_next->is_prev = new;
pos->is_next = new;
ix_insert_here(pos, new, AVL_AFTER);
}
static void
insert_into(struct iodev_snapshot **list, struct iodev_snapshot *iodev)
{
struct iodev_snapshot *tmp = *list;
avl_tree_t *l;
void *p;
avl_index_t where;
if (*list == NULL) {
*list = iodev;
ix_new_list(iodev);
return;
}
/*
* Optimize the search: instead of walking the entire list
* (which can contain thousands of nodes), search in the AVL
* tree the nearest node and reposition the startup point to
* this node rather than always starting from the beginning
* of the list.
*/
l = tmp->avl_list;
if (l != NULL) {
p = avl_find(l, iodev, &where);
if (p == NULL) {
p = avl_nearest(l, where, AVL_BEFORE);
}
if (p != NULL) {
tmp = (struct iodev_snapshot *)p;
}
}
for (;;) {
if (iodev_cmp(tmp, iodev) > 0) {
insert_before(list, tmp, iodev);
return;
}
if (tmp->is_next == NULL)
break;
tmp = tmp->is_next;
}
insert_after(list, tmp, iodev);
}
static int
disk_or_partition(enum iodev_type type)
{
return (type == IODEV_DISK || type == IODEV_PARTITION);
}
static int
disk_or_partition_or_iopath(enum iodev_type type)
{
return (type == IODEV_DISK || type == IODEV_PARTITION ||
type == IODEV_IOPATH_LTI);
}
static void
insert_iodev(struct snapshot *ss, struct iodev_snapshot *iodev)
{
struct iodev_snapshot *parent = find_parent(ss, iodev);
struct iodev_snapshot **list;
if (parent != NULL) {
list = &parent->is_children;
parent->is_nr_children++;
} else {
list = &ss->s_iodevs;
ss->s_nr_iodevs++;
}
insert_into(list, iodev);
}
/* return 1 if dev passes filter */
static int
iodev_match(struct iodev_snapshot *dev, struct iodev_filter *df)
{
int is_floppy = (strncmp(dev->is_name, "fd", 2) == 0);
char *isn, *ispn, *ifn;
char *path;
int ifnl;
size_t i;
/* no filter, pass */
if (df == NULL)
return (1); /* pass */
/* no filtered names, pass if not floppy and skipped */
if (df->if_nr_names == NULL)
return (!(df->if_skip_floppy && is_floppy));
isn = dev->is_name;
ispn = dev->is_pretty;
for (i = 0; i < df->if_nr_names; i++) {
ifn = df->if_names[i];
ifnl = strlen(ifn);
path = strchr(ifn, '.');
if ((strcmp(isn, ifn) == 0) ||
(ispn && (strcmp(ispn, ifn) == 0)))
return (1); /* pass */
/* if filter is a path allow partial match */
if (path &&
((strncmp(isn, ifn, ifnl) == 0) ||
(ispn && (strncmp(ispn, ifn, ifnl) == 0))))
return (1); /* pass */
}
return (0); /* fail */
}
/* return 1 if path is an mpxio path associated with dev */
static int
iodev_path_match(struct iodev_snapshot *dev, struct iodev_snapshot *path)
{
char *dn, *pn;
int dnl;
dn = dev->is_name;
pn = path->is_name;
dnl = strlen(dn);
if ((strncmp(pn, dn, dnl) == 0) && (pn[dnl] == '.'))
return (1); /* yes */
return (0); /* no */
}
/* select which I/O devices to collect stats for */
static void
choose_iodevs(struct snapshot *ss, struct iodev_snapshot *iodevs,
struct iodev_filter *df)
{
struct iodev_snapshot *pos, *ppos, *tmp, *ptmp;
int nr_iodevs;
int nr_iodevs_orig;
nr_iodevs = df ? df->if_max_iodevs : UNLIMITED_IODEVS;
nr_iodevs_orig = nr_iodevs;
if (nr_iodevs == UNLIMITED_IODEVS)
nr_iodevs = INT_MAX;
/* add the full matches */
pos = iodevs;
while (pos && nr_iodevs) {
tmp = pos;
pos = pos->is_next;
if (!iodev_match(tmp, df))
continue; /* failed full match */
list_del(&iodevs, tmp);
insert_iodev(ss, tmp);
/*
* Add all mpxio paths associated with match above. Added
* paths don't count against nr_iodevs.
*/
if (strchr(tmp->is_name, '.') == NULL) {
ppos = iodevs;
while (ppos) {
ptmp = ppos;
ppos = ppos->is_next;
if (!iodev_path_match(tmp, ptmp))
continue; /* not an mpxio path */
list_del(&iodevs, ptmp);
insert_iodev(ss, ptmp);
if (pos == ptmp)
pos = ppos;
}
}
nr_iodevs--;
}
/*
* If we had a filter, and *nothing* passed the filter then we
* don't want to fill the remaining slots - it is just confusing
* if we don that, it makes it look like the filter code is broken.
*/
if ((df->if_nr_names == NULL) || (nr_iodevs != nr_iodevs_orig)) {
/* now insert any iodevs into the remaining slots */
pos = iodevs;
while (pos && nr_iodevs) {
tmp = pos;
pos = pos->is_next;
if (df && df->if_skip_floppy &&
strncmp(tmp->is_name, "fd", 2) == 0)
continue;
list_del(&iodevs, tmp);
insert_iodev(ss, tmp);
--nr_iodevs;
}
}
/* clear the unwanted ones */
pos = iodevs;
while (pos) {
struct iodev_snapshot *tmp = pos;
pos = pos->is_next;
free_iodev(tmp);
}
}
static int
collate_controller(struct iodev_snapshot *controller,
struct iodev_snapshot *disk)
{
controller->is_stats.nread += disk->is_stats.nread;
controller->is_stats.nwritten += disk->is_stats.nwritten;
controller->is_stats.reads += disk->is_stats.reads;
controller->is_stats.writes += disk->is_stats.writes;
controller->is_stats.wtime += disk->is_stats.wtime;
controller->is_stats.wlentime += disk->is_stats.wlentime;
controller->is_stats.rtime += disk->is_stats.rtime;
controller->is_stats.rlentime += disk->is_stats.rlentime;
controller->is_crtime += disk->is_crtime;
controller->is_snaptime += disk->is_snaptime;
if (kstat_add(&disk->is_errors, &controller->is_errors))
return (errno);
return (0);
}
static int
acquire_iodev_stats(struct iodev_snapshot *list, kstat_ctl_t *kc)
{
struct iodev_snapshot *pos;
int err = 0;
for (pos = list; pos; pos = pos->is_next) {
/* controllers don't have stats (yet) */
if (pos->is_ksp != NULL) {
if (kstat_read(kc, pos->is_ksp, &pos->is_stats) == -1)
return (errno);
/* make sure crtime/snaptime is updated */
pos->is_crtime = pos->is_ksp->ks_crtime;
pos->is_snaptime = pos->is_ksp->ks_snaptime;
}
if ((err = acquire_iodev_stats(pos->is_children, kc)))
return (err);
if (pos->is_type == IODEV_CONTROLLER) {
struct iodev_snapshot *pos2 = pos->is_children;
for (; pos2; pos2 = pos2->is_next) {
if ((err = collate_controller(pos, pos2)))
return (err);
}
}
}
return (0);
}
static int
acquire_iodev_errors(struct snapshot *ss, kstat_ctl_t *kc)
{
kstat_t *ksp;
if (!(ss->s_types && SNAP_IODEV_ERRORS))
return (0);
for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) {
char kstat_name[KSTAT_STRLEN];
char *dname = kstat_name;
char *ename = ksp->ks_name;
struct iodev_snapshot *iodev;
if (ksp->ks_type != KSTAT_TYPE_NAMED)
continue;
if (strncmp(ksp->ks_class, "device_error", 12) != 0 &&
strncmp(ksp->ks_class, "iopath_error", 12) != 0)
continue;
/*
* Some drivers may not follow the naming convention
* for error kstats (i.e., drivername,err) so
* be sure we don't walk off the end.
*/
while (*ename && *ename != ',') {
*dname = *ename;
dname++;
ename++;
}
*dname = '\0';
iodev = find_iodev_by_name(ss->s_iodevs, kstat_name);
if (iodev == NULL)
continue;
if (kstat_read(kc, ksp, NULL) == -1)
return (errno);
if (kstat_copy(ksp, &iodev->is_errors) == -1)
return (errno);
}
return (0);
}
static void
get_ids(struct iodev_snapshot *iodev, const char *pretty)
{
int ctr, disk, slice, ret;
char *target;
const char *p1;
const char *p2;
if (pretty == NULL)
return;
if (sscanf(pretty, "c%d", &ctr) != 1)
return;
p1 = pretty;
while (*p1 && *p1 != 't')
++p1;
if (!*p1)
return;
++p1;
p2 = p1;
while (*p2 && *p2 != 'd')
++p2;
if (!*p2 || p2 == p1)
return;
target = safe_alloc(1 + p2 - p1);
(void) strlcpy(target, p1, 1 + p2 - p1);
ret = sscanf(p2, "d%d%*[sp]%d", &disk, &slice);
if (ret == 2 && iodev->is_type == IODEV_PARTITION) {
iodev->is_id.id = slice;
iodev->is_parent_id.id = disk;
(void) strlcpy(iodev->is_parent_id.tid, target, KSTAT_STRLEN);
} else if (ret == 1) {
if (iodev->is_type == IODEV_DISK) {
iodev->is_id.id = disk;
(void) strlcpy(iodev->is_id.tid, target, KSTAT_STRLEN);
iodev->is_parent_id.id = ctr;
} else if (iodev->is_type == IODEV_IOPATH_LTI) {
iodev->is_parent_id.id = disk;
(void) strlcpy(iodev->is_parent_id.tid,
target, KSTAT_STRLEN);
}
}
free(target);
}
static void
get_pretty_name(enum snapshot_types types, struct iodev_snapshot *iodev,
kstat_ctl_t *kc)
{
disk_list_t *dl;
char *pretty = NULL;
if (iodev->is_type == IODEV_NFS) {
if (!(types & SNAP_IODEV_PRETTY))
return;
iodev->is_pretty = lookup_nfs_name(iodev->is_name, kc);
return;
}
/* lookup/translate the kstat name */
dl = lookup_ks_name(iodev->is_name, (types & SNAP_IODEV_DEVID) ? 1 : 0);
if (dl == NULL)
return;
if (dl->dsk)
pretty = safe_strdup(dl->dsk);
if (types & SNAP_IODEV_PRETTY) {
if (dl->dname)
iodev->is_dname = safe_strdup(dl->dname);
}
if (dl->devidstr)
iodev->is_devid = safe_strdup(dl->devidstr);
get_ids(iodev, pretty);
/*
* we fill in pretty name wether it is asked for or not because
* it could be used in a filter by match_iodevs.
*/
iodev->is_pretty = pretty;
}
static enum iodev_type
get_iodev_type(kstat_t *ksp)
{
if (strcmp(ksp->ks_class, "disk") == 0)
return (IODEV_DISK);
if (strcmp(ksp->ks_class, "partition") == 0)
return (IODEV_PARTITION);
if (strcmp(ksp->ks_class, "nfs") == 0)
return (IODEV_NFS);
if (strcmp(ksp->ks_class, "iopath") == 0)
return (IODEV_IOPATH_LTI);
if (strcmp(ksp->ks_class, "tape") == 0)
return (IODEV_TAPE);
return (IODEV_UNKNOWN);
}
/* get the lun/target/initiator from the name, return 1 on success */
static int
get_lti(char *s,
char *lname, int *l, char *tname, int *t, char *iname, int *i)
{
int num = 0;
num = sscanf(s, "%[a-z]%d%*[.]%[a-z]%d%*[.]%[a-z]%d", lname, l,
tname, t, iname, i);
return ((num == 6) ? 1 : 0);
}
/* get the lun, target, and initiator name and instance */
static void
get_path_info(struct iodev_snapshot *io, char *mod, int *type, int *inst,
char *name, size_t size)
{
/*
* If it is iopath or ssd then pad the name with i/t/l so we can sort
* by alpha order and set type for IOPATH to DISK since we want to
* have it grouped with its ssd parent. The lun can be 5 digits,
* the target can be 4 digits, and the initiator can be 3 digits and
* the padding is done appropriately for string comparisons.
*/
if (disk_or_partition_or_iopath(io->is_type)) {
int i1, t1, l1;
char tname[KSTAT_STRLEN], iname[KSTAT_STRLEN];
char *ptr, lname[KSTAT_STRLEN];
i1 = t1 = l1 = 0;
(void) get_lti(io->is_name, lname, &l1, tname, &t1, iname, &i1);
*type = io->is_type;
if (io->is_type == IODEV_DISK) {
(void) snprintf(name, size, "%s%05d", lname, l1);
} else if (io->is_type == IODEV_PARTITION) {
ptr = strchr(io->is_name, ',');
(void) snprintf(name, size, "%s%05d%s", lname, l1, ptr);
} else {
(void) snprintf(name, size, "%s%05d.%s%04d.%s%03d",
lname, l1, tname, t1, iname, i1);
/* set to disk so we sort with disks */
*type = IODEV_DISK;
}
(void) strcpy(mod, lname);
*inst = l1;
} else {
(void) strcpy(mod, io->is_module);
(void) strcpy(name, io->is_name);
*type = io->is_type;
*inst = io->is_instance;
}
}
int
iodev_cmp(struct iodev_snapshot *io1, struct iodev_snapshot *io2)
{
int type1, type2;
int inst1, inst2;
char name1[KSTAT_STRLEN], name2[KSTAT_STRLEN];
char mod1[KSTAT_STRLEN], mod2[KSTAT_STRLEN];
get_path_info(io1, mod1, &type1, &inst1, name1, sizeof (name1));
get_path_info(io2, mod2, &type2, &inst2, name2, sizeof (name2));
if ((!disk_or_partition(type1)) ||
(!disk_or_partition(type2))) {
/* neutral sort order between disk and part */
if (type1 < type2) {
return (-1);
}
if (type1 > type2) {
return (1);
}
}
/* controller doesn't have ksp */
if (io1->is_ksp && io2->is_ksp) {
if (strcmp(mod1, mod2) != 0) {
return (strcmp(mod1, mod2));
}
if (inst1 < inst2) {
return (-1);
}
if (inst1 > inst2) {
return (1);
}
} else {
if (io1->is_id.id < io2->is_id.id) {
return (-1);
}
if (io1->is_id.id > io2->is_id.id) {
return (1);
}
}
return (strcmp(name1, name2));
}
/* update the target reads and writes */
static void
update_target(struct iodev_snapshot *tgt, struct iodev_snapshot *path)
{
tgt->is_stats.reads += path->is_stats.reads;
tgt->is_stats.writes += path->is_stats.writes;
tgt->is_stats.nread += path->is_stats.nread;
tgt->is_stats.nwritten += path->is_stats.nwritten;
tgt->is_stats.wcnt += path->is_stats.wcnt;
tgt->is_stats.rcnt += path->is_stats.rcnt;
/*
* Stash the t_delta in the crtime for use in show_disk
* NOTE: this can't be done in show_disk because the
* itl entry is removed for the old format
*/
tgt->is_crtime += hrtime_delta(path->is_crtime, path->is_snaptime);
tgt->is_snaptime += path->is_snaptime;
tgt->is_nr_children += 1;
}
/*
* Create a new synthetic device entry of the specified type. The supported
* synthetic types are IODEV_IOPATH_LT and IODEV_IOPATH_LI.
*/
static struct iodev_snapshot *
make_extended_device(int type, struct iodev_snapshot *old)
{
struct iodev_snapshot *tptr = NULL;
char *ptr;
int lun, tgt, initiator;
char lun_name[KSTAT_STRLEN];
char tgt_name[KSTAT_STRLEN];
char initiator_name[KSTAT_STRLEN];
if (old == NULL)
return (NULL);
if (get_lti(old->is_name,
lun_name, &lun, tgt_name, &tgt, initiator_name, &initiator) != 1) {
return (NULL);
}
tptr = safe_alloc(sizeof (*old));
bzero(tptr, sizeof (*old));
if (old->is_pretty != NULL) {
tptr->is_pretty = safe_alloc(strlen(old->is_pretty) + 1);
(void) strcpy(tptr->is_pretty, old->is_pretty);
}
bcopy(&old->is_parent_id, &tptr->is_parent_id,
sizeof (old->is_parent_id));
tptr->is_type = type;
if (type == IODEV_IOPATH_LT) {
/* make new synthetic entry that is the LT */
/* set the id to the target id */
tptr->is_id.id = tgt;
(void) snprintf(tptr->is_id.tid, sizeof (tptr->is_id.tid),
"%s%d", tgt_name, tgt);
(void) snprintf(tptr->is_name, sizeof (tptr->is_name),
"%s%d.%s%d", lun_name, lun, tgt_name, tgt);
if (old->is_pretty) {
ptr = strrchr(tptr->is_pretty, '.');
if (ptr)
*ptr = '\0';
}
} else if (type == IODEV_IOPATH_LI) {
/* make new synthetic entry that is the LI */
/* set the id to the initiator number */
tptr->is_id.id = initiator;
(void) snprintf(tptr->is_id.tid, sizeof (tptr->is_id.tid),
"%s%d", initiator_name, initiator);
(void) snprintf(tptr->is_name, sizeof (tptr->is_name),
"%s%d.%s%d", lun_name, lun, initiator_name, initiator);
if (old->is_pretty) {
ptr = strchr(tptr->is_pretty, '.');
if (ptr)
(void) snprintf(ptr + 1,
strlen(tptr->is_pretty) + 1,
"%s%d", initiator_name, initiator);
}
}
return (tptr);
}
/*
* This is to get the original -X LI format (e.g. ssd1.fp0). When an LTI kstat
* is found - traverse the children looking for the same initiator and sum
* them up. Add an LI entry and delete all of the LTI entries with the same
* initiator.
*/
static int
create_li_delete_lti(struct snapshot *ss, struct iodev_snapshot *list)
{
struct iodev_snapshot *pos, *entry, *parent;
int lun, tgt, initiator;
char lun_name[KSTAT_STRLEN];
char tgt_name[KSTAT_STRLEN];
char initiator_name[KSTAT_STRLEN];
int err;
for (entry = list; entry; entry = entry->is_next) {
if ((err = create_li_delete_lti(ss, entry->is_children)) != 0)
return (err);
if (entry->is_type == IODEV_IOPATH_LTI) {
parent = find_parent(ss, entry);
if (get_lti(entry->is_name, lun_name, &lun,
tgt_name, &tgt, initiator_name, &initiator) != 1) {
return (1);
}
pos = (parent == NULL) ? NULL : parent->is_children;
for (; pos; pos = pos->is_next) {
if (pos->is_id.id != -1 &&
pos->is_id.id == initiator &&
pos->is_type == IODEV_IOPATH_LI) {
/* found the same initiator */
update_target(pos, entry);
list_del(&parent->is_children, entry);
free_iodev(entry);
parent->is_nr_children--;
entry = pos;
break;
}
}
if (!pos) {
/* make the first LI entry */
pos = make_extended_device(
IODEV_IOPATH_LI, entry);
update_target(pos, entry);
if (parent) {
insert_before(&parent->is_children,
entry, pos);
list_del(&parent->is_children, entry);
free_iodev(entry);
} else {
insert_before(&ss->s_iodevs, entry,
pos);
list_del(&ss->s_iodevs, entry);
free_iodev(entry);
}
entry = pos;
}
}
}
return (0);
}
/*
* We have the LTI kstat, now add an entry for the LT that sums up all of
* the LTI's with the same target(t).
*/
static int
create_lt(struct snapshot *ss, struct iodev_snapshot *list)
{
struct iodev_snapshot *entry, *parent, *pos;
int lun, tgt, initiator;
char lun_name[KSTAT_STRLEN];
char tgt_name[KSTAT_STRLEN];
char initiator_name[KSTAT_STRLEN];
int err;
for (entry = list; entry; entry = entry->is_next) {
if ((err = create_lt(ss, entry->is_children)) != 0)
return (err);
if (entry->is_type == IODEV_IOPATH_LTI) {
parent = find_parent(ss, entry);
if (get_lti(entry->is_name, lun_name, &lun,
tgt_name, &tgt, initiator_name, &initiator) != 1) {
return (1);
}
pos = (parent == NULL) ? NULL : parent->is_children;
for (; pos; pos = pos->is_next) {
if (pos->is_id.id != -1 &&
pos->is_id.id == tgt &&
pos->is_type == IODEV_IOPATH_LT) {
/* found the same target */
update_target(pos, entry);
break;
}
}
if (!pos) {
pos = make_extended_device(
IODEV_IOPATH_LT, entry);
update_target(pos, entry);
if (parent) {
insert_before(&parent->is_children,
entry, pos);
parent->is_nr_children++;
} else {
insert_before(&ss->s_iodevs,
entry, pos);
}
}
}
}
return (0);
}
/* Find the longest is_name field to aid formatting of output */
static int
iodevs_is_name_maxlen(struct iodev_snapshot *list)
{
struct iodev_snapshot *entry;
int max = 0, cmax, len;
for (entry = list; entry; entry = entry->is_next) {
cmax = iodevs_is_name_maxlen(entry->is_children);
max = (cmax > max) ? cmax : max;
len = strlen(entry->is_name);
max = (len > max) ? len : max;
}
return (max);
}
int
acquire_iodevs(struct snapshot *ss, kstat_ctl_t *kc, struct iodev_filter *df)
{
kstat_t *ksp;
struct iodev_snapshot *pos;
struct iodev_snapshot *list = NULL;
int err = 0;
ss->s_nr_iodevs = 0;
ss->s_iodevs_is_name_maxlen = 0;
/*
* Call cleanup_iodevs_snapshot() so that a cache miss in
* lookup_ks_name() will result in a fresh snapshot.
*/
cleanup_iodevs_snapshot();
for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) {
enum iodev_type type;
if (ksp->ks_type != KSTAT_TYPE_IO)
continue;
/* e.g. "usb_byte_count" is not handled */
if ((type = get_iodev_type(ksp)) == IODEV_UNKNOWN)
continue;
if (df && !(type & df->if_allowed_types))
continue;
if ((pos = malloc(sizeof (struct iodev_snapshot))) == NULL) {
err = errno;
goto out;
}
(void) memset(pos, 0, sizeof (struct iodev_snapshot));
pos->is_type = type;
pos->is_crtime = ksp->ks_crtime;
pos->is_snaptime = ksp->ks_snaptime;
pos->is_id.id = IODEV_NO_ID;
pos->is_parent_id.id = IODEV_NO_ID;
pos->is_ksp = ksp;
pos->is_instance = ksp->ks_instance;
(void) strlcpy(pos->is_module, ksp->ks_module, KSTAT_STRLEN);
(void) strlcpy(pos->is_name, ksp->ks_name, KSTAT_STRLEN);
get_pretty_name(ss->s_types, pos, kc);
/*
* We must insert in sort order so e.g. vmstat -l
* chooses in order.
*/
insert_into(&list, pos);
}
choose_iodevs(ss, list, df);
/* before acquire_stats for collate_controller()'s benefit */
if (ss->s_types & SNAP_IODEV_ERRORS) {
if ((err = acquire_iodev_errors(ss, kc)) != 0)
goto out;
}
if ((err = acquire_iodev_stats(ss->s_iodevs, kc)) != 0)
goto out;
if (ss->s_types & SNAP_IOPATHS_LTI) {
/*
* -Y: kstats are LTI, need to create a synthetic LT
* for -Y output.
*/
if ((err = create_lt(ss, ss->s_iodevs)) != 0) {
return (err);
}
}
if (ss->s_types & SNAP_IOPATHS_LI) {
/*
* -X: kstats are LTI, need to create a synthetic LI and
* delete the LTI for -X output
*/
if ((err = create_li_delete_lti(ss, ss->s_iodevs)) != 0) {
return (err);
}
}
/* determine width of longest is_name */
ss->s_iodevs_is_name_maxlen = iodevs_is_name_maxlen(ss->s_iodevs);
err = 0;
out:
return (err);
}
void
free_iodev(struct iodev_snapshot *iodev)
{
while (iodev->is_children) {
struct iodev_snapshot *tmp = iodev->is_children;
iodev->is_children = iodev->is_children->is_next;
free_iodev(tmp);
}
if (iodev->avl_list) {
avl_remove(iodev->avl_list, iodev);
if (avl_numnodes(iodev->avl_list) == 0) {
avl_destroy(iodev->avl_list);
free(iodev->avl_list);
}
}
free(iodev->is_errors.ks_data);
free(iodev->is_pretty);
free(iodev->is_dname);
free(iodev->is_devid);
free(iodev);
}