Make wifi collector fail gracefully if metrics not available

This commit is contained in:
Matt Layher 2017-01-12 12:41:35 -05:00
parent 2884181cce
commit d3089f2ce8
No known key found for this signature in database
GPG key ID: 77BFE531397EDE94
6 changed files with 101 additions and 26 deletions

View file

@ -18,10 +18,12 @@ import (
"flag"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"github.com/mdlayher/wifi"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/common/log"
)
type wifiCollector struct {
@ -35,8 +37,6 @@ type wifiCollector struct {
StationTransmitRetriesTotal *prometheus.Desc
StationTransmitFailedTotal *prometheus.Desc
StationBeaconLossTotal *prometheus.Desc
stat wifiStater
}
var (
@ -51,16 +51,12 @@ var _ wifiStater = &wifi.Client{}
// wifiStater is an interface used to swap out a *wifi.Client for end to end tests.
type wifiStater interface {
Close() error
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"
)
@ -132,13 +128,23 @@ func NewWifiCollector() (Collector, error) {
labels,
nil,
),
stat: stat,
}, nil
}
func (c *wifiCollector) Update(ch chan<- prometheus.Metric) error {
ifis, err := c.stat.Interfaces()
stat, err := newWifiStater(*collectorWifi)
if err != nil {
// Cannot access wifi metrics, report no error
if os.IsNotExist(err) {
log.Debug("wifi collector metrics are not available for this system")
return nil
}
return fmt.Errorf("failed to access wifi data: %v", err)
}
defer stat.Close()
ifis, err := stat.Interfaces()
if err != nil {
return fmt.Errorf("failed to retrieve wifi interfaces: %v", err)
}
@ -149,7 +155,7 @@ func (c *wifiCollector) Update(ch chan<- prometheus.Metric) error {
continue
}
info, err := c.stat.StationInfo(ifi)
info, err := stat.StationInfo(ifi)
if err != nil {
return fmt.Errorf("failed to retrieve station info for device %s: %v",
ifi.Name, err)
@ -260,6 +266,8 @@ func (s *mockWifiStater) unmarshalJSONFile(filename string, v interface{}) error
return json.Unmarshal(b, v)
}
func (s *mockWifiStater) Close() error { return nil }
func (s *mockWifiStater) Interfaces() ([]*wifi.Interface, error) {
var ifis []*wifi.Interface
if err := s.unmarshalJSONFile("interfaces.json", &ifis); err != nil {

View file

@ -2,7 +2,7 @@ package netlink
import (
"errors"
"os"
"sync"
"sync/atomic"
)
@ -23,6 +23,12 @@ type Conn struct {
// seq is an atomically incremented integer used to provide sequence
// numbers when Conn.Send is called.
seq *uint32
// pid is an atomically set/loaded integer which is set to the PID assigned
// by netlink, when netlink sends its first response message. pidOnce performs
// the assignment exactl once.
pid *uint32
pidOnce sync.Once
}
// An osConn is an operating-system specific implementation of netlink
@ -53,6 +59,7 @@ func newConn(c osConn) *Conn {
return &Conn{
c: c,
seq: new(uint32),
pid: new(uint32),
}
}
@ -96,8 +103,8 @@ func (c *Conn) Execute(m Message) ([]Message, error) {
// If m.Header.Sequence is 0, it will be automatically populated using the
// next sequence number for this connection.
//
// If m.Header.PID is 0, it will be automatically populated using the
// process ID (PID) of this process.
// If m.Header.PID is 0, it will be automatically populated using a PID
// assigned by netlink.
func (c *Conn) Send(m Message) (Message, error) {
ml := nlmsgLength(len(m.Data))
@ -115,7 +122,7 @@ func (c *Conn) Send(m Message) (Message, error) {
}
if m.Header.PID == 0 {
m.Header.PID = uint32(os.Getpid())
m.Header.PID = atomic.LoadUint32(c.pid)
}
if err := c.c.Send(m); err != nil {
@ -127,14 +134,29 @@ func (c *Conn) Send(m Message) (Message, error) {
// Receive receives one or more messages from netlink. Multi-part messages are
// handled transparently and returned as a single slice of Messages, with the
// final empty "multi-part done" message removed. If any of the messages
// indicate a netlink error, that error will be returned.
// final empty "multi-part done" message removed.
//
// If a PID has not yet been assigned to this Conn by netlink, the PID will
// be set from the first received message. This PID will be used in all
// subsequent communications with netlink.
//
// If any of the messages indicate a netlink error, that error will be returned.
func (c *Conn) Receive() ([]Message, error) {
msgs, err := c.receive()
if err != nil {
return nil, err
}
if len(msgs) > 0 {
// netlink multicast messages from kernel have PID of 0, so don't
// assign 0 as the expected PID for next messages
if pid := msgs[0].Header.PID; pid != 0 {
c.pidOnce.Do(func() {
atomic.StoreUint32(c.pid, pid)
})
}
}
// Trim the final message with multi-part done indicator if
// present
if m := msgs[len(msgs)-1]; m.Header.Flags&HeaderFlagsMulti != 0 && m.Header.Type == HeaderTypeDone {
@ -200,10 +222,19 @@ func (c *Conn) nextSequence() uint32 {
// ensuring that they contain matching sequence numbers and PIDs.
func Validate(request Message, replies []Message) error {
for _, m := range replies {
if m.Header.Sequence != request.Header.Sequence {
// Check for mismatched sequence, unless:
// - request had no sequence, meaning we are probably validating
// a multicast reply
if m.Header.Sequence != request.Header.Sequence && request.Header.Sequence != 0 {
return errMismatchedSequence
}
if m.Header.PID != request.Header.PID {
// Check for mismatched PID, unless:
// - request had no PID, meaning we are either:
// - validating a multicast reply
// - netlink has not yet assigned us a PID
// - response had no PID, meaning it's from the kernel as a multicast reply
if m.Header.PID != request.Header.PID && request.Header.PID != 0 && m.Header.PID != 0 {
return errMismatchedPID
}
}

View file

@ -1,5 +1,15 @@
package wifi
import (
"errors"
)
var (
// errNotStation is returned when attempting to query station info for
// an interface which is not a station.
errNotStation = errors.New("interface is not a station")
)
// A Client is a type which can access WiFi device actions and statistics
// using operating system-specific operations.
type Client struct {
@ -18,6 +28,11 @@ func New() (*Client, error) {
}, nil
}
// Close releases resources used by a Client.
func (c *Client) Close() error {
return c.c.Close()
}
// Interfaces returns a list of the system's WiFi network interfaces.
func (c *Client) Interfaces() ([]*Interface, error) {
return c.c.Interfaces()
@ -26,11 +41,16 @@ func (c *Client) Interfaces() ([]*Interface, error) {
// StationInfo retrieves statistics about a WiFi interface operating in
// station mode.
func (c *Client) StationInfo(ifi *Interface) (*StationInfo, error) {
if ifi.Type != InterfaceTypeStation {
return nil, errNotStation
}
return c.c.StationInfo(ifi)
}
// An osClient is the operating system-specific implementation of Client.
type osClient interface {
Close() error
Interfaces() ([]*Interface, error)
StationInfo(ifi *Interface) (*StationInfo, error)
}

View file

@ -36,6 +36,7 @@ type client struct {
// genl is an interface over generic netlink, so netlink interactions can
// be stubbed in tests.
type genl interface {
Close() error
GetFamily(name string) (genetlink.Family, error)
Execute(m genetlink.Message, family uint16, flags netlink.HeaderFlags) ([]genetlink.Message, error)
}
@ -66,6 +67,11 @@ func initClient(c genl) (*client, error) {
}, nil
}
// Close closes the client's generic netlink connection.
func (c *client) Close() error {
return c.c.Close()
}
// Interfaces requests that nl80211 return a list of all WiFi interfaces present
// on this system.
func (c *client) Interfaces() ([]*Interface, error) {
@ -117,7 +123,12 @@ func (c *client) StationInfo(ifi *Interface) (*StationInfo, error) {
return nil, err
}
if len(msgs) > 1 {
switch len(msgs) {
case 0:
return nil, os.ErrNotExist
case 1:
break
default:
return nil, errMultipleMessages
}

View file

@ -24,6 +24,11 @@ func newClient() (*client, error) {
return nil, errUnimplemented
}
// Close always returns an error.
func (c *client) Close() error {
return errUnimplemented
}
// Interfaces always returns an error.
func (c *client) Interfaces() ([]*Interface, error) {
return nil, errUnimplemented

12
vendor/vendor.json vendored
View file

@ -51,10 +51,10 @@
"revisionTime": "2016-04-24T11:30:07Z"
},
{
"checksumSHA1": "87nUxyFGVJFXB6MQpGCGUHi5NY0=",
"checksumSHA1": "n31d2o+dY0HXZTDWE5Rc0+7NEjc=",
"path": "github.com/mdlayher/netlink",
"revision": "a65cbc3bb3f7a793b7d79ad7d19b16d471ddbd78",
"revisionTime": "2017-01-10T22:29:47Z"
"revision": "e5da4eb480835e5bce0dc5e526fe5f9a8002b54e",
"revisionTime": "2017-01-13T17:56:52Z"
},
{
"checksumSHA1": "+2roeIWCAjCC58tZcs12Vqgf1Io=",
@ -69,10 +69,10 @@
"revisionTime": "2017-01-04T04:59:06Z"
},
{
"checksumSHA1": "dSy4F6HRYyadhiQbv3Bof/Tdxec=",
"checksumSHA1": "l8M/rZH5s/ZVtCCyeiUQXZ5FosA=",
"path": "github.com/mdlayher/wifi",
"revision": "88fd1c0ec178645c1b7d300090b5a9d4b226b8e1",
"revisionTime": "2017-01-07T15:17:58Z"
"revision": "eb8b29b956ba5ff2fdd2d2f1f0b988b57fd3d8a3",
"revisionTime": "2017-01-12T20:47:29Z"
},
{
"checksumSHA1": "VzutdH69PUqRqhrDVv6F91ebQd4=",