Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for diskio tags & naming #1453

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- [#2137](https://github.com/influxdata/telegraf/pull/2137): Added userstats to mysql input plugin.
- [#2179](https://github.com/influxdata/telegraf/pull/2179): Added more InnoDB metric to MySQL plugin.
- [#2251](https://github.com/influxdata/telegraf/pull/2251): InfluxDB output: use own client for improved through-put and less allocations.
- [#1453](https://github.com/influxdata/telegraf/pull/1453): diskio: add support for name templates and udev tags.

### Bugfixes

Expand Down
85 changes: 84 additions & 1 deletion plugins/inputs/system/disk.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package system

import (
"fmt"
"regexp"
"strings"

"github.com/influxdata/telegraf"
Expand Down Expand Up @@ -82,7 +83,11 @@ type DiskIOStats struct {
ps PS

Devices []string
DeviceTags []string
NameTemplates []string
SkipSerialNumber bool

infoCache map[string]diskInfoCache
}

func (_ *DiskIOStats) Description() string {
Expand All @@ -96,6 +101,23 @@ var diskIoSampleConfig = `
# devices = ["sda", "sdb"]
## Uncomment the following line if you need disk serial numbers.
# skip_serial_number = false
#
## On systems which support it, device metadata can be added in the form of
## tags.
## Currently only Linux is supported via udev properties. You can view
## available properties for a device by running:
## 'udevadm info -q property -n /dev/sda'
# device_tags = ["ID_FS_TYPE", "ID_FS_USAGE"]
#
## Using the same metadata source as device_tags, you can also customize the
## name of the device via templates.
## The 'name_templates' parameter is a list of templates to try and apply to
## the device. The template may contain variables in the form of '$PROPERTY' or
## '${PROPERTY}'. The first template which does not contain any variables not
## present for the device is used as the device name tag.
## The typical use case is for LVM volumes, to get the VG/LV name instead of
## the near-meaningless DM-0 name.
# name_templates = ["$ID_FS_LABEL","$DM_VG_NAME/$DM_LV_NAME"]
`

func (_ *DiskIOStats) SampleConfig() string {
Expand Down Expand Up @@ -123,7 +145,10 @@ func (s *DiskIOStats) Gather(acc telegraf.Accumulator) error {
continue
}
tags := map[string]string{}
tags["name"] = io.Name
tags["name"] = s.diskName(io.Name)
for t, v := range s.diskTags(io.Name) {
tags[t] = v
}
if !s.SkipSerialNumber {
if len(io.SerialNumber) != 0 {
tags["serial"] = io.SerialNumber
Expand All @@ -148,6 +173,64 @@ func (s *DiskIOStats) Gather(acc telegraf.Accumulator) error {
return nil
}

var varRegex = regexp.MustCompile(`\$(?:\w+|\{\w+\})`)

func (s *DiskIOStats) diskName(devName string) string {
di, err := s.diskInfo(devName)
if err != nil {
// discard error :-(
// We can't return error because it's non-fatal to the Gather().
// And we have no logger, so we can't log it.
return devName
}
if di == nil {
return devName
}

for _, nt := range s.NameTemplates {
miss := false
name := varRegex.ReplaceAllStringFunc(nt, func(sub string) string {
sub = sub[1:] // strip leading '$'
if sub[0] == '{' {
sub = sub[1 : len(sub)-1] // strip leading & trailing '{' '}'
}
if v, ok := di[sub]; ok {
return v
}
miss = true
return ""
})

if !miss {
return name
}
}

return devName
}

func (s *DiskIOStats) diskTags(devName string) map[string]string {
di, err := s.diskInfo(devName)
if err != nil {
// discard error :-(
// We can't return error because it's non-fatal to the Gather().
// And we have no logger, so we can't log it.
return nil
}
if di == nil {
return nil
}

tags := map[string]string{}
for _, dt := range s.DeviceTags {
if v, ok := di[dt]; ok {
tags[dt] = v
}
}

return tags
}

func init() {
inputs.Add("disk", func() telegraf.Input {
return &DiskStats{ps: &systemPS{}}
Expand Down
66 changes: 66 additions & 0 deletions plugins/inputs/system/disk_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package system

import (
"bufio"
"fmt"
"os"
"strings"
"syscall"
)

type diskInfoCache struct {
stat syscall.Stat_t
values map[string]string
}

var udevPath = "/run/udev/data"

func (s *DiskIOStats) diskInfo(devName string) (map[string]string, error) {
fi, err := os.Stat("/dev/" + devName)
if err != nil {
return nil, err
}
stat, ok := fi.Sys().(*syscall.Stat_t)
if !ok {
return nil, nil
}

if s.infoCache == nil {
s.infoCache = map[string]diskInfoCache{}
}
ic, ok := s.infoCache[devName]
if ok {
return ic.values, nil
} else {
ic = diskInfoCache{
stat: *stat,
values: map[string]string{},
}
s.infoCache[devName] = ic
}
di := ic.values

major := stat.Rdev >> 8 & 0xff
minor := stat.Rdev & 0xff

f, err := os.Open(fmt.Sprintf("%s/b%d:%d", udevPath, major, minor))
if err != nil {
return nil, err
}
defer f.Close()
scnr := bufio.NewScanner(f)

for scnr.Scan() {
l := scnr.Text()
if len(l) < 4 || l[:2] != "E:" {
continue
}
kv := strings.SplitN(l[2:], "=", 2)
if len(kv) < 2 {
continue
}
di[kv[0]] = kv[1]
}

return di, nil
}
101 changes: 101 additions & 0 deletions plugins/inputs/system/disk_linux_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// +build linux

package system

import (
"io/ioutil"
"os"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

var nullDiskInfo = []byte(`
E:MY_PARAM_1=myval1
E:MY_PARAM_2=myval2
`)

// setupNullDisk sets up fake udev info as if /dev/null were a disk.
func setupNullDisk(t *testing.T) func() error {
td, err := ioutil.TempDir("", ".telegraf.TestDiskInfo")
require.NoError(t, err)

origUdevPath := udevPath

cleanFunc := func() error {
udevPath = origUdevPath
return os.RemoveAll(td)
}

udevPath = td
err = ioutil.WriteFile(td+"/b1:3", nullDiskInfo, 0644) // 1:3 is the 'null' device
if err != nil {
cleanFunc()
t.Fatal(err)
}

return cleanFunc
}

func TestDiskInfo(t *testing.T) {
clean := setupNullDisk(t)
defer clean()

s := &DiskIOStats{}
di, err := s.diskInfo("null")
require.NoError(t, err)
assert.Equal(t, "myval1", di["MY_PARAM_1"])
assert.Equal(t, "myval2", di["MY_PARAM_2"])

// test that data is cached
err = clean()
require.NoError(t, err)

di, err = s.diskInfo("null")
require.NoError(t, err)
assert.Equal(t, "myval1", di["MY_PARAM_1"])
assert.Equal(t, "myval2", di["MY_PARAM_2"])

// unfortunately we can't adjust mtime on /dev/null to test cache invalidation
}

// DiskIOStats.diskName isn't a linux specific function, but dependent
// functions are a no-op on non-Linux.
func TestDiskIOStats_diskName(t *testing.T) {
defer setupNullDisk(t)()

tests := []struct {
templates []string
expected string
}{
{[]string{"$MY_PARAM_1"}, "myval1"},
{[]string{"${MY_PARAM_1}"}, "myval1"},
{[]string{"x$MY_PARAM_1"}, "xmyval1"},
{[]string{"x${MY_PARAM_1}x"}, "xmyval1x"},
{[]string{"$MISSING", "$MY_PARAM_1"}, "myval1"},
{[]string{"$MY_PARAM_1", "$MY_PARAM_2"}, "myval1"},
{[]string{"$MISSING"}, "null"},
{[]string{"$MY_PARAM_1/$MY_PARAM_2"}, "myval1/myval2"},
{[]string{"$MY_PARAM_2/$MISSING"}, "null"},
}

for _, tc := range tests {
s := DiskIOStats{
NameTemplates: tc.templates,
}
assert.Equal(t, tc.expected, s.diskName("null"), "Templates: %#v", tc.templates)
}
}

// DiskIOStats.diskTags isn't a linux specific function, but dependent
// functions are a no-op on non-Linux.
func TestDiskIOStats_diskTags(t *testing.T) {
defer setupNullDisk(t)()

s := &DiskIOStats{
DeviceTags: []string{"MY_PARAM_2"},
}
dt := s.diskTags("null")
assert.Equal(t, map[string]string{"MY_PARAM_2": "myval2"}, dt)
}
9 changes: 9 additions & 0 deletions plugins/inputs/system/disk_other.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// +build !linux

package system

type diskInfoCache struct{}

func (s *DiskIOStats) diskInfo(devName string) (map[string]string, error) {
return nil, nil
}