205 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			205 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			Go
		
	
	
	
| package bond
 | |
| 
 | |
| import (
 | |
| 	"bufio"
 | |
| 	"fmt"
 | |
| 	"io/ioutil"
 | |
| 	"os"
 | |
| 	"path/filepath"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 
 | |
| 	"github.com/influxdata/telegraf"
 | |
| 	"github.com/influxdata/telegraf/plugins/inputs"
 | |
| )
 | |
| 
 | |
| // default host proc path
 | |
| const defaultHostProc = "/proc"
 | |
| 
 | |
| // env host proc variable name
 | |
| const envProc = "HOST_PROC"
 | |
| 
 | |
| type Bond struct {
 | |
| 	HostProc       string   `toml:"host_proc"`
 | |
| 	BondInterfaces []string `toml:"bond_interfaces"`
 | |
| }
 | |
| 
 | |
| var sampleConfig = `
 | |
|   ## Sets 'proc' directory path
 | |
|   ## If not specified, then default is /proc
 | |
|   # host_proc = "/proc"
 | |
| 
 | |
|   ## 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 proc path, get default value if config value and env variable are empty
 | |
| 	bond.loadPath()
 | |
| 	// list bond interfaces from bonding directory or gather all interfaces.
 | |
| 	bondNames, err := bond.listInterfaces()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	for _, bondName := range bondNames {
 | |
| 		bondAbsPath := bond.HostProc + "/net/bonding/" + bondName
 | |
| 		file, err := ioutil.ReadFile(bondAbsPath)
 | |
| 		if err != nil {
 | |
| 			acc.AddError(fmt.Errorf("error inspecting '%s' interface: %v", bondAbsPath, err))
 | |
| 			continue
 | |
| 		}
 | |
| 		rawFile := strings.TrimSpace(string(file))
 | |
| 		err = bond.gatherBondInterface(bondName, rawFile, acc)
 | |
| 		if err != nil {
 | |
| 			acc.AddError(fmt.Errorf("error 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,
 | |
| 	}
 | |
| 
 | |
| 	scanner := bufio.NewScanner(strings.NewReader(rawFile))
 | |
| 	for scanner.Scan() {
 | |
| 		line := scanner.Text()
 | |
| 		stats := strings.Split(line, ":")
 | |
| 		if len(stats) < 2 {
 | |
| 			continue
 | |
| 		}
 | |
| 		name := strings.TrimSpace(stats[0])
 | |
| 		value := strings.TrimSpace(stats[1])
 | |
| 		if strings.Contains(name, "Currently Active Slave") {
 | |
| 			fields["active_slave"] = value
 | |
| 		}
 | |
| 		if strings.Contains(name, "MII Status") {
 | |
| 			fields["status"] = 0
 | |
| 			if value == "up" {
 | |
| 				fields["status"] = 1
 | |
| 			}
 | |
| 			acc.AddFields("bond", fields, tags)
 | |
| 			return nil
 | |
| 		}
 | |
| 	}
 | |
| 	if err := scanner.Err(); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	return fmt.Errorf("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
 | |
| 
 | |
| 	scanner := bufio.NewScanner(strings.NewReader(rawFile))
 | |
| 	for scanner.Scan() {
 | |
| 		line := scanner.Text()
 | |
| 		stats := strings.Split(line, ":")
 | |
| 		if len(stats) < 2 {
 | |
| 			continue
 | |
| 		}
 | |
| 		name := strings.TrimSpace(stats[0])
 | |
| 		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)
 | |
| 		}
 | |
| 	}
 | |
| 	if err := scanner.Err(); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // loadPath can be used to read path firstly from config
 | |
| // if it is empty then try read from env variable
 | |
| func (bond *Bond) loadPath() {
 | |
| 	if bond.HostProc == "" {
 | |
| 		bond.HostProc = proc(envProc, defaultHostProc)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // proc can be used to read file paths from env
 | |
| func proc(env, path string) string {
 | |
| 	// try to read full file path
 | |
| 	if p := os.Getenv(env); p != "" {
 | |
| 		return p
 | |
| 	}
 | |
| 	// return default path
 | |
| 	return path
 | |
| }
 | |
| 
 | |
| func (bond *Bond) listInterfaces() ([]string, error) {
 | |
| 	var interfaces []string
 | |
| 	if len(bond.BondInterfaces) > 0 {
 | |
| 		interfaces = bond.BondInterfaces
 | |
| 	} else {
 | |
| 		paths, err := filepath.Glob(bond.HostProc + "/net/bonding/*")
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		for _, p := range paths {
 | |
| 			interfaces = append(interfaces, filepath.Base(p))
 | |
| 		}
 | |
| 	}
 | |
| 	return interfaces, nil
 | |
| }
 | |
| 
 | |
| func init() {
 | |
| 	inputs.Add("bond", func() telegraf.Input {
 | |
| 		return &Bond{}
 | |
| 	})
 | |
| }
 |