-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathkstat_solaris_test.go
497 lines (447 loc) · 13.7 KB
/
kstat_solaris_test.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
//
// All of these tests depend on being able to know what kstats exist
// on any Illumos / Solaris machine. I believe I've selected kstats
// that will always be there, but I could turn out to be wrong.
//
// We do our testing from outside the kstat package, so we see only
// its public API. For our purposes this is good enough.
package kstat_test
import (
"runtime"
"testing"
"time"
"github.com/siebenmann/go-kstat"
)
// Utility functions
// all of these are used for operations that are not supposed to fail,
// because the error message is generic.
// kstat.Open(); fail on error
func start(t *testing.T) *kstat.Token {
tok, err := kstat.Open()
if err != nil {
t.Fatalf("Open failure: %s", err)
}
return tok
}
// Token.Close(); fail on error
func stop(t *testing.T, tok *kstat.Token) {
err := tok.Close()
if err != nil {
t.Fatalf("Close failure: %s", err)
}
}
// Token.Lookup(); fail on error
func lookup(t *testing.T, tok *kstat.Token, module, name string) *kstat.KStat {
ks, err := tok.Lookup(module, -1, name)
if err != nil {
t.Fatalf("lookup failure on %s:-1:%s: %s", module, name, err)
}
return ks
}
// KStat.GetNamed(); fail on error
func kgetnamed(t *testing.T, ks *kstat.KStat, stat string) *kstat.Named {
n, err := ks.GetNamed(stat)
if err != nil {
t.Fatalf("getting '%s' from %s: %s", stat, ks, err)
}
return n
}
// Silliest little test possible.
func TestOpenClose(t *testing.T) {
tok := start(t)
stop(t, tok)
}
// Test Token.Lookup() and KStat.GetNamed() (because we can't test
// the latter without the former)
//
// cpu:0:sys:syscall is a Uint64 and should always be non-zero.
func TestLookupNamed(t *testing.T) {
tok := start(t)
ks := lookup(t, tok, "cpu", "sys")
// Verify that the kstat has the correct label. This doesn't just
// check for us getting the right kstat, it also checks that no
// trailing \x00s or other random garbage has wound up in the
// fields (since they are actually taken from the kernel data,
// not copied from the arguments we gave).
if ks.Module != "cpu" || ks.Name != "sys" || ks.Instance != 0 || ks.Class != "misc" {
t.Fatalf("cpu:0:sys kstat has bad module/instance/name/class value(s): %#v", ks)
}
n, err := ks.GetNamed("syscall")
if err != nil {
t.Fatalf("1st getting cpu:-1:sys:syscall failure: %s", err)
}
if n.Type != kstat.Uint64 {
t.Fatalf("%s has wrong type: %s", n, n.Type)
}
// Non-set fields should be zero valued, set field should
// have a non-zero value.
if n.UintVal == 0 || n.StringVal != "" || n.IntVal != 0 {
t.Fatalf("%s value fields are not right: %#v", n, n)
}
if n.Name != "syscall" {
t.Fatalf("%s kstat name mismatch: should be \"syscall\", is %q.", ks, n.Name)
}
// Do it directly:
n1, err := tok.GetNamed("cpu", -1, "sys", "syscall")
if err != nil {
t.Fatalf("2nd getting cpu:-1:sys:syscall' failure: %s", err)
}
// we cannot look at UintVal because it may or may not change (!?)
// answer: tok.GetNamed() does tok.Lookup() which does ks.Refresh()
// behind the scenes, which updates the kstat data.
if n1.Type != n.Type || n1.StringVal != n.StringVal || n1.IntVal != n.IntVal {
t.Fatalf("inconsistent values between original and tok.GetNamed: %#v vs %#v", n, n1)
}
stop(t, tok)
}
// Test that KStat.Refresh() seems to do something. This is a bit
// chancy, because we have to find a kstat that's guaranteed to
// change. We pick unix:*:system_misc:clk_intr and sleep for a
// second, because that seems like a pretty good bet.
//
// Note that picking eg cpu:0:sys:syscalls is risky, because it's a
// per-CPU statistic and in a multi-cpu system that CPU may not see
// syscalls ... or it may.
func TestRefresh(t *testing.T) {
tok := start(t)
ks := lookup(t, tok, "unix", "system_misc")
n := kgetnamed(t, ks, "clk_intr")
if n.Type != kstat.Uint32 {
t.Fatalf("%s has wrong type: %s", n, n.Type)
}
osnap := ks.Snaptime
time.Sleep(time.Second * 1)
err := ks.Refresh()
if err != nil {
t.Fatalf("%s refresh error: %s", ks, err)
}
if osnap == ks.Snaptime {
t.Fatalf("%s snaptime did not change: %d", ks, osnap)
}
n2 := kgetnamed(t, ks, "clk_intr")
if n2.UintVal == n.UintVal {
t.Fatalf("clk_intr count did not change: still %d\n", n2.UintVal)
}
stop(t, tok)
}
// Explicitly test that a .Lookup() calls .Refresh() aka kstat_read()
// and so sets ks.Snaptime
func TestLookupSnaptime(t *testing.T) {
tok := start(t)
ks := lookup(t, tok, "cpu", "sys")
if ks.Snaptime <= 0 {
t.Fatalf("%s Snaptime is zero (unset) after .Lookup()", ks)
}
stop(t, tok)
}
// General testing of Snaptime(s) not updating and then updating.
func TestSnaptimes(t *testing.T) {
tok := start(t)
ks := lookup(t, tok, "cpu", "sys")
osnap := ks.Snaptime
n := kgetnamed(t, ks, "syscall")
if osnap != ks.Snaptime {
t.Fatalf("getting %s changed KStat snaptime: %d vs %d", n, osnap, ks.Snaptime)
}
if n.Snaptime != ks.Snaptime {
t.Fatalf("%s snaptime not KStat snaptime: %d vs %d", n, n.Snaptime, ks.Snaptime)
}
n2 := kgetnamed(t, ks, "sysread")
if n2.Snaptime != n.Snaptime {
t.Fatalf("%s snaptime not %s snaptime: %d vs %d", n2, n, n2.Snaptime, n.Snaptime)
}
err := ks.Refresh()
if err != nil {
t.Fatalf("%s Refresh() error: %s", ks, err)
}
if osnap == ks.Snaptime {
t.Fatalf("%s Snaptime not updated after Refresh", ks)
}
stop(t, tok)
}
// Test that we do nothing after Token.Close().
func TestPostClose(t *testing.T) {
tok := start(t)
ks := lookup(t, tok, "cpu", "sys")
stop(t, tok)
_, err := tok.Lookup("cpu", -1, "sys")
if err == nil {
t.Fatalf("Lookup succeeds after Close")
}
err = ks.Refresh()
if err == nil {
t.Fatalf("KStat Refresh succeeds after Close")
}
res := tok.All()
if len(res) > 0 {
t.Fatalf("tok.All succeeds after Close")
}
_, err = ks.GetNamed("trap")
if err == nil {
t.Fatalf("ks.GetNamed succeeds after Close")
}
_, err = ks.AllNamed()
if err == nil {
t.Fatalf("ks.AllNamed succeeds after Close")
}
}
// Do a very simple test of Token.All() and KStat.AllNamed()
func TestAlls(t *testing.T) {
tok := start(t)
lst := tok.All()
if len(lst) == 0 {
t.Fatalf("tok.All gave us a zero-length list")
}
ks := lookup(t, tok, "cpu", "sys")
l2, err := ks.AllNamed()
if err != nil {
t.Fatalf("%s AllNamed failed: %s", ks, err)
}
if len(l2) == 0 {
t.Fatalf("%s AllNamed was empty", ks)
}
stop(t, tok)
}
// Test that we cannot do GetNamed() or AllNamed() on things that are
// not named stats.
// We use unix:0:vminfo as our test kstat for this, and also try to
// test against sd:0:sd0 (which should be an IoStat).
func TestNotNamed(t *testing.T) {
tok := start(t)
ks := lookup(t, tok, "unix", "vminfo")
if ks.Type != 0 {
t.Fatalf("unix:-1:vminfo has crazy type: %#v", ks)
}
// kstat -p says that this is a real field, but of course it
// doesn't actually exist as a named field; the name is a
// structure element and is mocked up by kstat(1).
r, err := ks.GetNamed("swap_alloc")
if err == nil {
t.Fatalf("getting %s as a named succeeded", r)
}
_, err = ks.AllNamed()
if err == nil {
t.Fatalf("ks.AllNamed() on %s succeeded", ks)
}
// Checking for success on lookup is paranoid, but ehh.
ks, err = tok.Lookup("sd", 0, "sd0")
if err != nil && ks.Type == kstat.IoStat {
_, err = ks.AllNamed()
if err == nil {
t.Fatalf("ks.AllNamed on %s succeeded", ks)
}
}
stop(t, tok)
}
// Test that getting invalid kstats or invalid named fields in
// valid kstats fails.
func TestNoSuch(t *testing.T) {
tok := start(t)
ks := lookup(t, tok, "cpu", "sys")
res, err := ks.GetNamed("nosuch")
if err == nil {
t.Fatalf("ks.GetNamed succeeded: %#v", res)
}
res2, err := tok.Lookup("nosuch", -1, "nosuch")
if err == nil {
t.Fatalf("tok.Lookup succeeded: %#v", res2)
}
stop(t, tok)
}
// Test empty-string lookups in both module and name portions.
// These should map to NULLs in the underlying kstat_lookup()
// call and then give us (predictable) wildcard results.
func TestWildcard(t *testing.T) {
tok := start(t)
// this should be cpu:0:sys, I believe
res, err := tok.Lookup("", -1, "sys")
if err != nil {
t.Fatalf("*:-1:sys lookup failed: %s", err)
}
if res.Instance != 0 || res.Module != "cpu" {
t.Fatalf("unexpected lookup return 1: %#v", res)
}
// this should be acpi:0:acpi.
res, err = tok.Lookup("acpi", -1, "")
if err != nil {
t.Fatalf("acpi:-1:* lookup failed: %s", err)
}
if res.Instance != 0 || res.Name != "acpi" {
t.Fatalf("unexpected lookup return 2: %#v", res)
}
stop(t, tok)
}
// Test that a given kstat has a constant KStat structure, even
// when looked up through multiple paths.
func TestSameKStat(t *testing.T) {
tok := start(t)
ks := lookup(t, tok, "cpu", "sys")
k2 := lookup(t, tok, "cpu", "sys")
if ks != k2 {
t.Fatalf("two lookups returned different KStat: %p %p", ks, k2)
}
n, err := tok.GetNamed("cpu", -1, "sys", "syscall")
if err != nil {
t.Fatalf("getnamed cpu:-1:sys:syscall failed: %s", err)
}
if ks != n.KStat {
t.Fatalf("lookup returned a different KStat than via GetNamed: %p %p", ks, n.KStat)
}
n2, err := tok.GetNamed("cpu", -1, "sys", "syscall")
if err != nil {
t.Fatalf("2nd getnammed cpu:-1:sys:syscall failed: %s", err)
}
if ks != n2.KStat {
t.Fatalf("second GetNamed returned a different KStat: %p %p", ks, n2.KStat)
}
stop(t, tok)
}
// Token.GetNamed; fail on error
func getnamed(t *testing.T, tok *kstat.Token, module, name, stat string) *kstat.Named {
n, err := tok.GetNamed(module, -1, name, stat)
if err != nil {
t.Fatalf("getnamed %s:-1:%s:%s failed: %s", module, name, stat, err)
}
return n
}
// Test named kstat stats other than Uint*
//
// We assume there will always be a cpu_info:*:cpu_info0 kstat, although
// maybe there are machines with CPUs but no 0. I wave my hands.
//
// We also assume that CPU 0 is online at one point.
func TestNamedTypes(t *testing.T) {
tok := start(t)
n := getnamed(t, tok, "cpu_info", "cpu_info0", "state")
if n.Type != kstat.CharData || n.StringVal == "" || n.UintVal != 0 || n.IntVal != 0 {
t.Fatalf("bad type or value for %s %s: %#v", n, n.Type, n)
}
// This test may create false negatives if cpu0 is offline,
// but I want a test to verify that the CharData copying code
// does not add junk on the end and does copy the whole
// string, since I had just such a bug at one point.
if n.StringVal != "on-line" {
t.Fatalf("bad value for %s: %#v", n, n)
}
n = getnamed(t, tok, "cpu_info", "cpu_info0", "brand")
if n.Type != kstat.String || n.StringVal == "" || n.UintVal != 0 || n.IntVal != 0 {
t.Fatalf("bad type or value for %s %s: %#v", n, n.Type, n)
}
n = getnamed(t, tok, "cpu_info", "cpu_info0", "family")
if n.Type != kstat.Int32 || n.StringVal != "" || n.UintVal != 0 || n.IntVal == 0 {
t.Fatalf("bad type or value for %s %s: %#v", n, n.Type, n)
}
n = getnamed(t, tok, "cpu_info", "cpu_info0", "clock_MHz")
if n.Type != kstat.Int64 || n.StringVal != "" || n.UintVal != 0 || n.IntVal == 0 {
t.Fatalf("bad type or value for %s %s: %#v", n, n.Type, n)
}
stop(t, tok)
}
//
// Find and retrieve an IO kstat.
// We try to look for stats having some non-zero value.
//
// Since apparently sd:0:sd0 is guaranteed to be the boot disk, we
// also specifically retrieve it and try to verify that as many fields
// as possible are good.
func TestDiskStat(t *testing.T) {
tok := start(t)
tlst := tok.All()
foundone := false
nonzero := false
for _, ks := range tlst {
if ks.Type != kstat.IoStat {
continue
}
foundone = true
io, err := ks.GetIO()
if err != nil {
t.Fatalf("%s GetIO failed: %s", ks, err)
}
if ks.Snaptime == 0 {
t.Fatalf("%s Snaptime is still 0", ks)
}
if io.Reads > 0 || io.Writes > 0 {
nonzero = true
break
}
}
if !foundone {
t.Fatalf("failed to find a single IO KStat, should be impossible?")
}
if !nonzero {
t.Fatalf("could not find a single IO KStat with read or write activity, should be impossible?")
}
// Okay, try sd:0:sd0 specifically.
ks := lookup(t, tok, "sd", "sd0")
io, err := ks.GetIO()
if err != nil {
t.Fatalf("%s GetIO error: %s", io, err)
}
// We assume the boot disk has to have some IO, right?
if io.Nread == 0 || io.Nwritten == 0 || io.Rlastupdate == 0 || io.Rlentime == 0 || io.Wtime == 0 {
t.Fatalf("%s IO values are odd: %+v", ks, io)
}
stop(t, tok)
}
// GetIO on a KStat for a closed token should fail, as other things do.
func TestDiskAfterClose(t *testing.T) {
tok := start(t)
ks := lookup(t, tok, "sd", "sd0")
if ks.Type != kstat.IoStat {
t.Fatalf("%s not an IoStat, is a %s", ks, ks.Type)
}
stop(t, tok)
_, err := ks.GetIO()
if err == nil {
t.Fatalf("%s GetIO succeeded after Close", ks)
}
}
// Calling GetIO should update the KStat Snaptime, because it should
// refresh the data. We check since we explicitly guarantee this in
// the documentation.
func TestDiskSnaptime(t *testing.T) {
tok := start(t)
ks := lookup(t, tok, "sd", "sd0")
osnap := ks.Snaptime
_, err := ks.GetIO()
if err != nil {
t.Fatalf("%s GetIO failed: %s", ks, err)
}
if ks.Snaptime == osnap {
t.Fatalf("%s Snaptime did not change after GetIO", ks)
}
stop(t, tok)
}
// This tries to test that that our usage of runtime.SetFinalizer()
// at least doesn't crash when we try to call the finalizer.
func TestTokenFinalizer(t *testing.T) {
tok := start(t)
tok = nil
runtime.GC()
_ = tok
}
// Test that we can at least call .Update() without error and that it
// fails after a .Close(). Since we can't force the kernel to create
// or delete kstats, this is the best we can do.
func TestUpdate(t *testing.T) {
tok := start(t)
// We assume that unix:0:sysinfo will never disappear in a
// kstat chain update.
ks := lookup(t, tok, "unix", "sysinfo")
_, err := tok.Update()
if err != nil {
t.Fatalf("token Update() failed: %s", err)
}
if !ks.Valid() {
t.Fatalf("%s invalid after Update()", ks)
}
stop(t, tok)
_, err = tok.Update()
if err == nil {
t.Fatalf("token Update() succeeded after Close()")
}
if ks.Valid() {
t.Fatalf("%s valid after Close()", ks)
}
}