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 ethtool plugin #963

Merged
merged 1 commit into from
Jun 13, 2024
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
3 changes: 2 additions & 1 deletion agent/metrics_agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
_ "flashcat.cloud/categraf/inputs/dns_query"
_ "flashcat.cloud/categraf/inputs/docker"
_ "flashcat.cloud/categraf/inputs/elasticsearch"
_ "flashcat.cloud/categraf/inputs/ethtool"
_ "flashcat.cloud/categraf/inputs/exec"
_ "flashcat.cloud/categraf/inputs/filecount"
_ "flashcat.cloud/categraf/inputs/googlecloud"
Expand All @@ -37,6 +38,7 @@ import (
_ "flashcat.cloud/categraf/inputs/http_response"
_ "flashcat.cloud/categraf/inputs/influxdb"
_ "flashcat.cloud/categraf/inputs/ipmi"
_ "flashcat.cloud/categraf/inputs/iptables"
_ "flashcat.cloud/categraf/inputs/ipvs"
_ "flashcat.cloud/categraf/inputs/jenkins"
_ "flashcat.cloud/categraf/inputs/jolokia_agent"
Expand Down Expand Up @@ -92,7 +94,6 @@ import (
_ "flashcat.cloud/categraf/inputs/whois"
_ "flashcat.cloud/categraf/inputs/xskyapi"
_ "flashcat.cloud/categraf/inputs/zookeeper"
_ "flashcat.cloud/categraf/inputs/iptables"
)

type MetricsAgent struct {
Expand Down
43 changes: 43 additions & 0 deletions conf/input.ethtool/ethtool.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Returns ethtool statistics for given interfaces
# This plugin ONLY supports Linux
# # collect interval
# interval = 15
[[instances]]
## List of interfaces to pull metrics for
# interface_include = ["eth0"]
interface_include = ["ens33","ens38"]
## List of interfaces to ignore when pulling metrics.
# interface_exclude = ["eth1"]
#interface_exclude = ["bond*", "br*", "cni*", "docker*", "flannel*", "lxcbr*", "lxdbr*", "tap*", "virbr*"]

## 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"]
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ require (
github.com/tomasen/fcgi_client v0.0.0-20180423082037-2bb3d819fd19
github.com/ugorji/go/codec v1.2.11
github.com/vishvananda/netlink v1.1.0 // indirect
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df // indirect
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df
github.com/vultr/govultr/v2 v2.17.2 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.2 // indirect
Expand Down
100 changes: 100 additions & 0 deletions inputs/ethtool/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
Forked from [telegraf/ethtool](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/ethtool)
____

# Ethtool Input Plugin

The ethtool input plugin pulls ethernet device stats. Fields pulled will depend
on the network device and driver.

## Configuration

```toml @sample.conf
# Returns ethtool statistics for given interfaces
# This plugin ONLY supports Linux
[[instances]]
## 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 categraf 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 categraf in several ways. The two recommended ways are listed below:

### Using systemd capabilities

If you are using systemd to run categraf, you may run
`systemctl edit categraf.service` and add the following:

```text
[Service]
AmbientCapabilities=CAP_SYS_ADMIN
```

### Configuring executable capabilities

If you are not using systemd to run categraf, you can configure the categraf
executable to have `CAP_SYS_ADMIN` when run.

```sh
sudo setcap CAP_SYS_ADMIN+epi $(which categraf)
```

N.B.: This capability is a filesystem attribute on the binary itself. The
attribute needs to be re-applied if the categraf binary is rotated (e.g. on
installation of new a categraf 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
```
134 changes: 134 additions & 0 deletions inputs/ethtool/command_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
//go:build linux

package ethtool

import (
"log"
"os"
"path/filepath"

"github.com/vishvananda/netns"
)

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 {
namespaceGoroutines map[string]*NamespaceGoroutine
}

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
}
namespaceGoroutine := &NamespaceGoroutine{
name: "",
handle: initialNamespace,
}
if err := namespaceGoroutine.Start(); err != nil {
log.Println("E! Failed to start goroutine for the initial namespace: ", err)
return err
}
c.namespaceGoroutines = map[string]*NamespaceGoroutine{
"": namespaceGoroutine,
}
return nil
}

func (c *CommandEthtool) DriverName(intf NamespacedInterface) (driver string, err error) {
return intf.Namespace.DriverName(intf)
}

func (c *CommandEthtool) Stats(intf NamespacedInterface) (stats map[string]uint64, err error) {
return intf.Namespace.Stats(intf)
}

func (c *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 {
log.Println("E! Could not get initial namespace: ", 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 := map[string]netns.NsHandle{}

if includeNamespaces {
namespaces, err := os.ReadDir(namespaceDirectory)
if err != nil {
log.Println("W! Could not find namespace directory: ", 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 {
log.Printf("W! 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{
name: namespace,
handle: handles[namespace],
}
if err := c.namespaceGoroutines[namespace].Start(); err != nil {
log.Printf("E! 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 {
log.Printf("W! Could not get interfaces from namespace [%q]: [%s]", namespace, err.Error())
continue
}
allInterfaces = append(allInterfaces, interfaces...)
}

return allInterfaces, nil
}
Loading
Loading