OpenSolaris_b135/cmd/avs/dsstat/sdbc_stats.c

Compare this file to the similar file:
Show the results in this format:

/*
 * 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 2010 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <inttypes.h>

#include <kstat.h>

#include <sys/nsctl/nsctl.h>
#include <sys/nsctl/sd_bcache.h>

#include "sdbc_stats.h"

#include "dsstat.h"
#include "common.h"
#include "report.h"

static sdbcstat_t *sdbc_top;
kstat_t *sdbc_global = NULL;

void sdbc_header();
int sdbc_value_check(sdbcstat_t *);
int sdbc_validate(kstat_t *);
uint32_t sdbc_getdelta(sdbcstat_t *, char *);

void sdbc_addstat(sdbcstat_t *);
sdbcstat_t *sdbc_delstat(sdbcstat_t *);
void center(int, char *);

/*
 * sdbc_discover() - looks for new statistics to be monitored.
 * Verifies that any statistics found are now already being
 * monitored.
 *
 */
int
sdbc_discover(kstat_ctl_t *kc)
{
	static int validated = 0;

	kstat_t *ksp;

	for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) {
		int kinst;
		char kname[KSTAT_STRLEN + 1];
		sdbcstat_t *cur;
		sdbcstat_t *sdbcstat = NULL;
		kstat_t *io_ksp;

		if (strcmp(ksp->ks_module, SDBC_KSTAT_MODULE) != 0 ||
		    strncmp(ksp->ks_name, SDBC_KSTAT_CDSTATS, 2) != 0)
			continue;

		if (kstat_read(kc, ksp, NULL) == -1)
			continue;

		/*
		 * Validate kstat structure
		 */
		if (! validated) {
			if (sdbc_validate(ksp))
				return (EINVAL);

			validated++;
		}

		/*
		 * Duplicate check
		 */
		for (cur = sdbc_top; cur; cur = cur->next) {
			char *cur_vname, *tst_vname;

			cur_vname = kstat_value(cur->pre_set,
			    SDBC_CDKSTAT_VOL_NAME);

			tst_vname = kstat_value(ksp,
			    SDBC_CDKSTAT_VOL_NAME);

			if (strncmp(cur_vname, tst_vname, NAMED_LEN) == 0)
				goto next;
		}

		/*
		 * Initialize new record
		 */
		sdbcstat = (sdbcstat_t *)calloc(1, sizeof (sdbcstat_t));

		kinst = ksp->ks_instance;

		/*
		 * Set kstat
		 */
		sdbcstat->pre_set = kstat_retrieve(kc, ksp);

		if (sdbcstat->pre_set == NULL)
			goto next;

		sdbcstat->collected |= GOT_SET_KSTAT;

		/*
		 * I/O kstat
		 */
		(void) sprintf(kname, "%s%d",  SDBC_IOKSTAT_CDSTATS, kinst);

		io_ksp = kstat_lookup(kc, SDBC_KSTAT_MODULE, kinst, kname);
		sdbcstat->pre_io = kstat_retrieve(kc, io_ksp);

		if (sdbcstat->pre_io == NULL)
			goto next;

		sdbcstat->collected |= GOT_IO_KSTAT;

next:
		/*
		 * Check if we got a complete set of stats
		 */
		if (sdbcstat == NULL)
			continue;

		if (SDBC_COMPLETE(sdbcstat->collected)) {
			(void) sdbc_delstat(sdbcstat);
			continue;
		}

		sdbc_addstat(sdbcstat);
	}

	if (sdbc_top == NULL)
		return (EAGAIN);

	return (0);
}

/*
 * sdbc_update() - updates all of the statistics currently being monitored.
 *
 */
