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

Linux_Wireless Plugin #3650

Closed
wants to merge 15 commits into from
1 change: 1 addition & 0 deletions plugins/inputs/all/all.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import (
_ "github.com/influxdata/telegraf/plugins/inputs/kapacitor"
_ "github.com/influxdata/telegraf/plugins/inputs/kubernetes"
_ "github.com/influxdata/telegraf/plugins/inputs/leofs"
_ "github.com/influxdata/telegraf/plugins/inputs/linux_wireless"
_ "github.com/influxdata/telegraf/plugins/inputs/logparser"
_ "github.com/influxdata/telegraf/plugins/inputs/lustre2"
_ "github.com/influxdata/telegraf/plugins/inputs/mailchimp"
Expand Down
18 changes: 18 additions & 0 deletions plugins/inputs/linux_wireless/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Linux Wireless Input Plugin

The Linux Wireless Plugin polls /proc/net/wireless for info and status on the Wireless network interfaces.
**This Plugin only works under Linux. A built-in OS-check exits on all other platforms.**
topic and adds messages to InfluxDB. This plugin allows a message to be in any of the supported `data_format` types.
davidgs marked this conversation as resolved.
Show resolved Hide resolved

## Configuration

```toml
# Read metrics from Wireless interface(s)
# dump_zeros will drop values that are zero
[[inputs.wireless]]
proc_net_wireless = "/proc/net/wireless"
dump_zeros = false
```

## Testing
The `wireless_test` mocks out the interaction with `/proc/net/wireless`. It requires no outside dependencies.
200 changes: 200 additions & 0 deletions plugins/inputs/linux_wireless/linux_wireless.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
package linux_wireless

import (
"github.com/influxdata/telegraf"
davidgs marked this conversation as resolved.
Show resolved Hide resolved
"github.com/influxdata/telegraf/plugins/inputs"
"io/ioutil"
"os"
"runtime"
"strconv"
"strings"
)

// default file paths
const (
NET_WIRELESS = "/net/wireless"
NET_PROC = "/proc"
)

// env variable names
const (
ENV_ROOT = "PROC_ROOT"
davidgs marked this conversation as resolved.
Show resolved Hide resolved
ENV_WIRELESS = "PROC_NET_WIRELESS"
)

type Wireless struct {
ProcNetWireless string `toml:"proc_net_wireless"`
DumpZeros bool `toml:"dump_zeros"`
}

// WirelessDataa struct to hold the tags, headers (measurements) and data
type WirelessData struct {
Headers []string
Data [][]int64
Tags []string
}

var sampleConfig = `
## file path for proc file. If empty default path will be used:
## /proc/net/wireless
## This can also be overridden with env variable, see README.
proc_net_wireless = "/proc/net/wireless"
## dump metrics with 0 values too
dump_zeros = true
`

func (ns *Wireless) Description() string {
return "Collect wireless interface link quality metrics"
}

func (ns *Wireless) SampleConfig() string {
return sampleConfig
}

func (ns *Wireless) Gather(acc telegraf.Accumulator) error {
// load paths, get from env if config values are empty
ns.loadPath()
// collect wireless data
wireless, err := ioutil.ReadFile(ns.ProcNetWireless)
if err != nil {
return err
}
err = ns.gatherWireless(wireless, acc)
if err != nil {
return err
}
return nil
}

func (ns *Wireless) gatherWireless(data []byte, acc telegraf.Accumulator) error {
wirelessData, err := loadWirelessTable(data, ns.DumpZeros)
if err != nil {
return err
}
// go through the WirelessData struct and create maps for
// Telegraf to deal with, then addd the data to the
// telegraf accumulator
for x := 0; x < len(wirelessData.Tags); x++ {
entries := map[string]interface{}{}
tags := map[string]string{
"interface": wirelessData.Tags[x],
}
for z := 0; z < len(wirelessData.Data[x]); z++ {
entries[wirelessData.Headers[z]] = wirelessData.Data[x][z]
davidgs marked this conversation as resolved.
Show resolved Hide resolved
}
acc.AddFields("wireless", entries, tags)
}
return nil
}

func loadWirelessTable(table []byte, dumpZeros bool) (WirelessData, error) {
wd := WirelessData{}
var value int64
var err error
myLines := strings.Split(string(table), "\n")
// split on '|' and trim the spaces
h1 := strings.Split(myLines[0], "|")
davidgs marked this conversation as resolved.
Show resolved Hide resolved
h2 := strings.Split(myLines[1], "|")
Copy link
Contributor

Choose a reason for hiding this comment

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

Check that length of h2 is at least the same length as h1 (and greater than 2) so the loop on 104 and other length-assumption references don't panic.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed and return proper error.

header_fields := make([]string, 11)
header_count := 1
// we'll collect the data and tags in here for now
tags := make([]string, len(myLines)-2)
data := make([][]int64, len(myLines)-2)
// trim out all the spaces.
for x := 0; x < len(h1); x++ {
h1[x] = strings.Trim(h1[x], " ")
h2[x] = strings.Trim(h2[x], " ")
}
// first 2 headers have a '-' in them, so join those and remove the '-'
// also, ignore the first one, since it is the interface name
header_fields[0] = strings.ToLower(strings.Replace(h1[1]+h2[1], "-", "", -1))
// next headers are composed with sub-headers, so build those.
for y := 2; y < len(h1)-2; y++ {
tmpStr := strings.Split(h2[y], " ")
for z := 0; z < len(tmpStr); z++ {
if tmpStr[z] == "" {
continue
}
header_fields[header_count] = strings.ToLower(strings.Replace(h1[y]+"_"+tmpStr[z], " ", "_", -1))
header_count++
}
}
// last 2 are simple multi-line headers, so join them
for t := len(h1) - 2; t < len(h1); t++ {
header_fields[header_count] = strings.ToLower(h1[t] + "_" + h2[t])
header_count++
}
// now let's go through the data and save it for return.
// if we're dumping zeros, we will also dump the header for the
// zero data.
for x := 2; x < len(myLines)-1; x++ {
data_count := 0
metrics := strings.Fields(myLines[x])
sub_data := make([]int64, len(metrics))
for z := 0; z < len(metrics)-2; z++ {
if strings.Index(metrics[z], ":") > 0 {
tags[x-2] = metrics[z]
} else {
if metrics[z] == "0" {
if dumpZeros {
continue
// if we're dumping zeros, we dump the header that goes with it.
//if x == len(header_fields) {
davidgs marked this conversation as resolved.
Show resolved Hide resolved
// fmt.Println("Dump Zeros")
//} else {
// header_fields = append(header_fields[:x], header_fields[x+1:]...)
//}

//continue
}
}

// clean up the string as they have extraneous characters in them
value, err = strconv.ParseInt(strings.Replace(metrics[z], ".", "", -1), 10, 64)
if err == nil {
sub_data[data_count] = value
data_count++
}

}
}
data[x-2] = sub_data
}
// Now fill out the Wireless struct and return it
wd.Headers = header_fields
wd.Tags = tags
wd.Data = data
return wd, nil
}

// loadPath can be used to read path firstly from config
// if it is empty then try read from env variables
func (ns *Wireless) loadPath() {
if ns.ProcNetWireless == "" {
ns.ProcNetWireless = proc(ENV_WIRELESS, NET_WIRELESS)
}
}

// 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
}
// try to read root path, or use default root path
root := os.Getenv(ENV_ROOT)
if root == "" {
root = NET_PROC
}
return root + path
}

