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

Add Bond Interface input plugin #3424

Merged
merged 5 commits into from
Nov 28, 2017
Merged
Show file tree
Hide file tree
Changes from 2 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
1 change: 1 addition & 0 deletions plugins/inputs/all/all.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
_ "github.com/influxdata/telegraf/plugins/inputs/amqp_consumer"
_ "github.com/influxdata/telegraf/plugins/inputs/apache"
_ "github.com/influxdata/telegraf/plugins/inputs/bcache"
_ "github.com/influxdata/telegraf/plugins/inputs/bond"
_ "github.com/influxdata/telegraf/plugins/inputs/cassandra"
_ "github.com/influxdata/telegraf/plugins/inputs/ceph"
_ "github.com/influxdata/telegraf/plugins/inputs/cgroup"
Expand Down
81 changes: 81 additions & 0 deletions plugins/inputs/bond/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Bond Input Plugin

The Bond Input plugin collects bond interface status, bond's slaves interfaces
status and failures count of bond's slaves interfaces.
The plugin collects these metrics from `/proc/net/bonding/*` files.

### Configuration:

```toml
[[inputs.bond]]
## Sets bonding directory path
## If not specified, then default is:
bond_path = "/proc/net/bonding"

## By default, telegraf gather stats for all bond interfaces
## Setting interfaces will restrict the stats to the specified
## bond interfaces.
bond_interfaces = ["bond0"]
```

### Measurements & Fields:

- bond
- status

- bond_slave
- failures
- status

### Description:

```
status
Status of bond interface or bonds's slave interface (down = 0, up = 1).

failures
Amount of failures for bond's slave interface.
```

### Tags:

- bond
- bond

- bond_slave
- bond
- interface

### Example output:

Configuration:

```
[[inputs.bond]]
## Sets bonding directory path
## If not specified, then default is:
bond_path = "/proc/net/bonding"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment out since this is default.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


## By default, telegraf gather stats for all bond interfaces
## Setting interfaces will restrict the stats to the specified
## bond interfaces.
bond_interfaces = ["bond0", "bond1"]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment this line out with a single #, so the default will be used.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

```

Run:

```
telegraf --config telegraf.conf --input-filter bond --test
```

Output:

```
* Plugin: inputs.bond, Collection 1
> bond,bond=bond1,host=local status=1i 1509704525000000000
> bond_slave,bond=bond1,interface=eth0,host=local status=1i,failures=0i 1509704525000000000
> bond_slave,host=local,bond=bond1,interface=eth1 status=1i,failures=0i 1509704525000000000
> bond,bond=bond0,host=isvetlov-mac.local status=1i 1509704525000000000
> bond_slave,bond=bond0,interface=eth1,host=local status=1i,failures=0i 1509704525000000000
> bond_slave,bond=bond0,interface=eth2,host=local status=1i,failures=0i 1509704525000000000
```
172 changes: 172 additions & 0 deletions plugins/inputs/bond/bond.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
package bond

import (
"fmt"
"io/ioutil"
"path/filepath"
"strconv"
"strings"

"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/plugins/inputs"
)

// default bond directory path
const (
BOND_PATH = "/proc/net/bonding"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say call this defaultBondPath because Go naming guidelines, but to match existing style we should only specify the proc location. We can add it to the config as host_proc = "/proc", but we should also load it from the HOST_PROC environment variable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

)

type Bond struct {
BondPath string `toml:"bond_path"`
BondInterfaces []string `toml:"bond_interfaces"`
}

var sampleConfig = `
## Sets bonding directory path
## If not specified, then default is:
bond_path = "/proc/net/bonding"

## By default, telegraf gather stats for all bond interfaces
## Setting interfaces will restrict the stats to the specified
## bond interfaces.
bond_interfaces = ["bond0"]
`

func (bond *Bond) Description() string {
return "Collect bond interface status, slaves statuses and failures count"
}

func (bond *Bond) SampleConfig() string {
return sampleConfig
}

func (bond *Bond) Gather(acc telegraf.Accumulator) error {
// load path, get default value if config value and env variables are empty;
// list bond interfaces from bonding directory or gather all interfaces.
err := bond.listInterfaces()
if err != nil {
return err
}
for _, bondName := range bond.BondInterfaces {
file, err := ioutil.ReadFile(bond.BondPath + "/" + bondName)
if err != nil {
acc.AddError(fmt.Errorf("E! error due inspecting '%s' interface: %v", bondName, err))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The log level E! will be added by the accumulator, so just "error inspecting '%s' interface: %v", only add a log level if directly using the logger. Check for this throughout pull request.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

continue
}
rawFile := strings.TrimSpace(string(file))
err = bond.gatherBondInterface(bondName, rawFile, acc)
if err != nil {
acc.AddError(fmt.Errorf("E! error due inspecting '%s' interface: %v", bondName, err))
}
}
return nil
}

func (bond *Bond) gatherBondInterface(bondName string, rawFile string, acc telegraf.Accumulator) error {
splitIndex := strings.Index(rawFile, "Slave Interface:")
if splitIndex == -1 {
splitIndex = len(rawFile)
}
bondPart := rawFile[:splitIndex]
slavePart := rawFile[splitIndex:]

err := bond.gatherBondPart(bondName, bondPart, acc)
if err != nil {
return err
}
err = bond.gatherSlavePart(bondName, slavePart, acc)
if err != nil {
return err
}
return nil
}

