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

feat: add mount option filtering to disk plugin #11039

Merged
merged 4 commits into from
May 12, 2022
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
5 changes: 5 additions & 0 deletions plugins/inputs/disk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ Note that `used_percent` is calculated by doing `used / (used + free)`, _not_

## Ignore mount points by filesystem type.
ignore_fs = ["tmpfs", "devtmpfs", "devfs", "iso9660", "overlay", "aufs", "squashfs"]

## Ignore mount points by mount options.
## The 'mount' command reports options of all mounts in parathesis.
## Bind mounts can be ignored with the special 'bind' option.
# ignore_mount_opts = []
```

### Docker container
Expand Down
7 changes: 4 additions & 3 deletions plugins/inputs/disk/disk.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ type DiskStats struct {
// Legacy support
LegacyMountPoints []string `toml:"mountpoints"`

MountPoints []string `toml:"mount_points"`
IgnoreFS []string `toml:"ignore_fs"`
MountPoints []string `toml:"mount_points"`
IgnoreFS []string `toml:"ignore_fs"`
IgnoreMountOpts []string `toml:"ignore_mount_opts"`

Log telegraf.Logger `toml:"-"`
}
Expand All @@ -35,7 +36,7 @@ func (ds *DiskStats) Init() error {
}

func (ds *DiskStats) Gather(acc telegraf.Accumulator) error {
disks, partitions, err := ds.ps.DiskUsage(ds.MountPoints, ds.IgnoreFS)
disks, partitions, err := ds.ps.DiskUsage(ds.MountPoints, ds.IgnoreMountOpts, ds.IgnoreFS)
if err != nil {
return fmt.Errorf("error getting disk usage info: %s", err)
}
Expand Down
128 changes: 112 additions & 16 deletions plugins/inputs/disk/disk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ func TestDiskUsage(t *testing.T) {
Fstype: "ext4",
Opts: []string{"rw", "noatime", "nodiratime", "errors=remount-ro"},
},
{
Device: "/dev/sda",
Mountpoint: "/var/rootbind",
Fstype: "ext4",
Opts: []string{"ro", "noatime", "nodiratime", "bind"},
},
}
duAll := []diskUtil.UsageStat{
{
Expand All @@ -65,18 +71,29 @@ func TestDiskUsage(t *testing.T) {
InodesFree: 468,
InodesUsed: 2000,
},
{
Path: "/var/rootbind",
Fstype: "ext4",
Total: 128,
Free: 23,
Used: 100,
InodesTotal: 1234,
InodesFree: 234,
InodesUsed: 1000,
},
}

mps.On("Partitions", true).Return(psAll, nil)
mps.On("OSGetenv", "HOST_MOUNT_PREFIX").Return("")
mps.On("PSDiskUsage", "/").Return(&duAll[0], nil)
mps.On("PSDiskUsage", "/home").Return(&duAll[1], nil)
mps.On("PSDiskUsage", "/var/rootbind").Return(&duAll[2], nil)

err = (&DiskStats{ps: mps}).Gather(&acc)
require.NoError(t, err)

numDiskMetrics := acc.NFields()
expectedAllDiskMetrics := 14
expectedAllDiskMetrics := 21
require.Equal(t, expectedAllDiskMetrics, numDiskMetrics)

tags1 := map[string]string{
Expand All @@ -91,6 +108,12 @@ func TestDiskUsage(t *testing.T) {
"device": "sdb",
"mode": "rw",
}
tags3 := map[string]string{
"path": fmt.Sprintf("%cvar%crootbind", os.PathSeparator, os.PathSeparator),
"fstype": "ext4",
"device": "sda",
"mode": "ro",
}

fields1 := map[string]interface{}{
"total": uint64(128),
Expand All @@ -110,20 +133,35 @@ func TestDiskUsage(t *testing.T) {
"inodes_used": uint64(2000),
"used_percent": float64(81.30081300813008),
}
fields3 := map[string]interface{}{
"total": uint64(128),
"used": uint64(100),
"free": uint64(23),
"inodes_total": uint64(1234),
"inodes_free": uint64(234),
"inodes_used": uint64(1000),
"used_percent": float64(81.30081300813008),
}
acc.AssertContainsTaggedFields(t, "disk", fields1, tags1)
acc.AssertContainsTaggedFields(t, "disk", fields2, tags2)
acc.AssertContainsTaggedFields(t, "disk", fields3, tags3)

// We expect 6 more DiskMetrics to show up with an explicit match on "/"
// We expect 7 more DiskMetrics to show up with an explicit match on "/"
// and /home not matching the /dev in MountPoints
err = (&DiskStats{ps: &mps, MountPoints: []string{"/", "/dev"}}).Gather(&acc)
require.NoError(t, err)
require.Equal(t, expectedAllDiskMetrics+7, acc.NFields())

// We should see all the diskpoints as MountPoints includes both
// / and /home
err = (&DiskStats{ps: &mps, MountPoints: []string{"/", "/home"}}).Gather(&acc)
// /, /home, and /var/rootbind
err = (&DiskStats{ps: &mps, MountPoints: []string{"/", "/home", "/var/rootbind"}}).Gather(&acc)
require.NoError(t, err)
require.Equal(t, 2*expectedAllDiskMetrics+7, acc.NFields())
require.Equal(t, expectedAllDiskMetrics+7*4, acc.NFields())

// We should see all the mounts as MountPoints except the bind mound
err = (&DiskStats{ps: &mps, IgnoreMountOpts: []string{"bind"}}).Gather(&acc)
require.NoError(t, err)
require.Equal(t, expectedAllDiskMetrics+7*6, acc.NFields())
}

func TestDiskUsageHostMountPrefix(t *testing.T) {
Expand Down Expand Up @@ -287,8 +325,18 @@ func TestDiskStats(t *testing.T) {
InodesFree: 468,
InodesUsed: 2000,
},
{
Path: "/var/rootbind",
Fstype: "ext4",
Total: 128,
Free: 23,
Used: 100,
InodesTotal: 1234,
InodesFree: 234,
InodesUsed: 1000,
},
}
duFiltered := []*diskUtil.UsageStat{
duMountFiltered := []*diskUtil.UsageStat{
{
Path: "/",
Fstype: "ext4",
Expand All @@ -300,6 +348,28 @@ func TestDiskStats(t *testing.T) {
InodesUsed: 1000,
},
}
duOptFiltered := []*diskUtil.UsageStat{
{
Path: "/",
Fstype: "ext4",
Total: 128,
Free: 23,
Used: 100,
InodesTotal: 1234,
InodesFree: 234,
InodesUsed: 1000,
},
{
Path: "/home",
Fstype: "ext4",
Total: 256,
Free: 46,
Used: 200,
InodesTotal: 2468,
InodesFree: 468,
InodesUsed: 2000,
},
}

psAll := []*diskUtil.PartitionStat{
{
Expand All @@ -314,26 +384,47 @@ func TestDiskStats(t *testing.T) {
Fstype: "ext4",
Opts: []string{"rw", "noatime", "nodiratime", "errors=remount-ro"},
},
{
Device: "/dev/sda",
Mountpoint: "/var/rootbind",
Fstype: "ext4",
Opts: []string{"ro", "noatime", "nodiratime", "bind"},
},
}

psFiltered := []*diskUtil.PartitionStat{
psMountFiltered := []*diskUtil.PartitionStat{
{
Device: "/dev/sda",
Mountpoint: "/",
Fstype: "ext4",
Opts: []string{"ro", "noatime", "nodiratime"},
},
}
psOptFiltered := []*diskUtil.PartitionStat{
{
Device: "/dev/sda",
Mountpoint: "/",
Fstype: "ext4",
Opts: []string{"ro", "noatime", "nodiratime"},
},
{
Device: "/dev/sdb",
Mountpoint: "/home",
Fstype: "ext4",
Opts: []string{"rw", "noatime", "nodiratime", "errors=remount-ro"},
},
}

mps.On("DiskUsage", []string(nil), []string(nil)).Return(duAll, psAll, nil)
mps.On("DiskUsage", []string{"/", "/dev"}, []string(nil)).Return(duFiltered, psFiltered, nil)
mps.On("DiskUsage", []string{"/", "/home"}, []string(nil)).Return(duAll, psAll, nil)
mps.On("DiskUsage", []string(nil), []string(nil), []string(nil)).Return(duAll, psAll, nil)
mps.On("DiskUsage", []string{"/", "/dev"}, []string(nil), []string(nil)).Return(duMountFiltered, psMountFiltered, nil)
mps.On("DiskUsage", []string{"/", "/home", "/var/rootbind"}, []string(nil), []string(nil)).Return(duAll, psAll, nil)
mps.On("DiskUsage", []string(nil), []string{"bind"}, []string(nil)).Return(duOptFiltered, psOptFiltered, nil)

err = (&DiskStats{ps: &mps}).Gather(&acc)
require.NoError(t, err)

numDiskMetrics := acc.NFields()
expectedAllDiskMetrics := 14
expectedAllDiskMetrics := 21
require.Equal(t, expectedAllDiskMetrics, numDiskMetrics)

tags1 := map[string]string{
Expand Down Expand Up @@ -370,17 +461,22 @@ func TestDiskStats(t *testing.T) {
acc.AssertContainsTaggedFields(t, "disk", fields1, tags1)
acc.AssertContainsTaggedFields(t, "disk", fields2, tags2)

// We expect 6 more DiskMetrics to show up with an explicit match on "/"
// and /home not matching the /dev in MountPoints
// We expect 7 more DiskMetrics to show up with an explicit match on "/"
// and /home and /var/rootbind not matching the /dev in MountPoints
err = (&DiskStats{ps: &mps, MountPoints: []string{"/", "/dev"}}).Gather(&acc)
require.NoError(t, err)
require.Equal(t, expectedAllDiskMetrics+7, acc.NFields())

// We should see all the diskpoints as MountPoints includes both
// / and /home
err = (&DiskStats{ps: &mps, MountPoints: []string{"/", "/home"}}).Gather(&acc)
// /, /home, and /var/rootbind
err = (&DiskStats{ps: &mps, MountPoints: []string{"/", "/home", "/var/rootbind"}}).Gather(&acc)
require.NoError(t, err)
require.Equal(t, expectedAllDiskMetrics+7*4, acc.NFields())

// We should see all the mounts as MountPoints except the bind mound
err = (&DiskStats{ps: &mps, IgnoreMountOpts: []string{"bind"}}).Gather(&acc)
require.NoError(t, err)
require.Equal(t, 2*expectedAllDiskMetrics+7, acc.NFields())
require.Equal(t, expectedAllDiskMetrics+7*6, acc.NFields())
}

func TestDiskUsageIssues(t *testing.T) {
Expand Down
4 changes: 2 additions & 2 deletions plugins/inputs/system/mock_PS.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ func (m *MockPS) CPUTimes(_, _ bool) ([]cpu.TimesStat, error) {
return r0, r1
}

func (m *MockPS) DiskUsage(mountPointFilter []string, fstypeExclude []string) ([]*disk.UsageStat, []*disk.PartitionStat, error) {
ret := m.Called(mountPointFilter, fstypeExclude)
func (m *MockPS) DiskUsage(mountPointFilter []string, mountOptsExclude []string, fstypeExclude []string) ([]*disk.UsageStat, []*disk.PartitionStat, error) {
ret := m.Called(mountPointFilter, mountOptsExclude, fstypeExclude)

r0 := ret.Get(0).([]*disk.UsageStat)
r1 := ret.Get(1).([]*disk.PartitionStat)
Expand Down
13 changes: 12 additions & 1 deletion plugins/inputs/system/ps.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (

type PS interface {
CPUTimes(perCPU, totalCPU bool) ([]cpu.TimesStat, error)
DiskUsage(mountPointFilter []string, fstypeExclude []string) ([]*disk.UsageStat, []*disk.PartitionStat, error)
DiskUsage(mountPointFilter []string, mountOptsExclude []string, fstypeExclude []string) ([]*disk.UsageStat, []*disk.PartitionStat, error)
NetIO() ([]net.IOCountersStat, error)
NetProto() ([]net.ProtoCountersStat, error)
DiskIO(names []string) (map[string]disk.IOCountersStat, error)
Expand Down Expand Up @@ -91,6 +91,7 @@ func newSet() *set {

func (s *SystemPS) DiskUsage(
mountPointFilter []string,
mountOptsExclude []string,
fstypeExclude []string,
) ([]*disk.UsageStat, []*disk.PartitionStat, error) {
parts, err := s.Partitions(true)
Expand All @@ -102,6 +103,10 @@ func (s *SystemPS) DiskUsage(
for _, filter := range mountPointFilter {
mountPointFilterSet.add(filter)
}
mountOptFilterSet := newSet()
for _, filter := range mountOptsExclude {
mountOptFilterSet.add(filter)
}
fstypeExcludeSet := newSet()
for _, filter := range fstypeExclude {
fstypeExcludeSet.add(filter)
Expand All @@ -120,9 +125,15 @@ func (s *SystemPS) DiskUsage(
var partitions []*disk.PartitionStat
hostMountPrefix := s.OSGetenv("HOST_MOUNT_PREFIX")

partitionRange:
for i := range parts {
p := parts[i]

for _, o := range p.Opts {
if !mountOptFilterSet.empty() && mountOptFilterSet.has(o) {
continue partitionRange
}
}
// If there is a filter set and if the mount point is not a
// member of the filter set, don't gather info on it.
if !mountPointFilterSet.empty() && !mountPointFilterSet.has(p.Mountpoint) {
Expand Down