1
0
Fork 0

Adding upstream version 1.34.4.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-05-24 07:26:29 +02:00
parent e393c3af3f
commit 4978089aab
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
4963 changed files with 677545 additions and 0 deletions

View file

@ -0,0 +1,110 @@
# Ethtool Input Plugin
This plugin collects ethernet device statistics. The available information
strongly depends on the network device and driver.
⭐ Telegraf v1.13.0
🏷️ system, network
💻 linux
## Global configuration options <!-- @/docs/includes/plugin_config.md -->
In addition to the plugin-specific configuration settings, plugins support
additional global and plugin configuration settings. These settings are used to
modify metrics, tags, and field or create aliases and configure ordering, etc.
See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
[CONFIGURATION.md]: ../../../docs/CONFIGURATION.md#plugins
## Configuration
```toml @sample.conf
# Returns ethtool statistics for given interfaces
# This plugin ONLY supports Linux
[[inputs.ethtool]]
## List of interfaces to pull metrics for
# interface_include = ["eth0"]
## 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"
## Reading statistics from interfaces in additional namespaces is also
## supported, so long as the namespaces are named (have a symlink in
## /var/run/netns). The telegraf process will also need the CAP_SYS_ADMIN
## permission.
## By default, only the current namespace will be used. For additional
## namespace support, at least one of `namespace_include` and
## `namespace_exclude` must be provided.
## To include all namespaces, set `namespace_include` to `["*"]`.
## The initial namespace (if anonymous) can be specified with the empty
## string ("").
## List of namespaces to pull metrics for
# namespace_include = []
## List of namespace to ignore when pulling metrics.
# namespace_exclude = []
## 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:
- `interface_include`
- `interface_exclude`
Note that loopback interfaces will be automatically ignored.
## Namespaces
Metrics from interfaces in additional namespaces will be retrieved if either
`namespace_include` or `namespace_exclude` is configured (to a non-empty list).
This requires `CAP_SYS_ADMIN` permissions to switch namespaces, which can be
granted to telegraf in several ways. The two recommended ways are listed below:
### Using systemd capabilities
If you are using systemd to run Telegraf, you may run
`systemctl edit telegraf.service` and add the following:
```text
[Service]
AmbientCapabilities=CAP_SYS_ADMIN
```
### Configuring executable capabilities
If you are not using systemd to run Telegraf, you can configure the Telegraf
executable to have `CAP_SYS_ADMIN` when run.
```sh
sudo setcap CAP_SYS_ADMIN+epi $(which telegraf)
```
N.B.: This capability is a filesystem attribute on the binary itself. The
attribute needs to be re-applied if the Telegraf binary is rotated (e.g. on
installation of new a Telegraf version from the system package manager).
## Metrics
Metrics are dependent on the network device and driver.
## Example Output
```text
ethtool,driver=igb,host=test01,interface=mgmt0 tx_queue_1_packets=280782i,rx_queue_5_csum_err=0i,tx_queue_4_restart=0i,tx_multicast=7i,tx_queue_1_bytes=39674885i,rx_queue_2_alloc_failed=0i,tx_queue_5_packets=173970i,tx_single_coll_ok=0i,rx_queue_1_drops=0i,tx_queue_2_restart=0i,tx_aborted_errors=0i,rx_queue_6_csum_err=0i,tx_queue_5_restart=0i,tx_queue_4_bytes=64810835i,tx_abort_late_coll=0i,tx_queue_4_packets=109102i,os2bmc_tx_by_bmc=0i,tx_bytes=427527435i,tx_queue_7_packets=66665i,dropped_smbus=0i,rx_queue_0_csum_err=0i,tx_flow_control_xoff=0i,rx_packets=25926536i,rx_queue_7_csum_err=0i,rx_queue_3_bytes=84326060i,rx_multicast=83771i,rx_queue_4_alloc_failed=0i,rx_queue_3_drops=0i,rx_queue_3_csum_err=0i,rx_errors=0i,tx_errors=0i,tx_queue_6_packets=183236i,rx_broadcast=24378893i,rx_queue_7_packets=88680i,tx_dropped=0i,rx_frame_errors=0i,tx_queue_3_packets=161045i,tx_packets=1257017i,rx_queue_1_csum_err=0i,tx_window_errors=0i,tx_dma_out_of_sync=0i,rx_length_errors=0i,rx_queue_5_drops=0i,tx_timeout_count=0i,rx_queue_4_csum_err=0i,rx_flow_control_xon=0i,tx_heartbeat_errors=0i,tx_flow_control_xon=0i,collisions=0i,tx_queue_0_bytes=29465801i,rx_queue_6_drops=0i,rx_queue_0_alloc_failed=0i,tx_queue_1_restart=0i,rx_queue_0_drops=0i,tx_broadcast=9i,tx_carrier_errors=0i,tx_queue_7_bytes=13777515i,tx_queue_7_restart=0i,rx_queue_5_bytes=50732006i,rx_queue_7_bytes=35744457i,tx_deferred_ok=0i,tx_multi_coll_ok=0i,rx_crc_errors=0i,rx_fifo_errors=0i,rx_queue_6_alloc_failed=0i,tx_queue_2_packets=175206i,tx_queue_0_packets=107011i,rx_queue_4_bytes=201364548i,rx_queue_6_packets=372573i,os2bmc_rx_by_host=0i,multicast=83771i,rx_queue_4_drops=0i,rx_queue_5_packets=130535i,rx_queue_6_bytes=139488035i,tx_fifo_errors=0i,tx_queue_5_bytes=84899130i,rx_queue_0_packets=24529563i,rx_queue_3_alloc_failed=0i,rx_queue_7_drops=0i,tx_queue_6_bytes=96288614i,tx_queue_2_bytes=22132949i,tx_tcp_seg_failed=0i,rx_queue_1_bytes=246703840i,rx_queue_0_bytes=1506870738i,tx_queue_0_restart=0i,rx_queue_2_bytes=111344804i,tx_tcp_seg_good=0i,tx_queue_3_restart=0i,rx_no_buffer_count=0i,rx_smbus=0i,rx_queue_1_packets=273865i,rx_over_errors=0i,os2bmc_tx_by_host=0i,rx_queue_1_alloc_failed=0i,rx_queue_7_alloc_failed=0i,rx_short_length_errors=0i,tx_hwtstamp_timeouts=0i,tx_queue_6_restart=0i,rx_queue_2_packets=207136i,tx_queue_3_bytes=70391970i,rx_queue_3_packets=112007i,rx_queue_4_packets=212177i,tx_smbus=0i,rx_long_byte_count=2480280632i,rx_queue_2_csum_err=0i,rx_missed_errors=0i,rx_bytes=2480280632i,rx_queue_5_alloc_failed=0i,rx_queue_2_drops=0i,os2bmc_rx_by_bmc=0i,rx_align_errors=0i,rx_long_length_errors=0i,interface_up=1i,rx_hwtstamp_cleared=0i,rx_flow_control_xoff=0i,speed=1000i,link=1i,duplex=1i,autoneg=1i 1564658080000000000
ethtool,driver=igb,host=test02,interface=mgmt0 rx_queue_2_bytes=111344804i,tx_queue_3_bytes=70439858i,multicast=83771i,rx_broadcast=24378975i,tx_queue_0_packets=107011i,rx_queue_6_alloc_failed=0i,rx_queue_6_drops=0i,rx_hwtstamp_cleared=0i,tx_window_errors=0i,tx_tcp_seg_good=0i,rx_queue_1_drops=0i,tx_queue_1_restart=0i,rx_queue_7_csum_err=0i,rx_no_buffer_count=0i,tx_queue_1_bytes=39675245i,tx_queue_5_bytes=84899130i,tx_broadcast=9i,rx_queue_1_csum_err=0i,tx_flow_control_xoff=0i,rx_queue_6_csum_err=0i,tx_timeout_count=0i,os2bmc_tx_by_bmc=0i,rx_queue_6_packets=372577i,rx_queue_0_alloc_failed=0i,tx_flow_control_xon=0i,rx_queue_2_drops=0i,tx_queue_2_packets=175206i,rx_queue_3_csum_err=0i,tx_abort_late_coll=0i,tx_queue_5_restart=0i,tx_dropped=0i,rx_queue_2_alloc_failed=0i,tx_multi_coll_ok=0i,rx_queue_1_packets=273865i,rx_flow_control_xon=0i,tx_single_coll_ok=0i,rx_length_errors=0i,rx_queue_7_bytes=35744457i,rx_queue_4_alloc_failed=0i,rx_queue_6_bytes=139488395i,rx_queue_2_csum_err=0i,rx_long_byte_count=2480288216i,rx_queue_1_alloc_failed=0i,tx_queue_0_restart=0i,rx_queue_0_csum_err=0i,tx_queue_2_bytes=22132949i,rx_queue_5_drops=0i,tx_dma_out_of_sync=0i,rx_queue_3_drops=0i,rx_queue_4_packets=212177i,tx_queue_6_restart=0i,rx_packets=25926650i,rx_queue_7_packets=88680i,rx_frame_errors=0i,rx_queue_3_bytes=84326060i,rx_short_length_errors=0i,tx_queue_7_bytes=13777515i,rx_queue_3_alloc_failed=0i,tx_queue_6_packets=183236i,rx_queue_0_drops=0i,rx_multicast=83771i,rx_queue_2_packets=207136i,rx_queue_5_csum_err=0i,rx_queue_5_packets=130535i,rx_queue_7_alloc_failed=0i,tx_smbus=0i,tx_queue_3_packets=161081i,rx_queue_7_drops=0i,tx_queue_2_restart=0i,tx_multicast=7i,tx_fifo_errors=0i,tx_queue_3_restart=0i,rx_long_length_errors=0i,tx_queue_6_bytes=96288614i,tx_queue_1_packets=280786i,tx_tcp_seg_failed=0i,rx_align_errors=0i,tx_errors=0i,rx_crc_errors=0i,rx_queue_0_packets=24529673i,rx_flow_control_xoff=0i,tx_queue_0_bytes=29465801i,rx_over_errors=0i,rx_queue_4_drops=0i,os2bmc_rx_by_bmc=0i,rx_smbus=0i,dropped_smbus=0i,tx_hwtstamp_timeouts=0i,rx_errors=0i,tx_queue_4_packets=109102i,tx_carrier_errors=0i,tx_queue_4_bytes=64810835i,tx_queue_4_restart=0i,rx_queue_4_csum_err=0i,tx_queue_7_packets=66665i,tx_aborted_errors=0i,rx_missed_errors=0i,tx_bytes=427575843i,collisions=0i,rx_queue_1_bytes=246703840i,rx_queue_5_bytes=50732006i,rx_bytes=2480288216i,os2bmc_rx_by_host=0i,rx_queue_5_alloc_failed=0i,rx_queue_3_packets=112007i,tx_deferred_ok=0i,os2bmc_tx_by_host=0i,tx_heartbeat_errors=0i,rx_queue_0_bytes=1506877506i,tx_queue_7_restart=0i,tx_packets=1257057i,rx_queue_4_bytes=201364548i,interface_up=0i,rx_fifo_errors=0i,tx_queue_5_packets=173970i,speed=1000i,link=1i,duplex=1i,autoneg=1i 1564658090000000000
```

View file

@ -0,0 +1,15 @@
//go:generate ../../../tools/readme_config_includer/generator
package ethtool
import (
_ "embed"
)
//go:embed sample.conf
var sampleConfig string
const pluginName = "ethtool"
func (*Ethtool) SampleConfig() string {
return sampleConfig
}

View file

@ -0,0 +1,366 @@
//go:build linux
package ethtool
import (
"fmt"
"net"
"os"
"path/filepath"
"regexp"
"strings"
"sync"
"github.com/vishvananda/netns"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/filter"
"github.com/influxdata/telegraf/internal/choice"
"github.com/influxdata/telegraf/plugins/inputs"
)
var downInterfacesBehaviors = []string{"expose", "skip"}
const (
tagInterface = "interface"
tagNamespace = "namespace"
tagDriverName = "driver"
fieldInterfaceUp = "interface_up"
)
type Ethtool struct {
// This is the list of interface names to include
InterfaceInclude []string `toml:"interface_include"`
// 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"`
// This is the list of namespace names to include
NamespaceInclude []string `toml:"namespace_include"`
// This is the list of namespace names to ignore
NamespaceExclude []string `toml:"namespace_exclude"`
// Normalization on the key names
NormalizeKeys []string `toml:"normalize_keys"`
Log telegraf.Logger `toml:"-"`
interfaceFilter filter.Filter
namespaceFilter filter.Filter
includeNamespaces bool
// the ethtool command
command command
}
type command interface {
init() error
driverName(intf namespacedInterface) (string, error)
interfaces(includeNamespaces bool) ([]namespacedInterface, error)
stats(intf namespacedInterface) (map[string]uint64, error)
get(intf namespacedInterface) (map[string]uint64, error)
}
type commandEthtool struct {
log telegraf.Logger
namespaceGoroutines map[string]*namespaceGoroutine
}
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)
}
// If no namespace include or exclude filters were provided, then default
// to just the initial namespace.
e.includeNamespaces = len(e.NamespaceInclude) > 0 || len(e.NamespaceExclude) > 0
if len(e.NamespaceInclude) == 0 && len(e.NamespaceExclude) == 0 {
e.NamespaceInclude = []string{""}
} else if len(e.NamespaceInclude) == 0 {
e.NamespaceInclude = []string{"*"}
}
e.namespaceFilter, err = filter.NewIncludeExcludeFilter(e.NamespaceInclude, e.NamespaceExclude)
if err != nil {
return err
}
if command, ok := e.command.(*commandEthtool); ok {
command.log = e.Log
}
return e.command.init()
}
func (e *Ethtool) Gather(acc telegraf.Accumulator) error {
// Get the list of interfaces
interfaces, err := e.command.interfaces(e.includeNamespaces)
if err != nil {
acc.AddError(err)
return nil
}
// 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(s)
if e.interfaceEligibleForGather(iface) {
wg.Add(1)
go func(i namespacedInterface) {
e.gatherEthtoolStats(i, acc)
wg.Done()
}(iface)
}
}
// Waiting for all the interfaces
wg.Wait()
return nil
}
func (e *Ethtool) interfaceEligibleForGather(iface namespacedInterface) 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
}
// Don't gather if it's not in a namespace matched by the filter
if !e.namespaceFilter.Match(iface.namespace.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.
func (e *Ethtool) gatherEthtoolStats(iface namespacedInterface, acc telegraf.Accumulator) {
tags := make(map[string]string)
tags[tagInterface] = iface.Name
tags[tagNamespace] = iface.namespace.name()
driverName, err := e.command.driverName(iface)
if err != nil {
acc.AddError(fmt.Errorf("%q driver: %w", iface.Name, err))
return
}
tags[tagDriverName] = driverName
fields := make(map[string]interface{})
stats, err := e.command.stats(iface)
if err != nil {
acc.AddError(fmt.Errorf("%q stats: %w", iface.Name, err))
return
}
fields[fieldInterfaceUp] = interfaceUp(iface)
for k, v := range stats {
fields[e.normalizeKey(k)] = v
}
cmdget, err := e.command.get(iface)
// error text is directly from running ethtool and syscalls
if err != nil && err.Error() != "operation not supported" {
acc.AddError(fmt.Errorf("%q get: %w", iface.Name, err))
return
}
for k, v := range cmdget {
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, " ", "_")
}
// aws has a conflicting name that needs to be renamed
if key == "interface_up" {
key = "interface_up_counter"
}
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 isLoopback(iface namespacedInterface) bool {
return (iface.Flags & net.FlagLoopback) != 0
}
func interfaceUp(iface namespacedInterface) bool {
return (iface.Flags & net.FlagUp) != 0
}
func newCommandEthtool() *commandEthtool {
return &commandEthtool{}
}
func (c *commandEthtool) init() error {
// Create the goroutine for the initial namespace
initialNamespace, err := netns.Get()
if err != nil {
return err
}
nspaceGoroutine := &namespaceGoroutine{
namespaceName: "",
handle: initialNamespace,
log: c.log,
}
if err := nspaceGoroutine.start(); err != nil {
c.log.Errorf(`Failed to start goroutine for the initial namespace: %s`, err)
return err
}
c.namespaceGoroutines = map[string]*namespaceGoroutine{
"": nspaceGoroutine,
}
return nil
}
func (*commandEthtool) driverName(intf namespacedInterface) (driver string, err error) {
return intf.namespace.driverName(intf)
}
func (*commandEthtool) stats(intf namespacedInterface) (stats map[string]uint64, err error) {
return intf.namespace.stats(intf)
}
func (*commandEthtool) get(intf namespacedInterface) (stats map[string]uint64, err error) {
return intf.namespace.get(intf)
}
func (c *commandEthtool) interfaces(includeNamespaces bool) ([]namespacedInterface, error) {
const namespaceDirectory = "/var/run/netns"
initialNamespace, err := netns.Get()
if err != nil {
c.log.Errorf("Could not get initial namespace: %s", err)
return nil, err
}
defer initialNamespace.Close()
// Gather the list of namespace names to from which to retrieve interfaces.
initialNamespaceIsNamed := false
var namespaceNames []string
// Handles are only used to create namespaced goroutines. We don't prefill
// with the handle for the initial namespace because we've already created
// its goroutine in Init().
handles := make(map[string]netns.NsHandle)
if includeNamespaces {
namespaces, err := os.ReadDir(namespaceDirectory)
if err != nil {
c.log.Warnf("Could not find namespace directory: %s", err)
}
// We'll always have at least the initial namespace, so add one to ensure
// we have capacity for it.
namespaceNames = make([]string, 0, len(namespaces)+1)
for _, namespace := range namespaces {
name := namespace.Name()
namespaceNames = append(namespaceNames, name)
handle, err := netns.GetFromPath(filepath.Join(namespaceDirectory, name))
if err != nil {
c.log.Warnf("Could not get handle for namespace %q: %s", name, err.Error())
continue
}
handles[name] = handle
if handle.Equal(initialNamespace) {
initialNamespaceIsNamed = true
}
}
}
// We don't want to gather interfaces from the same namespace twice, and
// it's possible, though unlikely, that the initial namespace is also a
// named interface.
if !initialNamespaceIsNamed {
namespaceNames = append(namespaceNames, "")
}
allInterfaces := make([]namespacedInterface, 0)
for _, namespace := range namespaceNames {
if _, ok := c.namespaceGoroutines[namespace]; !ok {
c.namespaceGoroutines[namespace] = &namespaceGoroutine{
namespaceName: namespace,
handle: handles[namespace],
log: c.log,
}
if err := c.namespaceGoroutines[namespace].start(); err != nil {
c.log.Errorf("Failed to start goroutine for namespace %q: %s", namespace, err.Error())
delete(c.namespaceGoroutines, namespace)
continue
}
}
interfaces, err := c.namespaceGoroutines[namespace].interfaces()
if err != nil {
c.log.Warnf("Could not get interfaces from namespace %q: %s", namespace, err.Error())
continue
}
allInterfaces = append(allInterfaces, interfaces...)
}
return allInterfaces, nil
}
func init() {
inputs.Add(pluginName, func() telegraf.Input {
return &Ethtool{
command: newCommandEthtool(),
}
})
}

View file

@ -0,0 +1,45 @@
//go:build !linux
package ethtool
import (
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/plugins/inputs"
)
type Ethtool struct {
// This is the list of interface names to include
InterfaceInclude []string `toml:"interface_include"`
// 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"`
// This is the list of namespace names to include
NamespaceInclude []string `toml:"namespace_include"`
// This is the list of namespace names to ignore
NamespaceExclude []string `toml:"namespace_exclude"`
// Normalization on the key names
NormalizeKeys []string `toml:"normalize_keys"`
Log telegraf.Logger `toml:"-"`
}
func (e *Ethtool) Init() error {
e.Log.Warn("Current platform is not supported")
return nil
}
func (*Ethtool) Gather(_ telegraf.Accumulator) error {
return nil
}
func init() {
inputs.Add(pluginName, func() telegraf.Input {
return &Ethtool{}
})
}

View file

@ -0,0 +1,987 @@
//go:build linux
package ethtool
import (
"errors"
"net"
"testing"
"github.com/stretchr/testify/require"
"github.com/influxdata/telegraf/testutil"
)
var (
eth *Ethtool
interfaceMap map[string]*interfaceMock
)
type interfaceMock struct {
name string
driverName string
namespaceName string
stat map[string]uint64
loopBack bool
interfaceUp bool
cmdGet map[string]uint64
}
type namespaceMock struct {
namespaceName string
}
func (n *namespaceMock) name() string {
return n.namespaceName
}
func (*namespaceMock) interfaces() ([]namespacedInterface, error) {
return nil, errors.New("it is a test bug to invoke this function")
}
func (*namespaceMock) driverName(_ namespacedInterface) (string, error) {
return "", errors.New("it is a test bug to invoke this function")
}
func (*namespaceMock) stats(_ namespacedInterface) (map[string]uint64, error) {
return nil, errors.New("it is a test bug to invoke this function")
}
func (*namespaceMock) get(_ namespacedInterface) (map[string]uint64, error) {
return nil, errors.New("it is a test bug to invoke this function")
}
type commandEthtoolMock struct {
interfaceMap map[string]*interfaceMock
}
func (*commandEthtoolMock) init() error {
// Not required for test mock
return nil
}
func (c *commandEthtoolMock) driverName(intf namespacedInterface) (string, error) {
i := c.interfaceMap[intf.Name]
if i != nil {
return i.driverName, nil
}
return "", errors.New("interface not found")
}
func (c *commandEthtoolMock) interfaces(includeNamespaces bool) ([]namespacedInterface, error) {
namespaces := map[string]*namespaceMock{"": {namespaceName: ""}}
interfaces := make([]namespacedInterface, 0)
for k, v := range c.interfaceMap {
if v.namespaceName != "" && !includeNamespaces {
continue
}
var flag net.Flags
// When interface is up
if v.interfaceUp {
flag |= net.FlagUp
}
// For loopback interface
if v.loopBack {
flag |= net.FlagLoopback
}
// Create a dummy interface
iface := net.Interface{
Index: 0,
MTU: 1500,
Name: k,
HardwareAddr: nil,
Flags: flag,
}
// Ensure there is a namespace if necessary
if _, ok := namespaces[v.namespaceName]; !ok {
namespaces[v.namespaceName] = &namespaceMock{
namespaceName: v.namespaceName,
}
}
interfaces = append(
interfaces,
namespacedInterface{
Interface: iface,
namespace: namespaces[v.namespaceName],
},
)
}
return interfaces, nil
}
func (c *commandEthtoolMock) stats(intf namespacedInterface) (map[string]uint64, error) {
i := c.interfaceMap[intf.Name]
if i != nil {
return i.stat, nil
}
return nil, errors.New("interface not found")
}
func (c *commandEthtoolMock) get(intf namespacedInterface) (map[string]uint64, error) {
i := c.interfaceMap[intf.Name]
if i != nil {
return i.cmdGet, nil
}
return nil, errors.New("interface not found")
}
func setup() {
interfaceMap = make(map[string]*interfaceMock)
eth1Stat := map[string]uint64{
"interface_up": 1,
"port_rx_1024_to_15xx": 25167245,
"port_rx_128_to_255": 1573526387,
"port_rx_15xx_to_jumbo": 137819058,
"port_rx_256_to_511": 772038107,
"port_rx_512_to_1023": 78294457,
"port_rx_64": 8798065,
"port_rx_65_to_127": 450348015,
"port_rx_bad": 0,
"port_rx_bad_bytes": 0,
"port_rx_bad_gtjumbo": 0,
"port_rx_broadcast": 6428250,
"port_rx_bytes": 893460472634,
"port_rx_control": 0,
"port_rx_dp_di_dropped_packets": 2772680304,
"port_rx_dp_hlb_fetch": 0,
"port_rx_dp_hlb_wait": 0,
"port_rx_dp_q_disabled_packets": 0,
"port_rx_dp_streaming_packets": 0,
"port_rx_good": 3045991334,
"port_rx_good_bytes": 893460472927,
"port_rx_gtjumbo": 0,
"port_rx_lt64": 0,
"port_rx_multicast": 1639566045,
"port_rx_nodesc_drops": 0,
"port_rx_overflow": 0,
"port_rx_packets": 3045991334,
"port_rx_pause": 0,
"port_rx_pm_discard_bb_overflow": 0,
"port_rx_pm_discard_mapping": 0,
"port_rx_pm_discard_qbb": 0,
"port_rx_pm_discard_vfifo_full": 0,
"port_rx_pm_trunc_bb_overflow": 0,
"port_rx_pm_trunc_qbb": 0,
"port_rx_pm_trunc_vfifo_full": 0,
"port_rx_unicast": 1399997040,
"port_tx_1024_to_15xx": 236,
"port_tx_128_to_255": 275090219,
"port_tx_15xx_to_jumbo": 926,
"port_tx_256_to_511": 48567221,
"port_tx_512_to_1023": 5142016,
"port_tx_64": 113903973,
"port_tx_65_to_127": 161935699,
"port_tx_broadcast": 8,
"port_tx_bytes": 94357131016,
"port_tx_control": 0,
"port_tx_lt64": 0,
"port_tx_multicast": 325891647,
"port_tx_packets": 604640290,
"port_tx_pause": 0,
"port_tx_unicast": 278748635,
"ptp_bad_syncs": 1,
"ptp_fast_syncs": 1,
"ptp_filter_matches": 0,
"ptp_good_syncs": 136151,
"ptp_invalid_sync_windows": 0,
"ptp_no_time_syncs": 1,
"ptp_non_filter_matches": 0,
"ptp_oversize_sync_windows": 53,
"ptp_rx_no_timestamp": 0,
"ptp_rx_timestamp_packets": 0,
"ptp_sync_timeouts": 1,
"ptp_timestamp_packets": 0,
"ptp_tx_timestamp_packets": 0,
"ptp_undersize_sync_windows": 3,
"rx-0.rx_packets": 55659234,
"rx-1.rx_packets": 87880538,
"rx-2.rx_packets": 26746234,
"rx-3.rx_packets": 103026471,
"rx-4.rx_packets": 0,
"rx_eth_crc_err": 0,
"rx_frm_trunc": 0,
"rx_inner_ip_hdr_chksum_err": 0,
"rx_inner_tcp_udp_chksum_err": 0,
"rx_ip_hdr_chksum_err": 0,
"rx_mcast_mismatch": 0,
"rx_merge_events": 0,
"rx_merge_packets": 0,
"rx_nodesc_trunc": 0,
"rx_noskb_drops": 0,
"rx_outer_ip_hdr_chksum_err": 0,
"rx_outer_tcp_udp_chksum_err": 0,
"rx_reset": 0,
"rx_tcp_udp_chksum_err": 0,
"rx_tobe_disc": 0,
"tx-0.tx_packets": 85843565,
"tx-1.tx_packets": 108642725,
"tx-2.tx_packets": 202596078,
"tx-3.tx_packets": 207561010,
"tx-4.tx_packets": 0,
"tx_cb_packets": 4,
"tx_merge_events": 11025,
"tx_pio_packets": 531928114,
"tx_pushes": 604643378,
"tx_tso_bursts": 0,
"tx_tso_fallbacks": 0,
"tx_tso_long_headers": 0,
}
eth1Get := map[string]uint64{
"autoneg": 1,
"duplex": 1,
"link": 1,
"speed": 1000,
}
eth1 := &interfaceMock{"eth1", "driver1", "", eth1Stat, false, true, eth1Get}
interfaceMap[eth1.name] = eth1
eth2Stat := map[string]uint64{
"interface_up": 0,
"port_rx_1024_to_15xx": 11529312,
"port_rx_128_to_255": 1868952037,
"port_rx_15xx_to_jumbo": 130339387,
"port_rx_256_to_511": 843846270,
"port_rx_512_to_1023": 173194372,
"port_rx_64": 9190374,
"port_rx_65_to_127": 507806115,
"port_rx_bad": 0,
"port_rx_bad_bytes": 0,
"port_rx_bad_gtjumbo": 0,
"port_rx_broadcast": 6648019,
"port_rx_bytes": 1007358162202,
"port_rx_control": 0,
"port_rx_dp_di_dropped_packets": 3164124639,
"port_rx_dp_hlb_fetch": 0,
"port_rx_dp_hlb_wait": 0,
"port_rx_dp_q_disabled_packets": 0,
"port_rx_dp_streaming_packets": 0,
"port_rx_good": 3544857867,
"port_rx_good_bytes": 1007358162202,
"port_rx_gtjumbo": 0,
"port_rx_lt64": 0,
"port_rx_multicast": 2231999743,
"port_rx_nodesc_drops": 0,
"port_rx_overflow": 0,
"port_rx_packets": 3544857867,
"port_rx_pause": 0,
"port_rx_pm_discard_bb_overflow": 0,
"port_rx_pm_discard_mapping": 0,
"port_rx_pm_discard_qbb": 0,
"port_rx_pm_discard_vfifo_full": 0,
"port_rx_pm_trunc_bb_overflow": 0,
"port_rx_pm_trunc_qbb": 0,
"port_rx_pm_trunc_vfifo_full": 0,
"port_rx_unicast": 1306210105,
"port_tx_1024_to_15xx": 379,
"port_tx_128_to_255": 202767251,
"port_tx_15xx_to_jumbo": 558,
"port_tx_256_to_511": 31454719,
"port_tx_512_to_1023": 6865731,
"port_tx_64": 17268276,
"port_tx_65_to_127": 272816313,
"port_tx_broadcast": 6,
"port_tx_bytes": 78071946593,
"port_tx_control": 0,
"port_tx_lt64": 0,
"port_tx_multicast": 239510586,
"port_tx_packets": 531173227,
"port_tx_pause": 0,
"port_tx_unicast": 291662635,
"ptp_bad_syncs": 0,
"ptp_fast_syncs": 0,
"ptp_filter_matches": 0,
"ptp_good_syncs": 0,
"ptp_invalid_sync_windows": 0,
"ptp_no_time_syncs": 0,
"ptp_non_filter_matches": 0,
"ptp_oversize_sync_windows": 0,
"ptp_rx_no_timestamp": 0,
"ptp_rx_timestamp_packets": 0,
"ptp_sync_timeouts": 0,
"ptp_timestamp_packets": 0,
"ptp_tx_timestamp_packets": 0,
"ptp_undersize_sync_windows": 0,
"rx-0.rx_packets": 84587075,
"rx-1.rx_packets": 74029305,
"rx-2.rx_packets": 134586471,
"rx-3.rx_packets": 87531322,
"rx-4.rx_packets": 0,
"rx_eth_crc_err": 0,
"rx_frm_trunc": 0,
"rx_inner_ip_hdr_chksum_err": 0,
"rx_inner_tcp_udp_chksum_err": 0,
"rx_ip_hdr_chksum_err": 0,
"rx_mcast_mismatch": 0,
"rx_merge_events": 0,
"rx_merge_packets": 0,
"rx_nodesc_trunc": 0,
"rx_noskb_drops": 0,
"rx_outer_ip_hdr_chksum_err": 0,
"rx_outer_tcp_udp_chksum_err": 0,
"rx_reset": 0,
"rx_tcp_udp_chksum_err": 0,
"rx_tobe_disc": 0,
"tx-0.tx_packets": 232521451,
"tx-1.tx_packets": 97876137,
"tx-2.tx_packets": 106822111,
"tx-3.tx_packets": 93955050,
"tx-4.tx_packets": 0,
"tx_cb_packets": 1,
"tx_merge_events": 8402,
"tx_pio_packets": 481040054,
"tx_pushes": 531174491,
"tx_tso_bursts": 128,
"tx_tso_fallbacks": 0,
"tx_tso_long_headers": 0,
}
eth2Get := map[string]uint64{
"autoneg": 0,
"duplex": 255,
"link": 0,
"speed": 9223372036854775807,
}
eth2 := &interfaceMock{"eth2", "driver1", "", eth2Stat, false, false, eth2Get}
interfaceMap[eth2.name] = eth2
eth3Stat := map[string]uint64{
"interface_up": 1,
"port_rx_1024_to_15xx": 25167245,
"port_rx_128_to_255": 1573526387,
"port_rx_15xx_to_jumbo": 137819058,
"port_rx_256_to_511": 772038107,
"port_rx_512_to_1023": 78294457,
"port_rx_64": 8798065,
"port_rx_65_to_127": 450348015,
"port_rx_bad": 0,
"port_rx_bad_bytes": 0,
"port_rx_bad_gtjumbo": 0,
"port_rx_broadcast": 6428250,
"port_rx_bytes": 893460472634,
"port_rx_control": 0,
"port_rx_dp_di_dropped_packets": 2772680304,
"port_rx_dp_hlb_fetch": 0,
"port_rx_dp_hlb_wait": 0,
"port_rx_dp_q_disabled_packets": 0,
"port_rx_dp_streaming_packets": 0,
"port_rx_good": 3045991334,
"port_rx_good_bytes": 893460472927,
"port_rx_gtjumbo": 0,
"port_rx_lt64": 0,
"port_rx_multicast": 1639566045,
"port_rx_nodesc_drops": 0,
"port_rx_overflow": 0,
"port_rx_packets": 3045991334,
"port_rx_pause": 0,
"port_rx_pm_discard_bb_overflow": 0,
"port_rx_pm_discard_mapping": 0,
"port_rx_pm_discard_qbb": 0,
"port_rx_pm_discard_vfifo_full": 0,
"port_rx_pm_trunc_bb_overflow": 0,
"port_rx_pm_trunc_qbb": 0,
"port_rx_pm_trunc_vfifo_full": 0,
"port_rx_unicast": 1399997040,
"port_tx_1024_to_15xx": 236,
"port_tx_128_to_255": 275090219,
"port_tx_15xx_to_jumbo": 926,
"port_tx_256_to_511": 48567221,
"port_tx_512_to_1023": 5142016,
"port_tx_64": 113903973,
"port_tx_65_to_127": 161935699,
"port_tx_broadcast": 8,
"port_tx_bytes": 94357131016,
"port_tx_control": 0,
"port_tx_lt64": 0,
"port_tx_multicast": 325891647,
"port_tx_packets": 604640290,
"port_tx_pause": 0,
"port_tx_unicast": 278748635,
"ptp_bad_syncs": 1,
"ptp_fast_syncs": 1,
"ptp_filter_matches": 0,
"ptp_good_syncs": 136151,
"ptp_invalid_sync_windows": 0,
"ptp_no_time_syncs": 1,
"ptp_non_filter_matches": 0,
"ptp_oversize_sync_windows": 53,
"ptp_rx_no_timestamp": 0,
"ptp_rx_timestamp_packets": 0,
"ptp_sync_timeouts": 1,
"ptp_timestamp_packets": 0,
"ptp_tx_timestamp_packets": 0,
"ptp_undersize_sync_windows": 3,
"rx-0.rx_packets": 55659234,
"rx-1.rx_packets": 87880538,
"rx-2.rx_packets": 26746234,
"rx-3.rx_packets": 103026471,
"rx-4.rx_packets": 0,
"rx_eth_crc_err": 0,
"rx_frm_trunc": 0,
"rx_inner_ip_hdr_chksum_err": 0,
"rx_inner_tcp_udp_chksum_err": 0,
"rx_ip_hdr_chksum_err": 0,
"rx_mcast_mismatch": 0,
"rx_merge_events": 0,
"rx_merge_packets": 0,
"rx_nodesc_trunc": 0,
"rx_noskb_drops": 0,
"rx_outer_ip_hdr_chksum_err": 0,
"rx_outer_tcp_udp_chksum_err": 0,
"rx_reset": 0,
"rx_tcp_udp_chksum_err": 0,
"rx_tobe_disc": 0,
"tx-0.tx_packets": 85843565,
"tx-1.tx_packets": 108642725,
"tx-2.tx_packets": 202596078,
"tx-3.tx_packets": 207561010,
"tx-4.tx_packets": 0,
"tx_cb_packets": 4,
"tx_merge_events": 11025,
"tx_pio_packets": 531928114,
"tx_pushes": 604643378,
"tx_tso_bursts": 0,
"tx_tso_fallbacks": 0,
"tx_tso_long_headers": 0,
}
eth3Get := map[string]uint64{
"autoneg": 1,
"duplex": 1,
"link": 1,
"speed": 1000,
}
eth3 := &interfaceMock{"eth3", "driver1", "namespace1", eth3Stat, false, true, eth3Get}
interfaceMap[eth3.name] = eth3
eth4Stat := map[string]uint64{
"interface_up": 1,
"port_rx_1024_to_15xx": 25167245,
"port_rx_128_to_255": 1573526387,
"port_rx_15xx_to_jumbo": 137819058,
"port_rx_256_to_511": 772038107,
"port_rx_512_to_1023": 78294457,
"port_rx_64": 8798065,
"port_rx_65_to_127": 450348015,
"port_rx_bad": 0,
"port_rx_bad_bytes": 0,
"port_rx_bad_gtjumbo": 0,
"port_rx_broadcast": 6428250,
"port_rx_bytes": 893460472634,
"port_rx_control": 0,
"port_rx_dp_di_dropped_packets": 2772680304,
"port_rx_dp_hlb_fetch": 0,
"port_rx_dp_hlb_wait": 0,
"port_rx_dp_q_disabled_packets": 0,
"port_rx_dp_streaming_packets": 0,
"port_rx_good": 3045991334,
"port_rx_good_bytes": 893460472927,
"port_rx_gtjumbo": 0,
"port_rx_lt64": 0,
"port_rx_multicast": 1639566045,
"port_rx_nodesc_drops": 0,
"port_rx_overflow": 0,
"port_rx_packets": 3045991334,
"port_rx_pause": 0,
"port_rx_pm_discard_bb_overflow": 0,
"port_rx_pm_discard_mapping": 0,
"port_rx_pm_discard_qbb": 0,
"port_rx_pm_discard_vfifo_full": 0,
"port_rx_pm_trunc_bb_overflow": 0,
"port_rx_pm_trunc_qbb": 0,
"port_rx_pm_trunc_vfifo_full": 0,
"port_rx_unicast": 1399997040,
"port_tx_1024_to_15xx": 236,
"port_tx_128_to_255": 275090219,
"port_tx_15xx_to_jumbo": 926,
"port_tx_256_to_511": 48567221,
"port_tx_512_to_1023": 5142016,
"port_tx_64": 113903973,
"port_tx_65_to_127": 161935699,
"port_tx_broadcast": 8,
"port_tx_bytes": 94357131016,
"port_tx_control": 0,
"port_tx_lt64": 0,
"port_tx_multicast": 325891647,
"port_tx_packets": 604640290,
"port_tx_pause": 0,
"port_tx_unicast": 278748635,
"ptp_bad_syncs": 1,
"ptp_fast_syncs": 1,
"ptp_filter_matches": 0,
"ptp_good_syncs": 136151,
"ptp_invalid_sync_windows": 0,
"ptp_no_time_syncs": 1,
"ptp_non_filter_matches": 0,
"ptp_oversize_sync_windows": 53,
"ptp_rx_no_timestamp": 0,
"ptp_rx_timestamp_packets": 0,
"ptp_sync_timeouts": 1,
"ptp_timestamp_packets": 0,
"ptp_tx_timestamp_packets": 0,
"ptp_undersize_sync_windows": 3,
"rx-0.rx_packets": 55659234,
"rx-1.rx_packets": 87880538,
"rx-2.rx_packets": 26746234,
"rx-3.rx_packets": 103026471,
"rx-4.rx_packets": 0,
"rx_eth_crc_err": 0,
"rx_frm_trunc": 0,
"rx_inner_ip_hdr_chksum_err": 0,
"rx_inner_tcp_udp_chksum_err": 0,
"rx_ip_hdr_chksum_err": 0,
"rx_mcast_mismatch": 0,
"rx_merge_events": 0,
"rx_merge_packets": 0,
"rx_nodesc_trunc": 0,
"rx_noskb_drops": 0,
"rx_outer_ip_hdr_chksum_err": 0,
"rx_outer_tcp_udp_chksum_err": 0,
"rx_reset": 0,
"rx_tcp_udp_chksum_err": 0,
"rx_tobe_disc": 0,
"tx-0.tx_packets": 85843565,
"tx-1.tx_packets": 108642725,
"tx-2.tx_packets": 202596078,
"tx-3.tx_packets": 207561010,
"tx-4.tx_packets": 0,
"tx_cb_packets": 4,
"tx_merge_events": 11025,
"tx_pio_packets": 531928114,
"tx_pushes": 604643378,
"tx_tso_bursts": 0,
"tx_tso_fallbacks": 0,
"tx_tso_long_headers": 0,
}
eth4Get := map[string]uint64{
"autoneg": 1,
"duplex": 1,
"link": 1,
"speed": 100,
}
eth4 := &interfaceMock{"eth4", "driver1", "namespace2", eth4Stat, false, true, eth4Get}
interfaceMap[eth4.name] = eth4
// dummy loopback including dummy stat to ensure that the ignore feature is working
lo0Stat := map[string]uint64{
"dummy": 0,
}
lo0Get := map[string]uint64{
"autoneg": 1,
"duplex": 1,
"link": 1,
"speed": 1000,
}
lo0 := &interfaceMock{"lo0", "", "", lo0Stat, true, true, lo0Get}
interfaceMap[lo0.name] = lo0
c := &commandEthtoolMock{interfaceMap}
eth = &Ethtool{
DownInterfaces: "expose",
command: c,
}
}
func toStringMapInterface(in map[string]uint64) map[string]interface{} {
m := map[string]interface{}{}
for k, v := range in {
m[k] = v
}
return m
}
func toStringMapUint(in map[string]interface{}) map[string]uint64 {
m := map[string]uint64{}
for k, v := range in {
t := v.(uint64)
m[k] = t
}
return m
}
func TestGather(t *testing.T) {
setup()
err := eth.Init()
require.NoError(t, err)
var acc testutil.Accumulator
err = eth.Gather(&acc)
require.NoError(t, err)
require.Len(t, acc.Metrics, 2)
expectedFieldsEth1 := toStringMapInterface(interfaceMap["eth1"].stat)
for k, v := range interfaceMap["eth1"].cmdGet {
expectedFieldsEth1[k] = v
}
expectedFieldsEth1["interface_up_counter"] = expectedFieldsEth1["interface_up"]
expectedFieldsEth1["interface_up"] = true
expectedTagsEth1 := map[string]string{
"interface": "eth1",
"driver": "driver1",
"namespace": "",
}
acc.AssertContainsTaggedFields(t, pluginName, expectedFieldsEth1, expectedTagsEth1)
expectedFieldsEth2 := toStringMapInterface(interfaceMap["eth2"].stat)
for k, v := range interfaceMap["eth2"].cmdGet {
expectedFieldsEth2[k] = v
}
expectedFieldsEth2["interface_up_counter"] = expectedFieldsEth2["interface_up"]
expectedFieldsEth2["interface_up"] = false
expectedTagsEth2 := map[string]string{
"driver": "driver1",
"interface": "eth2",
"namespace": "",
}
acc.AssertContainsTaggedFields(t, pluginName, expectedFieldsEth2, expectedTagsEth2)
}
func TestGatherIncludeInterfaces(t *testing.T) {
setup()
eth.InterfaceInclude = append(eth.InterfaceInclude, "eth1")
err := eth.Init()
require.NoError(t, err)
var acc testutil.Accumulator
err = eth.Gather(&acc)
require.NoError(t, err)
require.Len(t, acc.Metrics, 1)
// Should contain eth1
expectedFieldsEth1 := toStringMapInterface(interfaceMap["eth1"].stat)
for k, v := range interfaceMap["eth1"].cmdGet {
expectedFieldsEth1[k] = v
}
expectedFieldsEth1["interface_up_counter"] = expectedFieldsEth1["interface_up"]
expectedFieldsEth1["interface_up"] = true
expectedTagsEth1 := map[string]string{
"interface": "eth1",
"driver": "driver1",
"namespace": "",
}
acc.AssertContainsTaggedFields(t, pluginName, expectedFieldsEth1, expectedTagsEth1)
// Should not contain eth2
expectedFieldsEth2 := toStringMapInterface(interfaceMap["eth2"].stat)
for k, v := range interfaceMap["eth2"].cmdGet {
expectedFieldsEth2[k] = v
}
expectedFieldsEth2["interface_up_counter"] = expectedFieldsEth2["interface_up"]
expectedFieldsEth2["interface_up"] = false
expectedTagsEth2 := map[string]string{
"interface": "eth2",
"driver": "driver1",
"namespace": "",
}
acc.AssertDoesNotContainsTaggedFields(t, pluginName, expectedFieldsEth2, expectedTagsEth2)
}
func TestGatherIgnoreInterfaces(t *testing.T) {
setup()
eth.InterfaceExclude = append(eth.InterfaceExclude, "eth1")
err := eth.Init()
require.NoError(t, err)
var acc testutil.Accumulator
err = eth.Gather(&acc)
require.NoError(t, err)
require.Len(t, acc.Metrics, 1)
// Should not contain eth1
expectedFieldsEth1 := toStringMapInterface(interfaceMap["eth1"].stat)
for k, v := range interfaceMap["eth1"].cmdGet {
expectedFieldsEth1[k] = v
}
expectedFieldsEth1["interface_up_counter"] = expectedFieldsEth1["interface_up"]
expectedFieldsEth1["interface_up"] = true
expectedTagsEth1 := map[string]string{
"interface": "eth1",
"driver": "driver1",
"namespace": "",
}
acc.AssertDoesNotContainsTaggedFields(t, pluginName, expectedFieldsEth1, expectedTagsEth1)
// Should contain eth2
expectedFieldsEth2 := toStringMapInterface(interfaceMap["eth2"].stat)
for k, v := range interfaceMap["eth2"].cmdGet {
expectedFieldsEth2[k] = v
}
expectedFieldsEth2["interface_up_counter"] = expectedFieldsEth2["interface_up"]
expectedFieldsEth2["interface_up"] = false
expectedTagsEth2 := map[string]string{
"interface": "eth2",
"driver": "driver1",
"namespace": "",
}
acc.AssertContainsTaggedFields(t, pluginName, expectedFieldsEth2, expectedTagsEth2)
}
func TestSkipMetricsForInterfaceDown(t *testing.T) {
setup()
eth.DownInterfaces = "skip"
err := eth.Init()
require.NoError(t, err)
var acc testutil.Accumulator
err = eth.Gather(&acc)
require.NoError(t, err)
require.Len(t, acc.Metrics, 1)
expectedFieldsEth1 := toStringMapInterface(interfaceMap["eth1"].stat)
for k, v := range interfaceMap["eth1"].cmdGet {
expectedFieldsEth1[k] = v
}
expectedFieldsEth1["interface_up_counter"] = expectedFieldsEth1["interface_up"]
expectedFieldsEth1["interface_up"] = true
expectedTagsEth1 := map[string]string{
"interface": "eth1",
"driver": "driver1",
"namespace": "",
}
acc.AssertContainsTaggedFields(t, pluginName, expectedFieldsEth1, expectedTagsEth1)
}
func TestGatherIncludeNamespaces(t *testing.T) {
setup()
var acc testutil.Accumulator
eth.NamespaceInclude = append(eth.NamespaceInclude, "namespace1")
err := eth.Init()
require.NoError(t, err)
err = eth.Gather(&acc)
require.NoError(t, err)
require.Len(t, acc.Metrics, 1)
// Should contain eth3
expectedFieldsEth3 := toStringMapInterface(interfaceMap["eth3"].stat)
for k, v := range interfaceMap["eth3"].cmdGet {
expectedFieldsEth3[k] = v
}
expectedFieldsEth3["interface_up_counter"] = expectedFieldsEth3["interface_up"]
expectedFieldsEth3["interface_up"] = true
expectedTagsEth3 := map[string]string{
"interface": "eth3",
"driver": "driver1",
"namespace": "namespace1",
}
acc.AssertContainsTaggedFields(t, pluginName, expectedFieldsEth3, expectedTagsEth3)
// Should not contain eth2
expectedFieldsEth2 := toStringMapInterface(interfaceMap["eth2"].stat)
for k, v := range interfaceMap["eth2"].cmdGet {
expectedFieldsEth2[k] = v
}
expectedFieldsEth2["interface_up_counter"] = expectedFieldsEth2["interface_up"]
expectedFieldsEth2["interface_up"] = false
expectedTagsEth2 := map[string]string{
"interface": "eth2",
"driver": "driver1",
"namespace": "",
}
acc.AssertDoesNotContainsTaggedFields(t, pluginName, expectedFieldsEth2, expectedTagsEth2)
}
func TestGatherIgnoreNamespaces(t *testing.T) {
setup()
var acc testutil.Accumulator
eth.NamespaceExclude = append(eth.NamespaceExclude, "namespace2")
err := eth.Init()
require.NoError(t, err)
err = eth.Gather(&acc)
require.NoError(t, err)
require.Len(t, acc.Metrics, 3)
// Should not contain eth4
expectedFieldsEth4 := toStringMapInterface(interfaceMap["eth4"].stat)
for k, v := range interfaceMap["eth4"].cmdGet {
expectedFieldsEth4[k] = v
}
expectedFieldsEth4["interface_up_counter"] = expectedFieldsEth4["interface_up"]
expectedFieldsEth4["interface_up"] = true
expectedTagsEth4 := map[string]string{
"interface": "eth4",
"driver": "driver1",
"namespace": "namespace2",
}
acc.AssertDoesNotContainsTaggedFields(t, pluginName, expectedFieldsEth4, expectedTagsEth4)
// Should contain eth2
expectedFieldsEth2 := toStringMapInterface(interfaceMap["eth2"].stat)
for k, v := range interfaceMap["eth2"].cmdGet {
expectedFieldsEth2[k] = v
}
expectedFieldsEth2["interface_up_counter"] = expectedFieldsEth2["interface_up"]
expectedFieldsEth2["interface_up"] = false
expectedTagsEth2 := map[string]string{
"interface": "eth2",
"driver": "driver1",
"namespace": "",
}
acc.AssertContainsTaggedFields(t, pluginName, expectedFieldsEth2, expectedTagsEth2)
// Should contain eth3
expectedFieldsEth3 := toStringMapInterface(interfaceMap["eth3"].stat)
for k, v := range interfaceMap["eth3"].cmdGet {
expectedFieldsEth3[k] = v
}
expectedFieldsEth3["interface_up_counter"] = expectedFieldsEth3["interface_up"]
expectedFieldsEth3["interface_up"] = true
expectedTagsEth3 := map[string]string{
"interface": "eth3",
"driver": "driver1",
"namespace": "namespace1",
}
acc.AssertContainsTaggedFields(t, pluginName, expectedFieldsEth3, expectedTagsEth3)
}
type testCase struct {
normalization []string
stats map[string]interface{}
expectedFields map[string]interface{}
}
func TestNormalizedKeys(t *testing.T) {
cases := []testCase{
{
normalization: []string{"underscore"},
stats: map[string]interface{}{
"port rx": uint64(1),
" Port_tx": uint64(0),
"interface_up": uint64(0),
},
expectedFields: map[string]interface{}{
"port_rx": uint64(1),
"_Port_tx": uint64(0),
"interface_up": true,
"interface_up_counter": uint64(0),
},
},
{
normalization: []string{"underscore", "lower"},
stats: map[string]interface{}{
"Port rx": uint64(1),
" Port_tx": uint64(0),
"interface_up": uint64(0),
},
expectedFields: map[string]interface{}{
"port_rx": uint64(1),
"_port_tx": uint64(0),
"interface_up": true,
"interface_up_counter": uint64(0),
},
},
{
normalization: []string{"underscore", "lower", "trim"},
stats: map[string]interface{}{
" Port RX ": uint64(1),
" Port_tx": uint64(0),
"interface_up": uint64(0),
},
expectedFields: map[string]interface{}{
"port_rx": uint64(1),
"port_tx": uint64(0),
"interface_up": true,
"interface_up_counter": uint64(0),
},
},
{
normalization: []string{"underscore", "lower", "snakecase", "trim"},
stats: map[string]interface{}{
" Port RX ": uint64(1),
" Port_tx": uint64(0),
"interface_up": uint64(0),
},
expectedFields: map[string]interface{}{
"port_rx": uint64(1),
"port_tx": uint64(0),
"interface_up": true,
"interface_up_counter": uint64(0),
},
},
{
normalization: []string{"snakecase"},
stats: map[string]interface{}{
" PortRX ": uint64(1),
" PortTX": uint64(0),
"interface_up": uint64(0),
},
expectedFields: map[string]interface{}{
"port_rx": uint64(1),
"port_tx": uint64(0),
"interface_up": true,
"interface_up_counter": uint64(0),
},
},
{
stats: map[string]interface{}{
" Port RX ": uint64(1),
" Port_tx": uint64(0),
"interface_up": uint64(0),
},
expectedFields: map[string]interface{}{
" Port RX ": uint64(1),
" Port_tx": uint64(0),
"interface_up": true,
"interface_up_counter": uint64(0),
},
},
{
stats: map[string]interface{}{
" Port RX ": uint64(1),
" Port_tx": uint64(0),
},
expectedFields: map[string]interface{}{
" Port RX ": uint64(1),
" Port_tx": uint64(0),
"interface_up": true,
},
},
}
for _, c := range cases {
eth0 := &interfaceMock{"eth0", "e1000e", "", toStringMapUint(c.stats), false, true, map[string]uint64{}}
expectedTags := map[string]string{
"interface": eth0.name,
"driver": eth0.driverName,
"namespace": "",
}
interfaceMap = make(map[string]*interfaceMock)
interfaceMap[eth0.name] = eth0
cmd := &commandEthtoolMock{interfaceMap}
eth = &Ethtool{
NormalizeKeys: c.normalization,
command: cmd,
}
err := eth.Init()
require.NoError(t, err)
var acc testutil.Accumulator
err = eth.Gather(&acc)
require.NoError(t, err)
require.Len(t, acc.Metrics, 1)
acc.AssertContainsFields(t, pluginName, c.expectedFields)
acc.AssertContainsTaggedFields(t, pluginName, c.expectedFields, expectedTags)
}
}

View file

@ -0,0 +1,181 @@
//go:build linux
package ethtool
import (
"math"
"net"
"runtime"
"github.com/safchain/ethtool"
"github.com/vishvananda/netns"
"github.com/influxdata/telegraf"
)
type namespace interface {
name() string
interfaces() ([]namespacedInterface, error)
driverName(intf namespacedInterface) (string, error)
stats(intf namespacedInterface) (map[string]uint64, error)
get(intf namespacedInterface) (map[string]uint64, error)
}
type namespacedInterface struct {
net.Interface
namespace namespace
}
type namespacedAction struct {
result chan<- namespacedResult
f func(*namespaceGoroutine) (interface{}, error)
}
type namespacedResult struct {
result interface{}
err error
}
type namespaceGoroutine struct {
namespaceName string
handle netns.NsHandle
ethtoolClient *ethtool.Ethtool
c chan namespacedAction
log telegraf.Logger
}
func (n *namespaceGoroutine) name() string {
return n.namespaceName
}
func (n *namespaceGoroutine) interfaces() ([]namespacedInterface, error) {
interfaces, err := n.do(func(n *namespaceGoroutine) (interface{}, error) {
interfaces, err := net.Interfaces()
if err != nil {
return nil, err
}
namespacedInterfaces := make([]namespacedInterface, 0, len(interfaces))
for _, iface := range interfaces {
namespacedInterfaces = append(
namespacedInterfaces,
namespacedInterface{
Interface: iface,
namespace: n,
},
)
}
return namespacedInterfaces, nil
})
return interfaces.([]namespacedInterface), err
}
func (n *namespaceGoroutine) driverName(intf namespacedInterface) (string, error) {
driver, err := n.do(func(n *namespaceGoroutine) (interface{}, error) {
return n.ethtoolClient.DriverName(intf.Name)
})
return driver.(string), err
}
func (n *namespaceGoroutine) stats(intf namespacedInterface) (map[string]uint64, error) {
driver, err := n.do(func(n *namespaceGoroutine) (interface{}, error) {
return n.ethtoolClient.Stats(intf.Name)
})
return driver.(map[string]uint64), err
}
func (n *namespaceGoroutine) get(intf namespacedInterface) (map[string]uint64, error) {
result, err := n.do(func(n *namespaceGoroutine) (interface{}, error) {
ecmd := ethtool.EthtoolCmd{}
speed32, err := n.ethtoolClient.CmdGet(&ecmd, intf.Name)
if err != nil {
return nil, err
}
var speed = uint64(speed32)
if speed == math.MaxUint32 {
speed = math.MaxUint64
}
var link32 uint32
link32, err = n.ethtoolClient.LinkState(intf.Name)
if err != nil {
return nil, err
}
return map[string]uint64{
"speed": speed,
"duplex": uint64(ecmd.Duplex),
"autoneg": uint64(ecmd.Autoneg),
"link": uint64(link32),
}, nil
})
if result != nil {
return result.(map[string]uint64), err
}
return nil, err
}
// start locks a goroutine to an OS thread and ties it to the namespace, then
// loops for actions to run in the namespace.
func (n *namespaceGoroutine) start() error {
n.c = make(chan namespacedAction)
started := make(chan error)
go func() {
// We're going to hold this thread locked permanently. We're going to
// do this for every namespace. This makes it very likely that the Go
// runtime will spin up new threads to replace it. To avoid thread
// leaks, we don't unlock when we're done and instead let this thread
// die.
runtime.LockOSThread()
// If this goroutine is for the initial namespace, we are already in
// the correct namespace. Switching would require CAP_SYS_ADMIN, which
// we may not have. Don't switch if the desired namespace matches the
// current one.
initialNamespace, err := netns.Get()
if err != nil {
n.log.Errorf("Could not get initial namespace: %s", err)
started <- err
return
}
if !initialNamespace.Equal(n.handle) {
if err := netns.Set(n.handle); err != nil {
n.log.Errorf("Could not switch to namespace %q: %s", n.namespaceName, err.Error())
started <- err
return
}
}
// Every namespace needs its own connection to ethtool
e, err := ethtool.NewEthtool()
if err != nil {
n.log.Errorf("Could not create ethtool client for namespace %q: %s", n.namespaceName, err.Error())
started <- err
return
}
n.ethtoolClient = e
started <- nil
for command := range n.c {
result, err := command.f(n)
command.result <- namespacedResult{
result: result,
err: err,
}
close(command.result)
}
}()
return <-started
}
// do runs a function inside the OS thread tied to the namespace.
func (n *namespaceGoroutine) do(f func(*namespaceGoroutine) (interface{}, error)) (interface{}, error) {
result := make(chan namespacedResult)
n.c <- namespacedAction{
result: result,
f: f,
}
r := <-result
return r.result, r.err
}

View file

@ -0,0 +1,40 @@
# Returns ethtool statistics for given interfaces
# This plugin ONLY supports Linux
[[inputs.ethtool]]
## List of interfaces to pull metrics for
# interface_include = ["eth0"]
## 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"
## Reading statistics from interfaces in additional namespaces is also
## supported, so long as the namespaces are named (have a symlink in
## /var/run/netns). The telegraf process will also need the CAP_SYS_ADMIN
## permission.
## By default, only the current namespace will be used. For additional
## namespace support, at least one of `namespace_include` and
## `namespace_exclude` must be provided.
## To include all namespaces, set `namespace_include` to `["*"]`.
## The initial namespace (if anonymous) can be specified with the empty
## string ("").
## List of namespaces to pull metrics for
# namespace_include = []
## List of namespace to ignore when pulling metrics.
# namespace_exclude = []
## 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"]