-
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
nstat input plugin #1138
nstat input plugin #1138
Changes from 1 commit
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,3 @@ | ||
## Nstat input plugin | ||
|
||
Plugin collects network metrics from ```/proc/net/netstat```, ```/proc/net/snmp``` and ```/proc/net/snmp6``` files |
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 | ||
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. sampleConfig should be indented by two spaces |
||
proc_net_netstat = "/proc/net/netstat" | ||
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. Here I think we should do the same thing nstat does: https://github.com/shemminger/iproute2/blob/master/misc/nstat.c#L46, which is:
|
||
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{} | ||
}) | ||
} |
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]) | ||
} | ||
} | ||
} |
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.
readme will need to be improved,
model the readme after https://github.com/influxdata/telegraf/blob/master/plugins/inputs/EXAMPLE_README.md