int
sdbc_update(kstat_ctl_t *kc)
{
	kstat_t *ksp;
	sdbcstat_t *cur;

	/* Update global kstat information */
	ksp = kstat_lookup(kc, SDBC_KSTAT_MODULE, -1, SDBC_KSTAT_GSTATS);

	if (ksp == NULL)
		return (EAGAIN);

	if (sdbc_global)
		kstat_free(sdbc_global);

	sdbc_global = kstat_retrieve(kc, ksp);

	for (cur = sdbc_top; cur != NULL; cur = cur->next) {
		int kinst;
		char *kname, *cname, *pname;

		kstat_t *set_ksp, *io_ksp;

		cur->collected = 0;

		/*
		 * Age off old stats
		 */
		if (cur->cur_set != NULL) {
			kstat_free(cur->pre_set);
			kstat_free(cur->pre_io);

			cur->pre_set = cur->cur_set;
			cur->pre_io = cur->cur_io;
		}

		/*
		 * Update set kstat
		 */
		kinst = cur->pre_set->ks_instance;
		kname = cur->pre_set->ks_name;

		set_ksp = kstat_lookup(kc, SDBC_KSTAT_MODULE, kinst, kname);

		if ((cur->cur_set = kstat_retrieve(kc, set_ksp)) == NULL)
			continue;

		cur->collected |= GOT_SET_KSTAT;

		/*
		 * Validate set
		 */
		pname = kstat_value(cur->pre_set, SDBC_CDKSTAT_VOL_NAME);
		cname = kstat_value(cur->cur_set, SDBC_CDKSTAT_VOL_NAME);

		if (strncmp(pname, cname, NAMED_LEN) != 0)
			continue;

		/*
		 * Update I/O kstat
		 */
		kinst = cur->pre_io->ks_instance;
		kname = cur->pre_io->ks_name;

		io_ksp = kstat_lookup(kc, SDBC_KSTAT_MODULE, kinst, kname);

		if ((cur->cur_io = kstat_retrieve(kc, io_ksp)) == NULL)
			continue;

		cur->collected |= GOT_IO_KSTAT;
	}

	return (0);
}

/*
 * sdbc_report() - outputs statistics for the statistics currently being
 * monitored.  Deletes statistics for volumes that have been disabled.
 *
 */
int
sdbc_report()
{
	vslist_t *vslist = vs_top;
	sdbcstat_t *cur, *pre = NULL;

	if (sdbc_top == NULL)
		return (0);

	for (cur = sdbc_top; cur != NULL; ) { /* CSTYLED */
		static uint32_t linesout = 0;
		uint32_t *offline;

		char volname[NAMED_LEN + 1];
		char rmode[STAT_HDR_SIZE];
		char wmode[STAT_HDR_SIZE];

		/* Parse volume name */
		(void) strncpy(volname, kstat_value(cur->pre_set,
		    SDBC_CDKSTAT_VOL_NAME), NAMED_LEN);
		volname[NAMED_LEN] = '\0';

		/* Check to see if the user specified this volume */
		for (vslist = vs_top; vslist != NULL; vslist = vslist->next)
			if (strcmp(volname, vslist->volname) == 0)
				break;

		if (vs_top != NULL && vslist == NULL)
			goto next;

		/* Check if volume is offline and zflag applies */
		if (zflag && sdbc_value_check(cur) == 0)
			goto next;

		/* Output volume name */
		sdbc_header();

		(void) printf(DATA_C16, volname);

		if (SDBC_COMPLETE(cur->collected)) {
			sdbcstat_t *next = sdbc_delstat(cur);

			if (! pre)
				cur = sdbc_top = next;
			else
				cur = pre->next = next;

			(void) printf(" <<volume disabled>>\n");
			continue;
		}

		offline = kstat_value(cur->cur_set, SDBC_CDKSTAT_FAILED);
		if (*offline) {
			(void) printf(" <<volume offline>>\n");
			linesout++;
			goto next;
		}

		/* Type/status flags */
		if (dflags & FLAGS) {

			uint32_t *dhint, *nhint;
			uint32_t hints;

			dhint = kstat_value(cur->cur_set, SDBC_CDKSTAT_CDHINTS);
			nhint = kstat_value(sdbc_global, SDBC_GKSTAT_NODEHINTS);

			if (! nhint)
				return (EINVAL);

			hints = *nhint;
			hints &= (NSC_FORCED_WRTHRU | NSC_NO_FORCED_WRTHRU |
			    NSC_NOCACHE);
			hints |= *dhint;

			if (hints & NSC_NOCACHE)
				(void) strcpy(rmode, "D");
			else
				(void) strcpy(rmode, "C");

			if ((hints & NSC_FORCED_WRTHRU) || (hints & NSC_WRTHRU))
				(void) strcpy(wmode, "D");
			else
				(void) strcpy(wmode, "C");

			(void) printf(DATA_C2, rmode);
			(void) printf(DATA_C2, wmode);
		}

		/* Output set information */
		cd_report(cur);

next:
		pre = cur;
		cur = cur->next;
	}

	return (0);
}

