fix: add normalization of tags for ethtool input plugin (#9901)
This commit is contained in:
parent
47301e6ef4
commit
3e1ebdb4c7
|
|
@ -12,6 +12,15 @@ The ethtool input plugin pulls ethernet device stats. Fields pulled will depend
|
|||
|
||||
## List of interfaces to ignore when pulling metrics.
|
||||
# interface_exclude = ["eth1"]
|
||||
|
||||
## 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:
|
||||
## * snakecase: converts fooBarBaz to foo_bar_baz
|
||||
## * trim: removes leading and trailing whitespace
|
||||
## * lower: changes all capitalized letters to lowercase
|
||||
## * underscore: replaces spaces with underscores
|
||||
# normalize_keys = ["snakecase", "trim", "lower", "underscore"]
|
||||
```
|
||||
|
||||
Interfaces can be included or ignored using:
|
||||
|
|
|
|||
|
|
@ -20,6 +20,9 @@ type Ethtool struct {
|
|||
// This is the list of interface names to ignore
|
||||
InterfaceExclude []string `toml:"interface_exclude"`
|
||||
|
||||
// Normalization on the key names
|
||||
NormalizeKeys []string `toml:"normalize_keys"`
|
||||
|
||||
Log telegraf.Logger `toml:"-"`
|
||||
|
||||
// the ethtool command
|
||||
|
|
@ -38,6 +41,15 @@ const (
|
|||
|
||||
## List of interfaces to ignore when pulling metrics.
|
||||
# interface_exclude = ["eth1"]
|
||||
|
||||
## 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:
|
||||
## * snakecase: converts fooBarBaz to foo_bar_baz
|
||||
## * trim: removes leading and trailing whitespace
|
||||
## * lower: changes all capitalized letters to lowercase
|
||||
## * underscore: replaces spaces with underscores
|
||||
# normalize_keys = ["snakecase", "trim", "lower", "underscore"]
|
||||
`
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ package ethtool
|
|||
|
||||
import (
|
||||
"net"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
|
@ -81,12 +83,53 @@ func (e *Ethtool) gatherEthtoolStats(iface net.Interface, acc telegraf.Accumulat
|
|||
|
||||
fields[fieldInterfaceUp] = e.interfaceUp(iface)
|
||||
for k, v := range stats {
|
||||
fields[k] = v
|
||||
fields[e.normalizeKey(k)] = v
|
||||
}
|
||||
|
||||
acc.AddFields(pluginName, fields, tags)
|
||||
}
|
||||
|
||||
// normalize key string; order matters to avoid replacing whitespace with
|
||||
// underscores, then trying to trim those same underscores. Likewise with
|
||||
// camelcase before trying to lower case things.
|
||||
func (e *Ethtool) normalizeKey(key string) string {
|
||||
// must trim whitespace or this will have a leading _
|
||||
if inStringSlice(e.NormalizeKeys, "snakecase") {
|
||||
key = camelCase2SnakeCase(strings.TrimSpace(key))
|
||||
}
|
||||
// must occur before underscore, otherwise nothing to trim
|
||||
if inStringSlice(e.NormalizeKeys, "trim") {
|
||||
key = strings.TrimSpace(key)
|
||||
}
|
||||
if inStringSlice(e.NormalizeKeys, "lower") {
|
||||
key = strings.ToLower(key)
|
||||
}
|
||||
if inStringSlice(e.NormalizeKeys, "underscore") {
|
||||
key = strings.ReplaceAll(key, " ", "_")
|
||||
}
|
||||
|
||||
return key
|
||||
}
|
||||
|
||||
func camelCase2SnakeCase(value string) string {
|
||||
matchFirstCap := regexp.MustCompile("(.)([A-Z][a-z]+)")
|
||||
matchAllCap := regexp.MustCompile("([a-z0-9])([A-Z])")
|
||||
|
||||
snake := matchFirstCap.ReplaceAllString(value, "${1}_${2}")
|
||||
snake = matchAllCap.ReplaceAllString(snake, "${1}_${2}")
|
||||
return strings.ToLower(snake)
|
||||
}
|
||||
|
||||
func inStringSlice(slice []string, value string) bool {
|
||||
for _, item := range slice {
|
||||
if item == value {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (e *Ethtool) interfaceUp(iface net.Interface) bool {
|
||||
return (iface.Flags & net.FlagUp) != 0
|
||||
}
|
||||
|
|
|
|||
|
|
@ -380,3 +380,119 @@ func TestGatherIgnoreInterfaces(t *testing.T) {
|
|||
}
|
||||
acc.AssertContainsTaggedFields(t, pluginName, expectedFieldsEth2, expectedTagsEth2)
|
||||
}
|
||||
|
||||
type TestCase struct {
|
||||
normalization []string
|
||||
stats map[string]uint64
|
||||
expectedFields map[string]uint64
|
||||
}
|
||||
|
||||
func TestNormalizedKeys(t *testing.T) {
|
||||
cases := []TestCase{
|
||||
{
|
||||
normalization: []string{"underscore"},
|
||||
stats: map[string]uint64{
|
||||
"port rx": 1,
|
||||
" Port_tx": 0,
|
||||
"interface_up": 0,
|
||||
},
|
||||
expectedFields: map[string]uint64{
|
||||
"port_rx": 1,
|
||||
"_Port_tx": 0,
|
||||
"interface_up": 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
normalization: []string{"underscore", "lower"},
|
||||
stats: map[string]uint64{
|
||||
"Port rx": 1,
|
||||
" Port_tx": 0,
|
||||
"interface_up": 0,
|
||||
},
|
||||
expectedFields: map[string]uint64{
|
||||
"port_rx": 1,
|
||||
"_port_tx": 0,
|
||||
"interface_up": 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
normalization: []string{"underscore", "lower", "trim"},
|
||||
stats: map[string]uint64{
|
||||
" Port RX ": 1,
|
||||
" Port_tx": 0,
|
||||
"interface_up": 0,
|
||||
},
|
||||
expectedFields: map[string]uint64{
|
||||
"port_rx": 1,
|
||||
"port_tx": 0,
|
||||
"interface_up": 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
normalization: []string{"underscore", "lower", "snakecase", "trim"},
|
||||
stats: map[string]uint64{
|
||||
" Port RX ": 1,
|
||||
" Port_tx": 0,
|
||||
"interface_up": 0,
|
||||
},
|
||||
expectedFields: map[string]uint64{
|
||||
"port_rx": 1,
|
||||
"port_tx": 0,
|
||||
"interface_up": 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
normalization: []string{"snakecase"},
|
||||
stats: map[string]uint64{
|
||||
" PortRX ": 1,
|
||||
" PortTX": 0,
|
||||
"interface_up": 0,
|
||||
},
|
||||
expectedFields: map[string]uint64{
|
||||
"port_rx": 1,
|
||||
"port_tx": 0,
|
||||
"interface_up": 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
normalization: []string{},
|
||||
stats: map[string]uint64{
|
||||
" Port RX ": 1,
|
||||
" Port_tx": 0,
|
||||
"interface_up": 0,
|
||||
},
|
||||
expectedFields: map[string]uint64{
|
||||
" Port RX ": 1,
|
||||
" Port_tx": 0,
|
||||
"interface_up": 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
eth0 := &InterfaceMock{"eth0", "e1000e", c.stats, false, true}
|
||||
expectedTags := map[string]string{
|
||||
"interface": eth0.Name,
|
||||
"driver": eth0.DriverName,
|
||||
}
|
||||
|
||||
interfaceMap = make(map[string]*InterfaceMock)
|
||||
interfaceMap[eth0.Name] = eth0
|
||||
|
||||
cmd := &CommandEthtoolMock{interfaceMap}
|
||||
command = &Ethtool{
|
||||
InterfaceInclude: []string{},
|
||||
InterfaceExclude: []string{},
|
||||
NormalizeKeys: c.normalization,
|
||||
command: cmd,
|
||||
}
|
||||
|
||||
var acc testutil.Accumulator
|
||||
err := command.Gather(&acc)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, acc.Metrics, 1)
|
||||
|
||||
acc.AssertContainsFields(t, pluginName, toStringMapInterface(c.expectedFields))
|
||||
acc.AssertContainsTaggedFields(t, pluginName, toStringMapInterface(c.expectedFields), expectedTags)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue