/*
* 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, " ");
}