-
Notifications
You must be signed in to change notification settings - Fork 5.6k
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
Changes from 2 commits
18bc685
d3d7222
a943169
435d1f3
925f2d3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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" | ||
|
||
## By default, telegraf gather stats for all bond interfaces | ||
## Setting interfaces will restrict the stats to the specified | ||
## bond interfaces. | ||
bond_interfaces = ["bond0", "bond1"] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Comment this line out with a single There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
``` |
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" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would say call this There was a problem hiding this comment. Choose a reason for hiding this commentThe 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)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The log level There was a problem hiding this comment. Choose a reason for hiding this commentThe 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") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use a There was a problem hiding this comment. Choose a reason for hiding this commentThe 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)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why lowercase and replace spaces with underscores? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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{} | ||
}) | ||
} |
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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"}) | ||
} |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done