func (bond *Bond) gatherBondPart(bondName string, rawFile string, acc telegraf.Accumulator) error {
fields := make(map[string]interface{})
tags := map[string]string{
"bond": bondName,
}

lines := strings.Split(rawFile, "\n")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use a bufio.Scanner to avoid EOL encoding issues.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

for _, line := range lines {
stats := strings.Split(line, ":")
if len(stats) < 2 {
continue
}
name := strings.ToLower(strings.Replace(strings.TrimSpace(stats[0]), " ", "_", -1))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why lowercase and replace spaces with underscores?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's too excessive, my bad. Removed.

value := strings.TrimSpace(stats[1])
if strings.Contains(name, "mii_status") {
fields["status"] = 0
if value == "up" {
fields["status"] = 1
}
acc.AddFields("bond", fields, tags)
return nil
}
}
return fmt.Errorf("E! Couldn't find status info for '%s' ", bondName)
}

func (bond *Bond) gatherSlavePart(bondName string, rawFile string, acc telegraf.Accumulator) error {
var slave string
var status int

lines := strings.Split(rawFile, "\n")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bufio.Scanner

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

for _, line := range lines {
stats := strings.Split(line, ":")
if len(stats) < 2 {
continue
}
name := strings.ToLower(strings.Replace(strings.TrimSpace(stats[0]), " ", "_", -1))
value := strings.TrimSpace(stats[1])
if strings.Contains(name, "slave_interface") {
slave = value
}
if strings.Contains(name, "mii_status") {
status = 0
if value == "up" {
status = 1
}
}
if strings.Contains(name, "link_failure_count") {
count, err := strconv.Atoi(value)
if err != nil {
return err
}
fields := map[string]interface{}{
"status": status,
"failures": count,
}
tags := map[string]string{
"bond": bondName,
"interface": slave,
}
acc.AddFields("bond_slave", fields, tags)
}
}
return nil
}

func (bond *Bond) listInterfaces() error {
if bond.BondPath == "" {
bond.BondPath = BOND_PATH
}
if len(bond.BondInterfaces) == 0 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't cache the bond interfaces, or we won't be able to see new bonds without restarting Telegraf. Return the paths instead of adding them to the struct.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

paths, err := filepath.Glob(bond.BondPath + "/*")
if err != nil {
return err
}
var interfaces []string
for _, p := range paths {
interfaces = append(interfaces, filepath.Base(p))
}
bond.BondInterfaces = interfaces
}
return nil
}

func init() {
inputs.Add("bond", func() telegraf.Input {
return &Bond{}
})
}
77 changes: 77 additions & 0 deletions plugins/inputs/bond/bond_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package bond

import (
"testing"

"github.com/influxdata/telegraf/testutil"
)

var sampleTest802 = `
Ethernet Channel Bonding Driver: v3.5.0 (November 4, 2008)

Bonding Mode: IEEE 802.3ad Dynamic link aggregation
Transmit Hash Policy: layer2 (0)
MII Status: up
MII Polling Interval (ms): 100
Up Delay (ms): 0
Down Delay (ms): 0

802.3ad info
LACP rate: fast
Aggregator selection policy (ad_select): stable
bond bond0 has no active aggregator

Slave Interface: eth1
MII Status: up
Link Failure Count: 0
Permanent HW addr: 00:0c:29:f5:b7:11
Aggregator ID: N/A

Slave Interface: eth2
MII Status: up
Link Failure Count: 3
Permanent HW addr: 00:0c:29:f5:b7:1b
Aggregator ID: N/A
`

var sampleTestAB = `
Ethernet Channel Bonding Driver: v3.6.0 (September 26, 2009)

Bonding Mode: fault-tolerance (active-backup)
Primary Slave: eth2 (primary_reselect always)
Currently Active Slave: eth2
MII Status: up
MII Polling Interval (ms): 100
Up Delay (ms): 0
Down Delay (ms): 0

Slave Interface: eth3
MII Status: down
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's also possible for both of the interfaces to be up, right? Maybe we should gather the active slave on the bond master?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I've added active_slave field. Thanks for the note.

Speed: 1000 Mbps
Duplex: full
Link Failure Count: 2
Permanent HW addr:
Slave queue ID: 0

Slave Interface: eth2
MII Status: up
Speed: 100 Mbps
Duplex: full
Link Failure Count: 0
Permanent HW addr:
`

func TestGatherBondInterface(t *testing.T) {
var acc testutil.Accumulator
bond := &Bond{}

bond.gatherBondInterface("bond802", sampleTest802, &acc)
acc.AssertContainsTaggedFields(t, "bond", map[string]interface{}{"status": 1}, map[string]string{"bond": "bond802"})
acc.AssertContainsTaggedFields(t, "bond_slave", map[string]interface{}{"failures": 0, "status": 1}, map[string]string{"bond": "bond802", "interface": "eth1"})
acc.AssertContainsTaggedFields(t, "bond_slave", map[string]interface{}{"failures": 3, "status": 1}, map[string]string{"bond": "bond802", "interface": "eth2"})

bond.gatherBondInterface("bondAB", sampleTestAB, &acc)
acc.AssertContainsTaggedFields(t, "bond", map[string]interface{}{"status": 1}, map[string]string{"bond": "bondAB"})
acc.AssertContainsTaggedFields(t, "bond_slave", map[string]interface{}{"failures": 2, "status": 0}, map[string]string{"bond": "bondAB", "interface": "eth3"})
acc.AssertContainsTaggedFields(t, "bond_slave", map[string]interface{}{"failures": 0, "status": 1}, map[string]string{"bond": "bondAB", "interface": "eth2"})
}