Skip to content
Merged
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
33 changes: 31 additions & 2 deletions plugins/inputs/diskio/diskio.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ package diskio
import (
_ "embed"
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"time"
Expand Down Expand Up @@ -38,6 +40,9 @@ type DiskIO struct {
warnDiskTags map[string]bool
lastIOCounterStat map[string]disk.IOCountersStat
lastCollectTime time.Time
devPath string
runPath string
sysPath string
}

func (*DiskIO) SampleConfig() string {
Expand All @@ -60,14 +65,38 @@ func (d *DiskIO) Init() error {
d.warnDiskTags = make(map[string]bool)
d.lastIOCounterStat = make(map[string]disk.IOCountersStat)

d.devPath = string(os.PathSeparator) + "dev"
d.runPath = string(os.PathSeparator) + "run"
d.sysPath = string(os.PathSeparator) + "sys"

if prefix := os.Getenv("HOST_MOUNT_PREFIX"); prefix != "" {
d.devPath = filepath.Join(prefix, "dev")
d.runPath = filepath.Join(prefix, "run")
d.sysPath = filepath.Join(prefix, "sys")
}
if root := os.Getenv("HOST_ROOT"); root != "" {
d.devPath = filepath.Join(root, "dev")
d.runPath = filepath.Join(root, "run")
d.sysPath = filepath.Join(root, "sys")
}
if devPath := os.Getenv("HOST_DEV"); devPath != "" {
d.devPath = devPath
}
if runPath := os.Getenv("HOST_RUN"); runPath != "" {
d.runPath = runPath
}
if sysPath := os.Getenv("HOST_SYS"); sysPath != "" {
d.sysPath = sysPath
}

return nil
}

func (d *DiskIO) Gather(acc telegraf.Accumulator) error {
var devices []string
if d.deviceFilter == nil {
for _, dev := range d.Devices {
devices = append(devices, resolveName(dev))
devices = append(devices, d.resolveName(dev))
}
}

Expand All @@ -86,7 +115,7 @@ func (d *DiskIO) Gather(acc telegraf.Accumulator) error {
var devLinks []string
tags["name"], devLinks = d.diskName(io.Name)

if wwid := getDeviceWWID(io.Name); wwid != "" {
if wwid := d.getDeviceWWID(io.Name); wwid != "" {
tags["wwid"] = wwid
}

Expand Down
25 changes: 13 additions & 12 deletions plugins/inputs/diskio/diskio_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ type diskInfoCache struct {

func (d *DiskIO) diskInfo(devName string) (map[string]string, error) {
// Check if the device exists
path := "/dev/" + devName
path := fmt.Sprintf("%s/%s", d.devPath, devName)
var stat unix.Stat_t
if err := unix.Stat(path, &stat); err != nil {
return nil, fmt.Errorf("error reading %s: %w", path, err)
Expand All @@ -43,10 +43,10 @@ func (d *DiskIO) diskInfo(devName string) (map[string]string, error) {
} else {
major := unix.Major(uint64(stat.Rdev)) //nolint:unconvert // Conversion needed for some architectures
minor := unix.Minor(uint64(stat.Rdev)) //nolint:unconvert // Conversion needed for some architectures
udevDataPath = fmt.Sprintf("/run/udev/data/b%d:%d", major, minor)
udevDataPath = fmt.Sprintf("%s/udev/data/b%d:%d", d.runPath, major, minor)
if _, err := os.Stat(udevDataPath); err != nil {
// This path failed, try the fallback .udev style (non-systemd)
udevDataPath = "/dev/.udev/db/block:" + devName
udevDataPath = fmt.Sprintf("%s/.udev/db/block:%s", d.devPath, devName)
if _, err := os.Stat(udevDataPath); err != nil {
// Giving up, cannot retrieve disk info
return nil, fmt.Errorf("error reading %s: %w", udevDataPath, err)
Expand All @@ -66,10 +66,10 @@ func (d *DiskIO) diskInfo(devName string) (map[string]string, error) {
// This allows us to also "poison" it during test scenarios
sysBlockPath = ic.sysBlockPath
} else {
sysBlockPath = "/sys/class/block/" + devName
sysBlockPath = fmt.Sprintf("%s/class/block/%s", d.sysPath, devName)
}

devInfo, err := readDevData(sysBlockPath)
devInfo, err := readDevData(sysBlockPath, d.sysPath)
if err == nil {
for k, v := range devInfo {
info[k] = v
Expand All @@ -81,6 +81,7 @@ func (d *DiskIO) diskInfo(devName string) (map[string]string, error) {
d.infoCache[devName] = diskInfoCache{
modifiedAt: stat.Mtim.Nano(),
udevDataPath: udevDataPath,
sysBlockPath: sysBlockPath,
values: info,
}

Expand Down Expand Up @@ -128,7 +129,7 @@ func readUdevData(path string) (map[string]string, error) {
return info, nil
}

func readDevData(path string) (map[string]string, error) {
func readDevData(path, sysPath string) (map[string]string, error) {
// Open the file and read line-wise
f, err := os.Open(filepath.Join(path, "uevent"))
if err != nil {
Expand Down Expand Up @@ -158,32 +159,32 @@ func readDevData(path string) (map[string]string, error) {
// Find the DEVPATH property
if devlnk, err := filepath.EvalSymlinks(filepath.Join(path, "device")); err == nil {
devlnk = filepath.Join(devlnk, filepath.Base(path))
devlnk = strings.TrimPrefix(devlnk, "/sys")
devlnk = strings.TrimPrefix(devlnk, sysPath)
info["DEVPATH"] = devlnk
}

return info, nil
}

func resolveName(name string) string {
func (d *DiskIO) resolveName(name string) string {
resolved, err := filepath.EvalSymlinks(name)
if err == nil {
return resolved
}
if !errors.Is(err, fs.ErrNotExist) {
return name
}
// Try to prepend "/dev"
resolved, err = filepath.EvalSymlinks("/dev/" + name)
// Try to resolve relative to the host device path.
resolved, err = filepath.EvalSymlinks(fmt.Sprintf("%s/%s", d.devPath, name))
if err != nil {
return name
}

return resolved
}

func getDeviceWWID(name string) string {
path := fmt.Sprintf("/sys/block/%s/wwid", filepath.Base(name))
func (d *DiskIO) getDeviceWWID(name string) string {
path := fmt.Sprintf("%s/block/%s/wwid", d.sysPath, filepath.Base(name))
buf, err := os.ReadFile(path)
if err != nil {
return ""
Expand Down
121 changes: 95 additions & 26 deletions plugins/inputs/diskio/diskio_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,21 @@ package diskio

import (
"fmt"
"path/filepath"
"testing"

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

func TestDiskInfo(t *testing.T) {
plugin := &DiskIO{
infoCache: map[string]diskInfoCache{
"null": {
modifiedAt: 0,
udevDataPath: "testdata/udev.txt",
sysBlockPath: "testdata",
values: map[string]string{},
},
plugin := &DiskIO{}
require.NoError(t, plugin.Init())
plugin.infoCache = map[string]diskInfoCache{
"null": {
modifiedAt: 0,
udevDataPath: "testdata/udev.txt",
sysBlockPath: "testdata",
values: map[string]string{},
},
}

Expand Down Expand Up @@ -48,15 +49,15 @@ func TestDiskIOStats_diskName(t *testing.T) {

for i, tc := range tests {
t.Run(fmt.Sprintf("template %d", i), func(t *testing.T) {
plugin := DiskIO{
NameTemplates: tc.templates,
infoCache: map[string]diskInfoCache{
"null": {
modifiedAt: 0,
udevDataPath: "testdata/udev.txt",
sysBlockPath: "testdata",
values: map[string]string{},
},
plugin := &DiskIO{}
plugin.NameTemplates = tc.templates
require.NoError(t, plugin.Init())
plugin.infoCache = map[string]diskInfoCache{
"null": {
modifiedAt: 0,
udevDataPath: "testdata/udev.txt",
sysBlockPath: "testdata",
values: map[string]string{},
},
}
name, _ := plugin.diskName("null")
Expand All @@ -68,17 +69,85 @@ func TestDiskIOStats_diskName(t *testing.T) {
// DiskIOStats.diskTags isn't a linux specific function, but dependent
// functions are a no-op on non-Linux.
func TestDiskIOStats_diskTags(t *testing.T) {
plugin := &DiskIO{
DeviceTags: []string{"MY_PARAM_2"},
infoCache: map[string]diskInfoCache{
"null": {
modifiedAt: 0,
udevDataPath: "testdata/udev.txt",
sysBlockPath: "testdata",
values: map[string]string{},
},
plugin := &DiskIO{}
plugin.DeviceTags = []string{"MY_PARAM_2"}
require.NoError(t, plugin.Init())
plugin.infoCache = map[string]diskInfoCache{
"null": {
modifiedAt: 0,
udevDataPath: "testdata/udev.txt",
sysBlockPath: "testdata",
values: map[string]string{},
},
}
dt := plugin.diskTags("null")
require.Equal(t, map[string]string{"MY_PARAM_2": "myval2"}, dt)
}

func TestDiskInfoHonorsHostDev(t *testing.T) {
t.Setenv("HOST_DEV", filepath.Join("testdata", "hostfs", "dev"))

plugin := &DiskIO{}
require.NoError(t, plugin.Init())
plugin.infoCache = map[string]diskInfoCache{
"mockdev": {
modifiedAt: 0,
udevDataPath: "testdata/udev.txt",
values: map[string]string{},
},
}
di, err := plugin.diskInfo("mockdev")
require.NoError(t, err)
require.Equal(t, "myval1", di["MY_PARAM_1"])
}

func TestDiskInfoHonorsHostPrefixFallbacksForDev(t *testing.T) {
tests := []struct {
name string
env map[string]string
}{
{
name: "host root fallback",
env: map[string]string{
"HOST_ROOT": filepath.Join("testdata", "hostfs"),
"HOST_MOUNT_PREFIX": filepath.Join("testdata", "hostfs", "unused"),
},
},
{
name: "host mount prefix fallback",
env: map[string]string{
"HOST_MOUNT_PREFIX": filepath.Join("testdata", "hostfs"),
},
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
for key, value := range tc.env {
t.Setenv(key, value)
}

plugin := &DiskIO{}
require.NoError(t, plugin.Init())
plugin.infoCache = map[string]diskInfoCache{
"mockdev": {
modifiedAt: 0,
udevDataPath: "testdata/udev.txt",
values: map[string]string{},
},
}

di, err := plugin.diskInfo("mockdev")
require.NoError(t, err)
require.Equal(t, "myval1", di["MY_PARAM_1"])
})
}
}

func TestGetDeviceWWIDHonorsHostSys(t *testing.T) {
t.Setenv("HOST_SYS", filepath.Join("testdata", "hostfs", "sys"))

plugin := &DiskIO{}
require.NoError(t, plugin.Init())
require.Equal(t, "my-wwid", plugin.getDeviceWWID("sda"))
}
4 changes: 2 additions & 2 deletions plugins/inputs/diskio/diskio_other.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ func (*DiskIO) diskInfo(_ string) (map[string]string, error) {
return nil, nil
}

func resolveName(name string) string {
func (*DiskIO) resolveName(name string) string {
return name
}

func getDeviceWWID(_ string) string {
func (*DiskIO) getDeviceWWID(_ string) string {
return ""
}
Empty file.
1 change: 1 addition & 0 deletions plugins/inputs/diskio/testdata/hostfs/sys/block/sda/wwid
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
my-wwid
Loading