root/usr/src/cmd/stat/common/walkers.c
/*
 * 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 2006 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include "statcommon.h"

#include <string.h>
#include <errno.h>

/* max size of report change annotations */
#define LIST_SIZE 512

static char cpus_added[LIST_SIZE];
static char cpus_removed[LIST_SIZE];

static int
cpu_walk(struct snapshot *old, struct snapshot *new,
    snapshot_cb cb, void *data)
{
        int changed = 0;
        int i;

        /* CPUs can change state but not re-order */
        for (i = 0; i < new->s_nr_cpus; i++) {
                struct cpu_snapshot *cpu = NULL;
                struct cpu_snapshot *newcpu = &new->s_cpus[i];
                if (old)
                        cpu = &old->s_cpus[i];
                cb(cpu, newcpu, data);
                if (cpu == NULL)
                        changed = 1;
                else {
                        /*
                         * We only care about off/on line transitions
                         */
                        if ((CPU_ACTIVE(cpu) && !CPU_ACTIVE(newcpu)) ||
                            (!CPU_ACTIVE(cpu) && CPU_ACTIVE(newcpu)))
                                changed = 1;
                        if ((new->s_types & SNAP_PSETS) &&
                                cpu->cs_pset_id != newcpu->cs_pset_id)
                                changed = 1;
                }

        }

        return (changed);
}

static int
pset_walk(struct snapshot *old, struct snapshot *new,
    snapshot_cb cb, void *data)
{
        int i = 0;
        int j = 0;
        int changed = 0;

        while (old && i < old->s_nr_psets && j < new->s_nr_psets) {
                if (old->s_psets[i].ps_id < new->s_psets[j].ps_id) {
                        cb(&old->s_psets[i], NULL, data);
                        i++;
                        changed = 1;
                } else if (old->s_psets[i].ps_id > new->s_psets[j].ps_id) {
                        cb(NULL, &new->s_psets[j], data);
                        j++;
                        changed = 1;
                } else {
                        cb(&old->s_psets[i], &new->s_psets[j], data);
                        i++;
                        j++;
                }
        }

        while (old && i < old->s_nr_psets) {
                cb(&old->s_psets[i], NULL, data);
                i++;
                changed = 1;
        }

        while (j < new->s_nr_psets) {
                cb(NULL, &new->s_psets[j], data);
                j++;
                changed = 1;
        }

        return (changed);
}

static int
iodev_walk(struct iodev_snapshot *d1, struct iodev_snapshot *d2,
    snapshot_cb cb, void *data)
{
        int changed = 0;

        while (d1 && d2) {
                if (strcmp(d1->is_name, d2->is_name) < 0) {
                        changed = 1;
                        cb(d1, NULL, data);
                        (void) iodev_walk(d1->is_children, NULL, cb, data);
                        d1 = d1->is_next;
                } else if (strcmp(d1->is_name, d2->is_name) > 0) {
                        changed = 1;
                        cb(NULL, d2, data);
                        (void) iodev_walk(NULL, d2->is_children, cb, data);
                        d2 = d2->is_next;
                } else {
                        cb(d1, d2, data);
                        changed |= iodev_walk(d1->is_children,
                                        d2->is_children, cb, data);
                        d1 = d1->is_next;
                        d2 = d2->is_next;
                }
        }

        while (d1) {
                changed = 1;
                cb(d1, NULL, data);
                (void) iodev_walk(d1->is_children, NULL, cb, data);
                d1 = d1->is_next;
        }

        while (d2) {
                changed = 1;
                cb(NULL, d2, data);
                (void) iodev_walk(NULL, d2->is_children, cb, data);
                d2 = d2->is_next;
        }

        return (changed);
}

int
snapshot_walk(enum snapshot_types type, struct snapshot *old,
    struct snapshot *new, snapshot_cb cb, void *data)
{
        int changed = 0;

        switch (type) {
        case SNAP_CPUS:
                changed = cpu_walk(old, new, cb, data);
                break;

        case SNAP_PSETS:
                changed = pset_walk(old, new, cb, data);
                break;

        case SNAP_CONTROLLERS:
        case SNAP_IODEVS:
        case SNAP_IOPATHS_LI:
        case SNAP_IOPATHS_LTI:
                changed = iodev_walk(old ? old->s_iodevs : NULL,
                    new->s_iodevs, cb, data);
                break;

        default:
                break;
        }

        return (changed);
}

static void
add_nr_to_list(char *buf, unsigned long nr)
{
        char tmp[LIST_SIZE];

        (void) snprintf(tmp, LIST_SIZE, "%lu", nr);

        if (strlen(buf))
                (void) strlcat(buf, ", ", LIST_SIZE);

        (void) strlcat(buf, tmp, LIST_SIZE);
}

static void
cpu_report(void *v1, void *v2, void *data)
{
        int *pset = (int *)data;
        struct cpu_snapshot *c1 = (struct cpu_snapshot *)v1;
        struct cpu_snapshot *c2 = (struct cpu_snapshot *)v2;

        if (*pset && c1->cs_pset_id != c2->cs_pset_id) {
                (void) printf("<<processor %d moved from pset: %d to: %d>>\n",
                    c1->cs_id, c1->cs_pset_id, c2->cs_pset_id);
        }

        if (c1->cs_state == c2->cs_state)
                return;

        if (CPU_ONLINE(c1->cs_state) && !CPU_ONLINE(c2->cs_state))
                add_nr_to_list(cpus_removed, c1->cs_id);

        if (!CPU_ONLINE(c1->cs_state) && CPU_ONLINE(c2->cs_state))
                add_nr_to_list(cpus_added, c2->cs_id);
}