/*
 * sdbc_header() - outputs an appropriate header by referencing the
 * global variables dflsgs
 *
 */
void
sdbc_header()
{
	int rcount = 0;

	if (hflags == HEADERS_EXL)
		if ((linesout % DISPLAY_LINES) != 0)
			return;

	if (hflags == HEADERS_BOR)
		if (linesout != 0)
			return;

	if (hflags & HEADERS_ATT)
		if (hflags & HEADERS_OUT)
			return;
		else
			hflags |= HEADERS_OUT;

	if (linesout)
		(void) printf("\n");

	/* first line header */
	if (! (dflags & SUMMARY) && dflags != FLAGS) {

		(void) printf(VOL_HDR_FMT, " ");

		if (dflags & FLAGS) {
			(void) printf(STAT_HDR_FMT, " ");
			(void) printf(STAT_HDR_FMT, " ");
		}

		if (dflags & READ) {
			int size;

			size = KPS_HDR_SIZE * 2 + HIT_HDR_SIZE;
			center(size, "- read -");
			rcount++;
		}

		if (dflags & WRITE) {
			int size;

			size = KPS_HDR_SIZE * 2 + HIT_HDR_SIZE;
			center(size, "- write -");
			rcount++;
		}

		if (dflags != FLAGS)
			(void) printf("\n");
	}

	/* second line header */
	(void) printf(VOL_HDR_FMT, "volume");

	if (dflags & FLAGS) {
		(void) printf(STAT_HDR_FMT, "rd");
		(void) printf(STAT_HDR_FMT, "wr");
	}

	if (dflags & SUMMARY) {
		(void) printf(KPS_HDR_FMT, "ckps");
		(void) printf(KPS_HDR_FMT, "dkps");
		(void) printf(HIT_HDR_FMT, HIT_HDR_TXT);

		goto out;
	}

	if (dflags & READ) {
		(void) printf(KPS_HDR_FMT, "ckps");
		(void) printf(KPS_HDR_FMT, "dkps");
		(void) printf(HIT_HDR_FMT, RHIT_HDR_TXT);
	}

	if (dflags & WRITE) {
		(void) printf(KPS_HDR_FMT, "ckps");
		(void) printf(KPS_HDR_FMT, "dkps");
		(void) printf(HIT_HDR_FMT, WHIT_HDR_TXT);
	}

	if (dflags & DESTAGED)
		(void) printf(KPS_HDR_FMT, "dstg");

	if (dflags & WRCANCEL)
		(void) printf(KPS_HDR_FMT, "cwrl");

out:
	(void) printf("\n");
}

/*
 * sdbc_getstat() - find cache stat by name matching
 *
 * paraemters
 * 	char *vn - the volume name to match against
 * returns
 * 	sdbcstat_t * - the matching strcture, NULL if not found
 */
sdbcstat_t *
sdbc_getstat(char *vn)
{
	sdbcstat_t *cur, *pre = NULL;

	for (cur = sdbc_top; cur; ) { /* CSTYLED */
		char *volname =
		    kstat_value(cur->pre_set, SDBC_CDKSTAT_VOL_NAME);

		if (SDBC_COMPLETE(cur->collected)) {
			sdbcstat_t *next = sdbc_delstat(cur);

			if (! pre)
				cur = sdbc_top = next;
			else
				cur = pre->next = next;

			continue;
		}

		if (strncmp(volname, vn, NAMED_LEN) == 0)
			return (cur);

		pre = cur;
		cur = cur->next;
	}

	return (NULL);
}

/*
 * sdbc_addstat() - adds a fully populated sdbcstat_t structure
 * to the linked list of currently monitored kstats.  The structure
 * will be added in alphabetical order, using the volume name as the
 * key.
 *
 * parameters
 * 	sdbcstat_t *sdbcstat - to be added to the list.
 *
 */
