Skip to content

Commit

Permalink
Issue influxdata#2616: ZFS Zpool properties on Linux
Browse files Browse the repository at this point in the history
  • Loading branch information
yvasiyarov committed Nov 28, 2019
1 parent c3e3236 commit f3fd6a8
Show file tree
Hide file tree
Showing 5 changed files with 268 additions and 122 deletions.
10 changes: 8 additions & 2 deletions plugins/inputs/zfs/README.md
@@ -1,7 +1,7 @@
# ZFS plugin

This ZFS plugin provides metrics from your ZFS filesystems. It supports ZFS on
Linux and FreeBSD. It gets ZFS stat from `/proc/spl/kstat/zfs` on Linux and
Linux and FreeBSD. It gets ZFS stat from `/proc/spl/kstat/zfs` and zpool on Linux and
from `sysctl` and `zpool` on FreeBSD.

### Configuration:
Expand Down Expand Up @@ -180,7 +180,7 @@ each pool.

#### Pool Metrics (optional)

On Linux (reference: kstat accumulated time and queue length statistics):
On Linux (reference: kstat accumulated time and queue length statistics) and zpool statistics:

- zfs_pool
- nread (integer, bytes)
Expand All @@ -195,6 +195,12 @@ On Linux (reference: kstat accumulated time and queue length statistics):
- rupdate (integer, timestamp)
- wcnt (integer, count)
- rcnt (integer, count)
- allocated (integer, bytes)
- capacity (integer, bytes)
- dedupratio (float, ratio)
- free (integer, bytes)
- size (integer, bytes)
- fragmentation (integer, percent)

On FreeBSD:

Expand Down
99 changes: 99 additions & 0 deletions plugins/inputs/zfs/zfs.go
@@ -1,5 +1,13 @@
package zfs

import (
"bytes"
"fmt"
"os/exec"
"strconv"
"strings"
)

type Sysctl func(metric string) ([]string, error)
type Zpool func() ([]string, error)

Expand Down Expand Up @@ -30,6 +38,97 @@ func (z *Zfs) SampleConfig() string {
return sampleConfig
}

func (z *Zfs) getZpoolStats() (map[string]map[string]interface{}, error) {

poolFields := map[string]map[string]interface{}{}

lines, err := z.zpool()
if err != nil {
return poolFields, err
}

for _, line := range lines {
col := strings.Split(line, "\t")
if len(col) != 8 {
continue
}

health := col[1]
name := col[0]

fields := map[string]interface{}{
"name": name,
"health": health,
}

if health == "UNAVAIL" {

fields["size"] = int64(0)

} else {

size, err := strconv.ParseInt(col[2], 10, 64)
if err != nil {
return poolFields, fmt.Errorf("Error parsing size: %s", err)
}
fields["size"] = size

alloc, err := strconv.ParseInt(col[3], 10, 64)
if err != nil {
return poolFields, fmt.Errorf("Error parsing allocation: %s", err)
}
fields["allocated"] = alloc

free, err := strconv.ParseInt(col[4], 10, 64)
if err != nil {
return poolFields, fmt.Errorf("Error parsing free: %s", err)
}
fields["free"] = free

frag, err := strconv.ParseInt(strings.TrimSuffix(col[5], "%"), 10, 0)
if err != nil { // This might be - for RO devs
frag = 0
}
fields["fragmentation"] = frag

capval, err := strconv.ParseInt(col[6], 10, 0)
if err != nil {
return poolFields, fmt.Errorf("Error parsing capacity: %s", err)
}
fields["capacity"] = capval

dedup, err := strconv.ParseFloat(strings.TrimSuffix(col[7], "x"), 32)
if err != nil {
return poolFields, fmt.Errorf("Error parsing dedupratio: %s", err)
}
fields["dedupratio"] = dedup
}
poolFields[name] = fields
}

return poolFields, nil
}

func (z *Zfs) Description() string {
return "Read metrics of ZFS from arcstats, zfetchstats, vdev_cache_stats, and pools"
}

func run(command string, args ...string) ([]string, error) {
cmd := exec.Command(command, args...)
var outbuf, errbuf bytes.Buffer
cmd.Stdout = &outbuf
cmd.Stderr = &errbuf
err := cmd.Run()

stdout := strings.TrimSpace(outbuf.String())
stderr := strings.TrimSpace(errbuf.String())

if _, ok := err.(*exec.ExitError); ok {
return nil, fmt.Errorf("%s error: %s", command, stderr)
}
return strings.Split(stdout, "\n"), nil
}

