Skip to content

Commit 61ab9d9

Browse files
committed
Add support for raw kstat statistics
We have specific access functions and defined structures for all of what I consider the common and useful ones and a couple of escape hatches for roll-your-own access to others. Parts of the API and the implementation are provisional, especially around CopyTo.
1 parent cf499b3 commit 61ab9d9

File tree

4 files changed

+483
-23
lines changed

4 files changed

+483
-23
lines changed

README

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,17 @@ Go-kstat provides a Go API for the kstat kernel statistics system
22
on Solaris, Illumos, OmniOS, and other Solaris derived systems. For
33
general information on kstats, see the kstat(1) and kstat(3kstat)
44
manpages. For more documentation on the details of the package, see
5-
doc.go and kstat_solaris.go.
5+
doc.go, kstat_solaris.go, types_solaris_amd64.go, and raw_solaris.go.
66

77
This package is quite young, so the API may well change as I and
88
other people gain experience with using it.
99

10-
At the moment the API only supports access to what are called 'named'
11-
kstat statistics and IO statistics, which covers almost all kstats but
12-
leave some potentially interesting ones currently unavailable. See the
13-
SUPPORTED AND UNSUPPORTED KSTAT TYPES section of doc.go for more details.
10+
The API supports access to 'named' kstat statistics, IO statistics,
11+
and the most common and useful sorts of 'raw' kstat statistics
12+
(unix:0:sysinfo, unix:0:vminfo, unix:0:var, and mnt:*:mntinfo).
13+
Other raw kstat statistics are not explicitly supported, but the
14+
API provides some escape hatches for access to custom raw
15+
statistics.
1416

1517
This is a cgo-based package so it can't be cross compiled like a regular
1618
Go package. It may also have bugs with memory management, since it

doc.go

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44
// statistics. For more documentation on kstats, see kstat(1) and
55
// kstat(3kstat).
66
//
7-
// The package can retrieve what are called 'named' kstat statistics
8-
// and IO statistics, which covers almost all kstats you will normally
9-
// find in the kernel. You can see the names and types of other
10-
// kstats, but not currently retrieve data for them. Named statistics
11-
// are the most common type for general information; IO statistics are
12-
// exported by disks and some other things.
7+
// The package can retrieve what are called 'named' kstat statistics,
8+
// IO statistics, and the most common additional types of 'raw'
9+
// statistics, which covers almost all kstats you will normally find
10+
// in the kernel. You can see the names and types of other kstats, but
11+
// not currently retrieve data for them. Named statistics are the most
12+
// common type for general information; IO statistics are exported by
13+
// disks and some other things. Supported additional raw kstats are
14+
// unix:0:sysinfo, unix:0:vminfo, unix:0:var, and mnt:*:mntinfo.
1315
//
1416
// General usage for named statistics: call Open() to obtain a Token,
1517
// then call GetNamed() on it to obtain Named(s) for specific
@@ -51,7 +53,6 @@
5153
// because we do more memory allocation and deallocation than a C
5254
// program would (partly because we prioritize not leaking memory).
5355
//
54-
//
5556
// API LIMITATIONS AND TODOS
5657
//
5758
// Although we support refreshing specific kstats via KStat.Refresh(),
@@ -65,24 +66,23 @@
6566
// We support named kstats and IO kstats (KSTAT_TYPE_NAMED and
6667
// KSTAT_TYPE_IO / kstat_io_t respectively). kstat(1) also knows about
6768
// a number of magic specific 'raw' stats (which are generally custom
68-
// C structs); the most useful of these are probably unix:0:sysinfo,
69-
// unix:0:vminfo, and unix:0:var. We may support those three in the
70-
// future.
69+
// C structs); of these we support unix:0:sysinfo, unix:0:vminfo,
70+
// unix:0:var, and mnt:*:mntinfo for NFS filesystem mounts.
7171
//
7272
// In theory kstat supports general timer and interrupt stats. In
7373
// practice there is no use of KSTAT_TYPE_TIMER in the current Illumos
7474
// kernel source and very little use of KSTAT_TYPE_INTR (mostly by
7575
// very old hardware drivers, although the vioif driver uses it too).
76+
// Since I can't test KSTAT_TYPE_INTR stats, we don't currently
77+
// support it.
7678
//
77-
// There are also a few additional KSTAT_TYPE_RAW raw stats; a few are
78-
// useful and several are effectively obsolete. For various reasons we
79-
// don't currently support any of them and are unlikely to in the
80-
// immediate future. These specific raw stats are listed in
79+
// There are also a few additional KSTAT_TYPE_RAW raw stats that we
80+
// don't support, mostly because they seem to be effectively obsolete.
81+
// These specific raw stats can be found listed in
8182
// cmd/stat/kstat/kstat.h in the ks_raw_lookup array. See
82-
// cmd/stat/kstat/kstat.c for how they're interpreted.
83-
//
84-
// (Really, the only one you might miss is nfs:*:mntinfo, and that's
85-
// extra work to support due to a current cgo limitation.)
83+
// cmd/stat/kstat/kstat.c for how they're interpreted. If you need
84+
// access to one of these kstats, the KStat.CopyTo() and KStat.Raw()
85+
// methods give you an escape hatch to roll your own.
8686
//
8787
// Author: Chris Siebenmann
8888
// https://github.com/siebenmann/go-kstat

