/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (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 2004 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* sa_kstat - kstat statistic adapter, collects statistic data provided
* by kstat.
*/
#include <locale.h>
#include <string.h>
#include <assert.h>
#include <kstat.h>
#include <pool.h>
#include "utils.h"
#include <sys/pset.h>
#include "poolstat.h"
#include "poolstat_utils.h"
/* marks 'sdata_t' element as updated */
#define SD_UPDATED 1
/* Specified value for an invalid set. */
#define INVALID_SET -2
/* statistic data */
typedef struct sdata {
kstat_t sd_oks; /* old kstat */
kstat_t sd_nks; /* new kstat */
void *sd_udata; /* user data */
uint_t sd_state; /* state of this data (UPDATED) */
struct sdata *sd_next;
} sdata_t;
/* pset user data */
typedef struct {
psetid_t opset; /* old pset sysid */
psetid_t npset; /* new pset sysid */
} pset_ud_t;
/* shortcuts to access set's id in 'pset_ud_t' */
#define SD_OPSET(p) (((pset_ud_t *)(p)->sd_udata)->opset)
#define SD_NPSET(p) (((pset_ud_t *)(p)->sd_udata)->npset)
static kstat_ctl_t *ks_ctl; /* libkstat handle */
static sdata_t *cpu_list; /* list with cpu statistics */
static sdata_t *update_sdata_list(sdata_t *, kstat_ctl_t *, char *, int,
char *, int *);
static void update_cpu_list(sdata_t *list);
static void update_pset_stats(statistic_bag_t *, sdata_t *);
/*ARGSUSED*/
void
sa_kstat_init(void *unused)
{
if ((ks_ctl = kstat_open()) == NULL)
die(gettext(ERR_KSTAT_OPEN), get_errstr());
}
void
sa_kstat_update(statistic_bag_t *sbag, int flags)
{
/* The SA_REFRESH flag forces the update of local data structures. */
if (flags & SA_REFRESH) {
int ks_error = 0;
if (kstat_chain_update(ks_ctl) == -1)
die(gettext(ERR_KSTAT_DATA), get_errstr());
cpu_list = update_sdata_list(cpu_list, ks_ctl, "cpu",
-1, "sys", &ks_error);
if (ks_error)
die(gettext(ERR_KSTAT_DATA), get_errstr());
/* update info about cpu binding to processor sets */
update_cpu_list(cpu_list);
}
if (strcmp(sbag->sb_type, PSET_TYPE_NAME) == 0) {
update_pset_stats(sbag, cpu_list);
} else if (strcmp(sbag->sb_type, POOL_TYPE_NAME) == 0) {
return;
} else {
die(gettext(ERR_UNSUPP_STYPE), sbag->sb_type);
}
}
static void *
safe_kstat_data_lookup(kstat_t *ksp, char *name)
{
void *dp;
if ((dp = kstat_data_lookup(ksp, name)) == NULL)
die(gettext(ERR_KSTAT_DLOOKUP),
ksp->ks_name, name, get_errstr());
return (dp);
}
/*
* Find the delta over the interval between new_ksp and old_ksp.
* If old_ksp->ks_data is NULL and 'oz' is set then pretend
* that old_ksp is zero otherwise return 0.
*/
static uint64_t
delta(kstat_t *new_ksp, kstat_t *old_ksp, char *name, int oz)
{
kstat_named_t *new_ksn;
kstat_named_t *old_ksn;
new_ksn = (kstat_named_t *)safe_kstat_data_lookup(new_ksp, name);
if (old_ksp == NULL || old_ksp->ks_data == NULL)
return ((oz == 1) ? new_ksn->value.ui64 : 0);
old_ksn = (kstat_named_t *)safe_kstat_data_lookup(old_ksp, name);
return (new_ksn->value.ui64 - old_ksn->value.ui64);
}
/*
* Create a clone of the passed kstat_t structure 'kstat_t'. If
* 'fr' flag is set free the old ks_data structure in 'dst'.
*/
static void
kstat_clone(kstat_t *src, kstat_t *dst, int fr)
{
if (fr)
FREE(dst->ks_data);
*dst = *src;
if (src->ks_data != NULL) {
dst->ks_data = ZALLOC(src->ks_data_size);
(void) memcpy(dst->ks_data, src->ks_data, src->ks_data_size);
} else {
dst->ks_data = NULL;
dst->ks_data_size = 0;
}
}
/*
* Erase the data from 'src'.
*/
static void
kstat_erase(kstat_t *src)
{
FREE(src->ks_data);
(void) memset(src, 0, sizeof (*src));
}
/*
* Create a new statistic data object with its own copy of the passed
* kstat.
*/
static sdata_t *
sdata_new(kstat_t *ksp)
{
sdata_t *sdp;
NEW0(sdp);
kstat_clone(ksp, &sdp->sd_nks, 0);
return (sdp);
}
static void
sdata_free(sdata_t *sdp)
{
FREE(sdp->sd_oks.ks_data);
FREE(sdp->sd_nks.ks_data);
FREE(sdp->sd_udata);
FREE(sdp);
}
/*
* Create new or update an existing list of cpu statistics. For each
* cpu two kstats are kept. One old kstat which contains the data from
* the previous scan, and new with the current data. The old and the new
* kstats *must* be for the same instance and have the same kid.
* If 'instance' argument is set to -1 don't use it as a filter.
*/
static sdata_t *
update_sdata_list(sdata_t *list, kstat_ctl_t *kc, char *module,
int instance, char *name, int *errp)
{
kstat_t *ksp;
sdata_t *sdp, *sdpp; /* kstat instance pointer/previous-pointer */
for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) {
if (strcmp(ksp->ks_module, module) == 0 &&
(name == NULL || strcmp(ksp->ks_name, name) == 0) &&
(instance == -1 || ksp->ks_instance == instance)) {
if (kstat_read(kc, ksp, NULL) == -1) {
*errp = -1;
return (list);
}
/*
* Find the kstat in the existing list:
* If we find one for the same instance and with the
* same ks_kid we'll save it as old_kstat.
* If we find one for the same instance but with a
* different ks_kid we'll removed it.
*/
for (sdpp = sdp = list; sdp; sdp = sdp->sd_next) {
if (ksp->ks_instance ==
sdp->sd_nks.ks_instance) {
if (ksp->ks_kid == sdp->sd_nks.ks_kid) {
kstat_clone(&sdp->sd_nks,
&sdp->sd_oks, 1);
} else {
kstat_erase(&sdp->sd_oks);
}
kstat_clone(ksp, &sdp->sd_nks, 1);
sdp->sd_state |= SD_UPDATED;
break;
}
sdpp = sdp;
}
/* add a new kstat instance */
if (!sdp) {
/* first instance */
if (!list) {
list = sdata_new(ksp);
list->sd_state |= SD_UPDATED;
} else {
sdpp->sd_next = sdata_new(ksp);
sdpp->sd_next->sd_state |= SD_UPDATED;
}
}
}
}
/* remove untouched statistics */
sdp = list;
sdpp = NULL;
while (sdp != NULL) {
if (sdp->sd_state & SD_UPDATED) {
sdp->sd_state &= ~SD_UPDATED;
sdpp = sdp;
sdp = sdp->sd_next;
} else {
sdata_t *tmp;
if (sdpp == NULL)
list = sdp->sd_next;
else
sdpp->sd_next = sdp->sd_next;
tmp = sdp->sd_next;
sdata_free(sdp);
sdp = tmp;
}
}
*errp = 0;
return (list);
}
/*
* Update the pset assignment information for each cpu in the statistic
* data list.
*/
static void
update_cpu_list(sdata_t *list)
{
sdata_t *sdp;
for (sdp = list; sdp; sdp = sdp->sd_next) {
/* for new CPU create a new user data object */
if (sdp->sd_udata == NULL) {
sdp->sd_udata = ZALLOC(sizeof (pset_ud_t));
/*
* set its pset to invalid, so it will not be
* used in statistics calculation.
*/
SD_NPSET(sdp) = INVALID_SET;
}
/* copy the pset assignment information to the previous stat */
SD_OPSET(sdp) = SD_NPSET(sdp);
/* set the current assignment */
if (pset_assign(PS_QUERY, sdp->sd_nks.ks_instance,
&(SD_NPSET(sdp))) == -1)
SD_NPSET(sdp) = INVALID_SET;
}
}
/*
* Update statistic data for pset. Calculate the CPU usage in a pset.
*/
static void
update_pset_stats(statistic_bag_t *sbag, sdata_t *list)
{
sdata_t *sdp;
pset_statistic_bag_t *bag = (pset_statistic_bag_t *)sbag->bag;
uint64_t allticks, ust, kst, ist, wst;
ust = kst = ist = wst = 0;
for (sdp = list; sdp; sdp = sdp->sd_next) {
/*
* only calculate for the asked pset id and if the cpu belongs
* to the same set in the previous and in the current snapshot.
* It means that the usage for CPUs that were rebound during
* the sampling interval are not charged to any set.
*/
if ((SD_OPSET(sdp) == SD_NPSET(sdp)) &&
(SD_NPSET(sdp) == bag->pset_sb_sysid)) {
ust += delta(&sdp->sd_nks, &sdp->sd_oks,
"cpu_ticks_user", 0);
kst += delta(&sdp->sd_nks, &sdp->sd_oks,
"cpu_ticks_kernel", 0);
ist += delta(&sdp->sd_nks, &sdp->sd_oks,
"cpu_ticks_idle", 0);
wst += delta(&sdp->sd_nks, &sdp->sd_oks,
"cpu_ticks_wait", 0);
}
}
if ((allticks = ust + kst + wst + ist) != 0) {
bag->pset_sb_used =
(double)(ust + kst) / allticks * bag->pset_sb_size;
} else {
bag->pset_sb_used = 0.0;
}
}