func zpool() ([]string, error) {
return run("zpool", []string{"list", "-Hp", "-o", "name,health,size,alloc,free,fragmentation,capacity,dedupratio"}...)
}
88 changes: 10 additions & 78 deletions plugins/inputs/zfs/zfs_freebsd.go
Expand Up @@ -3,9 +3,7 @@
package zfs

import (
"bytes"
"fmt"
"os/exec"
"strconv"
"strings"

Expand All @@ -14,71 +12,25 @@ import (
)

func (z *Zfs) gatherPoolStats(acc telegraf.Accumulator) (string, error) {

lines, err := z.zpool()
poolFields, err := z.getZpoolStats()
if err != nil {
return "", err
}

pools := []string{}
for _, line := range lines {
col := strings.Split(line, "\t")

pools = append(pools, col[0])
for name, _ := range poolFields {
pools = append(pools, name)
}

if z.PoolMetrics {
for _, line := range lines {
col := strings.Split(line, "\t")
if len(col) != 8 {
continue
for name, fields := range poolFields {
tags := map[string]string{
"pool": name,
"health": fields["health"].(string),
}

tags := map[string]string{"pool": col[0], "health": col[1]}
fields := map[string]interface{}{}

if tags["health"] == "UNAVAIL" {

fields["size"] = int64(0)

} else {

size, err := strconv.ParseInt(col[2], 10, 64)
if err != nil {
return "", fmt.Errorf("Error parsing size: %s", err)
}
fields["size"] = size

alloc, err := strconv.ParseInt(col[3], 10, 64)
if err != nil {
return "", fmt.Errorf("Error parsing allocation: %s", err)
}
fields["allocated"] = alloc

free, err := strconv.ParseInt(col[4], 10, 64)
if err != nil {
return "", fmt.Errorf("Error parsing free: %s", err)
}
fields["free"] = free

frag, err := strconv.ParseInt(strings.TrimSuffix(col[5], "%"), 10, 0)
if err != nil { // This might be - for RO devs
frag = 0
}
fields["fragmentation"] = frag

capval, err := strconv.ParseInt(col[6], 10, 0)
if err != nil {
return "", fmt.Errorf("Error parsing capacity: %s", err)
}
fields["capacity"] = capval

dedup, err := strconv.ParseFloat(strings.TrimSuffix(col[7], "x"), 32)
if err != nil {
return "", fmt.Errorf("Error parsing dedupratio: %s", err)
}
fields["dedupratio"] = dedup
}
delete(fields, "name")
delete(fields, "health")

acc.AddFields("zfs_pool", fields, tags)
}
Expand All @@ -93,11 +45,11 @@ func (z *Zfs) Gather(acc telegraf.Accumulator) error {
kstatMetrics = []string{"arcstats", "zfetchstats", "vdev_cache_stats"}
}

tags := map[string]string{}
poolNames, err := z.gatherPoolStats(acc)
if err != nil {
return err
}
tags := map[string]string{"pools": poolNames}
tags["pools"] = poolNames

fields := make(map[string]interface{})
Expand All @@ -117,26 +69,6 @@ func (z *Zfs) Gather(acc telegraf.Accumulator) error {
return nil
}

func run(command string, args ...string) ([]string, error) {
cmd := exec.Command(command, args...)
var outbuf, errbuf bytes.Buffer
cmd.Stdout = &outbuf
cmd.Stderr = &errbuf
err := cmd.Run()

stdout := strings.TrimSpace(outbuf.String())
stderr := strings.TrimSpace(errbuf.String())

if _, ok := err.(*exec.ExitError); ok {
return nil, fmt.Errorf("%s error: %s", command, stderr)
}
return strings.Split(stdout, "\n"), nil
}

func zpool() ([]string, error) {
return run("zpool", []string{"list", "-Hp", "-o", "name,health,size,alloc,free,fragmentation,capacity,dedupratio"}...)
}

func sysctl(metric string) ([]string, error) {
return run("sysctl", []string{"-q", fmt.Sprintf("kstat.zfs.misc.%s", metric)}...)
}
Expand Down

0 comments on commit f3fd6a8

Please sign in to comment.