diff options
-rw-r--r-- | Kstat.pm | 38 | ||||
-rw-r--r-- | Kstat.xs | 1690 | ||||
-rw-r--r-- | META.yml | 20 | ||||
-rw-r--r-- | Makefile.PL | 60 | ||||
-rw-r--r-- | t/Kstat.t | 177 |
5 files changed, 1985 insertions, 0 deletions
diff --git a/Kstat.pm b/Kstat.pm new file mode 100644 index 0000000..b1ae5db --- /dev/null +++ b/Kstat.pm @@ -0,0 +1,38 @@ +# +# 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. +# + +# +# Kstat.pm provides the bootstrap for the Sun::Solaris::Kstat module. +# + +package Sun::Solaris::Kstat; +use strict; +use DynaLoader; +use vars qw($VERSION @ISA); +$VERSION = '1.3'; +@ISA = qw(DynaLoader); +bootstrap Sun::Solaris::Kstat $VERSION; +1; diff --git a/Kstat.xs b/Kstat.xs new file mode 100644 index 0000000..403c95f --- /dev/null +++ b/Kstat.xs @@ -0,0 +1,1690 @@ +/* + * 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 (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved. + */ + +/* + * Kstat.xs is a Perl XS (eXStension module) that makes the Solaris + * kstat(3KSTAT) facility available to Perl scripts. Kstat is a general-purpose + * mechanism for providing kernel statistics to users. The Solaris API is + * function-based (see the manpage for details), but for ease of use in Perl + * scripts this module presents the information as a nested hash data structure. + * It would be too inefficient to read every kstat in the system, so this module + * uses the Perl TIEHASH mechanism to implement a read-on-demand semantic, which + * only reads and updates kstats as and when they are actually accessed. + */ + +/* + * Ignored raw kstats. + * + * Some raw kstats are ignored by this module, these are listed below. The + * most common reason is that the kstats are stored as arrays and the ks_ndata + * and/or ks_data_size fields are invalid. In this case it is impossible to + * know how many records are in the array, so they can't be read. + * + * unix:*:sfmmu_percpu_stat + * This is stored as an array with one entry per cpu. Each element is of type + * struct sfmmu_percpu_stat. The ks_ndata and ks_data_size fields are bogus. + * + * ufs directio:*:UFS DirectIO Stats + * The structure definition used for these kstats (ufs_directio_kstats) is in a + * C file (uts/common/fs/ufs/ufs_directio.c) rather than a header file, so it + * isn't accessible. + * + * qlc:*:statistics + * This is a third-party driver for which we don't have source. + * + * mm:*:phys_installed + * This is stored as an array of uint64_t, with each pair of values being the + * (address, size) of a memory segment. The ks_ndata and ks_data_size fields + * are both zero. + * + * sockfs:*:sock_unix_list + * This is stored as an array with one entry per active socket. Each element + * is of type struct k_sockinfo. The ks_ndata and ks_data_size fields are both + * zero. + * + * Note that the ks_ndata and ks_data_size of many non-array raw kstats are + * also incorrect. The relevant assertions are therefore commented out in the + * appropriate raw kstat read routines. + */ + +/* Kstat related includes */ +#include <libgen.h> +#include <kstat.h> +#include <sys/var.h> +#include <sys/utsname.h> +#include <sys/sysinfo.h> +#include <sys/flock.h> +#include <sys/dnlc.h> +#include <nfs/nfs.h> +#include <nfs/nfs_clnt.h> + +/* Ultra-specific kstat includes */ +#ifdef __sparc +#include <vm/hat_sfmmu.h> /* from /usr/platform/sun4u/include */ +#include <sys/simmstat.h> /* from /usr/platform/sun4u/include */ +#include <sys/sysctrl.h> /* from /usr/platform/sun4u/include */ +#include <sys/fhc.h> /* from /usr/include */ +#endif + +/* + * Solaris #defines SP, which conflicts with the perl definition of SP + * We don't need the Solaris one, so get rid of it to avoid warnings + */ +#undef SP + +/* Perl XS includes */ +#include "EXTERN.h" +#include "perl.h" +#include "XSUB.h" + +/* Debug macros */ +#define DEBUG_ID "Sun::Solaris::Kstat" +#ifdef KSTAT_DEBUG +#define PERL_ASSERT(EXP) \ + ((void)((EXP) || (croak("%s: assertion failed at %s:%d: %s", \ + DEBUG_ID, __FILE__, __LINE__, #EXP), 0), 0)) +#define PERL_ASSERTMSG(EXP, MSG) \ + ((void)((EXP) || (croak(DEBUG_ID ": " MSG), 0), 0)) +#else +#define PERL_ASSERT(EXP) ((void)0) +#define PERL_ASSERTMSG(EXP, MSG) ((void)0) +#endif + +/* Macros for saving the contents of KSTAT_RAW structures */ +#if defined(HAS_QUAD) && defined(USE_64_BIT_INT) +#define NEW_IV(V) \ + (newSViv((IVTYPE) V)) +#define NEW_UV(V) \ + (newSVuv((UVTYPE) V)) +#else +#define NEW_IV(V) \ + (V >= IV_MIN && V <= IV_MAX ? newSViv((IVTYPE) V) : newSVnv((NVTYPE) V)) +#if defined(UVTYPE) +#define NEW_UV(V) \ + (V <= UV_MAX ? newSVuv((UVTYPE) V) : newSVnv((NVTYPE) V)) +# else +#define NEW_UV(V) \ + (V <= IV_MAX ? newSViv((IVTYPE) V) : newSVnv((NVTYPE) V)) +#endif +#endif +#define NEW_HRTIME(V) \ + newSVnv((NVTYPE) (V / 1000000000.0)) + +#define SAVE_FNP(H, F, K) \ + hv_store(H, K, sizeof (K) - 1, newSViv((IVTYPE)(uintptr_t)&F), 0) +#define SAVE_STRING(H, S, K, SS) \ + hv_store(H, #K, sizeof (#K) - 1, \ + newSVpvn(S->K, SS ? strlen(S->K) : sizeof(S->K)), 0) +#define SAVE_INT32(H, S, K) \ + hv_store(H, #K, sizeof (#K) - 1, NEW_IV(S->K), 0) +#define SAVE_UINT32(H, S, K) \ + hv_store(H, #K, sizeof (#K) - 1, NEW_UV(S->K), 0) +#define SAVE_INT64(H, S, K) \ + hv_store(H, #K, sizeof (#K) - 1, NEW_IV(S->K), 0) +#define SAVE_UINT64(H, S, K) \ + hv_store(H, #K, sizeof (#K) - 1, NEW_UV(S->K), 0) +#define SAVE_HRTIME(H, S, K) \ + hv_store(H, #K, sizeof (#K) - 1, NEW_HRTIME(S->K), 0) + +/* Private structure used for saving kstat info in the tied hashes */ +typedef struct { + char read; /* Kstat block has been read before */ + char valid; /* Kstat still exists in kstat chain */ + char strip_str; /* Strip KSTAT_DATA_CHAR fields */ + kstat_ctl_t *kstat_ctl; /* Handle returned by kstat_open */ + kstat_t *kstat; /* Handle used by kstat_read */ +} KstatInfo_t; + +/* typedef for apply_to_ties callback functions */ +typedef int (*ATTCb_t)(HV *, void *); + +/* typedef for raw kstat reader functions */ +typedef void (*kstat_raw_reader_t)(HV *, kstat_t *, int); + +/* Hash of "module:name" to KSTAT_RAW read functions */ +static HV *raw_kstat_lookup; + +/* + * Kstats come in two flavours, named and raw. Raw kstats are just C structs, + * so we need a function per raw kstat to convert the C struct into the + * corresponding perl hash. All such conversion functions are in the following + * section. + */ + +/* + * Definitions in /usr/include/sys/cpuvar.h and /usr/include/sys/sysinfo.h + */ + +static void +save_cpu_stat(HV *self, kstat_t *kp, int strip_str) +{ + cpu_stat_t *statp; + cpu_sysinfo_t *sysinfop; + cpu_syswait_t *syswaitp; + cpu_vminfo_t *vminfop; + + /* PERL_ASSERT(kp->ks_ndata == 1); */ + PERL_ASSERT(kp->ks_data_size == sizeof (cpu_stat_t)); + statp = (cpu_stat_t *)(kp->ks_data); + sysinfop = &statp->cpu_sysinfo; + syswaitp = &statp->cpu_syswait; + vminfop = &statp->cpu_vminfo; + + hv_store(self, "idle", 4, NEW_UV(sysinfop->cpu[CPU_IDLE]), 0); + hv_store(self, "user", 4, NEW_UV(sysinfop->cpu[CPU_USER]), 0); + hv_store(self, "kernel", 6, NEW_UV(sysinfop->cpu[CPU_KERNEL]), 0); + hv_store(self, "wait", 4, NEW_UV(sysinfop->cpu[CPU_WAIT]), 0); + hv_store(self, "wait_io", 7, NEW_UV(sysinfop->wait[W_IO]), 0); + hv_store(self, "wait_swap", 9, NEW_UV(sysinfop->wait[W_SWAP]), 0); + hv_store(self, "wait_pio", 8, NEW_UV(sysinfop->wait[W_PIO]), 0); + SAVE_UINT32(self, sysinfop, bread); + SAVE_UINT32(self, sysinfop, bwrite); + SAVE_UINT32(self, sysinfop, lread); + SAVE_UINT32(self, sysinfop, lwrite); + SAVE_UINT32(self, sysinfop, phread); + SAVE_UINT32(self, sysinfop, phwrite); + SAVE_UINT32(self, sysinfop, pswitch); + SAVE_UINT32(self, sysinfop, trap); + SAVE_UINT32(self, sysinfop, intr); + SAVE_UINT32(self, sysinfop, syscall); + SAVE_UINT32(self, sysinfop, sysread); + SAVE_UINT32(self, sysinfop, syswrite); + SAVE_UINT32(self, sysinfop, sysfork); + SAVE_UINT32(self, sysinfop, sysvfork); + SAVE_UINT32(self, sysinfop, sysexec); + SAVE_UINT32(self, sysinfop, readch); + SAVE_UINT32(self, sysinfop, writech); + SAVE_UINT32(self, sysinfop, rcvint); + SAVE_UINT32(self, sysinfop, xmtint); + SAVE_UINT32(self, sysinfop, mdmint); + SAVE_UINT32(self, sysinfop, rawch); + SAVE_UINT32(self, sysinfop, canch); + SAVE_UINT32(self, sysinfop, outch); + SAVE_UINT32(self, sysinfop, msg); + SAVE_UINT32(self, sysinfop, sema); + SAVE_UINT32(self, sysinfop, namei); + SAVE_UINT32(self, sysinfop, ufsiget); + SAVE_UINT32(self, sysinfop, ufsdirblk); + SAVE_UINT32(self, sysinfop, ufsipage); + SAVE_UINT32(self, sysinfop, ufsinopage); + SAVE_UINT32(self, sysinfop, inodeovf); + SAVE_UINT32(self, sysinfop, fileovf); + SAVE_UINT32(self, sysinfop, procovf); + SAVE_UINT32(self, sysinfop, intrthread); + SAVE_UINT32(self, sysinfop, intrblk); + SAVE_UINT32(self, sysinfop, idlethread); + SAVE_UINT32(self, sysinfop, inv_swtch); + SAVE_UINT32(self, sysinfop, nthreads); + SAVE_UINT32(self, sysinfop, cpumigrate); + SAVE_UINT32(self, sysinfop, xcalls); + SAVE_UINT32(self, sysinfop, mutex_adenters); + SAVE_UINT32(self, sysinfop, rw_rdfails); + SAVE_UINT32(self, sysinfop, rw_wrfails); + SAVE_UINT32(self, sysinfop, modload); + SAVE_UINT32(self, sysinfop, modunload); + SAVE_UINT32(self, sysinfop, bawrite); +#ifdef STATISTICS /* see header file */ + SAVE_UINT32(self, sysinfop, rw_enters); + SAVE_UINT32(self, sysinfop, win_uo_cnt); + SAVE_UINT32(self, sysinfop, win_uu_cnt); + SAVE_UINT32(self, sysinfop, win_so_cnt); + SAVE_UINT32(self, sysinfop, win_su_cnt); + SAVE_UINT32(self, sysinfop, win_suo_cnt); +#endif + + SAVE_INT32(self, syswaitp, iowait); + SAVE_INT32(self, syswaitp, swap); + SAVE_INT32(self, syswaitp, physio); + + SAVE_UINT32(self, vminfop, pgrec); + SAVE_UINT32(self, vminfop, pgfrec); + SAVE_UINT32(self, vminfop, pgin); + SAVE_UINT32(self, vminfop, pgpgin); + SAVE_UINT32(self, vminfop, pgout); + SAVE_UINT32(self, vminfop, pgpgout); + SAVE_UINT32(self, vminfop, swapin); + SAVE_UINT32(self, vminfop, pgswapin); + SAVE_UINT32(self, vminfop, swapout); + SAVE_UINT32(self, vminfop, pgswapout); + SAVE_UINT32(self, vminfop, zfod); + SAVE_UINT32(self, vminfop, dfree); + SAVE_UINT32(self, vminfop, scan); + SAVE_UINT32(self, vminfop, rev); + SAVE_UINT32(self, vminfop, hat_fault); + SAVE_UINT32(self, vminfop, as_fault); + SAVE_UINT32(self, vminfop, maj_fault); + SAVE_UINT32(self, vminfop, cow_fault); + SAVE_UINT32(self, vminfop, prot_fault); + SAVE_UINT32(self, vminfop, softlock); + SAVE_UINT32(self, vminfop, kernel_asflt); + SAVE_UINT32(self, vminfop, pgrrun); + SAVE_UINT32(self, vminfop, execpgin); + SAVE_UINT32(self, vminfop, execpgout); + SAVE_UINT32(self, vminfop, execfree); + SAVE_UINT32(self, vminfop, anonpgin); + SAVE_UINT32(self, vminfop, anonpgout); + SAVE_UINT32(self, vminfop, anonfree); + SAVE_UINT32(self, vminfop, fspgin); + SAVE_UINT32(self, vminfop, fspgout); + SAVE_UINT32(self, vminfop, fsfree); +} + +/* + * Definitions in /usr/include/sys/var.h + */ + +static void +save_var(HV *self, kstat_t *kp, int strip_str) +{ + struct var *varp; + + /* PERL_ASSERT(kp->ks_ndata == 1); */ + PERL_ASSERT(kp->ks_data_size == sizeof (struct var)); + varp = (struct var *)(kp->ks_data); + + SAVE_INT32(self, varp, v_buf); + SAVE_INT32(self, varp, v_call); + SAVE_INT32(self, varp, v_proc); + SAVE_INT32(self, varp, v_maxupttl); + SAVE_INT32(self, varp, v_nglobpris); + SAVE_INT32(self, varp, v_maxsyspri); + SAVE_INT32(self, varp, v_clist); + SAVE_INT32(self, varp, v_maxup); + SAVE_INT32(self, varp, v_hbuf); + SAVE_INT32(self, varp, v_hmask); + SAVE_INT32(self, varp, v_pbuf); + SAVE_INT32(self, varp, v_sptmap); + SAVE_INT32(self, varp, v_maxpmem); + SAVE_INT32(self, varp, v_autoup); + SAVE_INT32(self, varp, v_bufhwm); +} + +/* + * Definition in /usr/include/sys/dnlc.h + */ + +static void +save_ncstats(HV *self, kstat_t *kp, int strip_str) +{ + struct ncstats *ncstatsp; + + /* PERL_ASSERT(kp->ks_ndata == 1); */ + PERL_ASSERT(kp->ks_data_size == sizeof (struct ncstats)); + ncstatsp = (struct ncstats *)(kp->ks_data); + + SAVE_INT32(self, ncstatsp, hits); + SAVE_INT32(self, ncstatsp, misses); + SAVE_INT32(self, ncstatsp, enters); + SAVE_INT32(self, ncstatsp, dbl_enters); + SAVE_INT32(self, ncstatsp, long_enter); + SAVE_INT32(self, ncstatsp, long_look); + SAVE_INT32(self, ncstatsp, move_to_front); + SAVE_INT32(self, ncstatsp, purges); +} + +/* + * Definition in /usr/include/sys/sysinfo.h + */ + +static void +save_sysinfo(HV *self, kstat_t *kp, int strip_str) +{ + sysinfo_t *sysinfop; + + /* PERL_ASSERT(kp->ks_ndata == 1); */ + PERL_ASSERT(kp->ks_data_size == sizeof (sysinfo_t)); + sysinfop = (sysinfo_t *)(kp->ks_data); + + SAVE_UINT32(self, sysinfop, updates); + SAVE_UINT32(self, sysinfop, runque); + SAVE_UINT32(self, sysinfop, runocc); + SAVE_UINT32(self, sysinfop, swpque); + SAVE_UINT32(self, sysinfop, swpocc); + SAVE_UINT32(self, sysinfop, waiting); +} + +/* + * Definition in /usr/include/sys/sysinfo.h + */ + +static void +save_vminfo(HV *self, kstat_t *kp, int strip_str) +{ + vminfo_t *vminfop; + + /* PERL_ASSERT(kp->ks_ndata == 1); */ + PERL_ASSERT(kp->ks_data_size == sizeof (vminfo_t)); + vminfop = (vminfo_t *)(kp->ks_data); + + SAVE_UINT64(self, vminfop, freemem); + SAVE_UINT64(self, vminfop, swap_resv); + SAVE_UINT64(self, vminfop, swap_alloc); + SAVE_UINT64(self, vminfop, swap_avail); + SAVE_UINT64(self, vminfop, swap_free); + SAVE_UINT64(self, vminfop, updates); +} + +/* + * Definition in /usr/include/nfs/nfs_clnt.h + */ + +static void +save_nfs(HV *self, kstat_t *kp, int strip_str) +{ + struct mntinfo_kstat *mntinfop; + + /* PERL_ASSERT(kp->ks_ndata == 1); */ + PERL_ASSERT(kp->ks_data_size == sizeof (struct mntinfo_kstat)); + mntinfop = (struct mntinfo_kstat *)(kp->ks_data); + + SAVE_STRING(self, mntinfop, mik_proto, strip_str); + SAVE_UINT32(self, mntinfop, mik_vers); + SAVE_UINT32(self, mntinfop, mik_flags); + SAVE_UINT32(self, mntinfop, mik_secmod); + SAVE_UINT32(self, mntinfop, mik_curread); + SAVE_UINT32(self, mntinfop, mik_curwrite); + SAVE_INT32(self, mntinfop, mik_timeo); + SAVE_INT32(self, mntinfop, mik_retrans); + SAVE_UINT32(self, mntinfop, mik_acregmin); + SAVE_UINT32(self, mntinfop, mik_acregmax); + SAVE_UINT32(self, mntinfop, mik_acdirmin); + SAVE_UINT32(self, mntinfop, mik_acdirmax); + hv_store(self, "lookup_srtt", 11, + NEW_UV(mntinfop->mik_timers[0].srtt), 0); + hv_store(self, "lookup_deviate", 14, + NEW_UV(mntinfop->mik_timers[0].deviate), 0); + hv_store(self, "lookup_rtxcur", 13, + NEW_UV(mntinfop->mik_timers[0].rtxcur), 0); + hv_store(self, "read_srtt", 9, + NEW_UV(mntinfop->mik_timers[1].srtt), 0); + hv_store(self, "read_deviate", 12, + NEW_UV(mntinfop->mik_timers[1].deviate), 0); + hv_store(self, "read_rtxcur", 11, + NEW_UV(mntinfop->mik_timers[1].rtxcur), 0); + hv_store(self, "write_srtt", 10, + NEW_UV(mntinfop->mik_timers[2].srtt), 0); + hv_store(self, "write_deviate", 13, + NEW_UV(mntinfop->mik_timers[2].deviate), 0); + hv_store(self, "write_rtxcur", 12, + NEW_UV(mntinfop->mik_timers[2].rtxcur), 0); + SAVE_UINT32(self, mntinfop, mik_noresponse); + SAVE_UINT32(self, mntinfop, mik_failover); + SAVE_UINT32(self, mntinfop, mik_remap); + SAVE_STRING(self, mntinfop, mik_curserver, strip_str); +} + +/* + * The following struct => hash functions are all only present on the sparc + * platform, so they are all conditionally compiled depending on __sparc + */ + +/* + * Definition in /usr/platform/sun4u/include/vm/hat_sfmmu.h + */ + +#ifdef __sparc +static void +save_sfmmu_global_stat(HV *self, kstat_t *kp, int strip_str) +{ + struct sfmmu_global_stat *sfmmugp; + + /* PERL_ASSERT(kp->ks_ndata == 1); */ + PERL_ASSERT(kp->ks_data_size == sizeof (struct sfmmu_global_stat)); + sfmmugp = (struct sfmmu_global_stat *)(kp->ks_data); + + SAVE_INT32(self, sfmmugp, sf_tsb_exceptions); + SAVE_INT32(self, sfmmugp, sf_tsb_raise_exception); + SAVE_INT32(self, sfmmugp, sf_pagefaults); + SAVE_INT32(self, sfmmugp, sf_uhash_searches); + SAVE_INT32(self, sfmmugp, sf_uhash_links); + SAVE_INT32(self, sfmmugp, sf_khash_searches); + SAVE_INT32(self, sfmmugp, sf_khash_links); + SAVE_INT32(self, sfmmugp, sf_swapout); + SAVE_INT32(self, sfmmugp, sf_tsb_alloc); + SAVE_INT32(self, sfmmugp, sf_tsb_allocfail); + SAVE_INT32(self, sfmmugp, sf_tsb_sectsb_create); + SAVE_INT32(self, sfmmugp, sf_scd_1sttsb_alloc); + SAVE_INT32(self, sfmmugp, sf_scd_2ndtsb_alloc); + SAVE_INT32(self, sfmmugp, sf_scd_1sttsb_allocfail); + SAVE_INT32(self, sfmmugp, sf_scd_2ndtsb_allocfail); + SAVE_INT32(self, sfmmugp, sf_tteload8k); + SAVE_INT32(self, sfmmugp, sf_tteload64k); + SAVE_INT32(self, sfmmugp, sf_tteload512k); + SAVE_INT32(self, sfmmugp, sf_tteload4m); + SAVE_INT32(self, sfmmugp, sf_tteload32m); + SAVE_INT32(self, sfmmugp, sf_tteload256m); + SAVE_INT32(self, sfmmugp, sf_tsb_load8k); + SAVE_INT32(self, sfmmugp, sf_tsb_load4m); + SAVE_INT32(self, sfmmugp, sf_hblk_hit); + SAVE_INT32(self, sfmmugp, sf_hblk8_ncreate); + SAVE_INT32(self, sfmmugp, sf_hblk8_nalloc); + SAVE_INT32(self, sfmmugp, sf_hblk1_ncreate); + SAVE_INT32(self, sfmmugp, sf_hblk1_nalloc); + SAVE_INT32(self, sfmmugp, sf_hblk_slab_cnt); + SAVE_INT32(self, sfmmugp, sf_hblk_reserve_cnt); + SAVE_INT32(self, sfmmugp, sf_hblk_recurse_cnt); + SAVE_INT32(self, sfmmugp, sf_hblk_reserve_hit); + SAVE_INT32(self, sfmmugp, sf_get_free_success); + SAVE_INT32(self, sfmmugp, sf_get_free_throttle); + SAVE_INT32(self, sfmmugp, sf_get_free_fail); + SAVE_INT32(self, sfmmugp, sf_put_free_success); + SAVE_INT32(self, sfmmugp, sf_put_free_fail); + SAVE_INT32(self, sfmmugp, sf_pgcolor_conflict); + SAVE_INT32(self, sfmmugp, sf_uncache_conflict); + SAVE_INT32(self, sfmmugp, sf_unload_conflict); + SAVE_INT32(self, sfmmugp, sf_ism_uncache); + SAVE_INT32(self, sfmmugp, sf_ism_recache); + SAVE_INT32(self, sfmmugp, sf_recache); + SAVE_INT32(self, sfmmugp, sf_steal_count); + SAVE_INT32(self, sfmmugp, sf_pagesync); + SAVE_INT32(self, sfmmugp, sf_clrwrt); + SAVE_INT32(self, sfmmugp, sf_pagesync_invalid); + SAVE_INT32(self, sfmmugp, sf_kernel_xcalls); + SAVE_INT32(self, sfmmugp, sf_user_xcalls); + SAVE_INT32(self, sfmmugp, sf_tsb_grow); + SAVE_INT32(self, sfmmugp, sf_tsb_shrink); + SAVE_INT32(self, sfmmugp, sf_tsb_resize_failures); + SAVE_INT32(self, sfmmugp, sf_tsb_reloc); + SAVE_INT32(self, sfmmugp, sf_user_vtop); + SAVE_INT32(self, sfmmugp, sf_ctx_inv); + SAVE_INT32(self, sfmmugp, sf_tlb_reprog_pgsz); + SAVE_INT32(self, sfmmugp, sf_region_remap_demap); + SAVE_INT32(self, sfmmugp, sf_create_scd); + SAVE_INT32(self, sfmmugp, sf_join_scd); + SAVE_INT32(self, sfmmugp, sf_leave_scd); + SAVE_INT32(self, sfmmugp, sf_destroy_scd); +} +#endif + +/* + * Definition in /usr/platform/sun4u/include/vm/hat_sfmmu.h + */ + +#ifdef __sparc +static void +save_sfmmu_tsbsize_stat(HV *self, kstat_t *kp, int strip_str) +{ + struct sfmmu_tsbsize_stat *sfmmutp; + + /* PERL_ASSERT(kp->ks_ndata == 1); */ + PERL_ASSERT(kp->ks_data_size == sizeof (struct sfmmu_tsbsize_stat)); + sfmmutp = (struct sfmmu_tsbsize_stat *)(kp->ks_data); + + SAVE_INT32(self, sfmmutp, sf_tsbsz_8k); + SAVE_INT32(self, sfmmutp, sf_tsbsz_16k); + SAVE_INT32(self, sfmmutp, sf_tsbsz_32k); + SAVE_INT32(self, sfmmutp, sf_tsbsz_64k); + SAVE_INT32(self, sfmmutp, sf_tsbsz_128k); + SAVE_INT32(self, sfmmutp, sf_tsbsz_256k); + SAVE_INT32(self, sfmmutp, sf_tsbsz_512k); + SAVE_INT32(self, sfmmutp, sf_tsbsz_1m); + SAVE_INT32(self, sfmmutp, sf_tsbsz_2m); + SAVE_INT32(self, sfmmutp, sf_tsbsz_4m); +} +#endif + +/* + * Definition in /usr/platform/sun4u/include/sys/simmstat.h + */ + +#ifdef __sparc +static void +save_simmstat(HV *self, kstat_t *kp, int strip_str) +{ + uchar_t *simmstatp; + SV *list; + int i; + + /* PERL_ASSERT(kp->ks_ndata == 1); */ + PERL_ASSERT(kp->ks_data_size == sizeof (uchar_t) * SIMM_COUNT); + + list = newSVpv("", 0); + for (i = 0, simmstatp = (uchar_t *)(kp->ks_data); + i < SIMM_COUNT - 1; i++, simmstatp++) { + sv_catpvf(list, "%d,", *simmstatp); + } + sv_catpvf(list, "%d", *simmstatp); + hv_store(self, "status", 6, list, 0); +} +#endif + +/* + * Used by save_temperature to make CSV lists from arrays of + * short temperature values + */ + +#ifdef __sparc +static SV * +short_array_to_SV(short *shortp, int len) +{ + SV *list; + + list = newSVpv("", 0); + for (; len > 1; len--, shortp++) { + sv_catpvf(list, "%d,", *shortp); + } + sv_catpvf(list, "%d", *shortp); + return (list); +} + +/* + * Definition in /usr/platform/sun4u/include/sys/fhc.h + */ + +static void +save_temperature(HV *self, kstat_t *kp, int strip_str) +{ + struct temp_stats *tempsp; + + /* PERL_ASSERT(kp->ks_ndata == 1); */ + PERL_ASSERT(kp->ks_data_size == sizeof (struct temp_stats)); + tempsp = (struct temp_stats *)(kp->ks_data); + + SAVE_UINT32(self, tempsp, index); + hv_store(self, "l1", 2, short_array_to_SV(tempsp->l1, L1_SZ), 0); + hv_store(self, "l2", 2, short_array_to_SV(tempsp->l2, L2_SZ), 0); + hv_store(self, "l3", 2, short_array_to_SV(tempsp->l3, L3_SZ), 0); + hv_store(self, "l4", 2, short_array_to_SV(tempsp->l4, L4_SZ), 0); + hv_store(self, "l5", 2, short_array_to_SV(tempsp->l5, L5_SZ), 0); + SAVE_INT32(self, tempsp, max); + SAVE_INT32(self, tempsp, min); + SAVE_INT32(self, tempsp, state); + SAVE_INT32(self, tempsp, temp_cnt); + SAVE_INT32(self, tempsp, shutdown_cnt); + SAVE_INT32(self, tempsp, version); + SAVE_INT32(self, tempsp, trend); + SAVE_INT32(self, tempsp, override); +} +#endif + +/* + * Not actually defined anywhere - just a short. Yuck. + */ + +#ifdef __sparc +static void +save_temp_over(HV *self, kstat_t *kp, int strip_str) +{ + short *shortp; + + /* PERL_ASSERT(kp->ks_ndata == 1); */ + PERL_ASSERT(kp->ks_data_size == sizeof (short)); + + shortp = (short *)(kp->ks_data); + hv_store(self, "override", 8, newSViv(*shortp), 0); +} +#endif + +/* + * Defined in /usr/platform/sun4u/include/sys/sysctrl.h + * (Well, sort of. Actually there's no structure, just a list of #defines + * enumerating *some* of the array indexes.) + */ + +#ifdef __sparc +static void +save_ps_shadow(HV *self, kstat_t *kp, int strip_str) +{ + uchar_t *ucharp; + + /* PERL_ASSERT(kp->ks_ndata == 1); */ + PERL_ASSERT(kp->ks_data_size == SYS_PS_COUNT); + + ucharp = (uchar_t *)(kp->ks_data); + hv_store(self, "core_0", 6, newSViv(*ucharp++), 0); + hv_store(self, "core_1", 6, newSViv(*ucharp++), 0); + hv_store(self, "core_2", 6, newSViv(*ucharp++), 0); + hv_store(self, "core_3", 6, newSViv(*ucharp++), 0); + hv_store(self, "core_4", 6, newSViv(*ucharp++), 0); + hv_store(self, "core_5", 6, newSViv(*ucharp++), 0); + hv_store(self, "core_6", 6, newSViv(*ucharp++), 0); + hv_store(self, "core_7", 6, newSViv(*ucharp++), 0); + hv_store(self, "pps_0", 5, newSViv(*ucharp++), 0); + hv_store(self, "clk_33", 6, newSViv(*ucharp++), 0); + hv_store(self, "clk_50", 6, newSViv(*ucharp++), 0); + hv_store(self, "v5_p", 4, newSViv(*ucharp++), 0); + hv_store(self, "v12_p", 5, newSViv(*ucharp++), 0); + hv_store(self, "v5_aux", 6, newSViv(*ucharp++), 0); + hv_store(self, "v5_p_pch", 8, newSViv(*ucharp++), 0); + hv_store(self, "v12_p_pch", 9, newSViv(*ucharp++), 0); + hv_store(self, "v3_pch", 6, newSViv(*ucharp++), 0); + hv_store(self, "v5_pch", 6, newSViv(*ucharp++), 0); + hv_store(self, "p_fan", 5, newSViv(*ucharp++), 0); +} +#endif + +/* + * Definition in /usr/platform/sun4u/include/sys/fhc.h + */ + +#ifdef __sparc +static void +save_fault_list(HV *self, kstat_t *kp, int strip_str) +{ + struct ft_list *faultp; + int i; + char name[KSTAT_STRLEN + 7]; /* room for 999999 faults */ + + /* PERL_ASSERT(kp->ks_ndata == 1); */ + /* PERL_ASSERT(kp->ks_data_size == sizeof (struct ft_list)); */ + + for (i = 1, faultp = (struct ft_list *)(kp->ks_data); + i <= 999999 && i <= kp->ks_data_size / sizeof (struct ft_list); + i++, faultp++) { + (void) snprintf(name, sizeof (name), "unit_%d", i); + hv_store(self, name, strlen(name), newSViv(faultp->unit), 0); + (void) snprintf(name, sizeof (name), "type_%d", i); + hv_store(self, name, strlen(name), newSViv(faultp->type), 0); + (void) snprintf(name, sizeof (name), "fclass_%d", i); + hv_store(self, name, strlen(name), newSViv(faultp->fclass), 0); + (void) snprintf(name, sizeof (name), "create_time_%d", i); + hv_store(self, name, strlen(name), + NEW_UV(faultp->create_time), 0); + (void) snprintf(name, sizeof (name), "msg_%d", i); + hv_store(self, name, strlen(name), newSVpv(faultp->msg, 0), 0); + } +} +#endif + +/* + * We need to be able to find the function corresponding to a particular raw + * kstat. To do this we ignore the instance and glue the module and name + * together to form a composite key. We can then use the data in the kstat + * structure to find the appropriate function. We use a perl hash to manage the + * lookup, where the key is "module:name" and the value is a pointer to the + * appropriate C function. + * + * Note that some kstats include the instance number as part of the module + * and/or name. This could be construed as a bug. However, to work around this + * we omit any digits from the module and name as we build the table in + * build_raw_kstat_loopup(), and we remove any digits from the module and name + * when we look up the functions in lookup_raw_kstat_fn() + */ + +/* + * This function is called when the XS is first dlopen()ed, and builds the + * lookup table as described above. + */ + +static void +build_raw_kstat_lookup() + { + /* Create new hash */ + raw_kstat_lookup = newHV(); + + SAVE_FNP(raw_kstat_lookup, save_cpu_stat, "cpu_stat:cpu_stat"); + SAVE_FNP(raw_kstat_lookup, save_var, "unix:var"); + SAVE_FNP(raw_kstat_lookup, save_ncstats, "unix:ncstats"); + SAVE_FNP(raw_kstat_lookup, save_sysinfo, "unix:sysinfo"); + SAVE_FNP(raw_kstat_lookup, save_vminfo, "unix:vminfo"); + SAVE_FNP(raw_kstat_lookup, save_nfs, "nfs:mntinfo"); +#ifdef __sparc + SAVE_FNP(raw_kstat_lookup, save_sfmmu_global_stat, + "unix:sfmmu_global_stat"); + SAVE_FNP(raw_kstat_lookup, save_sfmmu_tsbsize_stat, + "unix:sfmmu_tsbsize_stat"); + SAVE_FNP(raw_kstat_lookup, save_simmstat, "unix:simm-status"); + SAVE_FNP(raw_kstat_lookup, save_temperature, "unix:temperature"); + SAVE_FNP(raw_kstat_lookup, save_temp_over, "unix:temperature override"); + SAVE_FNP(raw_kstat_lookup, save_ps_shadow, "unix:ps_shadow"); + SAVE_FNP(raw_kstat_lookup, save_fault_list, "unix:fault_list"); +#endif +} + +/* + * This finds and returns the raw kstat reader function corresponding to the + * supplied module and name. If no matching function exists, 0 is returned. + */ + +static kstat_raw_reader_t lookup_raw_kstat_fn(char *module, char *name) + { + char key[KSTAT_STRLEN * 2]; + register char *f, *t; + SV **entry; + kstat_raw_reader_t fnp; + + /* Copy across module & name, removing any digits - see comment above */ + for (f = module, t = key; *f != '\0'; f++, t++) { + while (*f != '\0' && isdigit(*f)) { f++; } + *t = *f; + } + *t++ = ':'; + for (f = name; *f != '\0'; f++, t++) { + while (*f != '\0' && isdigit(*f)) { + f++; + } + *t = *f; + } + *t = '\0'; + + /* look up & return the function, or teturn 0 if not found */ + if ((entry = hv_fetch(raw_kstat_lookup, key, strlen(key), FALSE)) == 0) + { + fnp = 0; + } else { + fnp = (kstat_raw_reader_t)(uintptr_t)SvIV(*entry); + } + return (fnp); +} + +/* + * This module converts the flat list returned by kstat_read() into a perl hash + * tree keyed on module, instance, name and statistic. The following functions + * provide code to create the nested hashes, and to iterate over them. + */ + +/* + * Given module, instance and name keys return a pointer to the hash tied to + * the bottommost hash. If the hash already exists, we just return a pointer + * to it, otherwise we create the hash and any others also required above it in + * the hierarchy. The returned tiehash is blessed into the + * Sun::Solaris::Kstat::_Stat class, so that the appropriate TIEHASH methods are + * called when the bottommost hash is accessed. If the is_new parameter is + * non-null it will be set to TRUE if a new tie has been created, and FALSE if + * the tie already existed. + */ + +static HV * +get_tie(SV *self, char *module, int instance, char *name, int *is_new) +{ + char str_inst[11]; /* big enough for up to 10^10 instances */ + char *key[3]; /* 3 part key: module, instance, name */ + int k; + int new; + HV *hash; + HV *tie; + + /* Create the keys */ + (void) snprintf(str_inst, sizeof (str_inst), "%d", instance); + key[0] = module; + key[1] = str_inst; + key[2] = name; + + /* Iteratively descend the tree, creating new hashes as required */ + hash = (HV *)SvRV(self); + for (k = 0; k < 3; k++) { + SV **entry; + + SvREADONLY_off(hash); + entry = hv_fetch(hash, key[k], strlen(key[k]), TRUE); + + /* If the entry doesn't exist, create it */ + if (! SvOK(*entry)) { + HV *newhash; + SV *rv; + + newhash = newHV(); + rv = newRV_noinc((SV *)newhash); + sv_setsv(*entry, rv); + SvREFCNT_dec(rv); + if (k < 2) { + SvREADONLY_on(newhash); + } + SvREADONLY_on(*entry); + SvREADONLY_on(hash); + hash = newhash; + new = 1; + + /* Otherwise it already existed */ + } else { + SvREADONLY_on(hash); + hash = (HV *)SvRV(*entry); + new = 0; + } + } + + /* Create and bless a hash for the tie, if necessary */ + if (new) { + SV *tieref; + HV *stash; + + tie = newHV(); + tieref = newRV_noinc((SV *)tie); + stash = gv_stashpv("Sun::Solaris::Kstat::_Stat", TRUE); + sv_bless(tieref, stash); + + /* Add TIEHASH magic */ + hv_magic(hash, (GV *)tieref, 'P'); + SvREADONLY_on(hash); + + /* Otherwise, just find the existing tied hash */ + } else { + MAGIC *mg; + + mg = mg_find((SV *)hash, 'P'); + PERL_ASSERTMSG(mg != 0, "get_tie: lost P magic"); + tie = (HV *)SvRV(mg->mg_obj); + } + if (is_new) { + *is_new = new; + } + return (tie); +} + +/* + * This is an iterator function used to traverse the hash hierarchy and apply + * the passed function to the tied hashes at the bottom of the hierarchy. If + * any of the callback functions return 0, 0 is returned, otherwise 1 + */ + +static int +apply_to_ties(SV *self, ATTCb_t cb, void *arg) +{ + HV *hash1; + HE *entry1; + long s; + int ret; + + hash1 = (HV *)SvRV(self); + hv_iterinit(hash1); + ret = 1; + + /* Iterate over each module */ + while (entry1 = hv_iternext(hash1)) { + HV *hash2; + HE *entry2; + + hash2 = (HV *)SvRV(hv_iterval(hash1, entry1)); + hv_iterinit(hash2); + + /* Iterate over each module:instance */ + while (entry2 = hv_iternext(hash2)) { + HV *hash3; + HE *entry3; + + hash3 = (HV *)SvRV(hv_iterval(hash2, entry2)); + hv_iterinit(hash3); + + /* Iterate over each module:instance:name */ + while (entry3 = hv_iternext(hash3)) { + HV *hash4; + MAGIC *mg; + HV *tie; + + /* Get the tie */ + hash4 = (HV *)SvRV(hv_iterval(hash3, entry3)); + mg = mg_find((SV *)hash4, 'P'); + PERL_ASSERTMSG(mg != 0, + "apply_to_ties: lost P magic"); + + /* Apply the callback */ + if (! cb((HV *)SvRV(mg->mg_obj), arg)) { + ret = 0; + } + } + } + } + return (ret); +} + +/* + * Mark this HV as valid - used by update() when pruning deleted kstat nodes + */ + +static int +set_valid(HV *self, void *arg) +{ + MAGIC *mg; + + mg = mg_find((SV *)self, '~'); + PERL_ASSERTMSG(mg != 0, "set_valid: lost ~ magic"); + ((KstatInfo_t *)SvPVX(mg->mg_obj))->valid = (int)(intptr_t)arg; + return (1); +} + +/* + * Prune invalid kstat nodes. This is called when kstat_chain_update() detects + * that the kstat chain has been updated. This removes any hash tree entries + * that no longer have a corresponding kstat. If del is non-null it will be + * set to the keys of the deleted kstat nodes, if any. If any entries are + * deleted 1 will be retured, otherwise 0 + */ + +static int +prune_invalid(SV *self, AV *del) +{ + HV *hash1; + HE *entry1; + STRLEN klen; + char *module, *instance, *name, *key; + int ret; + + hash1 = (HV *)SvRV(self); + hv_iterinit(hash1); + ret = 0; + + /* Iterate over each module */ + while (entry1 = hv_iternext(hash1)) { + HV *hash2; + HE *entry2; + + module = HePV(entry1, PL_na); + hash2 = (HV *)SvRV(hv_iterval(hash1, entry1)); + hv_iterinit(hash2); + + /* Iterate over each module:instance */ + while (entry2 = hv_iternext(hash2)) { + HV *hash3; + HE *entry3; + + instance = HePV(entry2, PL_na); + hash3 = (HV *)SvRV(hv_iterval(hash2, entry2)); + hv_iterinit(hash3); + + /* Iterate over each module:instance:name */ + while (entry3 = hv_iternext(hash3)) { + HV *hash4; + MAGIC *mg; + HV *tie; + + name = HePV(entry3, PL_na); + hash4 = (HV *)SvRV(hv_iterval(hash3, entry3)); + mg = mg_find((SV *)hash4, 'P'); + PERL_ASSERTMSG(mg != 0, + "prune_invalid: lost P magic"); + tie = (HV *)SvRV(mg->mg_obj); + mg = mg_find((SV *)tie, '~'); + PERL_ASSERTMSG(mg != 0, + "prune_invalid: lost ~ magic"); + + /* If this is marked as invalid, prune it */ + if (((KstatInfo_t *)SvPVX( + (SV *)mg->mg_obj))->valid == FALSE) { + SvREADONLY_off(hash3); + key = HePV(entry3, klen); + hv_delete(hash3, key, klen, G_DISCARD); + SvREADONLY_on(hash3); + if (del) { + av_push(del, + newSVpvf("%s:%s:%s", + module, instance, name)); + } + ret = 1; + } + } + + /* If the module:instance:name hash is empty prune it */ + if (HvKEYS(hash3) == 0) { + SvREADONLY_off(hash2); + key = HePV(entry2, klen); + hv_delete(hash2, key, klen, G_DISCARD); + SvREADONLY_on(hash2); + } + } + /* If the module:instance hash is empty prune it */ + if (HvKEYS(hash2) == 0) { + SvREADONLY_off(hash1); + key = HePV(entry1, klen); + hv_delete(hash1, key, klen, G_DISCARD); + SvREADONLY_on(hash1); + } + } + return (ret); +} + +/* + * Named kstats are returned as a list of key/values. This function converts + * such a list into the equivalent perl datatypes, and stores them in the passed + * hash. + */ + +static void +save_named(HV *self, kstat_t *kp, int strip_str) +{ + kstat_named_t *knp; + int n; + SV* value; + + for (n = kp->ks_ndata, knp = KSTAT_NAMED_PTR(kp); n > 0; n--, knp++) { + switch (knp->data_type) { + case KSTAT_DATA_CHAR: + value = newSVpv(knp->value.c, strip_str ? + strlen(knp->value.c) : sizeof (knp->value.c)); + break; + case KSTAT_DATA_INT32: + value = newSViv(knp->value.i32); + break; + case KSTAT_DATA_UINT32: + value = NEW_UV(knp->value.ui32); + break; + case KSTAT_DATA_INT64: + value = NEW_UV(knp->value.i64); + break; + case KSTAT_DATA_UINT64: + value = NEW_UV(knp->value.ui64); + break; + case KSTAT_DATA_STRING: + if (KSTAT_NAMED_STR_PTR(knp) == NULL) + value = newSVpv("null", sizeof ("null") - 1); + else + value = newSVpv(KSTAT_NAMED_STR_PTR(knp), + KSTAT_NAMED_STR_BUFLEN(knp) -1); + break; + default: + PERL_ASSERTMSG(0, "kstat_read: invalid data type"); + break; + } + hv_store(self, knp->name, strlen(knp->name), value, 0); + } +} + +/* + * Save kstat interrupt statistics + */ + +static void +save_intr(HV *self, kstat_t *kp, int strip_str) +{ + kstat_intr_t *kintrp; + int i; + static char *intr_names[] = + { "hard", "soft", "watchdog", "spurious", "multiple_service" }; + + PERL_ASSERT(kp->ks_ndata == 1); + PERL_ASSERT(kp->ks_data_size == sizeof (kstat_intr_t)); + kintrp = KSTAT_INTR_PTR(kp); + + for (i = 0; i < KSTAT_NUM_INTRS; i++) { + hv_store(self, intr_names[i], strlen(intr_names[i]), + NEW_UV(kintrp->intrs[i]), 0); + } +} + +/* + * Save IO statistics + */ + +static void +save_io(HV *self, kstat_t *kp, int strip_str) +{ + kstat_io_t *kiop; + + PERL_ASSERT(kp->ks_ndata == 1); + PERL_ASSERT(kp->ks_data_size == sizeof (kstat_io_t)); + kiop = KSTAT_IO_PTR(kp); + SAVE_UINT64(self, kiop, nread); + SAVE_UINT64(self, kiop, nwritten); + SAVE_UINT32(self, kiop, reads); + SAVE_UINT32(self, kiop, writes); + SAVE_HRTIME(self, kiop, wtime); + SAVE_HRTIME(self, kiop, wlentime); + SAVE_HRTIME(self, kiop, wlastupdate); + SAVE_HRTIME(self, kiop, rtime); + SAVE_HRTIME(self, kiop, rlentime); + SAVE_HRTIME(self, kiop, rlastupdate); + SAVE_UINT32(self, kiop, wcnt); + SAVE_UINT32(self, kiop, rcnt); +} + +/* + * Save timer statistics + */ + +static void +save_timer(HV *self, kstat_t *kp, int strip_str) +{ + kstat_timer_t *ktimerp; + + PERL_ASSERT(kp->ks_ndata == 1); + PERL_ASSERT(kp->ks_data_size == sizeof (kstat_timer_t)); + ktimerp = KSTAT_TIMER_PTR(kp); + SAVE_STRING(self, ktimerp, name, strip_str); + SAVE_UINT64(self, ktimerp, num_events); + SAVE_HRTIME(self, ktimerp, elapsed_time); + SAVE_HRTIME(self, ktimerp, min_time); + SAVE_HRTIME(self, ktimerp, max_time); + SAVE_HRTIME(self, ktimerp, start_time); + SAVE_HRTIME(self, ktimerp, stop_time); +} + +/* + * Read kstats and copy into the supplied perl hash structure. If refresh is + * true, this function is being called as part of the update() method. In this + * case it is only necessary to read the kstats if they have previously been + * accessed (kip->read == TRUE). If refresh is false, this function is being + * called prior to returning a value to the caller. In this case, it is only + * necessary to read the kstats if they have not previously been read. If the + * kstat_read() fails, 0 is returned, otherwise 1 + */ + +static int +read_kstats(HV *self, int refresh) +{ + MAGIC *mg; + KstatInfo_t *kip; + kstat_raw_reader_t fnp; + + /* Find the MAGIC KstatInfo_t data structure */ + mg = mg_find((SV *)self, '~'); + PERL_ASSERTMSG(mg != 0, "read_kstats: lost ~ magic"); + kip = (KstatInfo_t *)SvPVX(mg->mg_obj); + + /* Return early if we don't need to actually read the kstats */ + if ((refresh && ! kip->read) || (! refresh && kip->read)) { + return (1); + } + + /* Read the kstats and return 0 if this fails */ + if (kstat_read(kip->kstat_ctl, kip->kstat, NULL) < 0) { + return (0); + } + + /* Save the read data */ + hv_store(self, "snaptime", 8, NEW_HRTIME(kip->kstat->ks_snaptime), 0); + switch (kip->kstat->ks_type) { + case KSTAT_TYPE_RAW: + if ((fnp = lookup_raw_kstat_fn(kip->kstat->ks_module, + kip->kstat->ks_name)) != 0) { + fnp(self, kip->kstat, kip->strip_str); + } + break; + case KSTAT_TYPE_NAMED: + save_named(self, kip->kstat, kip->strip_str); + break; + case KSTAT_TYPE_INTR: + save_intr(self, kip->kstat, kip->strip_str); + break; + case KSTAT_TYPE_IO: + save_io(self, kip->kstat, kip->strip_str); + break; + case KSTAT_TYPE_TIMER: + save_timer(self, kip->kstat, kip->strip_str); + break; + default: + PERL_ASSERTMSG(0, "read_kstats: illegal kstat type"); + break; + } + kip->read = TRUE; + return (1); +} + +/* + * The XS code exported to perl is below here. Note that the XS preprocessor + * has its own commenting syntax, so all comments from this point on are in + * that form. + */ + +/* The following XS methods are the ABI of the Sun::Solaris::Kstat package */ + +MODULE = Sun::Solaris::Kstat PACKAGE = Sun::Solaris::Kstat +PROTOTYPES: ENABLE + + # Create the raw kstat to store function lookup table on load +BOOT: + build_raw_kstat_lookup(); + + # + # The Sun::Solaris::Kstat constructor. This builds the nested + # name::instance::module hash structure, but doesn't actually read the + # underlying kstats. This is done on demand by the TIEHASH methods in + # Sun::Solaris::Kstat::_Stat + # + +SV* +new(class, ...) + char *class; +PREINIT: + HV *stash; + kstat_ctl_t *kc; + SV *kcsv; + kstat_t *kp; + KstatInfo_t kstatinfo; + int sp, strip_str; +CODE: + /* Check we have an even number of arguments, excluding the class */ + sp = 1; + if (((items - sp) % 2) != 0) { + croak(DEBUG_ID ": new: invalid number of arguments"); + } + + /* Process any (name => value) arguments */ + strip_str = 0; + while (sp < items) { + SV *name, *value; + + name = ST(sp); + sp++; + value = ST(sp); + sp++; + if (strcmp(SvPVX(name), "strip_strings") == 0) { + strip_str = SvTRUE(value); + } else { + croak(DEBUG_ID ": new: invalid parameter name '%s'", + SvPVX(name)); + } + } + + /* Open the kstats handle */ + if ((kc = kstat_open()) == 0) { + XSRETURN_UNDEF; + } + + /* Create a blessed hash ref */ + RETVAL = (SV *)newRV_noinc((SV *)newHV()); + stash = gv_stashpv(class, TRUE); + sv_bless(RETVAL, stash); + + /* Create a place to save the KstatInfo_t structure */ + kcsv = newSVpv((char *)&kc, sizeof (kc)); + sv_magic(SvRV(RETVAL), kcsv, '~', 0, 0); + SvREFCNT_dec(kcsv); + + /* Initialise the KstatsInfo_t structure */ + kstatinfo.read = FALSE; + kstatinfo.valid = TRUE; + kstatinfo.strip_str = strip_str; + kstatinfo.kstat_ctl = kc; + + /* Scan the kstat chain, building hash entries for the kstats */ + for (kp = kc->kc_chain; kp != 0; kp = kp->ks_next) { + HV *tie; + SV *kstatsv; + + /* Don't bother storing the kstat headers */ + if (strncmp(kp->ks_name, "kstat_", 6) == 0) { + continue; + } + + /* Don't bother storing raw stats we don't understand */ + if (kp->ks_type == KSTAT_TYPE_RAW && + lookup_raw_kstat_fn(kp->ks_module, kp->ks_name) == 0) { +#ifdef REPORT_UNKNOWN + (void) fprintf(stderr, + "Unknown kstat type %s:%d:%s - %d of size %d\n", + kp->ks_module, kp->ks_instance, kp->ks_name, + kp->ks_ndata, kp->ks_data_size); +#endif + continue; + } + + /* Create a 3-layer hash hierarchy - module.instance.name */ + tie = get_tie(RETVAL, kp->ks_module, kp->ks_instance, + kp->ks_name, 0); + + /* Save the data necessary to read the kstat info on demand */ + hv_store(tie, "class", 5, newSVpv(kp->ks_class, 0), 0); + hv_store(tie, "crtime", 6, NEW_HRTIME(kp->ks_crtime), 0); + kstatinfo.kstat = kp; + kstatsv = newSVpv((char *)&kstatinfo, sizeof (kstatinfo)); + sv_magic((SV *)tie, kstatsv, '~', 0, 0); + SvREFCNT_dec(kstatsv); + } + SvREADONLY_on(SvRV(RETVAL)); + /* SvREADONLY_on(RETVAL); */ +OUTPUT: + RETVAL + + # + # Update the perl hash structure so that it is in line with the kernel kstats + # data. Only kstats athat have previously been accessed are read, + # + + # Scalar context: true/false + # Array context: (\@added, \@deleted) +void +update(self) + SV* self; +PREINIT: + MAGIC *mg; + kstat_ctl_t *kc; + kstat_t *kp; + int ret; + AV *add, *del; +PPCODE: + /* Find the hidden KstatInfo_t structure */ + mg = mg_find(SvRV(self), '~'); + PERL_ASSERTMSG(mg != 0, "update: lost ~ magic"); + kc = *(kstat_ctl_t **)SvPVX(mg->mg_obj); + + /* Update the kstat chain, and return immediately on error. */ + if ((ret = kstat_chain_update(kc)) == -1) { + if (GIMME_V == G_ARRAY) { + EXTEND(SP, 2); + PUSHs(sv_newmortal()); + PUSHs(sv_newmortal()); + } else { + EXTEND(SP, 1); + PUSHs(sv_2mortal(newSViv(ret))); + } + } + + /* Create the arrays to be returned if in an array context */ + if (GIMME_V == G_ARRAY) { + add = newAV(); + del = newAV(); + } else { + add = 0; + del = 0; + } + + /* + * If the kstat chain hasn't changed we can just reread any stats + * that have already been read + */ + if (ret == 0) { + if (! apply_to_ties(self, (ATTCb_t)read_kstats, (void *)TRUE)) { + if (GIMME_V == G_ARRAY) { + EXTEND(SP, 2); + PUSHs(sv_2mortal(newRV_noinc((SV *)add))); + PUSHs(sv_2mortal(newRV_noinc((SV *)del))); + } else { + EXTEND(SP, 1); + PUSHs(sv_2mortal(newSViv(-1))); + } + } + + /* + * Otherwise we have to update the Perl structure so that it is in + * agreement with the new kstat chain. We do this in such a way as to + * retain all the existing structures, just adding or deleting the + * bare minimum. + */ + } else { + KstatInfo_t kstatinfo; + + /* + * Step 1: set the 'invalid' flag on each entry + */ + apply_to_ties(self, &set_valid, (void *)FALSE); + + /* + * Step 2: Set the 'valid' flag on all entries still in the + * kernel kstat chain + */ + kstatinfo.read = FALSE; + kstatinfo.valid = TRUE; + kstatinfo.kstat_ctl = kc; + for (kp = kc->kc_chain; kp != 0; kp = kp->ks_next) { + int new; + HV *tie; + + /* Don't bother storing the kstat headers or types */ + if (strncmp(kp->ks_name, "kstat_", 6) == 0) { + continue; + } + + /* Don't bother storing raw stats we don't understand */ + if (kp->ks_type == KSTAT_TYPE_RAW && + lookup_raw_kstat_fn(kp->ks_module, kp->ks_name) + == 0) { +#ifdef REPORT_UNKNOWN + (void) printf("Unknown kstat type %s:%d:%s " + "- %d of size %d\n", kp->ks_module, + kp->ks_instance, kp->ks_name, + kp->ks_ndata, kp->ks_data_size); +#endif + continue; + } + + /* Find the tied hash associated with the kstat entry */ + tie = get_tie(self, kp->ks_module, kp->ks_instance, + kp->ks_name, &new); + + /* If newly created store the associated kstat info */ + if (new) { + SV *kstatsv; + + /* + * Save the data necessary to read the kstat + * info on demand + */ + hv_store(tie, "class", 5, + newSVpv(kp->ks_class, 0), 0); + hv_store(tie, "crtime", 6, + NEW_HRTIME(kp->ks_crtime), 0); + kstatinfo.kstat = kp; + kstatsv = newSVpv((char *)&kstatinfo, + sizeof (kstatinfo)); + sv_magic((SV *)tie, kstatsv, '~', 0, 0); + SvREFCNT_dec(kstatsv); + + /* Save the key on the add list, if required */ + if (GIMME_V == G_ARRAY) { + av_push(add, newSVpvf("%s:%d:%s", + kp->ks_module, kp->ks_instance, + kp->ks_name)); + } + + /* If the stats already exist, just update them */ + } else { + MAGIC *mg; + KstatInfo_t *kip; + + /* Find the hidden KstatInfo_t */ + mg = mg_find((SV *)tie, '~'); + PERL_ASSERTMSG(mg != 0, "update: lost ~ magic"); + kip = (KstatInfo_t *)SvPVX(mg->mg_obj); + + /* Mark the tie as valid */ + kip->valid = TRUE; + + /* Re-save the kstat_t pointer. If the kstat + * has been deleted and re-added since the last + * update, the address of the kstat structure + * will have changed, even though the kstat will + * still live at the same place in the perl + * hash tree structure. + */ + kip->kstat = kp; + + /* Reread the stats, if read previously */ + read_kstats(tie, TRUE); + } + } + + /* + *Step 3: Delete any entries still marked as 'invalid' + */ + ret = prune_invalid(self, del); + + } + if (GIMME_V == G_ARRAY) { + EXTEND(SP, 2); + PUSHs(sv_2mortal(newRV_noinc((SV *)add))); + PUSHs(sv_2mortal(newRV_noinc((SV *)del))); + } else { + EXTEND(SP, 1); + PUSHs(sv_2mortal(newSViv(ret))); + } + + + # + # Destructor. Closes the kstat connection + # + +void +DESTROY(self) + SV *self; +PREINIT: + MAGIC *mg; + kstat_ctl_t *kc; +CODE: + mg = mg_find(SvRV(self), '~'); + PERL_ASSERTMSG(mg != 0, "DESTROY: lost ~ magic"); + kc = *(kstat_ctl_t **)SvPVX(mg->mg_obj); + if (kstat_close(kc) != 0) { + croak(DEBUG_ID ": kstat_close: failed"); + } + + # + # The following XS methods implement the TIEHASH mechanism used to update the + # kstats hash structure. These are blessed into a package that isn't + # visible to callers of the Sun::Solaris::Kstat module + # + +MODULE = Sun::Solaris::Kstat PACKAGE = Sun::Solaris::Kstat::_Stat +PROTOTYPES: ENABLE + + # + # If a value has already been read, return it. Otherwise read the appropriate + # kstat and then return the value + # + +SV* +FETCH(self, key) + SV* self; + SV* key; +PREINIT: + char *k; + STRLEN klen; + SV **value; +CODE: + self = SvRV(self); + k = SvPV(key, klen); + if (strNE(k, "class") && strNE(k, "crtime")) { + read_kstats((HV *)self, FALSE); + } + value = hv_fetch((HV *)self, k, klen, FALSE); + if (value) { + RETVAL = *value; SvREFCNT_inc(RETVAL); + } else { + RETVAL = &PL_sv_undef; + } +OUTPUT: + RETVAL + + # + # Save the passed value into the kstat hash. Read the appropriate kstat first, + # if necessary. Note that this DOES NOT update the underlying kernel kstat + # structure. + # + +SV* +STORE(self, key, value) + SV* self; + SV* key; + SV* value; +PREINIT: + char *k; + STRLEN klen; +CODE: + self = SvRV(self); + k = SvPV(key, klen); + if (strNE(k, "class") && strNE(k, "crtime")) { + read_kstats((HV *)self, FALSE); + } + SvREFCNT_inc(value); + RETVAL = *(hv_store((HV *)self, k, klen, value, 0)); + SvREFCNT_inc(RETVAL); +OUTPUT: + RETVAL + + # + # Check for the existence of the passed key. Read the kstat first if necessary + # + +bool +EXISTS(self, key) + SV* self; + SV* key; +PREINIT: + char *k; +CODE: + self = SvRV(self); + k = SvPV(key, PL_na); + if (strNE(k, "class") && strNE(k, "crtime")) { + read_kstats((HV *)self, FALSE); + } + RETVAL = hv_exists_ent((HV *)self, key, 0); +OUTPUT: + RETVAL + + + # + # Hash iterator initialisation. Read the kstats if necessary. + # + +SV* +FIRSTKEY(self) + SV* self; +PREINIT: + HE *he; +PPCODE: + self = SvRV(self); + read_kstats((HV *)self, FALSE); + hv_iterinit((HV *)self); + if (he = hv_iternext((HV *)self)) { + EXTEND(SP, 1); + PUSHs(hv_iterkeysv(he)); + } + + # + # Return hash iterator next value. Read the kstats if necessary. + # + +SV* +NEXTKEY(self, lastkey) + SV* self; + SV* lastkey; +PREINIT: + HE *he; +PPCODE: + self = SvRV(self); + if (he = hv_iternext((HV *)self)) { + EXTEND(SP, 1); + PUSHs(hv_iterkeysv(he)); + } + + + # + # Delete the specified hash entry. + # + +SV* +DELETE(self, key) + SV *self; + SV *key; +CODE: + self = SvRV(self); + RETVAL = hv_delete_ent((HV *)self, key, 0, 0); + if (RETVAL) { + SvREFCNT_inc(RETVAL); + } else { + RETVAL = &PL_sv_undef; + } +OUTPUT: + RETVAL + + # + # Clear the entire hash. This will stop any update() calls rereading this + # kstat until it is accessed again. + # + +void +CLEAR(self) + SV* self; +PREINIT: + MAGIC *mg; + KstatInfo_t *kip; +CODE: + self = SvRV(self); + hv_clear((HV *)self); + mg = mg_find(self, '~'); + PERL_ASSERTMSG(mg != 0, "CLEAR: lost ~ magic"); + kip = (KstatInfo_t *)SvPVX(mg->mg_obj); + kip->read = FALSE; + kip->valid = TRUE; + hv_store((HV *)self, "class", 5, newSVpv(kip->kstat->ks_class, 0), 0); + hv_store((HV *)self, "crtime", 6, NEW_HRTIME(kip->kstat->ks_crtime), 0); diff --git a/META.yml b/META.yml new file mode 100644 index 0000000..152b3b3 --- /dev/null +++ b/META.yml @@ -0,0 +1,20 @@ +--- #YAML:1.0 +name: Sun-Solaris-Kstat +version: 1.3 +abstract: ~ +author: [] +license: unknown +distribution_type: module +configure_requires: + ExtUtils::MakeMaker: 0 +build_requires: + ExtUtils::MakeMaker: 0 +requires: {} +no_index: + directory: + - t + - inc +generated_by: ExtUtils::MakeMaker version 6.57_05 +meta-spec: + url: http://module-build.sourceforge.net/META-spec-v1.4.html + version: 1.4 diff --git a/Makefile.PL b/Makefile.PL new file mode 100644 index 0000000..386d8c4 --- /dev/null +++ b/Makefile.PL @@ -0,0 +1,60 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +use ExtUtils::MakeMaker; +my ($defines, $includes) = ('', ''); + +# Uncomment this to turn on assertions +#$defines .= '-DKSTAT_DEBUG '; + +# Uncomment this to report unknown binary kstats +#$defines .= '-DREPORT_UNKNOWN '; + +# +# Because there are still platform specific raw kstats, we need to include the +# platform specific header files, so work out where they live. +# +my $arch = qx{uname -p}; chomp($arch); +if ($arch eq 'sparc') { + $includes .= '-I/usr/platform/sun4u/include' . + ' -I/usr/platform/sun4v/include'; +} elsif ($arch eq 'i386') { + $includes .= '-I/usr/platform/i86pc/include' . + ' -I/usr/platform/i86xpv/include'; +} else { + die("Unknown architecture $arch\n"); +} + +WriteMakefile( + NAME => 'Sun::Solaris::Kstat', + VERSION_FROM => 'Kstat.pm', + LIBS => [ '-lkstat' ], + DEFINE => $defines, + INC => $includes, + OBJECT => '$(O_FILES)', +); diff --git a/t/Kstat.t b/t/Kstat.t new file mode 100644 index 0000000..8d1f92c --- /dev/null +++ b/t/Kstat.t @@ -0,0 +1,177 @@ +# +# Copyright 2005 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# 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 +# +#ident "%Z%%M% %I% %E% SMI" +# +# test script for Sun::Solaris::Kstat +# + +use strict; + +# Visit all the leaf nodes - +# will generate a die if there are any structure mismatches +sub visit_all($) +{ + my ($ks) = @_; + foreach my $m (sort(keys(%$ks))) { + foreach my $i (sort(keys(%{$ks->{$m}}))) { + foreach my $n (sort(keys(%{$ks->{$m}->{$i}}))) { + foreach my $k (sort( + keys(%{$ks->{$m}->{$i}->{$n}}))) { + my $v = $ks->{$m}->{$i}->{$n}->{$k}; + } + } + } + } + return(1); +} + +BEGIN { $| = 1; print "1..15\n"; } +my $loaded; +END {print "not ok 1\n" unless $loaded;} +use Sun::Solaris::Kstat; +$loaded = 1; +print "ok 1\n"; + +# Check we can create a Kstat object OK +my ($test, $ks); +$test = 2; +if (! eval { $ks = Sun::Solaris::Kstat->new() }) { + print("not ok $test: $@"); +} else { + print("ok $test\n"); +} +$test++; + +# Check FIRSTKEY/NEXTKEY/FETCH and for structure mismatches +if (! eval { visit_all($ks) }) { + print("not ok $test: $@"); +} else { + print("ok $test\n"); +} +$test++; + +# Find a cpu number +my $cpu = (keys(%{$ks->{cpu_info}}))[0]; +my $cpu_info = "cpu_info$cpu"; + +# Check EXISTS +if (exists($ks->{cpu_info}{$cpu}{$cpu_info}{state})) { + print("ok $test\n"); +} else { + print("not ok $test\n"); +} +$test++; + +# Check DELETE +my $val = delete($ks->{cpu_info}{$cpu}{$cpu_info}{state}); +if (defined($val) && ($val =~ /^on-line/ || $val =~ /^off-line/)) { + print("ok $test\n"); +} else { + print("not ok $test ($val)\n"); +} +$test++; + +# 5.004_04 has a broken hv_delete +if ($] < 5.00405) { + print("ok $test\n"); + $test++; + print("ok $test\n"); + $test++; +} else { + if (! exists($ks->{cpu_info}{$cpu}{$cpu_info}{state})) { + print("ok $test\n"); + } else { + print("not ok $test\n"); + } + $test++; + $val = $ks->{cpu_info}{$cpu}{$cpu_info}{state}; + if (! defined($val)) { + print("ok $test\n"); + } else { + print("not ok $test\n"); + } + $test++; +} + +# Check STORE +$ks->{cpu_info}{$cpu}{$cpu_info}{state} = "california"; +if ($ks->{cpu_info}{$cpu}{$cpu_info}{state} eq "california") { + print("ok $test\n"); +} else { + print("not ok $test\n"); +} +$test++; + +# Check CLEAR +my @bvals = sort(keys(%{$ks->{cpu_info}{$cpu}{$cpu_info}})); +%{$ks->{cpu_info}{$cpu}{$cpu_info}} = (); +my @avals = sort(keys(%{$ks->{cpu_info}{$cpu}{$cpu_info}})); +while (@bvals || @avals) { + my $a = shift(@avals); + my $b = shift(@bvals); + if ($a ne $b) { print("not ok $test ($a ne $b)\n"); last; } +} +print("ok $test\n") if (! @avals && ! @bvals); +$test++; + +# Check updates +if (! defined(eval { $ks->update() })) { + print("not ok $test: $@"); +} else { + print("ok $test\n"); +} +$test++; + +# Check readonly-ness of hash structure +eval { $ks->{cpu_info}{$cpu}{$cpu_info} = {}; }; +print($@ =~ /^Modification of a read-only/i ? "ok $test\n" : "not ok $test\n"); +$test++; + +eval { $ks->{cpu_info}{$cpu} = {}; }; +print($@ =~ /^Modification of a read-only/i ? "ok $test\n" : "not ok $test\n"); +$test++; + +eval { $ks->{cpu_info} = {}; }; +print($@ =~ /^Modification of a read-only/i ? "ok $test\n" : "not ok $test\n"); +$test++; + +# Check timestamps +my $then = $ks->{cpu_info}{$cpu}{$cpu_info}{snaptime}; +sleep(3); +if (! defined(eval { $ks->update() })) { + print("not ok $test: $@"); +} else { + print("ok $test\n"); +} +$test++; +my $interval = $ks->{cpu_info}{$cpu}{$cpu_info}{snaptime} - $then; +if ($interval >= 2.5 && $interval <= 3.5) { + print("ok $test\n"); +} else { + print("not ok $test\n"); +} +$test++; + +exit(0); |