-
Notifications
You must be signed in to change notification settings - Fork 5.8k
Add Bond Interface input plugin #3424
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
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"] | ||
|
Contributor
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
Contributor
Author
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" | ||
|
||
| ) | ||
|
|
||
| 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)) | ||
|
||
| 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") | ||
|
||
| 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, "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") | ||
|
||
| 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 { | ||
|
||
| 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 | ||
|
Contributor
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?
Contributor
Author
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