mirror of
https://github.com/prometheus/node_exporter.git
synced 2024-12-23 00:46:08 +01:00
659 lines
20 KiB
Go
659 lines
20 KiB
Go
|
//
|
||
|
// The kstat package provides a Go interface to the Solaris/OmniOS
|
||
|
// kstat(s) system for user-level access to a lot of kernel
|
||
|
// statistics. For more documentation on kstats, see kstat(1) and
|
||
|
// kstat(3kstat).
|
||
|
//
|
||
|
// In an ideal world the package documentation would go here. This is
|
||
|
// not an ideal world, because any number of tools like godoc choke on
|
||
|
// Go files that are not for their architecture (although I'll admit
|
||
|
// it's a hard problem). So see doc.go for the actual package level
|
||
|
// documentation.
|
||
|
//
|
||
|
// However, I refuse to push function level API documentation off to another
|
||
|
// file, at least at the moment. It would be a horrible mess.
|
||
|
//
|
||
|
|
||
|
package kstat
|
||
|
|
||
|
// #cgo LDFLAGS: -lkstat
|
||
|
//
|
||
|
// #include <sys/types.h>
|
||
|
// #include <stdlib.h>
|
||
|
// #include <strings.h>
|
||
|
// #include <kstat.h>
|
||
|
//
|
||
|
// /* We have to reach through unions, which cgo doesn't support.
|
||
|
// So we have our own cheesy little routines for it. These assume
|
||
|
// they are always being called on validly-typed named kstats.
|
||
|
// */
|
||
|
//
|
||
|
// char *get_named_char(kstat_named_t *knp) {
|
||
|
// return knp->value.str.addr.ptr;
|
||
|
// }
|
||
|
//
|
||
|
// uint64_t get_named_uint(kstat_named_t *knp) {
|
||
|
// if (knp->data_type == KSTAT_DATA_UINT32)
|
||
|
// return knp->value.ui32;
|
||
|
// else
|
||
|
// return knp->value.ui64;
|
||
|
// }
|
||
|
//
|
||
|
// int64_t get_named_int(kstat_named_t *knp) {
|
||
|
// if (knp->data_type == KSTAT_DATA_INT32)
|
||
|
// return knp->value.i32;
|
||
|
// else
|
||
|
// return knp->value.i64;
|
||
|
// }
|
||
|
//
|
||
|
// /* Let's not try to do C pointer arithmetic in Go and get it wrong */
|
||
|
// kstat_named_t *get_nth_named(kstat_t *ks, uint_t n) {
|
||
|
// kstat_named_t *knp;
|
||
|
// if (!ks || !ks->ks_data || ks->ks_type != KSTAT_TYPE_NAMED || n >= ks->ks_ndata)
|
||
|
// return NULL;
|
||
|
// knp = KSTAT_NAMED_PTR(ks);
|
||
|
// return knp + n;
|
||
|
// }
|
||
|
//
|
||
|
import "C"
|
||
|
|
||
|
import (
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"runtime"
|
||
|
"unsafe"
|
||
|
)
|
||
|
|
||
|
// Token is an access token for obtaining kstats.
|
||
|
type Token struct {
|
||
|
kc *C.struct_kstat_ctl
|
||
|
|
||
|
// ksm maps kstat_t pointers to our Go-level KStats for them.
|
||
|
// kstat_t's stay constant over the lifetime of a token, so
|
||
|
// we want to keep unique KStats. This holds some Go-level
|
||
|
// memory down, but I wave my hands.
|
||
|
ksm map[*C.struct_kstat]*KStat
|
||
|
}
|
||
|
|
||
|
// Open returns a kstat Token that is used to obtain kstats. It corresponds
|
||
|
// to kstat_open(). You should call .Close() when you're done and then not
|
||
|
// use any KStats or Nameds obtained through this token.
|
||
|
//
|
||
|
// (Failing to call .Close() will cause memory leaks.)
|
||
|
func Open() (*Token, error) {
|
||
|
r, err := C.kstat_open()
|
||
|
if r == nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
t := Token{}
|
||
|
t.kc = r
|
||
|
t.ksm = make(map[*C.struct_kstat]*KStat)
|
||
|
// A 'func (t *Token) Close()' is equivalent to
|
||
|
// 'func Close(t *Token)'. The latter is what SetFinalizer()
|
||
|
// needs.
|
||
|
runtime.SetFinalizer(&t, (*Token).Close)
|
||
|
return &t, nil
|
||
|
}
|
||
|
|
||
|
// Close a kstat access token. A closed token cannot be used for
|
||
|
// anything and cannot be reopened.
|
||
|
//
|
||
|
// After a Token has been closed it remains safe to look at fields
|
||
|
// on KStat and Named objects obtained through the Token, but it is
|
||
|
// not safe to call methods on them other than String(); doing so
|
||
|
// may cause memory corruption, although we try to avoid that.
|
||
|
//
|
||
|
// This corresponds to kstat_close().
|
||
|
func (t *Token) Close() error {
|
||
|
if t == nil || t.kc == nil {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Go through our KStats and null out fields that are no longer
|
||
|
// valid. We opt to do this before we actually destroy the memory
|
||
|
// KStat.ksp is pointing to by calling kstat_close().
|
||
|
for _, v := range t.ksm {
|
||
|
v.ksp = nil
|
||
|
v.tok = nil
|
||
|
}
|
||
|
|
||
|
res, err := C.kstat_close(t.kc)
|
||
|
t.kc = nil
|
||
|
|
||
|
// clear the map to drop all references to KStats.
|
||
|
t.ksm = make(map[*C.struct_kstat]*KStat)
|
||
|
|
||
|
// cancel finalizer
|
||
|
runtime.SetFinalizer(&t, nil)
|
||
|
|
||
|
if res != 0 {
|
||
|
return err
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Update synchronizes the Token to the current state of available
|
||
|
// kernel kstats, returning true if the kernel's list of available
|
||
|
// kstats changed and false otherwise. If there have been no changes
|
||
|
// in the kernel's kstat list, all KStats remain valid. If there was a
|
||
|
// kstat update, some or all of the KStats obtained through the Token
|
||
|
// may now be invalid. Some of the now-invalid KStats may still exist
|
||
|
// and be the same thing, but if so they will have to be looked up
|
||
|
// again.
|
||
|
//
|
||
|
// (This happens if, for example, a device disappears and then
|
||
|
// reappears. At the kernel level, the device's kstat is deleted when
|
||
|
// it disappears and then is recreated when it reappears; the kernel
|
||
|
// considers the recreated version to be a different kstat, although
|
||
|
// it has the same module:instance:name. Note that the same
|
||
|
// module:instance:name still existing does not guarantee that the
|
||
|
// kstat is for the same thing; one disk might have removed and then
|
||
|
// an entirely different new disk added.)
|
||
|
//
|
||
|
// Update corresponds to kstat_chain_update().
|
||
|
func (t *Token) Update() (bool, error) {
|
||
|
if t == nil || t.kc == nil {
|
||
|
return true, errors.New("token is closed")
|
||
|
}
|
||
|
oid := t.kc.kc_chain_id
|
||
|
// NOTE that we can't assume err == nil on success and just
|
||
|
// check for err != nil. The error return is set from errno,
|
||
|
// and kstat_chain_update() does not guarantee that errno is
|
||
|
// 0 if it succeeds.
|
||
|
nid, err := C.kstat_chain_update(t.kc)
|
||
|
switch {
|
||
|
case nid < 0:
|
||
|
// We generously assume that if there has been an
|
||
|
// error, the chain is intact. Otherwise we should
|
||
|
// invalidate all KStats in t.ksm, as in .Close().
|
||
|
// assumption: err != nil if n < 0.
|
||
|
return false, err
|
||
|
case nid == 0:
|
||
|
// No change is good news.
|
||
|
return false, nil
|
||
|
case nid == oid:
|
||
|
// Should never be the case, but...
|
||
|
return false, fmt.Errorf("new KCID is old KCID: %d", nid)
|
||
|
}
|
||
|
|
||
|
// The simple approach to KStats after a chain update would be
|
||
|
// to invalidate all existing KStats. However, we can do
|
||
|
// better. kstat_chain_update() implicitly guarantees that it
|
||
|
// will not reuse memory addresses of kstat_t structures for
|
||
|
// different ones within a single call, so we can walk the
|
||
|
// chain and look for addresses that we already know; the
|
||
|
// KStats for those addresses are still valid.
|
||
|
|
||
|
// Copy all valid chain entries that we have in the token ksm
|
||
|
// map to a new map and delete them from the old (current) map.
|
||
|
nksm := make(map[*C.struct_kstat]*KStat)
|
||
|
for r := t.kc.kc_chain; r != nil; r = r.ks_next {
|
||
|
if v, ok := t.ksm[r]; ok {
|
||
|
nksm[r] = v
|
||
|
delete(t.ksm, r)
|
||
|
}
|
||
|
}
|
||
|
// Anything left in t.ksm is an old chain entry that was
|
||
|
// removed by kstat_chain_update(). Explicitly zap their
|
||
|
// KStat's references to make them invalid.
|
||
|
for _, v := range t.ksm {
|
||
|
v.ksp = nil
|
||
|
v.tok = nil
|
||
|
}
|
||
|
// Make our new ksm map the current ksm map.
|
||
|
t.ksm = nksm
|
||
|
|
||
|
return true, nil
|
||
|
}
|
||
|
|
||
|
// All returns an array of all available KStats.
|
||
|
//
|
||
|
// (It has no error return because due to how kstats are implemented,
|
||
|
// it cannot fail.)
|
||
|
func (t *Token) All() []*KStat {
|
||
|
n := []*KStat{}
|
||
|
if t == nil || t.kc == nil {
|
||
|
return n
|
||
|
}
|
||
|
|
||
|
for r := t.kc.kc_chain; r != nil; r = r.ks_next {
|
||
|
n = append(n, newKStat(t, r))
|
||
|
}
|
||
|
return n
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// allocate a C string for a non-blank string; otherwise return nil
|
||
|
func maybeCString(src string) *C.char {
|
||
|
if src == "" {
|
||
|
return nil
|
||
|
}
|
||
|
return C.CString(src)
|
||
|
}
|
||
|
|
||
|
// free a non-nil C string
|
||
|
func maybeFree(cs *C.char) {
|
||
|
if cs != nil {
|
||
|
C.free(unsafe.Pointer(cs))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// strndup behaves like the C function; given a *C.char and a len, it
|
||
|
// returns a string that is up to len characters long at most.
|
||
|
// Shorn of casts, it is:
|
||
|
// C.GoStringN(p, C.strnlen(p, len))
|
||
|
//
|
||
|
// strndup() is necessary to copy fields of the type 'char
|
||
|
// name[SIZE];' where a string of exactly SIZE length will not be
|
||
|
// null-terminated. GoStringN() will always copy trailing null bytes
|
||
|
// and other garbage; GoString()'s internal strlen() may run off the
|
||
|
// end of the 'name' field and either fault or copy too much.
|
||
|
func strndup(cs *C.char, len C.size_t) string {
|
||
|
// credit: Ian Lance Taylor in
|
||
|
// https://github.com/golang/go/issues/12428
|
||
|
return C.GoStringN(cs, C.int(C.strnlen(cs, len)))
|
||
|
}
|
||
|
|
||
|
// Lookup looks up a particular kstat. module and name may be "" and
|
||
|
// instance may be -1 to mean 'the first one that kstats can find'.
|
||
|
// It also refreshes (or retrieves) the kstat's data and thus sets
|
||
|
// Snaptime.
|
||
|
//
|
||
|
// Lookup() corresponds to kstat_lookup() *plus kstat_read()*.
|
||
|
func (t *Token) Lookup(module string, instance int, name string) (*KStat, error) {
|
||
|
if t == nil || t.kc == nil {
|
||
|
return nil, errors.New("Token not valid or closed")
|
||
|
}
|
||
|
|
||
|
ms := maybeCString(module)
|
||
|
ns := maybeCString(name)
|
||
|
r, err := C.kstat_lookup(t.kc, ms, C.int(instance), ns)
|
||
|
maybeFree(ms)
|
||
|
maybeFree(ns)
|
||
|
|
||
|
if r == nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
k := newKStat(t, r)
|
||
|
|
||
|
// People rarely look up kstats to not use them, so we immediately
|
||
|
// attempt to kstat_read() the data. If this fails, we don't return
|
||
|
// the kstat. However, we don't scrub it from the kstat_t mapping
|
||
|
// that the Token maintains; we have no reason to believe that it
|
||
|
// needs to be remade. Our return of nil is a convenience to avoid
|
||
|
// problems in callers.
|
||
|
// TODO: this may be a mistake in the API.
|
||
|
//
|
||
|
// NOTE: this means that calling Lookup() on an existing KStat
|
||
|
// (either directly or via tok.GetNamed()) has the effect of
|
||
|
// updating its statistics data to the current time. Right now
|
||
|
// we consider this a feature.
|
||
|
err = k.Refresh()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return k, nil
|
||
|
}
|
||
|
|
||
|
// GetNamed obtains the Named representing a particular (named) kstat
|
||
|
// module:instance:name:statistic statistic. It always returns current
|
||
|
// data for the kstat statistic, even if it's called repeatedly for the
|
||
|
// same statistic.
|
||
|
//
|
||
|
// It is equivalent to .Lookup() then KStat.GetNamed().
|
||
|
func (t *Token) GetNamed(module string, instance int, name, stat string) (*Named, error) {
|
||
|
stats, err := t.Lookup(module, instance, name)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return stats.GetNamed(stat)
|
||
|
}
|
||
|
|
||
|
// -----
|
||
|
|
||
|
// KSType is the type of the data in a KStat.
|
||
|
type KSType int
|
||
|
|
||
|
// The different types of data that a KStat may contain, ie these
|
||
|
// are the value of a KStat.Type. We currently only support getting
|
||
|
// Named and IO statistics.
|
||
|
const (
|
||
|
RawStat KSType = C.KSTAT_TYPE_RAW
|
||
|
NamedStat KSType = C.KSTAT_TYPE_NAMED
|
||
|
IntrStat KSType = C.KSTAT_TYPE_INTR
|
||
|
IoStat KSType = C.KSTAT_TYPE_IO
|
||
|
TimerStat KSType = C.KSTAT_TYPE_TIMER
|
||
|
)
|
||
|
|
||
|
func (tp KSType) String() string {
|
||
|
switch tp {
|
||
|
case RawStat:
|
||
|
return "raw"
|
||
|
case NamedStat:
|
||
|
return "named"
|
||
|
case IntrStat:
|
||
|
return "interrupt"
|
||
|
case IoStat:
|
||
|
return "io"
|
||
|
case TimerStat:
|
||
|
return "timer"
|
||
|
default:
|
||
|
return fmt.Sprintf("kstat_type:%d", tp)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// KStat is the access handle for the collection of statistics for a
|
||
|
// particular module:instance:name kstat.
|
||
|
//
|
||
|
type KStat struct {
|
||
|
Module string
|
||
|
Instance int
|
||
|
Name string
|
||
|
|
||
|
// Class is eg 'net' or 'disk'. In kstat(1) it shows up as a
|
||
|
// ':class' statistic.
|
||
|
Class string
|
||
|
// Type is the type of kstat.
|
||
|
Type KSType
|
||
|
|
||
|
// Creation time of a kstat in nanoseconds since sometime.
|
||
|
// See gethrtime(3) and kstat(3kstat).
|
||
|
Crtime int64
|
||
|
// Snaptime is what kstat(1) reports as 'snaptime', the time
|
||
|
// that this data was obtained. As with Crtime, it is in
|
||
|
// nanoseconds since some arbitrary point in time.
|
||
|
// Snaptime may not be valid until .Refresh() or .GetNamed()
|
||
|
// has been called.
|
||
|
Snaptime int64
|
||
|
|
||
|
ksp *C.struct_kstat
|
||
|
// We need access to the token to refresh the data
|
||
|
tok *Token
|
||
|
}
|
||
|
|
||
|
// newKStat is our internal KStat constructor.
|
||
|
//
|
||
|
// This also has the responsibility of maintaining (and using) the
|
||
|
// kstat_t to KStat mapping cache, so that we don't recreate new
|
||
|
// KStats for the same kstat_t all the time.
|
||
|
func newKStat(tok *Token, ks *C.struct_kstat) *KStat {
|
||
|
if kst, ok := tok.ksm[ks]; ok {
|
||
|
return kst
|
||
|
}
|
||
|
|
||
|
kst := KStat{}
|
||
|
kst.ksp = ks
|
||
|
kst.tok = tok
|
||
|
|
||
|
kst.Instance = int(ks.ks_instance)
|
||
|
kst.Module = strndup((*C.char)(unsafe.Pointer(&ks.ks_module)), C.KSTAT_STRLEN)
|
||
|
kst.Name = strndup((*C.char)(unsafe.Pointer(&ks.ks_name)), C.KSTAT_STRLEN)
|
||
|
kst.Class = strndup((*C.char)(unsafe.Pointer(&ks.ks_class)), C.KSTAT_STRLEN)
|
||
|
kst.Type = KSType(ks.ks_type)
|
||
|
kst.Crtime = int64(ks.ks_crtime)
|
||
|
|
||
|
// Inside the kernel, the ks_snaptime of a kstat is of course
|
||
|
// a global thing. This 'global' snaptime is copied to user
|
||
|
// level as part of the kstat header(s) on kstat_open(), which
|
||
|
// means that kstats that have never been kstat_read() by us
|
||
|
// are almost certain to have a non-zero ks_snaptime (because
|
||
|
// someone, somewhere, will have read them since the system
|
||
|
// booted, eg 'kstat -p | grep ...' reads all kstats).
|
||
|
// Because this ks_snaptime is not useful, we don't copy it
|
||
|
// to Snaptime; instead we leave Snaptime unset (zero) as
|
||
|
// an explicit signal that this KStat has never had its data
|
||
|
// read.
|
||
|
//
|
||
|
//kst.Snaptime = int64(ks.ks_snaptime)
|
||
|
|
||
|
tok.ksm[ks] = &kst
|
||
|
return &kst
|
||
|
}
|
||
|
|
||
|
// invalid is a desperate attempt to keep usage errors from causing
|
||
|
// memory corruption. Don't count on it.
|
||
|
func (k *KStat) invalid() bool {
|
||
|
return k == nil || k.ksp == nil || k.tok == nil || k.tok.kc == nil
|
||
|
}
|
||
|
|
||
|
// setup does validity checks and setup, such as loading data via Refresh().
|
||
|
// It applies only to named kstats.
|
||
|
//
|
||
|
// TODO: setup() vs prep() is a code smell.
|
||
|
func (k *KStat) setup() error {
|
||
|
if k.invalid() {
|
||
|
return errors.New("invalid KStat or closed token")
|
||
|
}
|
||
|
|
||
|
if k.ksp.ks_type != C.KSTAT_TYPE_NAMED {
|
||
|
return fmt.Errorf("kstat %s (type %d) is not a named kstat", k, k.ksp.ks_type)
|
||
|
}
|
||
|
|
||
|
// Do the initial load of the data if necessary.
|
||
|
if k.ksp.ks_data == nil {
|
||
|
if err := k.Refresh(); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (k *KStat) String() string {
|
||
|
return fmt.Sprintf("%s:%d:%s (%s)", k.Module, k.Instance, k.Name, k.Class)
|
||
|
}
|
||
|
|
||
|
// Valid returns true if a KStat is still valid after a Token.Update()
|
||
|
// call has returned true. If a KStat becomes invalid after an update,
|
||
|
// its fields remain available but you can no longer call methods on
|
||
|
// it. You may be able to look it up again with token.Lookup(k.Module,
|
||
|
// k.Instance, k.Name), although it's possible that the
|
||
|
// module:instance:name now refers to something else. Even if it is
|
||
|
// still the same thing, there is no continuity in the actual
|
||
|
// statistics once Valid becomes false; you must restart tracking from
|
||
|
// scratch.
|
||
|
//
|
||
|
// (For example, if one disk is removed from the system and another is
|
||
|
// added, the new disk may use the same module:instance:name as some
|
||
|
// of the old disk's KStats. Your .Lookup() may succeed, but what you
|
||
|
// get back is not in any way a continuation of the old disk's
|
||
|
// information.)
|
||
|
//
|
||
|
// Valid also returns false after the KStat's token has been closed.
|
||
|
func (k *KStat) Valid() bool {
|
||
|
return !k.invalid()
|
||
|
}
|
||
|
|
||
|
// Refresh the statistics data for a KStat.
|
||
|
//
|
||
|
// Note that this does not update any existing Named objects for
|
||
|
// statistics from this KStat. You must re-do .GetNamed() to get
|
||
|
// new ones in order to see any updates.
|
||
|
//
|
||
|
// Under the hood this does a kstat_read(). You don't need to call it
|
||
|
// explicitly before obtaining statistics from a KStat.
|
||
|
func (k *KStat) Refresh() error {
|
||
|
if k.invalid() {
|
||
|
return errors.New("invalid KStat or closed token")
|
||
|
}
|
||
|
|
||
|
res, err := C.kstat_read(k.tok.kc, k.ksp, nil)
|
||
|
if res == -1 {
|
||
|
return err
|
||
|
}
|
||
|
k.Snaptime = int64(k.ksp.ks_snaptime)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// GetIO retrieves the IO statistics data from an IoStat type
|
||
|
// KStat. It always refreshes the KStat to provide current data.
|
||
|
//
|
||
|
// It corresponds to kstat_read() followed by getting a copy of
|
||
|
// ks_data (which is a kstat_io_t).
|
||
|
func (k *KStat) GetIO() (*IO, error) {
|
||
|
if err := k.Refresh(); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
if k.ksp.ks_type != C.KSTAT_TYPE_IO {
|
||
|
return nil, fmt.Errorf("kstat %s (type %d) is not an IO kstat", k, k.ksp.ks_type)
|
||
|
}
|
||
|
|
||
|
// We make our own copy of ks_data (as an IO) so that we don't
|
||
|
// point into C-owned memory. 'go tool cgo -godef' apparently
|
||
|
// guarantees that the IO struct/type it creates has exactly
|
||
|
// the same in-memory layout as the C struct, so we can safely
|
||
|
// do this copy and expect to get good results.
|
||
|
io := IO{}
|
||
|
io = *((*IO)(k.ksp.ks_data))
|
||
|
return &io, nil
|
||
|
}
|
||
|
|
||
|
// GetNamed obtains a particular named statistic from a KStat. It does
|
||
|
// not refresh the KStat's statistics data, so multiple calls to
|
||
|
// GetNamed on a single KStat will get a coherent set of statistic
|
||
|
// values from it.
|
||
|
//
|
||
|
// It corresponds to kstat_data_lookup().
|
||
|
func (k *KStat) GetNamed(name string) (*Named, error) {
|
||
|
if err := k.setup(); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
ns := C.CString(name)
|
||
|
r, err := C.kstat_data_lookup(k.ksp, ns)
|
||
|
C.free(unsafe.Pointer(ns))
|
||
|
if r == nil || err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return newNamed(k, (*C.struct_kstat_named)(r)), err
|
||
|
}
|
||
|
|
||
|
// AllNamed returns an array of all named statistics for a particular
|
||
|
// named-type KStat. Entries are returned in no particular order.
|
||
|
func (k *KStat) AllNamed() ([]*Named, error) {
|
||
|
if err := k.setup(); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
lst := make([]*Named, k.ksp.ks_ndata)
|
||
|
for i := C.uint_t(0); i < k.ksp.ks_ndata; i++ {
|
||
|
ks := C.get_nth_named(k.ksp, i)
|
||
|
if ks == nil {
|
||
|
panic("get_nth_named returned surprise nil")
|
||
|
}
|
||
|
lst[i] = newNamed(k, ks)
|
||
|
}
|
||
|
return lst, nil
|
||
|
}
|
||
|
|
||
|
// Named represents a particular kstat named statistic, ie the full
|
||
|
// module:instance:name:statistic
|
||
|
// and its current value.
|
||
|
//
|
||
|
// Name and Type are always valid, but only one of StringVal, IntVal,
|
||
|
// or UintVal is valid for any particular statistic; which one is
|
||
|
// valid is determined by its Type. Generally you'll already know what
|
||
|
// type a given named kstat statistic is; I don't believe Solaris
|
||
|
// changes their type once they're defined.
|
||
|
type Named struct {
|
||
|
Name string
|
||
|
Type NamedType
|
||
|
|
||
|
// Only one of the following values is valid; the others are zero
|
||
|
// values.
|
||
|
//
|
||
|
// StringVal holds the value for both CharData and String Type(s).
|
||
|
StringVal string
|
||
|
IntVal int64
|
||
|
UintVal uint64
|
||
|
|
||
|
// The Snaptime this Named was obtained. Note that while you
|
||
|
// use the parent KStat's Crtime, you cannot use its Snaptime.
|
||
|
// The KStat may have been refreshed since this Named was
|
||
|
// created, which updates the Snaptime.
|
||
|
Snaptime int64
|
||
|
|
||
|
// Pointer to the parent KStat, for access to the full name
|
||
|
// and the crtime associated with this Named.
|
||
|
KStat *KStat
|
||
|
}
|
||
|
|
||
|
func (ks *Named) String() string {
|
||
|
return fmt.Sprintf("%s:%d:%s:%s", ks.KStat.Module, ks.KStat.Instance, ks.KStat.Name, ks.Name)
|
||
|
}
|
||
|
|
||
|
// NamedType represents the various types of named kstat statistics.
|
||
|
type NamedType int
|
||
|
|
||
|
// The different types of data that a named kstat statistic can be
|
||
|
// (ie, these are the potential values of Named.Type).
|
||
|
const (
|
||
|
CharData NamedType = C.KSTAT_DATA_CHAR
|
||
|
Int32 NamedType = C.KSTAT_DATA_INT32
|
||
|
Uint32 NamedType = C.KSTAT_DATA_UINT32
|
||
|
Int64 NamedType = C.KSTAT_DATA_INT64
|
||
|
Uint64 NamedType = C.KSTAT_DATA_UINT64
|
||
|
String NamedType = C.KSTAT_DATA_STRING
|
||
|
|
||
|
// CharData is found in StringVal. At the moment we assume that
|
||
|
// it is a real string, because this matches how it seems to be
|
||
|
// used for short strings in the Solaris kernel. Someday we may
|
||
|
// find something that uses it as just a data dump for 16 bytes.
|
||
|
|
||
|
// Solaris sys/kstat.h also has _FLOAT (5) and _DOUBLE (6) types,
|
||
|
// but labels them as obsolete.
|
||
|
)
|
||
|
|
||
|
func (tp NamedType) String() string {
|
||
|
switch tp {
|
||
|
case CharData:
|
||
|
return "char"
|
||
|
case Int32:
|
||
|
return "int32"
|
||
|
case Uint32:
|
||
|
return "uint32"
|
||
|
case Int64:
|
||
|
return "int64"
|
||
|
case Uint64:
|
||
|
return "uint64"
|
||
|
case String:
|
||
|
return "string"
|
||
|
default:
|
||
|
return fmt.Sprintf("named_type-%d", tp)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Create a new Stat from the kstat_named_t
|
||
|
// We set the appropriate *Value field.
|
||
|
func newNamed(k *KStat, knp *C.struct_kstat_named) *Named {
|
||
|
st := Named{}
|
||
|
st.KStat = k
|
||
|
st.Name = strndup((*C.char)(unsafe.Pointer(&knp.name)), C.KSTAT_STRLEN)
|
||
|
st.Type = NamedType(knp.data_type)
|
||
|
st.Snaptime = k.Snaptime
|
||
|
|
||
|
switch st.Type {
|
||
|
case String:
|
||
|
// The comments in sys/kstat.h explicitly guarantee
|
||
|
// that these strings are null-terminated, although
|
||
|
// knp.value.str.len also holds the length.
|
||
|
st.StringVal = C.GoString(C.get_named_char(knp))
|
||
|
case CharData:
|
||
|
// Solaris/etc appears to use CharData for short strings
|
||
|
// so that they can be embedded directly into
|
||
|
// knp.value.c[16] instead of requiring an out of line
|
||
|
// allocation. In theory we may find someone who is
|
||
|
// using it as 128-bit ints or the like.
|
||
|
// However I scanned the Illumos kernel source and
|
||
|
// everyone using it appears to really be using it for
|
||
|
// strings.
|
||
|
st.StringVal = strndup((*C.char)(unsafe.Pointer(&knp.value)), 16)
|
||
|
case Int32, Int64:
|
||
|
st.IntVal = int64(C.get_named_int(knp))
|
||
|
case Uint32, Uint64:
|
||
|
st.UintVal = uint64(C.get_named_uint(knp))
|
||
|
default:
|
||
|
// TODO: should do better.
|
||
|
panic(fmt.Sprintf("unknown stat type: %d", st.Type))
|
||
|
}
|
||
|
return &st
|
||
|
}
|