void
sdbc_addstat(sdbcstat_t *sdbcstat)
{
	sdbcstat_t *cur;

	if (sdbc_top == NULL) {
		sdbc_top = sdbcstat;
		return;
	}

	for (cur = sdbc_top; cur != NULL; cur = cur->next) {
		char *cur_vname, *nxt_vname, *tst_vname;

		cur_vname = kstat_value(cur->pre_set,
		    SDBC_CDKSTAT_VOL_NAME);
		tst_vname = kstat_value(sdbcstat->pre_set,
		    SDBC_CDKSTAT_VOL_NAME);

		if (strncmp(cur_vname, tst_vname, NAMED_LEN) > 0) {
			if (cur == sdbc_top)
				sdbc_top = sdbcstat;

			sdbcstat->next = cur;

			return;
		}

		/*
		 * If we get to the last item in the list, then just
		 * add this one to the end
		 */
		if (cur->next == NULL) {
			cur->next = sdbcstat;
			return;
		}

		nxt_vname = kstat_value(cur->next->pre_set,
		    SDBC_CDKSTAT_VOL_NAME);

		if (strncmp(nxt_vname, tst_vname, NAMED_LEN) > 0) {
			sdbcstat->next = cur->next;
			cur->next = sdbcstat;
			return;
		}
	}
}

/*
 * sdbc_delstat() - deallocate memory for the structure being
 * passed in.
 *
 * parameters
 * 	sdbcstat_t *sdbcstat - structure to be deallocated
 *
 * returns
 * 	sdbcstat_t * - pointer to the "next" structures in the
 * 	linked list. May be NULL if we are removing the last
 * 	structure in the linked list.
 */
sdbcstat_t *
sdbc_delstat(sdbcstat_t *sdbcstat)
{

	sdbcstat_t *next = sdbcstat->next;

	kstat_free(sdbcstat->pre_set);
	kstat_free(sdbcstat->pre_io);
	kstat_free(sdbcstat->cur_set);
	kstat_free(sdbcstat->cur_io);

	free(sdbcstat);
	sdbcstat = NULL;

	return (next);
}

/*
 * sdbc_value_check() - Checks for activity, supports -z switch
 *
 * parameters
 * 	sdbcstat_t *sdbcstat - structure to be checked
 *
 * returns
 * 	1 - activity
 * 	0 - no activity
 */
int
sdbc_value_check(sdbcstat_t *sdbcstat)
{
	if (SDBC_COMPLETE(sdbcstat->collected))
		return (1);

	if (sdbc_getdelta(sdbcstat, SDBC_CDKSTAT_CACHE_READ) != 0)
		return (1);

	if (sdbc_getdelta(sdbcstat, SDBC_CDKSTAT_DISK_READ) != 0)
		return (1);

	if (sdbc_getdelta(sdbcstat, SDBC_CDKSTAT_CACHE_WRITE) != 0)
		return (1);

	if (sdbc_getdelta(sdbcstat, SDBC_CDKSTAT_DISK_WRITE) != 0)
		return (1);

	if (sdbc_getdelta(sdbcstat, SDBC_CDKSTAT_WRCANCELNS) != 0)
		return (1);

	if (io_value_check(sdbcstat->pre_io->ks_data,
	    sdbcstat->cur_io->ks_data) != 0)
		return (1);

	return (0);
}

/*
 * sdbc_validate() - validates the structure of the kstats by attempting to
 *                   lookup fields used by this module
 *
 * parameters
 *	kstat_t *ksp - kstat to be examined
 *
 * returns
 * 	1 - one or more fields missing
 * 	0 - all fields present
 */
int
sdbc_validate(kstat_t *ksp)
{
	if (! kstat_value(ksp, SDBC_CDKSTAT_VOL_NAME) ||
	    ! kstat_value(ksp, SDBC_CDKSTAT_FAILED) ||
	    ! kstat_value(ksp, SDBC_CDKSTAT_CDHINTS) ||
	    ! kstat_value(ksp, SDBC_CDKSTAT_CACHE_READ) ||
	    ! kstat_value(ksp, SDBC_CDKSTAT_DISK_READ) ||
	    ! kstat_value(ksp, SDBC_CDKSTAT_CACHE_WRITE) ||
	    ! kstat_value(ksp, SDBC_CDKSTAT_DISK_WRITE) ||
	    ! kstat_value(ksp, SDBC_CDKSTAT_DESTAGED) ||
	    ! kstat_value(ksp, SDBC_CDKSTAT_WRCANCELNS))
		return (1);

	return (0);
}

/*
 * sdbc_getvalues() - populates a values structure with data obtained from the
 *                    kstat
 *
 * parameters
 * 	sdbcstat_t *sdbcstat - pointer to the structure containing the kstats
 * 	sdbcvals_t *vals - pointer to the structure that will receive the values
 * 	int flags - flags that describe adjustments made to the values
 *
 * returns
 * 	1 - failure
 * 	0 - success
 */
