mirror of
				https://github.com/prometheus/node_exporter.git
				synced 2025-08-20 18:33:52 -07:00 
			
		
		
		
	Make wifi collector fail gracefully if metrics not available
This commit is contained in:
		
							parent
							
								
									2884181cce
								
							
						
					
					
						commit
						d3089f2ce8
					
				|  | @ -18,10 +18,12 @@ import ( | ||||||
| 	"flag" | 	"flag" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io/ioutil" | 	"io/ioutil" | ||||||
|  | 	"os" | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| 
 | 
 | ||||||
| 	"github.com/mdlayher/wifi" | 	"github.com/mdlayher/wifi" | ||||||
| 	"github.com/prometheus/client_golang/prometheus" | 	"github.com/prometheus/client_golang/prometheus" | ||||||
|  | 	"github.com/prometheus/common/log" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type wifiCollector struct { | type wifiCollector struct { | ||||||
|  | @ -35,8 +37,6 @@ type wifiCollector struct { | ||||||
| 	StationTransmitRetriesTotal  *prometheus.Desc | 	StationTransmitRetriesTotal  *prometheus.Desc | ||||||
| 	StationTransmitFailedTotal   *prometheus.Desc | 	StationTransmitFailedTotal   *prometheus.Desc | ||||||
| 	StationBeaconLossTotal       *prometheus.Desc | 	StationBeaconLossTotal       *prometheus.Desc | ||||||
| 
 |  | ||||||
| 	stat wifiStater |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var ( | 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.
 | // wifiStater is an interface used to swap out a *wifi.Client for end to end tests.
 | ||||||
| type wifiStater interface { | type wifiStater interface { | ||||||
|  | 	Close() error | ||||||
| 	Interfaces() ([]*wifi.Interface, error) | 	Interfaces() ([]*wifi.Interface, error) | ||||||
| 	StationInfo(ifi *wifi.Interface) (*wifi.StationInfo, error) | 	StationInfo(ifi *wifi.Interface) (*wifi.StationInfo, error) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func NewWifiCollector() (Collector, error) { | func NewWifiCollector() (Collector, error) { | ||||||
| 	stat, err := newWifiStater(*collectorWifi) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, fmt.Errorf("failed to access wifi data: %v", err) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	const ( | 	const ( | ||||||
| 		subsystem = "wifi" | 		subsystem = "wifi" | ||||||
| 	) | 	) | ||||||
|  | @ -132,13 +128,23 @@ func NewWifiCollector() (Collector, error) { | ||||||
| 			labels, | 			labels, | ||||||
| 			nil, | 			nil, | ||||||
| 		), | 		), | ||||||
| 
 |  | ||||||
| 		stat: stat, |  | ||||||
| 	}, nil | 	}, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (c *wifiCollector) Update(ch chan<- prometheus.Metric) error { | 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 { | 	if err != nil { | ||||||
| 		return fmt.Errorf("failed to retrieve wifi interfaces: %v", err) | 		return fmt.Errorf("failed to retrieve wifi interfaces: %v", err) | ||||||
| 	} | 	} | ||||||
|  | @ -149,7 +155,7 @@ func (c *wifiCollector) Update(ch chan<- prometheus.Metric) error { | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		info, err := c.stat.StationInfo(ifi) | 		info, err := stat.StationInfo(ifi) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return fmt.Errorf("failed to retrieve station info for device %s: %v", | 			return fmt.Errorf("failed to retrieve station info for device %s: %v", | ||||||
| 				ifi.Name, err) | 				ifi.Name, err) | ||||||
|  | @ -260,6 +266,8 @@ func (s *mockWifiStater) unmarshalJSONFile(filename string, v interface{}) error | ||||||
| 	return json.Unmarshal(b, v) | 	return json.Unmarshal(b, v) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (s *mockWifiStater) Close() error { return nil } | ||||||
|  | 
 | ||||||
| func (s *mockWifiStater) Interfaces() ([]*wifi.Interface, error) { | func (s *mockWifiStater) Interfaces() ([]*wifi.Interface, error) { | ||||||
| 	var ifis []*wifi.Interface | 	var ifis []*wifi.Interface | ||||||
| 	if err := s.unmarshalJSONFile("interfaces.json", &ifis); err != nil { | 	if err := s.unmarshalJSONFile("interfaces.json", &ifis); err != nil { | ||||||
|  |  | ||||||
							
								
								
									
										47
									
								
								vendor/github.com/mdlayher/netlink/conn.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										47
									
								
								vendor/github.com/mdlayher/netlink/conn.go
									
									
									
										generated
									
									
										vendored
									
									
								
							|  | @ -2,7 +2,7 @@ package netlink | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"os" | 	"sync" | ||||||
| 	"sync/atomic" | 	"sync/atomic" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -23,6 +23,12 @@ type Conn struct { | ||||||
| 	// seq is an atomically incremented integer used to provide sequence
 | 	// seq is an atomically incremented integer used to provide sequence
 | ||||||
| 	// numbers when Conn.Send is called.
 | 	// numbers when Conn.Send is called.
 | ||||||
| 	seq *uint32 | 	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
 | // An osConn is an operating-system specific implementation of netlink
 | ||||||
|  | @ -53,6 +59,7 @@ func newConn(c osConn) *Conn { | ||||||
| 	return &Conn{ | 	return &Conn{ | ||||||
| 		c:   c, | 		c:   c, | ||||||
| 		seq: new(uint32), | 		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
 | // If m.Header.Sequence is 0, it will be automatically populated using the
 | ||||||
| // next sequence number for this connection.
 | // next sequence number for this connection.
 | ||||||
| //
 | //
 | ||||||
| // If m.Header.PID is 0, it will be automatically populated using the
 | // If m.Header.PID is 0, it will be automatically populated using a PID
 | ||||||
| // process ID (PID) of this process.
 | // assigned by netlink.
 | ||||||
| func (c *Conn) Send(m Message) (Message, error) { | func (c *Conn) Send(m Message) (Message, error) { | ||||||
| 	ml := nlmsgLength(len(m.Data)) | 	ml := nlmsgLength(len(m.Data)) | ||||||
| 
 | 
 | ||||||
|  | @ -115,7 +122,7 @@ func (c *Conn) Send(m Message) (Message, error) { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if m.Header.PID == 0 { | 	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 { | 	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
 | // Receive receives one or more messages from netlink.  Multi-part messages are
 | ||||||
| // handled transparently and returned as a single slice of Messages, with the
 | // handled transparently and returned as a single slice of Messages, with the
 | ||||||
| // final empty "multi-part done" message removed.  If any of the messages
 | // final empty "multi-part done" message removed.
 | ||||||
| // indicate a netlink error, that error will be returned.
 | //
 | ||||||
|  | // 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) { | func (c *Conn) Receive() ([]Message, error) { | ||||||
| 	msgs, err := c.receive() | 	msgs, err := c.receive() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		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
 | 	// Trim the final message with multi-part done indicator if
 | ||||||
| 	// present
 | 	// present
 | ||||||
| 	if m := msgs[len(msgs)-1]; m.Header.Flags&HeaderFlagsMulti != 0 && m.Header.Type == HeaderTypeDone { | 	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.
 | // ensuring that they contain matching sequence numbers and PIDs.
 | ||||||
| func Validate(request Message, replies []Message) error { | func Validate(request Message, replies []Message) error { | ||||||
| 	for _, m := range replies { | 	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 | 			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 | 			return errMismatchedPID | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
							
								
								
									
										20
									
								
								vendor/github.com/mdlayher/wifi/client.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										20
									
								
								vendor/github.com/mdlayher/wifi/client.go
									
									
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,5 +1,15 @@ | ||||||
| package wifi | 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
 | // A Client is a type which can access WiFi device actions and statistics
 | ||||||
| // using operating system-specific operations.
 | // using operating system-specific operations.
 | ||||||
| type Client struct { | type Client struct { | ||||||
|  | @ -18,6 +28,11 @@ func New() (*Client, error) { | ||||||
| 	}, nil | 	}, 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.
 | // Interfaces returns a list of the system's WiFi network interfaces.
 | ||||||
| func (c *Client) Interfaces() ([]*Interface, error) { | func (c *Client) Interfaces() ([]*Interface, error) { | ||||||
| 	return c.c.Interfaces() | 	return c.c.Interfaces() | ||||||
|  | @ -26,11 +41,16 @@ func (c *Client) Interfaces() ([]*Interface, error) { | ||||||
| // StationInfo retrieves statistics about a WiFi interface operating in
 | // StationInfo retrieves statistics about a WiFi interface operating in
 | ||||||
| // station mode.
 | // station mode.
 | ||||||
| func (c *Client) StationInfo(ifi *Interface) (*StationInfo, error) { | func (c *Client) StationInfo(ifi *Interface) (*StationInfo, error) { | ||||||
|  | 	if ifi.Type != InterfaceTypeStation { | ||||||
|  | 		return nil, errNotStation | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	return c.c.StationInfo(ifi) | 	return c.c.StationInfo(ifi) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // An osClient is the operating system-specific implementation of Client.
 | // An osClient is the operating system-specific implementation of Client.
 | ||||||
| type osClient interface { | type osClient interface { | ||||||
|  | 	Close() error | ||||||
| 	Interfaces() ([]*Interface, error) | 	Interfaces() ([]*Interface, error) | ||||||
| 	StationInfo(ifi *Interface) (*StationInfo, error) | 	StationInfo(ifi *Interface) (*StationInfo, error) | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										13
									
								
								vendor/github.com/mdlayher/wifi/client_linux.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										13
									
								
								vendor/github.com/mdlayher/wifi/client_linux.go
									
									
									
										generated
									
									
										vendored
									
									
								
							|  | @ -36,6 +36,7 @@ type client struct { | ||||||
| // genl is an interface over generic netlink, so netlink interactions can
 | // genl is an interface over generic netlink, so netlink interactions can
 | ||||||
| // be stubbed in tests.
 | // be stubbed in tests.
 | ||||||
| type genl interface { | type genl interface { | ||||||
|  | 	Close() error | ||||||
| 	GetFamily(name string) (genetlink.Family, error) | 	GetFamily(name string) (genetlink.Family, error) | ||||||
| 	Execute(m genetlink.Message, family uint16, flags netlink.HeaderFlags) ([]genetlink.Message, error) | 	Execute(m genetlink.Message, family uint16, flags netlink.HeaderFlags) ([]genetlink.Message, error) | ||||||
| } | } | ||||||
|  | @ -66,6 +67,11 @@ func initClient(c genl) (*client, error) { | ||||||
| 	}, nil | 	}, 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
 | // Interfaces requests that nl80211 return a list of all WiFi interfaces present
 | ||||||
| // on this system.
 | // on this system.
 | ||||||
| func (c *client) Interfaces() ([]*Interface, error) { | func (c *client) Interfaces() ([]*Interface, error) { | ||||||
|  | @ -117,7 +123,12 @@ func (c *client) StationInfo(ifi *Interface) (*StationInfo, error) { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if len(msgs) > 1 { | 	switch len(msgs) { | ||||||
|  | 	case 0: | ||||||
|  | 		return nil, os.ErrNotExist | ||||||
|  | 	case 1: | ||||||
|  | 		break | ||||||
|  | 	default: | ||||||
| 		return nil, errMultipleMessages | 		return nil, errMultipleMessages | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										5
									
								
								vendor/github.com/mdlayher/wifi/client_others.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								vendor/github.com/mdlayher/wifi/client_others.go
									
									
									
										generated
									
									
										vendored
									
									
								
							|  | @ -24,6 +24,11 @@ func newClient() (*client, error) { | ||||||
| 	return nil, errUnimplemented | 	return nil, errUnimplemented | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // Close always returns an error.
 | ||||||
|  | func (c *client) Close() error { | ||||||
|  | 	return errUnimplemented | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // Interfaces always returns an error.
 | // Interfaces always returns an error.
 | ||||||
| func (c *client) Interfaces() ([]*Interface, error) { | func (c *client) Interfaces() ([]*Interface, error) { | ||||||
| 	return nil, errUnimplemented | 	return nil, errUnimplemented | ||||||
|  |  | ||||||
							
								
								
									
										12
									
								
								vendor/vendor.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								vendor/vendor.json
									
									
									
									
										vendored
									
									
								
							|  | @ -51,10 +51,10 @@ | ||||||
| 			"revisionTime": "2016-04-24T11:30:07Z" | 			"revisionTime": "2016-04-24T11:30:07Z" | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			"checksumSHA1": "87nUxyFGVJFXB6MQpGCGUHi5NY0=", | 			"checksumSHA1": "n31d2o+dY0HXZTDWE5Rc0+7NEjc=", | ||||||
| 			"path": "github.com/mdlayher/netlink", | 			"path": "github.com/mdlayher/netlink", | ||||||
| 			"revision": "a65cbc3bb3f7a793b7d79ad7d19b16d471ddbd78", | 			"revision": "e5da4eb480835e5bce0dc5e526fe5f9a8002b54e", | ||||||
| 			"revisionTime": "2017-01-10T22:29:47Z" | 			"revisionTime": "2017-01-13T17:56:52Z" | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			"checksumSHA1": "+2roeIWCAjCC58tZcs12Vqgf1Io=", | 			"checksumSHA1": "+2roeIWCAjCC58tZcs12Vqgf1Io=", | ||||||
|  | @ -69,10 +69,10 @@ | ||||||
| 			"revisionTime": "2017-01-04T04:59:06Z" | 			"revisionTime": "2017-01-04T04:59:06Z" | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			"checksumSHA1": "dSy4F6HRYyadhiQbv3Bof/Tdxec=", | 			"checksumSHA1": "l8M/rZH5s/ZVtCCyeiUQXZ5FosA=", | ||||||
| 			"path": "github.com/mdlayher/wifi", | 			"path": "github.com/mdlayher/wifi", | ||||||
| 			"revision": "88fd1c0ec178645c1b7d300090b5a9d4b226b8e1", | 			"revision": "eb8b29b956ba5ff2fdd2d2f1f0b988b57fd3d8a3", | ||||||
| 			"revisionTime": "2017-01-07T15:17:58Z" | 			"revisionTime": "2017-01-12T20:47:29Z" | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			"checksumSHA1": "VzutdH69PUqRqhrDVv6F91ebQd4=", | 			"checksumSHA1": "VzutdH69PUqRqhrDVv6F91ebQd4=", | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue