From efa25665ecf607b4945c9c057e0b8ea952e6e26d Mon Sep 17 00:00:00 2001 From: Matt Layher Date: Mon, 9 Jan 2017 14:37:59 -0500 Subject: [PATCH] Add initial wifi collector, bump netlink to fix 32-bit builds --- README.md | 1 + collector/fixtures/e2e-output.txt | 27 ++ collector/fixtures/wifi/interfaces.json | 10 + .../fixtures/wifi/wlan0/stationinfo.json | 10 + collector/wifi_linux.go | 281 ++++++++++++++++++ end-to-end-test.sh | 2 + vendor/github.com/mdlayher/netlink/conn.go | 5 +- .../mdlayher/netlink/sockopt_linux_386.go | 2 +- vendor/vendor.json | 6 +- 9 files changed, 337 insertions(+), 7 deletions(-) create mode 100644 collector/fixtures/wifi/interfaces.json create mode 100644 collector/fixtures/wifi/wlan0/stationinfo.json create mode 100644 collector/wifi_linux.go diff --git a/README.md b/README.md index 558ffab7..70e218d2 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,7 @@ runit | Exposes service status from [runit](http://smarden.org/runit/). | _any_ supervisord | Exposes service status from [supervisord](http://supervisord.org/). | _any_ systemd | Exposes service and system status from [systemd](http://www.freedesktop.org/wiki/Software/systemd/). | Linux tcpstat | Exposes TCP connection status information from `/proc/net/tcp` and `/proc/net/tcp6`. (Warning: the current version has potential performance issues in high load situations.) | Linux +wifi | Exposes WiFi device and station statistics. | Linux ### Deprecated diff --git a/collector/fixtures/e2e-output.txt b/collector/fixtures/e2e-output.txt index bde69eab..4bcea402 100644 --- a/collector/fixtures/e2e-output.txt +++ b/collector/fixtures/e2e-output.txt @@ -1972,6 +1972,33 @@ node_sockstat_sockets_used 229 # HELP node_textfile_scrape_error 1 if there was an error opening or reading a file, 0 otherwise # TYPE node_textfile_scrape_error gauge node_textfile_scrape_error 0 +# HELP node_wifi_interface_frequency_hertz The current frequency a WiFi interface is operating at, in hertz. +# TYPE node_wifi_interface_frequency_hertz gauge +node_wifi_interface_frequency_hertz{device="wlan0"} 2.412e+09 +# HELP node_wifi_station_beacon_loss_total The total number of times a station has detected a beacon loss. +# TYPE node_wifi_station_beacon_loss_total counter +node_wifi_station_beacon_loss_total{device="wlan0"} 1 +# HELP node_wifi_station_connected_seconds_total The total number of seconds a station has been connected to an access point. +# TYPE node_wifi_station_connected_seconds_total counter +node_wifi_station_connected_seconds_total{device="wlan0"} 30 +# HELP node_wifi_station_inactive_seconds The number of seconds since any wireless activity has occurred on a station. +# TYPE node_wifi_station_inactive_seconds gauge +node_wifi_station_inactive_seconds{device="wlan0"} 0.4 +# HELP node_wifi_station_receive_bits_per_second The current WiFi receive bitrate of a station, in bits per second. +# TYPE node_wifi_station_receive_bits_per_second gauge +node_wifi_station_receive_bits_per_second{device="wlan0"} 1.28e+08 +# HELP node_wifi_station_signal_dbm The current WiFi signal strength, in decibel-milliwatts (dBm). +# TYPE node_wifi_station_signal_dbm gauge +node_wifi_station_signal_dbm{device="wlan0"} -52 +# HELP node_wifi_station_transmit_bits_per_second The current WiFi transmit bitrate of a station, in bits per second. +# TYPE node_wifi_station_transmit_bits_per_second gauge +node_wifi_station_transmit_bits_per_second{device="wlan0"} 1.64e+08 +# HELP node_wifi_station_transmit_failed_total The total number of times a station has failed to send a packet. +# TYPE node_wifi_station_transmit_failed_total counter +node_wifi_station_transmit_failed_total{device="wlan0"} 2 +# HELP node_wifi_station_transmit_retries_total The total number of times a station has had to retry while sending a packet. +# TYPE node_wifi_station_transmit_retries_total counter +node_wifi_station_transmit_retries_total{device="wlan0"} 10 # HELP node_zfsArc_anon_evictable_data kstat.zfs.misc.arcstats.anon_evictable_data # TYPE node_zfsArc_anon_evictable_data untyped node_zfsArc_anon_evictable_data 0 diff --git a/collector/fixtures/wifi/interfaces.json b/collector/fixtures/wifi/interfaces.json new file mode 100644 index 00000000..f5395d76 --- /dev/null +++ b/collector/fixtures/wifi/interfaces.json @@ -0,0 +1,10 @@ +[ + { + "name": "wlan0", + "type": 2, + "frequency": 2412 + }, + { + "type": 10 + } +] diff --git a/collector/fixtures/wifi/wlan0/stationinfo.json b/collector/fixtures/wifi/wlan0/stationinfo.json new file mode 100644 index 00000000..84bcb695 --- /dev/null +++ b/collector/fixtures/wifi/wlan0/stationinfo.json @@ -0,0 +1,10 @@ +{ + "connected": 30000000000, + "inactive": 400000000, + "receivebitrate": 128000000, + "transmitbitrate": 164000000, + "signal": -52, + "transmitretries": 10, + "transmitfailed": 2, + "beaconloss": 1 +} diff --git a/collector/wifi_linux.go b/collector/wifi_linux.go new file mode 100644 index 00000000..4eb1b687 --- /dev/null +++ b/collector/wifi_linux.go @@ -0,0 +1,281 @@ +// 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. + +package collector + +import ( + "encoding/json" + "flag" + "fmt" + "io/ioutil" + "path/filepath" + + "github.com/mdlayher/wifi" + "github.com/prometheus/client_golang/prometheus" +) + +type wifiCollector struct { + InterfaceFrequencyHertz *prometheus.Desc + + StationConnectedSecondsTotal *prometheus.Desc + StationInactiveSeconds *prometheus.Desc + StationReceiveBitsPerSecond *prometheus.Desc + StationTransmitBitsPerSecond *prometheus.Desc + StationSignalDBM *prometheus.Desc + StationTransmitRetriesTotal *prometheus.Desc + StationTransmitFailedTotal *prometheus.Desc + StationBeaconLossTotal *prometheus.Desc + + stat wifiStater +} + +var ( + collectorWifi = flag.String("collector.wifi", "", "test fixtures to use for wifi collector metrics") +) + +func init() { + Factories["wifi"] = NewWifiCollector +} + +var _ wifiStater = &wifi.Client{} + +// wifiStater is an interface used to swap out a *wifi.Client for end to end tests. +type wifiStater interface { + Interfaces() ([]*wifi.Interface, error) + StationInfo(ifi *wifi.Interface) (*wifi.StationInfo, error) +} + +func NewWifiCollector() (Collector, error) { + stat, err := newWifiStater(*collectorWifi) + if err != nil { + return nil, fmt.Errorf("failed to access wifi data: %v", err) + } + + const ( + subsystem = "wifi" + ) + + var ( + labels = []string{"device"} + ) + + return &wifiCollector{ + InterfaceFrequencyHertz: prometheus.NewDesc( + prometheus.BuildFQName(Namespace, subsystem, "interface_frequency_hertz"), + "The current frequency a WiFi interface is operating at, in hertz.", + labels, + nil, + ), + + StationConnectedSecondsTotal: prometheus.NewDesc( + prometheus.BuildFQName(Namespace, subsystem, "station_connected_seconds_total"), + "The total number of seconds a station has been connected to an access point.", + labels, + nil, + ), + + StationInactiveSeconds: prometheus.NewDesc( + prometheus.BuildFQName(Namespace, subsystem, "station_inactive_seconds"), + "The number of seconds since any wireless activity has occurred on a station.", + labels, + nil, + ), + + StationReceiveBitsPerSecond: prometheus.NewDesc( + prometheus.BuildFQName(Namespace, subsystem, "station_receive_bits_per_second"), + "The current WiFi receive bitrate of a station, in bits per second.", + labels, + nil, + ), + + StationTransmitBitsPerSecond: prometheus.NewDesc( + prometheus.BuildFQName(Namespace, subsystem, "station_transmit_bits_per_second"), + "The current WiFi transmit bitrate of a station, in bits per second.", + labels, + nil, + ), + + StationSignalDBM: prometheus.NewDesc( + prometheus.BuildFQName(Namespace, subsystem, "station_signal_dbm"), + "The current WiFi signal strength, in decibel-milliwatts (dBm).", + labels, + nil, + ), + + StationTransmitRetriesTotal: prometheus.NewDesc( + prometheus.BuildFQName(Namespace, subsystem, "station_transmit_retries_total"), + "The total number of times a station has had to retry while sending a packet.", + labels, + nil, + ), + + StationTransmitFailedTotal: prometheus.NewDesc( + prometheus.BuildFQName(Namespace, subsystem, "station_transmit_failed_total"), + "The total number of times a station has failed to send a packet.", + labels, + nil, + ), + + StationBeaconLossTotal: prometheus.NewDesc( + prometheus.BuildFQName(Namespace, subsystem, "station_beacon_loss_total"), + "The total number of times a station has detected a beacon loss.", + labels, + nil, + ), + + stat: stat, + }, nil +} + +func (c *wifiCollector) Update(ch chan<- prometheus.Metric) error { + ifis, err := c.stat.Interfaces() + if err != nil { + return fmt.Errorf("failed to retrieve wifi interfaces: %v", err) + } + + for _, ifi := range ifis { + // Only collect metrics on stations for now + if ifi.Type != wifi.InterfaceTypeStation { + continue + } + + info, err := c.stat.StationInfo(ifi) + if err != nil { + return fmt.Errorf("failed to retrieve station info for device %s: %v", + ifi.Name, err) + } + + ch <- prometheus.MustNewConstMetric( + c.InterfaceFrequencyHertz, + prometheus.GaugeValue, + mHzToHz(ifi.Frequency), + ifi.Name, + ) + + c.updateStationStats(ch, ifi.Name, info) + } + + return nil +} + +func (c *wifiCollector) updateStationStats(ch chan<- prometheus.Metric, device string, info *wifi.StationInfo) { + ch <- prometheus.MustNewConstMetric( + c.StationConnectedSecondsTotal, + prometheus.CounterValue, + info.Connected.Seconds(), + device, + ) + + ch <- prometheus.MustNewConstMetric( + c.StationInactiveSeconds, + prometheus.GaugeValue, + info.Inactive.Seconds(), + device, + ) + + ch <- prometheus.MustNewConstMetric( + c.StationReceiveBitsPerSecond, + prometheus.GaugeValue, + float64(info.ReceiveBitrate), + device, + ) + + ch <- prometheus.MustNewConstMetric( + c.StationTransmitBitsPerSecond, + prometheus.GaugeValue, + float64(info.TransmitBitrate), + device, + ) + + ch <- prometheus.MustNewConstMetric( + c.StationSignalDBM, + prometheus.GaugeValue, + float64(info.Signal), + device, + ) + + ch <- prometheus.MustNewConstMetric( + c.StationTransmitRetriesTotal, + prometheus.CounterValue, + float64(info.TransmitRetries), + device, + ) + + ch <- prometheus.MustNewConstMetric( + c.StationTransmitFailedTotal, + prometheus.CounterValue, + float64(info.TransmitFailed), + device, + ) + + ch <- prometheus.MustNewConstMetric( + c.StationBeaconLossTotal, + prometheus.CounterValue, + float64(info.BeaconLoss), + device, + ) +} + +func mHzToHz(mHz int) float64 { + return float64(mHz) * 1000 * 1000 +} + +// All code below this point is used to assist with end-to-end tests for +// the wifi collector, since wifi devices are not available in CI. + +// newWifiStater determines if mocked test fixtures from files should be used for +// collecting wifi metrics, or if package wifi should be used. +func newWifiStater(fixtures string) (wifiStater, error) { + if fixtures != "" { + return &mockWifiStater{ + fixtures: fixtures, + }, nil + } + + return wifi.New() +} + +var _ wifiStater = &mockWifiStater{} + +type mockWifiStater struct { + fixtures string +} + +func (s *mockWifiStater) unmarshalJSONFile(filename string, v interface{}) error { + b, err := ioutil.ReadFile(filepath.Join(s.fixtures, filename)) + if err != nil { + return err + } + + return json.Unmarshal(b, v) +} + +func (s *mockWifiStater) Interfaces() ([]*wifi.Interface, error) { + var ifis []*wifi.Interface + if err := s.unmarshalJSONFile("interfaces.json", &ifis); err != nil { + return nil, err + } + + return ifis, nil +} + +func (s *mockWifiStater) StationInfo(ifi *wifi.Interface) (*wifi.StationInfo, error) { + p := filepath.Join(ifi.Name, "stationinfo.json") + + var info wifi.StationInfo + if err := s.unmarshalJSONFile(p, &info); err != nil { + return nil, err + } + + return &info, nil +} diff --git a/end-to-end-test.sh b/end-to-end-test.sh index 0fdca823..2e92dad5 100755 --- a/end-to-end-test.sh +++ b/end-to-end-test.sh @@ -24,6 +24,7 @@ collectors=$(cat << COLLECTORS textfile bonding megacli + wifi zfs COLLECTORS ) @@ -70,6 +71,7 @@ fi -collectors.enabled="$(echo ${collectors} | tr ' ' ',')" \ -collector.textfile.directory="collector/fixtures/textfile/two_metric_files/" \ -collector.megacli.command="collector/fixtures/megacli" \ + -collector.wifi="collector/fixtures/wifi" \ -web.listen-address "127.0.0.1:${port}" \ -log.level="debug" > "${tmpdir}/node_exporter.log" 2>&1 & diff --git a/vendor/github.com/mdlayher/netlink/conn.go b/vendor/github.com/mdlayher/netlink/conn.go index 7e0f503f..c658c771 100644 --- a/vendor/github.com/mdlayher/netlink/conn.go +++ b/vendor/github.com/mdlayher/netlink/conn.go @@ -2,7 +2,6 @@ package netlink import ( "errors" - "math" "os" "sync/atomic" ) @@ -102,8 +101,8 @@ func (c *Conn) Execute(m Message) ([]Message, error) { func (c *Conn) Send(m Message) (Message, error) { ml := nlmsgLength(len(m.Data)) - // TODO(mdlayher): fine-tune this limit. ~4GiB is a huge message. - if ml > math.MaxUint32 { + // TODO(mdlayher): fine-tune this limit. + if ml > (1024 * 32) { return Message{}, errors.New("netlink message data too large") } diff --git a/vendor/github.com/mdlayher/netlink/sockopt_linux_386.go b/vendor/github.com/mdlayher/netlink/sockopt_linux_386.go index 0e3a7480..2355e456 100644 --- a/vendor/github.com/mdlayher/netlink/sockopt_linux_386.go +++ b/vendor/github.com/mdlayher/netlink/sockopt_linux_386.go @@ -4,7 +4,7 @@ // +build linux,386 -package raw +package netlink import ( "syscall" diff --git a/vendor/vendor.json b/vendor/vendor.json index aa5b03ee..65c87f81 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -51,10 +51,10 @@ "revisionTime": "2016-04-24T11:30:07Z" }, { - "checksumSHA1": "dG2VS6m8AS9wvpZh7aSx5AX001U=", + "checksumSHA1": "87nUxyFGVJFXB6MQpGCGUHi5NY0=", "path": "github.com/mdlayher/netlink", - "revision": "1291b75abe0cc0cb335f110466bf1f02590c916d", - "revisionTime": "2017-01-04T04:59:06Z" + "revision": "a65cbc3bb3f7a793b7d79ad7d19b16d471ddbd78", + "revisionTime": "2017-01-10T22:29:47Z" }, { "checksumSHA1": "+2roeIWCAjCC58tZcs12Vqgf1Io=",