/*ARGSUSED*/
static void
pset_report(void *v1, void *v2, void *data)
{
        struct pset_snapshot *p1 = (struct pset_snapshot *)v1;
        struct pset_snapshot *p2 = (struct pset_snapshot *)v2;

        if (p2 == NULL) {
                (void) printf("<<pset destroyed: %u>>\n", p1->ps_id);
                return;
        }

        if (p1 == NULL)
                (void) printf("<<pset created: %u>>\n", p2->ps_id);
}

static void
get_child_list(struct iodev_snapshot *iodev, char *buf)
{
        char tmp[LIST_SIZE];
        struct iodev_snapshot *pos = iodev->is_children;

        while (pos) {
                if (pos->is_type == IODEV_PARTITION) {
                        add_nr_to_list(buf, pos->is_id.id);
                } else if (pos->is_type == IODEV_DISK) {
                        if (strlen(buf))
                                (void) strlcat(buf, ", ", LIST_SIZE);
                        (void) strlcat(buf, "t", LIST_SIZE);
                        (void) strlcat(buf, pos->is_id.tid, LIST_SIZE);
                        (void) strlcat(buf, "d", LIST_SIZE);
                        *tmp = '\0';
                        add_nr_to_list(tmp, pos->is_id.id);
                        (void) strlcat(buf, tmp, LIST_SIZE);
                }
                pos = pos->is_next;
        }
}

static void
iodev_changed(struct iodev_snapshot *iodev, int added)
{
        char tmp[LIST_SIZE];
        int is_disk = iodev->is_type == IODEV_DISK;
        char *name = iodev->is_name;

        if (iodev->is_pretty)
                name = iodev->is_pretty;

        switch (iodev->is_type) {
        case IODEV_IOPATH_LT:
        case IODEV_IOPATH_LI:
        case IODEV_IOPATH_LTI:
                (void) printf("<<multi-path %s: %s>>\n",
                    added ? "added" : "removed", name);
                break;
        case IODEV_PARTITION:
                (void) printf("<<partition %s: %s>>\n",
                    added ? "added" : "removed", name);
                break;
        case IODEV_NFS:
                (void) printf("<<NFS %s: %s>>\n",
                    added ? "mounted" : "unmounted", name);
                break;
        case IODEV_TAPE:
                (void) printf("<<device %s: %s>>\n",
                    added ? "added" : "removed", name);
                break;
        case IODEV_CONTROLLER:
        case IODEV_DISK:
                *tmp = '\0';
                get_child_list(iodev, tmp);
                (void) printf("<<%s %s: %s", is_disk ? "disk" : "controller",
                    added ? "added" : "removed", name);
                if (!*tmp) {
                        (void) printf(">>\n");
                        return;
                }
                (void) printf(" (%s %s)>>\n", is_disk ? "slices" : "disks",
                    tmp);
                break;
        };
}

static void
iodev_report(struct iodev_snapshot *d1, struct iodev_snapshot *d2)
{
        while (d1 && d2) {
                if (iodev_cmp(d1, d2) < 0) {
                        iodev_changed(d1, 0);
                        d1 = d1->is_next;
                } else if (iodev_cmp(d1, d2) > 0) {
                        iodev_changed(d2, 1);
                        d2 = d2->is_next;
                } else {
                        iodev_report(d1->is_children, d2->is_children);
                        d1 = d1->is_next;
                        d2 = d2->is_next;
                }
        }

        while (d1) {
                iodev_changed(d1, 0);
                d1 = d1->is_next;
        }

        while (d2) {
                iodev_changed(d2, 1);
                d2 = d2->is_next;
        }
}

void
snapshot_report_changes(struct snapshot *old, struct snapshot *new)
{
        int pset;

        if (old == NULL || new == NULL)
                return;

        if (old->s_types != new->s_types)
                return;

        pset = old->s_types & SNAP_PSETS;

        cpus_removed[0] = '\0';
        cpus_added[0] = '\0';

        if (old->s_types & SNAP_CPUS)
                (void) snapshot_walk(SNAP_CPUS, old, new, cpu_report, &pset);

        if (cpus_added[0]) {
                (void) printf("<<processors added: %s>>\n",
                    cpus_added);
        }
        if (cpus_removed[0]) {
                (void) printf("<<processors removed: %s>>\n",
                    cpus_removed);
        }
        if (pset) {
                (void) snapshot_walk(SNAP_PSETS, old, new,
                    pset_report, NULL);
        }

        iodev_report(old->s_iodevs, new->s_iodevs);
}

/*ARGSUSED*/
static void
dummy_cb(void *v1, void *v2, void *data)
{
}

int
snapshot_has_changed(struct snapshot *old, struct snapshot *new)
{
        int ret = 0;
        int cpu_mask = SNAP_CPUS | SNAP_PSETS | SNAP_SYSTEM;
        int iodev_mask = SNAP_CONTROLLERS | SNAP_IODEVS |
                        SNAP_IOPATHS_LI | SNAP_IOPATHS_LTI;

        if (old == NULL)
                return (1);

        if (new == NULL)
                return (EINVAL);

        if (old->s_types != new->s_types)
                return (EINVAL);

        if (!ret && (old->s_types & cpu_mask))
                ret = snapshot_walk(SNAP_CPUS, old, new, dummy_cb, NULL);
        if (!ret && (old->s_types & SNAP_PSETS))
                ret = snapshot_walk(SNAP_PSETS, old, new, dummy_cb, NULL);
        if (!ret && (old->s_types & iodev_mask))
                ret = snapshot_walk(SNAP_IODEVS, old, new, dummy_cb, NULL);

        return (ret);
}