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

nstat input plugin #1138

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -35,6 +35,7 @@ import (
_ "github.com/influxdata/telegraf/plugins/inputs/net_response"
_ "github.com/influxdata/telegraf/plugins/inputs/nginx"
_ "github.com/influxdata/telegraf/plugins/inputs/nsq"
_ "github.com/influxdata/telegraf/plugins/inputs/nstat"
_ "github.com/influxdata/telegraf/plugins/inputs/ntpq"
_ "github.com/influxdata/telegraf/plugins/inputs/passenger"
_ "github.com/influxdata/telegraf/plugins/inputs/phpfpm"
Expand Down
3 changes: 3 additions & 0 deletions plugins/inputs/nstat/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## Nstat input plugin
Copy link
Contributor

Choose a reason for hiding this comment

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

readme will need to be improved,
model the readme after https://github.com/influxdata/telegraf/blob/master/plugins/inputs/EXAMPLE_README.md


Plugin collects network metrics from ```/proc/net/netstat```, ```/proc/net/snmp``` and ```/proc/net/snmp6``` files
183 changes: 183 additions & 0 deletions plugins/inputs/nstat/nstat.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
package nstat

import (
"bytes"
"io/ioutil"
"strconv"

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

var (
zeroByte = []byte("0")
newLineByte = []byte("\n")
colonByte = []byte(":")
)

type Nstat struct {
ProcNetNetstat string `toml:"proc_net_netstat"`
ProcNetSNMP string `toml:"proc_net_snmp"`
ProcNetSNMP6 string `toml:"proc_net_snmp6"`
DumpZeros bool `toml:"dump_zeros"`
}

var sampleConfig = `
# file paths
Copy link
Contributor

Choose a reason for hiding this comment

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

sampleConfig should be indented by two spaces

proc_net_netstat = "/proc/net/netstat"
Copy link
Contributor

Choose a reason for hiding this comment

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

Here I think we should do the same thing nstat does: https://github.com/shemminger/iproute2/blob/master/misc/nstat.c#L46,

which is:

  1. Use overrides here (but default to "")
  2. try PROC_NET_NETSTAT env var
  3. try PROC_ROOT env var + "net/netstat"
  4. use "/proc/net/netstat"

proc_net_snmp = "/proc/net/snmp"
proc_net_snmp6 = "/proc/net/snmp6"
# dump metrics with 0 values too
dump_zeros = true
`

func (ns *Nstat) Description() string {
return "Collect network metrics from '/proc/net/netstat', '/proc/net/snmp' & '/proc/net/snmp6' files"
}

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

func (ns *Nstat) Gather(acc telegraf.Accumulator) error {
netstat, err := ioutil.ReadFile(ns.ProcNetNetstat)
if err != nil {
return err
}

// collect netstat data
err = ns.gatherNetstat(netstat, acc)
if err != nil {
return err
}

// collect SNMP data
snmp, err := ioutil.ReadFile(ns.ProcNetSNMP)
if err != nil {
return err
}
err = ns.gatherSNMP(snmp, acc)
if err != nil {
return err
}

// collect SNMP6 data
snmp6, err := ioutil.ReadFile(ns.ProcNetSNMP6)
if err != nil {
return err
}
err = ns.gatherSNMP6(snmp6, acc)
if err != nil {
return err
}
return nil
}

func (ns *Nstat) gatherNetstat(data []byte, acc telegraf.Accumulator) error {
metrics, err := loadUglyTable(data, ns.DumpZeros)
if err != nil {
return err
}
tags := map[string]string{
"name": "netstat",
}
acc.AddFields("nstat", metrics, tags)
return nil
}

func (ns *Nstat) gatherSNMP(data []byte, acc telegraf.Accumulator) error {
metrics, err := loadUglyTable(data, ns.DumpZeros)
if err != nil {
return err
}
tags := map[string]string{
"name": "snmp",
}
acc.AddFields("nstat", metrics, tags)
return nil
}

func (ns *Nstat) gatherSNMP6(data []byte, acc telegraf.Accumulator) error {
metrics, err := loadGoodTable(data, ns.DumpZeros)
if err != nil {
return err
}
tags := map[string]string{
"name": "snmp6",
}
acc.AddFields("nstat", metrics, tags)
return nil
}

// loadGoodTable can be used to parse string heap that
// headers and values are arranged in right order
func loadGoodTable(table []byte, dumpZeros bool) (map[string]interface{}, error) {
entries := map[string]interface{}{}
fields := bytes.Fields(table)
var value int64
var err error
// iterate over two values each time
// first value is header, second is value
for i := 0; i < len(fields); i = i + 2 {
// counter is zero
if bytes.Equal(fields[i+1], zeroByte) {
if !dumpZeros {
continue
} else {
entries[string(fields[i])] = int64(0)
continue
}
}
// the counter is not zero, so parse it.
value, err = strconv.ParseInt(string(fields[i+1]), 10, 64)
if err == nil {
entries[string(fields[i])] = value
}
}
return entries, nil
}

// loadUglyTable can be used to parse string heap that
// the headers and values are splitted with a newline
func loadUglyTable(table []byte, dumpZeros bool) (map[string]interface{}, error) {
entries := map[string]interface{}{}
// split the lines by newline
lines := bytes.Split(table, newLineByte)
var value int64
var err error
// iterate over lines, take 2 lines each time
// first line contains header names
// second line contains values
for i := 0; i < len(lines); i = i + 2 {
if len(lines[i]) == 0 {
continue
}
headers := bytes.Fields(lines[i])
prefix := bytes.TrimSuffix(headers[0], colonByte)
metrics := bytes.Fields(lines[i+1])

for j := 1; j < len(headers); j++ {
// counter is zero
if bytes.Equal(metrics[j], zeroByte) {
if !dumpZeros {
continue
} else {
entries[string(append(prefix, headers[j]...))] = int64(0)
continue
}
}
// the counter is not zero, so parse it.
value, err = strconv.ParseInt(string(metrics[j]), 10, 64)
if err == nil {
entries[string(append(prefix, headers[j]...))] = value
}
}
}
return entries, nil
}

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

import "testing"

func TestLoadUglyTable(t *testing.T) {
uglyStr := `IpExt: InNoRoutes InTruncatedPkts InMcastPkts InCEPkts
IpExt: 332 433718 0 2660494435`
parsed := map[string]interface{}{
"IpExtInNoRoutes": int64(332),
"IpExtInTruncatedPkts": int64(433718),
"IpExtInMcastPkts": int64(0),
"IpExtInCEPkts": int64(2660494435),
}

got, err := loadUglyTable([]byte(uglyStr), true)
if err != nil {
t.Fatal(err)
}

if len(got) == 0 {
t.Fatalf("want %+v, got %+v", parsed, got)
}

for key := range parsed {
if parsed[key].(int64) != got[key].(int64) {
t.Fatalf("want %+v, got %+v", parsed[key], got[key])
}
}
}

func TestLoadGoodTable(t *testing.T) {
goodStr := `Ip6InReceives 11707
Ip6InTooBigErrors 0
Ip6InDelivers 62
Ip6InMcastOctets 1242966`

parsed := map[string]interface{}{
"Ip6InReceives": int64(11707),
"Ip6InTooBigErrors": int64(0),
"Ip6InDelivers": int64(62),
"Ip6InMcastOctets": int64(1242966),
}
got, err := loadGoodTable([]byte(goodStr), true)
if err != nil {
t.Fatal(err)
}
if len(got) == 0 {
t.Fatalf("want %+v, got %+v", parsed, got)
}

for key := range parsed {
if parsed[key].(int64) != got[key].(int64) {
t.Fatalf("want %+v, got %+v", parsed[key], got[key])
}
}
}