int
sdbc_getvalues(sdbcstat_t *sdbcstat, sdbcvals_t *vals, int flags)
{
	int divisor = 0;
	int factors;
	uint64_t hr_etime;
	double etime;

	kstat_io_t *cur;
	kstat_io_t *pre;

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

	cur = sdbcstat->cur_io->ks_data;
	pre = sdbcstat->pre_io->ks_data;

	hr_etime = hrtime_delta(pre->rlastupdate, cur->rlastupdate);
	etime = hr_etime / (double)NANOSEC;

	/* read data */
	vals->cache_read =
	    FBA_SIZE(sdbc_getdelta(sdbcstat, SDBC_CDKSTAT_CACHE_READ));
	vals->disk_read =
	    FBA_SIZE(sdbc_getdelta(sdbcstat, SDBC_CDKSTAT_DISK_READ));


	vals->total_reads = vals->cache_read + vals->disk_read;

	if (vals->cache_read == 0)
		vals->read_hit = 0.0;
	else
		vals->read_hit =
		    ((float)vals->cache_read / vals->total_reads) * 100.0;

	/* write data */
	vals->cache_write =
	    FBA_SIZE(sdbc_getdelta(sdbcstat, SDBC_CDKSTAT_CACHE_WRITE));
	vals->disk_write =
	    FBA_SIZE(sdbc_getdelta(sdbcstat, SDBC_CDKSTAT_DISK_WRITE));

	vals->total_writes = vals->cache_write + vals->disk_write;

	vals->destaged =
	    FBA_SIZE(sdbc_getdelta(sdbcstat, SDBC_CDKSTAT_DESTAGED));

	if (vals->cache_write == 0)
		vals->write_hit = 0.0;
	else
		vals->write_hit = ((float)vals->cache_write /
		    (vals->total_writes - vals->destaged)) * 100.0;

	/* miscellaneous */
	vals->write_cancellations =
	    FBA_SIZE(sdbc_getdelta(sdbcstat, SDBC_CDKSTAT_WRCANCELNS));

	vals->total_cache = vals->cache_read + vals->cache_write;
	vals->total_disk = vals->disk_read + vals->disk_write;

	/* total cache hit calculation */
	vals->cache_hit = 0;
	factors = 0;

	if (vals->cache_read != 0) {
		vals->cache_hit += vals->read_hit;
		factors++;
	}

	if (vals->cache_write != 0) {
		vals->cache_hit += vals->write_hit;
		factors++;
	}

	if (vals->cache_hit)
		vals->cache_hit /= (float)factors;

	/* adjustments */
	divisor = 1;

	if (flags & SDBC_KBYTES)
		divisor *= KILOBYTE;
	if ((flags & SDBC_INTAVG) && (etime > 0))
		divisor *= etime;

	if (divisor != 1) {
		vals->cache_read /= divisor;
		vals->disk_read /= divisor;
		vals->total_reads /= divisor;

		vals->cache_write /= divisor;
		vals->disk_write /= divisor;
		vals->total_writes /= divisor;

		vals->total_cache /= divisor;
		vals->total_disk /= divisor;

		vals->destaged /= divisor;
		vals->write_cancellations /= divisor;
	}

	return (0);
}

/*
 * sdbc_getdelta() - calculates the difference between two kstat fields
 *
 * parameters
 * 	sdbcstat_t *sdbcstat - the SDBC stat strcture containing the two fields
 * 	char *name - the name of the fields
 * returns
 * 	uint32_t value of the differences adjusted for overflow of the data type
 */
uint32_t
sdbc_getdelta(sdbcstat_t *sdbcstat, char *name)
{
	uint32_t *cur_val;
	uint32_t *pre_val;

	pre_val = kstat_value(sdbcstat->pre_set, name);
	cur_val = kstat_value(sdbcstat->cur_set, name);

	return (u32_delta(*pre_val, *cur_val));
}

void
center(int size, char *hdr)
{
	int lpad = 0;
	int rpad = 0;
	char fmt[10];

	if (size == 0)
		return;

	if (strlen(hdr) < size) {
		lpad = (size - strlen(hdr)) / 2;

		if (lpad * 2 < size)
			lpad++;

		rpad = size - (lpad + strlen(hdr));
	}

output:
	(void) sprintf(fmt, "%%%ds%%s%%%ds", lpad, rpad);
	(void) printf(fmt, " ", hdr, " ");
}