feat(ethtool): Possibility to skip gathering metrics for downed interfaces (#12087)

This commit is contained in:
Paweł Żak 2022-10-25 13:02:41 +02:00 committed by GitHub
parent 2ed413bdb0
commit 284edccf92
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 103 additions and 19 deletions

View File

@ -14,6 +14,12 @@ on the network device and driver.
## List of interfaces to ignore when pulling metrics.
# interface_exclude = ["eth1"]
## Plugin behavior for downed interfaces
## Available choices:
## - expose: collect & report metrics for down interfaces
## - skip: ignore interfaces that are marked down
# down_interfaces = "expose"
## Some drivers declare statistics with extra whitespace, different spacing,
## and mix cases. This list, when enabled, can be used to clean the keys.
## Here are the current possible normalizations:

View File

@ -5,11 +5,14 @@ import (
"net"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/filter"
)
//go:embed sample.conf
var sampleConfig string
var downInterfacesBehaviors = []string{"expose", "skip"}
type Command interface {
Init() error
DriverName(intf string) (string, error)
@ -24,11 +27,16 @@ type Ethtool struct {
// This is the list of interface names to ignore
InterfaceExclude []string `toml:"interface_exclude"`
// Behavior regarding metrics for downed interfaces
DownInterfaces string `toml:" down_interfaces"`
// Normalization on the key names
NormalizeKeys []string `toml:"normalize_keys"`
Log telegraf.Logger `toml:"-"`
interfaceFilter filter.Filter
// the ethtool command
command Command
}

View File

@ -4,6 +4,7 @@
package ethtool
import (
"fmt"
"net"
"regexp"
"strings"
@ -14,6 +15,7 @@ import (
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/filter"
"github.com/influxdata/telegraf/internal/choice"
"github.com/influxdata/telegraf/plugins/inputs"
)
@ -21,6 +23,24 @@ type CommandEthtool struct {
ethtool *ethtoolLib.Ethtool
}
func (e *Ethtool) Init() error {
var err error
e.interfaceFilter, err = filter.NewIncludeExcludeFilter(e.InterfaceInclude, e.InterfaceExclude)
if err != nil {
return err
}
if e.DownInterfaces == "" {
e.DownInterfaces = "expose"
}
if err = choice.Check(e.DownInterfaces, downInterfacesBehaviors); err != nil {
return fmt.Errorf("down_interfaces: %w", err)
}
return e.command.Init()
}
func (e *Ethtool) Gather(acc telegraf.Accumulator) error {
// Get the list of interfaces
interfaces, err := e.command.Interfaces()
@ -29,17 +49,11 @@ func (e *Ethtool) Gather(acc telegraf.Accumulator) error {
return nil
}
interfaceFilter, err := filter.NewIncludeExcludeFilter(e.InterfaceInclude, e.InterfaceExclude)
if err != nil {
return err
}
// parallelize the ethtool call in event of many interfaces
var wg sync.WaitGroup
for _, iface := range interfaces {
// Check this isn't a loop back and that its matched by the filter
if (iface.Flags&net.FlagLoopback == 0) && interfaceFilter.Match(iface.Name) {
if e.interfaceEligibleForGather(iface) {
wg.Add(1)
go func(i net.Interface) {
@ -54,9 +68,18 @@ func (e *Ethtool) Gather(acc telegraf.Accumulator) error {
return nil
}
// Initialise the Command Tool
func (e *Ethtool) Init() error {
return e.command.Init()
func (e *Ethtool) interfaceEligibleForGather(iface net.Interface) bool {
// Don't gather if it is a loop back, or it isn't matched by the filter
if isLoopback(iface) || !e.interfaceFilter.Match(iface.Name) {
return false
}
// For downed interfaces, gather only for "expose"
if !interfaceUp(iface) {
return e.DownInterfaces == "expose"
}
return true
}
// Gather the stats for the interface.
@ -81,7 +104,7 @@ func (e *Ethtool) gatherEthtoolStats(iface net.Interface, acc telegraf.Accumulat
return
}
fields[fieldInterfaceUp] = e.interfaceUp(iface)
fields[fieldInterfaceUp] = interfaceUp(iface)
for k, v := range stats {
fields[e.normalizeKey(k)] = v
}
@ -134,7 +157,11 @@ func inStringSlice(slice []string, value string) bool {
return false
}
func (e *Ethtool) interfaceUp(iface net.Interface) bool {
func isLoopback(iface net.Interface) bool {
return (iface.Flags & net.FlagLoopback) != 0
}
func interfaceUp(iface net.Interface) bool {
return (iface.Flags & net.FlagUp) != 0
}

View File

@ -292,6 +292,7 @@ func setup() {
command = &Ethtool{
InterfaceInclude: []string{},
InterfaceExclude: []string{},
DownInterfaces: "expose",
command: c,
}
}
@ -315,9 +316,12 @@ func toStringMapUint(in map[string]interface{}) map[string]uint64 {
func TestGather(t *testing.T) {
setup()
var acc testutil.Accumulator
err := command.Gather(&acc)
err := command.Init()
require.NoError(t, err)
var acc testutil.Accumulator
err = command.Gather(&acc)
require.NoError(t, err)
require.Len(t, acc.Metrics, 2)
@ -342,11 +346,14 @@ func TestGather(t *testing.T) {
func TestGatherIncludeInterfaces(t *testing.T) {
setup()
var acc testutil.Accumulator
command.InterfaceInclude = append(command.InterfaceInclude, "eth1")
err := command.Gather(&acc)
err := command.Init()
require.NoError(t, err)
var acc testutil.Accumulator
err = command.Gather(&acc)
require.NoError(t, err)
require.Len(t, acc.Metrics, 1)
@ -373,11 +380,14 @@ func TestGatherIncludeInterfaces(t *testing.T) {
func TestGatherIgnoreInterfaces(t *testing.T) {
setup()
var acc testutil.Accumulator
command.InterfaceExclude = append(command.InterfaceExclude, "eth1")
err := command.Gather(&acc)
err := command.Init()
require.NoError(t, err)
var acc testutil.Accumulator
err = command.Gather(&acc)
require.NoError(t, err)
require.Len(t, acc.Metrics, 1)
@ -402,6 +412,30 @@ func TestGatherIgnoreInterfaces(t *testing.T) {
acc.AssertContainsTaggedFields(t, pluginName, expectedFieldsEth2, expectedTagsEth2)
}
func TestSkipMetricsForInterfaceDown(t *testing.T) {
setup()
command.DownInterfaces = "skip"
err := command.Init()
require.NoError(t, err)
var acc testutil.Accumulator
err = command.Gather(&acc)
require.NoError(t, err)
require.Len(t, acc.Metrics, 1)
expectedFieldsEth1 := toStringMapInterface(interfaceMap["eth1"].Stat)
expectedFieldsEth1["interface_up_counter"] = expectedFieldsEth1["interface_up"]
expectedFieldsEth1["interface_up"] = true
expectedTagsEth1 := map[string]string{
"interface": "eth1",
"driver": "driver1",
}
acc.AssertContainsTaggedFields(t, pluginName, expectedFieldsEth1, expectedTagsEth1)
}
type TestCase struct {
normalization []string
stats map[string]interface{}
@ -525,8 +559,11 @@ func TestNormalizedKeys(t *testing.T) {
command: cmd,
}
err := command.Init()
require.NoError(t, err)
var acc testutil.Accumulator
err := command.Gather(&acc)
err = command.Gather(&acc)
require.NoError(t, err)
require.Len(t, acc.Metrics, 1)

View File

@ -6,6 +6,12 @@
## List of interfaces to ignore when pulling metrics.
# interface_exclude = ["eth1"]
## Plugin behavior for downed interfaces
## Available choices:
## - expose: collect & report metrics for down interfaces
## - skip: ignore interfaces that are marked down
# down_interfaces = "expose"
## Some drivers declare statistics with extra whitespace, different spacing,
## and mix cases. This list, when enabled, can be used to clean the keys.
## Here are the current possible normalizations: