/* * 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. */ /* * sa_kstat - kstat statistic adapter, collects statistic data provided * by kstat. */ #include #include #include #include #include #include "utils.h" #include #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; } }