diff options
author | Sam Kottler <skottler@users.noreply.github.com> | 2017-04-11 08:45:19 -0700 |
---|---|---|
committer | Tobias Schmidt <ts@soundcloud.com> | 2017-04-11 17:45:19 +0200 |
commit | 6eafa51fa81b24b50dd819a47540db2db8948a39 (patch) | |
tree | bd18c7328c454a6cc59f92a3f605a38a3be2f3ef | |
parent | 84b65edb048d3f1e64b2d54a784154fb906e6cce (diff) | |
download | prometheus_node_collector-6eafa51fa81b24b50dd819a47540db2db8948a39.tar.bz2 prometheus_node_collector-6eafa51fa81b24b50dd819a47540db2db8948a39.tar.xz prometheus_node_collector-6eafa51fa81b24b50dd819a47540db2db8948a39.zip |
Add ARP collector for Linux (#540)
* Implement commonalities and linux support for ARP collection
* Add ARP collector to fixtures and run as part of e2e tests
* Bubble up scanner errors
* Use single return values where it makes sense
* Add missing annotation
* Move arp_common into arp_linux
* Add license header to arp_linux.go
* Address initial feedback
* Use strings.Fields instead of strings.Split
* Deal with scanner.Err() rather than throwing away errors
* Check for scan errors in-line before interacting with the entries map
* Don't interact with potentially empty text from scan
* Check for scan errors outside the scan loop
* Add comment about moving procfs parsing
* Add more direct comment
* Update initialism style to match go style guide
* Put function args on the same line
* Add TODO in front of comment about procfs extraction
* Guard against strings.Fields returning an empty slice
* Be more defensive about ARP table format and use upcase more broadly
* Enable the ARP collector by default
* Add ARP collector to the README
* Remove 'entry'
-rw-r--r-- | README.md | 1 | ||||
-rw-r--r-- | collector/arp_linux.go | 102 | ||||
-rw-r--r-- | collector/fixtures/e2e-output.txt | 5 | ||||
-rw-r--r-- | collector/fixtures/proc/net/arp | 7 | ||||
-rwxr-xr-x | end-to-end-test.sh | 1 | ||||
-rw-r--r-- | node_exporter.go | 2 |
6 files changed, 117 insertions, 1 deletions
@@ -21,6 +21,7 @@ Which collectors are used is controlled by the `--collectors.enabled` flag. | |||
21 | 21 | ||
22 | Name | Description | OS | 22 | Name | Description | OS |
23 | ---------|-------------|---- | 23 | ---------|-------------|---- |
24 | arp | Exposes ARP statistics from `/proc/net/arp`. | Linux | ||
24 | conntrack | Shows conntrack statistics (does nothing if no `/proc/sys/net/netfilter/` present). | Linux | 25 | conntrack | Shows conntrack statistics (does nothing if no `/proc/sys/net/netfilter/` present). | Linux |
25 | cpu | Exposes CPU statistics | Darwin, Dragonfly, FreeBSD | 26 | cpu | Exposes CPU statistics | Darwin, Dragonfly, FreeBSD |
26 | diskstats | Exposes disk I/O statistics from `/proc/diskstats`. | Linux | 27 | 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 0000000..71c1c5a --- /dev/null +++ b/collector/arp_linux.go | |||
@@ -0,0 +1,102 @@ | |||
1 | // Copyright 2017 The Prometheus Authors | ||
2 | // Licensed under the Apache License, Version 2.0 (the "License"); | ||
3 | // you may not use this file except in compliance with the License. | ||
4 | // You may obtain a copy of the License at | ||
5 | // | ||
6 | // http://www.apache.org/licenses/LICENSE-2.0 | ||
7 | // | ||
8 | // Unless required by applicable law or agreed to in writing, software | ||
9 | // distributed under the License is distributed on an "AS IS" BASIS, | ||
10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
11 | // See the License for the specific language governing permissions and | ||
12 | // limitations under the License. | ||
13 | |||
14 | // +build !noarp | ||
15 | |||
16 | package collector | ||
17 | |||
18 | import ( | ||
19 | "bufio" | ||
20 | "fmt" | ||
21 | "io" | ||
22 | "os" | ||
23 | "strings" | ||
24 | |||
25 | "github.com/prometheus/client_golang/prometheus" | ||
26 | ) | ||
27 | |||
28 | type arpCollector struct { | ||
29 | entries *prometheus.Desc | ||
30 | } | ||
31 | |||
32 | func init() { | ||
33 | Factories["arp"] = NewARPCollector | ||
34 | } | ||
35 | |||
36 | // NewARPCollector returns a new Collector exposing ARP stats. | ||
37 | func NewARPCollector() (Collector, error) { | ||
38 | return &arpCollector{ | ||
39 | entries: prometheus.NewDesc( | ||
40 | prometheus.BuildFQName(Namespace, "arp", "entries"), | ||
41 | "ARP entries by device", | ||
42 | []string{"device"}, nil, | ||
43 | ), | ||
44 | }, nil | ||
45 | } | ||
46 | |||
47 | func getARPEntries() (map[string]uint32, error) { | ||
48 | file, err := os.Open(procFilePath("net/arp")) | ||
49 | if err != nil { | ||
50 | return nil, err | ||
51 | } | ||
52 | defer file.Close() | ||
53 | |||
54 | entries, err := parseARPEntries(file) | ||
55 | if err != nil { | ||
56 | return nil, err | ||
57 | } | ||
58 | |||
59 | return entries, nil | ||
60 | } | ||
61 | |||
62 | // TODO: This should get extracted to the github.com/prometheus/procfs package | ||
63 | // to support more complete parsing of /proc/net/arp. Instead of adding | ||
64 | // more fields to this function's return values it should get moved and | ||
65 | // changed to support each field. | ||
66 | func parseARPEntries(data io.Reader) (map[string]uint32, error) { | ||
67 | scanner := bufio.NewScanner(data) | ||
68 | entries := make(map[string]uint32) | ||
69 | |||
70 | for scanner.Scan() { | ||
71 | columns := strings.Fields(scanner.Text()) | ||
72 | |||
73 | if len(columns) < 6 { | ||
74 | return nil, fmt.Errorf("unexpected ARP table format") | ||
75 | } | ||
76 | |||
77 | if columns[0] != "IP" { | ||
78 | deviceIndex := len(columns) - 1 | ||
79 | entries[columns[deviceIndex]]++ | ||
80 | } | ||
81 | } | ||
82 | |||
83 | if err := scanner.Err(); err != nil { | ||
84 | return nil, fmt.Errorf("failed to parse ARP info: %s", err) | ||
85 | } | ||
86 | |||
87 | return entries, nil | ||
88 | } | ||
89 | |||
90 | func (c *arpCollector) Update(ch chan<- prometheus.Metric) error { | ||
91 | entries, err := getARPEntries() | ||
92 | if err != nil { | ||
93 | return fmt.Errorf("could not get ARP entries: %s", err) | ||
94 | } | ||
95 | |||
96 | for device, entryCount := range entries { | ||
97 | ch <- prometheus.MustNewConstMetric( | ||
98 | c.entries, prometheus.GaugeValue, float64(entryCount), device) | ||
99 | } | ||
100 | |||
101 | return nil | ||
102 | } | ||
diff --git a/collector/fixtures/e2e-output.txt b/collector/fixtures/e2e-output.txt index 06c27ae..1828299 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 | |||
71 | http_response_size_bytes{handler="prometheus",quantile="0.99"} NaN | 71 | http_response_size_bytes{handler="prometheus",quantile="0.99"} NaN |
72 | http_response_size_bytes_sum{handler="prometheus"} 0 | 72 | http_response_size_bytes_sum{handler="prometheus"} 0 |
73 | http_response_size_bytes_count{handler="prometheus"} 0 | 73 | http_response_size_bytes_count{handler="prometheus"} 0 |
74 | # HELP node_arp_entries ARP entries by device | ||
75 | # TYPE node_arp_entries gauge | ||
76 | node_arp_entries{device="eth0"} 3 | ||
77 | node_arp_entries{device="eth1"} 3 | ||
74 | # HELP node_bonding_active Number of active slaves per bonding interface. | 78 | # HELP node_bonding_active Number of active slaves per bonding interface. |
75 | # TYPE node_bonding_active gauge | 79 | # TYPE node_bonding_active gauge |
76 | node_bonding_active{master="bond0"} 0 | 80 | node_bonding_active{master="bond0"} 0 |
@@ -2106,6 +2110,7 @@ node_procs_running 2 | |||
2106 | # TYPE node_scrape_collector_duration_seconds gauge | 2110 | # TYPE node_scrape_collector_duration_seconds gauge |
2107 | # HELP node_scrape_collector_success node_exporter: Whether a collector succeeded. | 2111 | # HELP node_scrape_collector_success node_exporter: Whether a collector succeeded. |
2108 | # TYPE node_scrape_collector_success gauge | 2112 | # TYPE node_scrape_collector_success gauge |
2113 | node_scrape_collector_success{collector="arp"} 1 | ||
2109 | node_scrape_collector_success{collector="bonding"} 1 | 2114 | node_scrape_collector_success{collector="bonding"} 1 |
2110 | node_scrape_collector_success{collector="buddyinfo"} 1 | 2115 | node_scrape_collector_success{collector="buddyinfo"} 1 |
2111 | node_scrape_collector_success{collector="conntrack"} 1 | 2116 | 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 0000000..84c67f8 --- /dev/null +++ b/collector/fixtures/proc/net/arp | |||
@@ -0,0 +1,7 @@ | |||
1 | IP address HW type Flags HW address Mask Device | ||
2 | 192.168.1.1 0x1 0x2 cc:aa:dd:ee:aa:bb * eth0 | ||
3 | 192.168.1.2 0x1 0x2 bb:cc:dd:ee:ff:aa * eth0 | ||
4 | 192.168.1.3 0x1 0x2 aa:bb:cc:dd:ee:ff * eth0 | ||
5 | 192.168.1.4 0x1 0x2 dd:ee:ff:aa:bb:cc * eth1 | ||
6 | 192.168.1.5 0x1 0x2 ee:ff:aa:bb:cc:dd * eth1 | ||
7 | 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 e074556..f47fbb5 100755 --- a/end-to-end-test.sh +++ b/end-to-end-test.sh | |||
@@ -3,6 +3,7 @@ | |||
3 | set -euf -o pipefail | 3 | set -euf -o pipefail |
4 | 4 | ||
5 | collectors=$(cat << COLLECTORS | 5 | collectors=$(cat << COLLECTORS |
6 | arp | ||
6 | buddyinfo | 7 | buddyinfo |
7 | conntrack | 8 | conntrack |
8 | diskstats | 9 | diskstats |
diff --git a/node_exporter.go b/node_exporter.go index 4a7f88a..f6daa2d 100644 --- a/node_exporter.go +++ b/node_exporter.go | |||
@@ -32,7 +32,7 @@ import ( | |||
32 | ) | 32 | ) |
33 | 33 | ||
34 | const ( | 34 | const ( |
35 | defaultCollectors = "conntrack,cpu,diskstats,entropy,edac,exec,filefd,filesystem,hwmon,infiniband,loadavg,mdadm,meminfo,netdev,netstat,sockstat,stat,textfile,time,uname,vmstat,wifi,zfs" | 35 | 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" |
36 | ) | 36 | ) |
37 | 37 | ||
38 | var ( | 38 | var ( |