From 171d3252853fd187fd3c2ca52714680cfaa58df6 Mon Sep 17 00:00:00 2001 From: Akshay Moghe Date: Thu, 25 Oct 2018 12:14:19 -0700 Subject: [PATCH] Add IPVS input plugin (#4890) --- Gopkg.lock | 24 ++++++ Gopkg.toml | 8 ++ plugins/inputs/all/all.go | 1 + plugins/inputs/ipvs/README.md | 26 +++++++ plugins/inputs/ipvs/ipvs.go | 112 +++++++++++++++++++++++++++ plugins/inputs/ipvs/ipvs_notlinux.go | 3 + 6 files changed, 174 insertions(+) create mode 100644 plugins/inputs/ipvs/README.md create mode 100644 plugins/inputs/ipvs/ipvs.go create mode 100644 plugins/inputs/ipvs/ipvs_notlinux.go diff --git a/Gopkg.lock b/Gopkg.lock index f5f119a7f9697..8c500356c4f48 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -328,6 +328,14 @@ revision = "47565b4f722fb6ceae66b95f853feed578a4a51c" version = "v0.3.3" +[[projects]] + branch = "master" + digest = "1:809792497a26f3936462cc5787a0d644b4d3cbfd59587e4f8845a9396ca2eb8a" + name = "github.com/docker/libnetwork" + packages = ["ipvs"] + pruneopts = "" + revision = "d7b61745d16675c9f548b19f06fda80d422a74f0" + [[projects]] digest = "1:6d6672f85a84411509885eaa32f597577873de00e30729b9bb0eb1e1faa49c12" name = "github.com/eapache/go-resiliency" @@ -988,6 +996,21 @@ pruneopts = "" revision = "1731857f09b1f38450e2c12409748407822dc6be" +[[projects]] + digest = "1:026b6ceaabbacaa147e94a63579efc3d3c73e00c73b67fa5c43ab46191ed04eb" + name = "github.com/vishvananda/netlink" + packages = ["nl"] + pruneopts = "" + revision = "b2de5d10e38ecce8607e6b438b6d174f389a004e" + +[[projects]] + branch = "master" + digest = "1:c09fddfdd491edaa4383396503e57023a26e5a824283a78c2310613a1252c649" + name = "github.com/vishvananda/netns" + packages = ["."] + pruneopts = "" + revision = "13995c7128ccc8e51e9a6bd2b551020a27180abd" + [[projects]] digest = "1:343f20460c11a0d0529fe532553bfef9446918d1a1fda6d8661eb27d5b1a68b8" name = "github.com/vjeantet/grok" @@ -1424,6 +1447,7 @@ "github.com/docker/docker/api/types/registry", "github.com/docker/docker/api/types/swarm", "github.com/docker/docker/client", + "github.com/docker/libnetwork/ipvs", "github.com/eclipse/paho.mqtt.golang", "github.com/go-logfmt/logfmt", "github.com/go-redis/redis", diff --git a/Gopkg.toml b/Gopkg.toml index dba4ec4b3f8b9..23b8444fe6523 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -237,3 +237,11 @@ [[constraint]] branch = "master" name = "golang.org/x/oauth2" + +[[constraint]] + branch = "master" + name = "github.com/docker/libnetwork" + +[[override]] + name = "github.com/vishvananda/netlink" + revision = "b2de5d10e38ecce8607e6b438b6d174f389a004e" diff --git a/plugins/inputs/all/all.go b/plugins/inputs/all/all.go index 4263aebf12f38..6117aa2716a3b 100644 --- a/plugins/inputs/all/all.go +++ b/plugins/inputs/all/all.go @@ -51,6 +51,7 @@ import ( _ "github.com/influxdata/telegraf/plugins/inputs/ipmi_sensor" _ "github.com/influxdata/telegraf/plugins/inputs/ipset" _ "github.com/influxdata/telegraf/plugins/inputs/iptables" + _ "github.com/influxdata/telegraf/plugins/inputs/ipvs" _ "github.com/influxdata/telegraf/plugins/inputs/jolokia" _ "github.com/influxdata/telegraf/plugins/inputs/jolokia2" _ "github.com/influxdata/telegraf/plugins/inputs/jti_openconfig_telemetry" diff --git a/plugins/inputs/ipvs/README.md b/plugins/inputs/ipvs/README.md new file mode 100644 index 0000000000000..78bb94574b4f8 --- /dev/null +++ b/plugins/inputs/ipvs/README.md @@ -0,0 +1,26 @@ +# IPVS Input Plugin (Linux) + +The IPVS input plugin uses the linux kernel netlink socket interface to gather +metrics about ipvs virtual and real servers. + +## Configuration + +[[inputs.ipvs]] + # no configuration + +## Permissions + +Assuming you installed the telegraf package via one of the published packages, +the process will be running as the `telegraf` user. However, in order for this +plugin to communicate over netlink sockets it needs the telegraf process to be +running as `root` (or some user with `CAP_NET_ADMIN` and `CAP_NET_RAW`). Be sure +to ensure these permissions before running telegraf with this plugin included. + +## Sample Output + +This is what you can expect the emitted metrics to look like + +``` +ipvs_virtual_server,address=172.18.64.234,address_family=inet,netmask=32,port=9000,protocol=tcp,sched=mh_418 bytes_out=0i,pps_in=0i,pps_out=0i,cps=0i,pkts_in=0i,pkts_out=0i,connections=0i,bytes_in=0i 1540407540000000000 +ipvs_virtual_server,address_family=inet,fwmark=47,netmask=32,sched=mh_418 connections=0i,pkts_in=0i,bytes_out=0i,pps_in=0i,pps_out=0i,pkts_out=0i,bytes_in=0i,cps=0i 1540407540000000000 +``` diff --git a/plugins/inputs/ipvs/ipvs.go b/plugins/inputs/ipvs/ipvs.go new file mode 100644 index 0000000000000..5a4e0dc667191 --- /dev/null +++ b/plugins/inputs/ipvs/ipvs.go @@ -0,0 +1,112 @@ +// +build linux + +package ipvs + +import ( + "errors" + "fmt" + "math/bits" + "strconv" + "syscall" + + "github.com/docker/libnetwork/ipvs" + "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/plugins/inputs" +) + +// IPVS holds the state for this input plugin +type IPVS struct { + handle *ipvs.Handle +} + +// Description returns a description string +func (i *IPVS) Description() string { + return "Collect virtual and real server stats from Linux IPVS" +} + +// SampleConfig returns a sample configuration for this input plugin +func (i *IPVS) SampleConfig() string { + return `` +} + +// Gather gathers the stats +func (i *IPVS) Gather(acc telegraf.Accumulator) error { + if i.handle == nil { + h, err := ipvs.New("") // TODO: make the namespace configurable + if err != nil { + return errors.New("Unable to open IPVS handle") + } + i.handle = h + } + + services, err := i.handle.GetServices() + if err != nil { + i.handle.Close() + i.handle = nil // trigger a reopen on next call to gather + return errors.New("Failed to list IPVS services") + } + for _, s := range services { + fields := map[string]interface{}{ + "connections": s.Stats.Connections, + "pkts_in": s.Stats.PacketsIn, + "pkts_out": s.Stats.PacketsOut, + "bytes_in": s.Stats.BytesIn, + "bytes_out": s.Stats.BytesOut, + "pps_in": s.Stats.PPSIn, + "pps_out": s.Stats.PPSOut, + "cps": s.Stats.CPS, + } + acc.AddGauge("ipvs_virtual_server", fields, serviceTags(s)) + } + + return nil +} + +// helper: given a Service, return tags that identify it +func serviceTags(s *ipvs.Service) map[string]string { + ret := map[string]string{ + "sched": s.SchedName, + "netmask": fmt.Sprintf("%d", bits.OnesCount32(s.Netmask)), + "address_family": addressFamilyToString(s.AddressFamily), + } + // Per the ipvsadm man page, a virtual service is defined "based on + // protocol/addr/port or firewall mark" + if s.FWMark > 0 { + ret["fwmark"] = strconv.Itoa(int(s.FWMark)) + } else { + ret["protocol"] = protocolToString(s.Protocol) + ret["address"] = s.Address.String() + ret["port"] = strconv.Itoa(int(s.Port)) + } + return ret +} + +// helper: convert protocol uint16 to human readable string (if possible) +func protocolToString(p uint16) string { + switch p { + case syscall.IPPROTO_TCP: + return "tcp" + case syscall.IPPROTO_UDP: + return "udp" + case syscall.IPPROTO_SCTP: + return "sctp" + default: + return fmt.Sprintf("%d", p) + } +} + +// helper: convert addressFamily to a human readable string +func addressFamilyToString(af uint16) string { + switch af { + case syscall.AF_INET: + return "inet" + case syscall.AF_INET6: + return "inet6" + default: + return fmt.Sprintf("%d", af) + } +} + +func init() { + inputs.Add("ipvs", func() telegraf.Input { return &IPVS{} }) +} diff --git a/plugins/inputs/ipvs/ipvs_notlinux.go b/plugins/inputs/ipvs/ipvs_notlinux.go new file mode 100644 index 0000000000000..bbbb1240b62a8 --- /dev/null +++ b/plugins/inputs/ipvs/ipvs_notlinux.go @@ -0,0 +1,3 @@ +// +build !linux + +package ipvs