func init() {
// this only works on linux, so if we're not running on Linux, punt.
if runtime.GOOS != "linux" {
return
}
inputs.Add("linux_wireless", func() telegraf.Input {
return &Wireless{}
})
}
73 changes: 73 additions & 0 deletions plugins/inputs/linux_wireless/linux_wireless_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package linux_wireless

import (
"testing"
)

func TestLoadWirelessTable(t *testing.T) {
// line of input
input := `Inter-| sta-| Quality | Discarded packets | Missed | WE
face | tus | link level noise | nwid crypt frag retry misc | beacon | 22
wlan0: 0000 0. -256. -256. 0 0 0 0 0 0`
// the headers we expect from that line of input
headers := []string{"status", "quality_link", "quality_level", "quality_noise", "discarded_packets_nwid", "discarded_packets_crypt",
"discarded_packets_frag", "discarded_packets_retry", "discarded_packets_misc", "missed_beacon", "we_22"}
// the map of data we expect.
parsed := map[string]interface{}{
"status": int64(0),
"quality_link": int64(0),
"quality_level": int64(-256),
"quality_noise": int64(-256),
"discarded_packets_nwid": int64(0),
"discarded_packets_crypt": int64(0),
"discarded_packets_frag": int64(0),
"discarded_packets_retry": int64(0),
"discarded_packets_misc": int64(0),
"missed_beacon": int64(0),
"we_22": int64(0),
}
// the tags we expect
test_tags := map[string]interface{}{
"interface": "wlan0:",
}
// Map of the entries we get back from the table
entries := map[string]interface{}{}

// load the table from the input.
got, err := loadWirelessTable([]byte(input), true)
if err != nil {
t.Fatal(err)
}
// the WirelessData struct holds arrays of the values, so
// move them into appropriate maps.
tags := map[string]string{}
for x := 0; x < len(got.Tags); x++ {
entries := map[string]interface{}{}
tags = map[string]string{
"interface": got.Tags[x],
}
for z := 0; z < len(got.Data[x]); z++ {
entries[got.Headers[z]] = got.Data[x][z]
}
}
// make sure we got the same number of headers back we expect.
if len(got.Headers) != len(headers) {
t.Fatalf("want %+v, got %+v", headers, got.Headers)
}
// create the data map
for z := 0; z < len(got.Data[0]); z++ {
entries[got.Headers[z]] = got.Data[0][z]
}
// verify the data map
for key := range parsed {
if parsed[key] != entries[key] {
t.Fatalf("want %+v, got %+v", parsed[key], entries[key])
}
}
// verify the tag map
for key := range tags {
if test_tags[key] != tags[key] {
t.Fatalf("want %+v, got %+v", test_tags[key], tags[key])
}
}
}