diff --git a/README.md b/README.md index d139cef4..645925f5 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ Which collectors are used is controlled by the `--collectors.enabled` flag. Name | Description | OS ---------|-------------|---- +arp | Exposes ARP statistics from `/proc/net/arp`. | Linux conntrack | Shows conntrack statistics (does nothing if no `/proc/sys/net/netfilter/` present). | Linux cpu | Exposes CPU statistics | Darwin, Dragonfly, FreeBSD diskstats | Exposes disk I/O statistics from `/proc/diskstats`. | Linux diff --git a/collector/arp_linux.go b/collector/arp_linux.go new file mode 100644 index 00000000..71c1c5ad --- /dev/null +++ b/collector/arp_linux.go @@ -0,0 +1,102 @@ +// Copyright 2017 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build !noarp + +package collector + +import ( + "bufio" + "fmt" + "io" + "os" + "strings" + + "github.com/prometheus/client_golang/prometheus" +) + +type arpCollector struct { + entries *prometheus.Desc +} + +func init() { + Factories["arp"] = NewARPCollector +} + +// NewARPCollector returns a new Collector exposing ARP stats. +func NewARPCollector() (Collector, error) { + return &arpCollector{ + entries: prometheus.NewDesc( + prometheus.BuildFQName(Namespace, "arp", "entries"), + "ARP entries by device", + []string{"device"}, nil, + ), + }, nil +} + +func getARPEntries() (map[string]uint32, error) { + file, err := os.Open(procFilePath("net/arp")) + if err != nil { + return nil, err + } + defer file.Close() + + entries, err := parseARPEntries(file) + if err != nil { + return nil, err + } + + return entries, nil +} + +// TODO: This should get extracted to the github.com/prometheus/procfs package +// to support more complete parsing of /proc/net/arp. Instead of adding +// more fields to this function's return values it should get moved and +// changed to support each field. +func parseARPEntries(data io.Reader) (map[string]uint32, error) { + scanner := bufio.NewScanner(data) + entries := make(map[string]uint32) + + for scanner.Scan() { + columns := strings.Fields(scanner.Text()) + + if len(columns) < 6 { + return nil, fmt.Errorf("unexpected ARP table format") + } + + if columns[0] != "IP" { + deviceIndex := len(columns) - 1 + entries[columns[deviceIndex]]++ + } + } + + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("failed to parse ARP info: %s", err) + } + + return entries, nil +} + +func (c *arpCollector) Update(ch chan<- prometheus.Metric) error { + entries, err := getARPEntries() + if err != nil { + return fmt.Errorf("could not get ARP entries: %s", err) + } + + for device, entryCount := range entries { + ch <- prometheus.MustNewConstMetric( + c.entries, prometheus.GaugeValue, float64(entryCount), device) + } + + return nil +} diff --git a/collector/fixtures/e2e-output.txt b/collector/fixtures/e2e-output.txt index 06c27aef..18282991 100644 --- a/collector/fixtures/e2e-output.txt +++ b/collector/fixtures/e2e-output.txt @@ -71,6 +71,10 @@ http_response_size_bytes{handler="prometheus",quantile="0.9"} NaN http_response_size_bytes{handler="prometheus",quantile="0.99"} NaN http_response_size_bytes_sum{handler="prometheus"} 0 http_response_size_bytes_count{handler="prometheus"} 0 +# HELP node_arp_entries ARP entries by device +# TYPE node_arp_entries gauge +node_arp_entries{device="eth0"} 3 +node_arp_entries{device="eth1"} 3 # HELP node_bonding_active Number of active slaves per bonding interface. # TYPE node_bonding_active gauge node_bonding_active{master="bond0"} 0 @@ -2106,6 +2110,7 @@ node_procs_running 2 # TYPE node_scrape_collector_duration_seconds gauge # HELP node_scrape_collector_success node_exporter: Whether a collector succeeded. # TYPE node_scrape_collector_success gauge +node_scrape_collector_success{collector="arp"} 1 node_scrape_collector_success{collector="bonding"} 1 node_scrape_collector_success{collector="buddyinfo"} 1 node_scrape_collector_success{collector="conntrack"} 1 diff --git a/collector/fixtures/proc/net/arp b/collector/fixtures/proc/net/arp new file mode 100644 index 00000000..84c67f8c --- /dev/null +++ b/collector/fixtures/proc/net/arp @@ -0,0 +1,7 @@ +IP address HW type Flags HW address Mask Device +192.168.1.1 0x1 0x2 cc:aa:dd:ee:aa:bb * eth0 +192.168.1.2 0x1 0x2 bb:cc:dd:ee:ff:aa * eth0 +192.168.1.3 0x1 0x2 aa:bb:cc:dd:ee:ff * eth0 +192.168.1.4 0x1 0x2 dd:ee:ff:aa:bb:cc * eth1 +192.168.1.5 0x1 0x2 ee:ff:aa:bb:cc:dd * eth1 +192.168.1.6 0x1 0x2 ff:aa:bb:cc:dd:ee * eth1 diff --git a/end-to-end-test.sh b/end-to-end-test.sh index e0745563..f47fbb50 100755 --- a/end-to-end-test.sh +++ b/end-to-end-test.sh @@ -3,6 +3,7 @@ set -euf -o pipefail collectors=$(cat << COLLECTORS + arp buddyinfo conntrack diskstats diff --git a/node_exporter.go b/node_exporter.go index 4a7f88a2..f6daa2d4 100644 --- a/node_exporter.go +++ b/node_exporter.go @@ -32,7 +32,7 @@ import ( ) const ( - defaultCollectors = "conntrack,cpu,diskstats,entropy,edac,exec,filefd,filesystem,hwmon,infiniband,loadavg,mdadm,meminfo,netdev,netstat,sockstat,stat,textfile,time,uname,vmstat,wifi,zfs" + defaultCollectors = "arp,conntrack,cpu,diskstats,entropy,edac,exec,filefd,filesystem,hwmon,infiniband,loadavg,mdadm,meminfo,netdev,netstat,sockstat,stat,textfile,time,uname,vmstat,wifi,zfs" ) var (