raw_solaris.go

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
//
2+
// Really raw access to KStat data
3+
4+
package kstat
5+
6+
// #cgo LDFLAGS: -lkstat
7+
//
8+
// #include <sys/types.h>
9+
// #include <stdlib.h>
10+
// #include <strings.h>
11+
// #include <kstat.h>
12+
// #include <nfs/nfs_clnt.h>
13+
//
14+
import "C"
15+
import (
16+
"errors"
17+
"fmt"
18+
"reflect"
19+
"unsafe"
20+
)
21+
22+
// Raw is the raw data of a KStat. The actual bytes are in Data;
23+
// Ndata is kstat_t.ks_ndata, and is not normally useful.
24+
//
25+
// Note that with RawStat KStats, it turns out that Ndata == len(Data).
26+
// This is contrary to its meaning for other types of kstats.
27+
type Raw struct {
28+
Data []byte
29+
Ndata uint64
30+
Snaptime int64
31+
KStat *KStat
32+
}
33+
34+
// TODO: better functionality split here
35+
func (k *KStat) prep() error {
36+
if k.invalid() {
37+
return errors.New("invalid KStat or closed token")
38+
}
39+
40+
// Do the initial load of the data if necessary.
41+
if k.ksp.ks_data == nil {
42+
if err := k.Refresh(); err != nil {
43+
return err
44+
}
45+
}
46+
return nil
47+
}
48+
49+
// Raw returns the raw byte data of a KStat. It may be called on any
50+
// KStat. It does not refresh the KStat's data.
51+
func (k *KStat) Raw() (*Raw, error) {
52+
if err := k.prep(); err != nil {
53+
return nil, err
54+
}
55+
r := Raw{}
56+
r.KStat = k
57+
r.Snaptime = k.Snaptime
58+
r.Ndata = uint64(k.ksp.ks_ndata)
59+
// The forced C.int() conversion is dangerous, because C.int
60+
// is not necessarily large enough to contain a
61+
// size_t. However this is the interface that Go gives us, so
62+
// we live with it.
63+
r.Data = C.GoBytes(unsafe.Pointer(k.ksp.ks_data), C.int(k.ksp.ks_data_size))
64+
return &r, nil
65+
}
66+
67+
func (tok *Token) prepunix(name string, size uintptr) (*KStat, error) {
68+
k, err := tok.Lookup("unix", 0, name)
69+
if err != nil {
70+
return nil, err
71+
}
72+
// TODO: handle better?
73+
if k.ksp.ks_type != C.KSTAT_TYPE_RAW {
74+
return nil, fmt.Errorf("%s is wrong type %s", k, k.Type)
75+
}
76+
if uintptr(k.ksp.ks_data_size) != size {
77+
return nil, fmt.Errorf("%s is wrong size %d (should be %d)", k, k.ksp.ks_data_size, size)
78+
}
79+
return k, nil
80+
}
81+
82+
// Sysinfo returns the KStat and the statistics from unix:0:sysinfo.
83+
// It always returns a current, refreshed copy.
84+
func (tok *Token) Sysinfo() (*KStat, *Sysinfo, error) {
85+
var si Sysinfo
86+
k, err := tok.prepunix("sysinfo", unsafe.Sizeof(si))
87+
if err != nil {
88+
return nil, nil, err
89+
}
90+
si = *((*Sysinfo)(k.ksp.ks_data))
91+
return k, &si, nil
92+
}
93+
94+
// Vminfo returns the KStat and the statistics from unix:0:vminfo.
95+
// It always returns a current, refreshed copy.
96+
func (tok *Token) Vminfo() (*KStat, *Vminfo, error) {
97+
var vi Vminfo
98+
k, err := tok.prepunix("vminfo", unsafe.Sizeof(vi))
99+
if err != nil {
100+
return nil, nil, err
101+
}
102+
vi = *((*Vminfo)(k.ksp.ks_data))
103+
return k, &vi, nil
104+
}
105+
106+
// Var returns the KStat and the statistics from unix:0:var.
107+
// It always returns a current, refreshed copy.
108+
func (tok *Token) Var() (*KStat, *Var, error) {
109+
var vi Var
110+
k, err := tok.prepunix("var", unsafe.Sizeof(vi))
111+
if err != nil {
112+
return nil, nil, err
113+
}
114+
vi = *((*Var)(k.ksp.ks_data))
115+
return k, &vi, nil
116+
}
117+
118+
// GetMntinfo retrieves a Mntinfo struct from a nfs:*:mntinfo KStat.
119+
// It does not force a refresh of the KStat.
120+
func (k *KStat) GetMntinfo() (*Mntinfo, error) {
121+
var mi Mntinfo
122+
if err := k.prep(); err != nil {
123+
return nil, err
124+
}
125+
if k.Type != RawStat || k.Module != "nfs" || k.Name != "mntinfo" {
126+
return nil, errors.New("KStat is not a Mntinfo kstat")
127+
}
128+
if uintptr(k.ksp.ks_data_size) != unsafe.Sizeof(mi) {
129+
return nil, fmt.Errorf("KStat is wrong size %d (should be %d)", k.ksp.ks_data_size, unsafe.Sizeof(mi))
130+
}
131+
mi = *((*Mntinfo)(k.ksp.ks_data))
132+
return &mi, nil
133+
}
134+
135+
//
136+
// Support for copying semi-arbitrary structures out of raw
137+
// KStats.
138+
//
139+
140+
// safeThing returns true if a given type is either a simple defined
141+
// size primitive integer type or an array and/or struct composed
142+
// entirely of safe things. A safe thing is entirely self contained
143+
// and may be initialized from random memory without breaking Go's
144+
// memory safety (although the values it contains may be garbage).
145+
//
146+
func safeThing(t reflect.Type) bool {
147+
switch t.Kind() {
148+
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
149+
return true
150+
case reflect.Array:
151+
// an array is safe if it's an array of something safe
152+
return safeThing(t.Elem())
153+
case reflect.Struct:
154+
// a struct is safe if all its components are safe
155+
for i := 0; i < t.NumField(); i++ {
156+
if !safeThing(t.Field(i).Type) {
157+
return false
158+
}
159+
}
160+
return true
161+
default:
162+
// other things are not safe.
163+
return false
164+
}
165+
}
166+
167+
// TODO: add floats to the supported list? It's unlikely to be needed
168+
// but it should just work.
169+
170+
// CopyTo copies a RawStat KStat into a struct that you supply a
171+
// pointer to. The size of the struct must exactly match the size of
172+
// the RawStat's data.
173+
//
174+
// CopyStat imposes conditions on the struct that you are copying to:
175+
// it must be composed entirely of primitive integer types with defined
176+
// sizes (intN and uintN), or arrays and structs that ultimately only
177+
// contain them. All fields should be exported.
178+
//
179+
// If you give CopyStat a bad argument, it generally panics.
180+
//
181+
// This API is provisional and may be changed or deleted.
182+
func (k *KStat) CopyTo(ptr interface{}) error {
183+
if err := k.prep(); err != nil {
184+
return err
185+
}
186+
187+
if k.Type != RawStat {
188+
return errors.New("KStat is not a RawStat")
189+
}
190+
191+
// Validity checks: not nil value, not nil pointer value,
192+
// is a pointer to struct.
193+
if ptr == nil {
194+
panic("CopyTo given nil pointer")
195+
}
196+
vp := reflect.ValueOf(ptr)
197+
if vp.Kind() != reflect.Ptr {
198+
panic("CopyTo not given a pointer")
199+
}
200+
if vp.IsNil() {
201+
panic("CopyTo given nil pointer")
202+
}
203+
dst := vp.Elem()
204+
if dst.Kind() != reflect.Struct {
205+
panic("CopyTo: not pointer to struct")
206+
}
207+
// Is the struct safe to copy into, which means primitive types
208+
// and structs/arrays of primitive types?
209+
if !safeThing(dst.Type()) {
210+
panic("CopyTo: not a safe structure, contains unsupported fields")
211+
}
212+
if !dst.CanSet() {
213+
panic("CopyTo: struct cannot be set for some reason")
214+
}
215+
216+
// Verify that the size of the target struct matches the size
217+
// of the raw KStat.
218+
if uintptr(k.ksp.ks_data_size) != dst.Type().Size() {
219+
return errors.New("struct size does not match KStat size")
220+
}
221+
222+
// The following is exactly the magic that we performed for
223+
// specific types earlier. We take k.ksp.ks_data and turn
224+
// it into a typed pointer to the target object's type:
225+
//
226+
// src := ((*<type>)(k.kps.ks_data))
227+
src := reflect.NewAt(dst.Type(), unsafe.Pointer(k.ksp.ks_data))
228+
229+
// We now dereference that into the destination to copy the
230+
// data:
231+
//
232+
// dst = *src
233+
dst.Set(reflect.Indirect(src))
234+
235+
return nil
236+
}

0 commit comments

Comments
 (0)