fix: add normalization of tags for ethtool input plugin (#9901)

This commit is contained in:
Joshua Powers 2021-10-19 14:44:36 -06:00 committed by GitHub
parent 47301e6ef4
commit 3e1ebdb4c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 181 additions and 1 deletions

View File

@ -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:

View File

@ -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"]
`
)

View File

@ -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
}

View File

@ -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)
}
}