Add zfs pool stats collection.

This commit is contained in:
Allen Petersen 2015-12-08 01:50:42 -08:00
parent d62e63c448
commit eb78b9268f
2 changed files with 207 additions and 55 deletions

View File

@ -1,6 +1,7 @@
package zfs package zfs
import ( import (
"fmt"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
@ -12,6 +13,7 @@ import (
type Zfs struct { type Zfs struct {
KstatPath string KstatPath string
KstatMetrics []string KstatMetrics []string
PoolMetrics bool
} }
var sampleConfig = ` var sampleConfig = `
@ -22,6 +24,9 @@ var sampleConfig = `
# By default, telegraf gather all zfs stats # By default, telegraf gather all zfs stats
# If not specified, then default is: # If not specified, then default is:
# kstatMetrics = ["arcstats", "zfetchstats", "vdev_cache_stats"] # kstatMetrics = ["arcstats", "zfetchstats", "vdev_cache_stats"]
#
# By default, don't gather zpool stats
# poolMetrics = false
` `
func (z *Zfs) SampleConfig() string { func (z *Zfs) SampleConfig() string {
@ -32,7 +37,7 @@ func (z *Zfs) Description() string {
return "Read metrics of ZFS from arcstats, zfetchstats and vdev_cache_stats" return "Read metrics of ZFS from arcstats, zfetchstats and vdev_cache_stats"
} }
func getTags(kstatPath string) map[string]string { func getPoolStats(kstatPath string, poolMetrics bool, acc plugins.Accumulator) (map[string]string, error) {
var pools string var pools string
poolsDirs, _ := filepath.Glob(kstatPath + "/*/io") poolsDirs, _ := filepath.Glob(kstatPath + "/*/io")
for _, poolDir := range poolsDirs { for _, poolDir := range poolsDirs {
@ -42,8 +47,49 @@ func getTags(kstatPath string) map[string]string {
pools += "::" pools += "::"
} }
pools += pool pools += pool
if poolMetrics {
err := readPoolStats(poolDir, pool, acc)
if err != nil {
return nil, err
} }
return map[string]string{"pools": pools} }
}
return map[string]string{"pools": pools}, nil
}
func readPoolStats(poolIoPath string, poolName string, acc plugins.Accumulator) error {
lines, err := internal.ReadLines(poolIoPath)
if err != nil {
return err
}
if len(lines) != 3 {
return err
}
keys := strings.Fields(lines[1])
values := strings.Fields(lines[2])
keyCount := len(keys)
if keyCount != len(values) {
return fmt.Errorf("Key and value count don't match Keys:%v Values:%v", keys, values)
}
tag := map[string]string{"pool": poolName}
for i := 0; i < keyCount; i++ {
value, err := strconv.ParseInt(values[i], 10, 64)
if err != nil {
return err
}
acc.Add(keys[i], value, tag)
}
return nil
} }
func (z *Zfs) Gather(acc plugins.Accumulator) error { func (z *Zfs) Gather(acc plugins.Accumulator) error {
@ -57,7 +103,10 @@ func (z *Zfs) Gather(acc plugins.Accumulator) error {
kstatPath = "/proc/spl/kstat/zfs" kstatPath = "/proc/spl/kstat/zfs"
} }
tags := getTags(kstatPath) tags, err := getPoolStats(kstatPath, z.PoolMetrics, acc)
if err != nil {
return err
}
for _, metric := range kstatMetrics { for _, metric := range kstatMetrics {
lines, err := internal.ReadLines(kstatPath + "/" + metric) lines, err := internal.ReadLines(kstatPath + "/" + metric)

View File

@ -1,7 +1,6 @@
package zfs package zfs
import ( import (
"fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"testing" "testing"
@ -121,6 +120,10 @@ delegations 4 0
hits 4 0 hits 4 0
misses 4 0 misses 4 0
` `
const pool_ioContents = `11 3 0x00 1 80 2225326830828 32953476980628
nread nwritten reads writes wtime wlentime wupdate rtime rlentime rupdate wcnt rcnt
1884160 6450688 22 978 272187126 2850519036 2263669418655 424226814 2850519036 2263669871823 0 0
`
var testKstatPath = os.TempDir() + "/telegraf/proc/spl/kstat/zfs" var testKstatPath = os.TempDir() + "/telegraf/proc/spl/kstat/zfs"
@ -129,6 +132,50 @@ type metrics struct {
value int64 value int64
} }
func TestZfsPoolMetrics(t *testing.T) {
err := os.MkdirAll(testKstatPath, 0755)
require.NoError(t, err)
err = os.MkdirAll(testKstatPath+"/HOME", 0755)
require.NoError(t, err)
err = ioutil.WriteFile(testKstatPath+"/HOME/io", []byte(pool_ioContents), 0644)
require.NoError(t, err)
err = ioutil.WriteFile(testKstatPath+"/arcstats", []byte(arcstatsContents), 0644)
require.NoError(t, err)
poolMetrics := getPoolMetrics()
var acc testutil.Accumulator
//one pool, all metrics
tags := map[string]string{
"pool": "HOME",
}
z := &Zfs{KstatPath: testKstatPath, KstatMetrics: []string{"arcstats"}}
err = z.Gather(&acc)
require.NoError(t, err)
for _, metric := range poolMetrics {
assert.True(t, !acc.HasIntValue(metric.name), metric.name)
assert.True(t, !acc.CheckTaggedValue(metric.name, metric.value, tags))
}
z = &Zfs{KstatPath: testKstatPath, KstatMetrics: []string{"arcstats"}, PoolMetrics: true}
err = z.Gather(&acc)
require.NoError(t, err)
for _, metric := range poolMetrics {
assert.True(t, acc.HasIntValue(metric.name), metric.name)
assert.True(t, acc.CheckTaggedValue(metric.name, metric.value, tags))
}
err = os.RemoveAll(os.TempDir() + "/telegraf")
require.NoError(t, err)
}
func TestZfsGeneratesMetrics(t *testing.T) { func TestZfsGeneratesMetrics(t *testing.T) {
err := os.MkdirAll(testKstatPath, 0755) err := os.MkdirAll(testKstatPath, 0755)
require.NoError(t, err) require.NoError(t, err)
@ -148,7 +195,60 @@ func TestZfsGeneratesMetrics(t *testing.T) {
err = ioutil.WriteFile(testKstatPath+"/vdev_cache_stats", []byte(vdev_cache_statsContents), 0644) err = ioutil.WriteFile(testKstatPath+"/vdev_cache_stats", []byte(vdev_cache_statsContents), 0644)
require.NoError(t, err) require.NoError(t, err)
intMetrics := []*metrics{ intMetrics := getKstatMetrics()
var acc testutil.Accumulator
//one pool, all metrics
tags := map[string]string{
"pools": "HOME",
}
z := &Zfs{KstatPath: testKstatPath}
err = z.Gather(&acc)
require.NoError(t, err)
for _, metric := range intMetrics {
assert.True(t, acc.HasIntValue(metric.name), metric.name)
assert.True(t, acc.CheckTaggedValue(metric.name, metric.value, tags))
}
//two pools, all metrics
err = os.MkdirAll(testKstatPath+"/STORAGE", 0755)
require.NoError(t, err)
err = ioutil.WriteFile(testKstatPath+"/STORAGE/io", []byte(""), 0644)
require.NoError(t, err)
tags = map[string]string{
"pools": "HOME::STORAGE",
}
z = &Zfs{KstatPath: testKstatPath}
err = z.Gather(&acc)
require.NoError(t, err)
for _, metric := range intMetrics {
assert.True(t, acc.HasIntValue(metric.name), metric.name)
assert.True(t, acc.CheckTaggedValue(metric.name, metric.value, tags))
}
//two pools, one metric
z = &Zfs{KstatPath: testKstatPath, KstatMetrics: []string{"arcstats"}}
err = z.Gather(&acc)
require.NoError(t, err)
for _, metric := range intMetrics {
assert.True(t, acc.HasIntValue(metric.name), metric.name)
assert.True(t, acc.CheckTaggedValue(metric.name, metric.value, tags))
}
err = os.RemoveAll(os.TempDir() + "/telegraf")
require.NoError(t, err)
}
func getKstatMetrics() []*metrics {
return []*metrics{
{ {
name: "arcstats_hits", name: "arcstats_hits",
value: 5968846374, value: 5968846374,
@ -550,54 +650,57 @@ func TestZfsGeneratesMetrics(t *testing.T) {
value: 0, value: 0,
}, },
} }
var acc testutil.Accumulator
//one pool, all metrics
tags := map[string]string{
"pools": "HOME",
} }
z := &Zfs{KstatPath: testKstatPath} func getPoolMetrics() []*metrics {
err = z.Gather(&acc) return []*metrics{
require.NoError(t, err) {
name: "nread",
for _, metric := range intMetrics { value: 1884160,
fmt.Println(metric.name) },
assert.True(t, acc.HasIntValue(metric.name), metric.name) {
assert.True(t, acc.CheckTaggedValue(metric.name, metric.value, tags)) name: "nwritten",
value: 6450688,
},
{
name: "reads",
value: 22,
},
{
name: "writes",
value: 978,
},
{
name: "wtime",
value: 272187126,
},
{
name: "wlentime",
value: 2850519036,
},
{
name: "wupdate",
value: 2263669418655,
},
{
name: "rtime",
value: 424226814,
},
{
name: "rlentime",
value: 2850519036,
},
{
name: "rupdate",
value: 2263669871823,
},
{
name: "wcnt",
value: 0,
},
{
name: "rcnt",
value: 0,
},
} }
//two pools, all metrics
err = os.MkdirAll(testKstatPath+"/STORAGE", 0755)
require.NoError(t, err)
err = ioutil.WriteFile(testKstatPath+"/STORAGE/io", []byte(""), 0644)
require.NoError(t, err)
tags = map[string]string{
"pools": "HOME::STORAGE",
}
z = &Zfs{KstatPath: testKstatPath}
err = z.Gather(&acc)
require.NoError(t, err)
for _, metric := range intMetrics {
assert.True(t, acc.HasIntValue(metric.name), metric.name)
assert.True(t, acc.CheckTaggedValue(metric.name, metric.value, tags))
}
//two pools, one metric
z = &Zfs{KstatPath: testKstatPath, KstatMetrics: []string{"arcstats"}}
err = z.Gather(&acc)
require.NoError(t, err)
for _, metric := range intMetrics {
assert.True(t, acc.HasIntValue(metric.name), metric.name)
assert.True(t, acc.CheckTaggedValue(metric.name, metric.value, tags))
}
err = os.RemoveAll(os.TempDir() + "/telegraf")
require.NoError(t, err)
} }