diff --git a/triax/api-types.go b/backend/v2/api-types.go similarity index 94% rename from triax/api-types.go rename to backend/v2/api-types.go index eb64590..51f6688 100644 --- a/triax/api-types.go +++ b/backend/v2/api-types.go @@ -1,4 +1,4 @@ -package triax +package v2 import ( "bytes" @@ -6,7 +6,7 @@ import ( "strings" ) -const loginPath = "login/" +const loginPath = "api/login/" // request for /api/login. type loginRequest struct { @@ -20,7 +20,7 @@ type loginResponse struct { Message string `json:"message"` } -const boardPath = "system/board/" +const boardPath = "api/system/board/" // Board is the response from /api/board/ type Board struct { @@ -44,7 +44,7 @@ type Board struct { System string `json:"system"` } -const ghnStatusPath = "ghn/status/" +const ghnStatusPath = "api/ghn/status/" // response from /api/ghn/status. type ghnStatusResponse []struct { @@ -54,7 +54,7 @@ type ghnStatusResponse []struct { Registered uint `json:"registered"` } -const sysinfoPath = "system/info/" +const sysinfoPath = "api/system/info/" // response from /api/system/info. type sysinfoResponse struct { @@ -68,7 +68,7 @@ type sysinfoResponse struct { Load float64 `json:"load"` } -const syseocPath = "config/system/eoc/" +const syseocPath = "api/config/system/eoc/" // response from /config/system/eoc. type syseocResponse struct { @@ -103,7 +103,7 @@ func (mal MacAddrList) Index(s string) int { return -1 } -const nodeStatusPath = "node/status/" +const nodeStatusPath = "api/node/status/" // response from /api/node/status/. The key is a mangeled form // of the AP's MAC address, and should be ignored. @@ -118,7 +118,7 @@ type nodeStatusResponse map[string]struct { Statistics Statistics `json:"statistics"` Status string `json:"status"` Statusid int `json:"statusid"` - Sysinfo struct { + Sysinfo *struct { Load float64 `json:"load"` Uptime uint `json:"uptime"` } `json:"sysinfo"` diff --git a/backend/v2/backend.go b/backend/v2/backend.go new file mode 100644 index 0000000..3e3963d --- /dev/null +++ b/backend/v2/backend.go @@ -0,0 +1,33 @@ +package v2 + +import ( + "context" + "net/http" + + "github.com/digineo/triax-eoc-exporter/client" + "github.com/digineo/triax-eoc-exporter/types" +) + +func init() { + client.Register(New) +} + +func New(ctx context.Context, c *client.Client) (types.Backend, error) { + b := backend{c} + + req := loginRequest{Username: c.Username, Password: c.Password} + res := loginResponse{} + _, err := c.ApiRequestRaw(ctx, http.MethodPost, loginPath, &req, &res) + + if err != nil { + return nil, err + } + + c.SetCookie(res.Cookie) + + return &b, nil +} + +type backend struct { + *client.Client +} diff --git a/backend/v2/client_board.go b/backend/v2/client_board.go new file mode 100644 index 0000000..0514e6b --- /dev/null +++ b/backend/v2/client_board.go @@ -0,0 +1,10 @@ +package v2 + +import ( + "context" +) + +func (c *backend) Board(ctx context.Context) (board Board, err error) { + err = c.Get(ctx, boardPath, &board) + return +} diff --git a/triax/client_metrics.go b/backend/v2/client_metrics.go similarity index 91% rename from triax/client_metrics.go rename to backend/v2/client_metrics.go index 3b93b66..cd8664d 100644 --- a/triax/client_metrics.go +++ b/backend/v2/client_metrics.go @@ -1,4 +1,4 @@ -package triax +package v2 import ( "context" @@ -8,7 +8,7 @@ import ( "time" ) -func (c *Client) Metrics(ctx context.Context) (*Metrics, error) { +func (c *backend) Metrics(ctx context.Context) (*Metrics, error) { sysinfo := sysinfoResponse{} // uptime, memory syseoc := syseocResponse{} // EoC port names ghn := ghnStatusResponse{} // G.HN port status @@ -61,12 +61,15 @@ func (c *Client) Metrics(ctx context.Context) (*Metrics, error) { ep.MAC = node.Mac ep.Status = node.Statusid ep.StatusText = node.Status - ep.Uptime = node.Sysinfo.Uptime - ep.Load = node.Sysinfo.Load ep.GhnPortNumber = -1 ep.GhnStats = node.GhnStats ep.Statistics = node.Statistics + if sysinfo := node.Sysinfo; sysinfo != nil { + ep.Uptime = &sysinfo.Uptime + ep.Load = &sysinfo.Load + } + if node.RegTimestamp != "" { val, err := strconv.Atoi(node.RegTimestamp) if err != nil { diff --git a/triax/client_test.go b/backend/v2/client_test.go similarity index 98% rename from triax/client_test.go rename to backend/v2/client_test.go index 8f873a8..d9f93ca 100644 --- a/triax/client_test.go +++ b/backend/v2/client_test.go @@ -1,4 +1,4 @@ -package triax +package v2 import ( "encoding/json" diff --git a/triax/client_update.go b/backend/v2/client_update.go similarity index 68% rename from triax/client_update.go rename to backend/v2/client_update.go index 984f2c5..c851617 100644 --- a/triax/client_update.go +++ b/backend/v2/client_update.go @@ -1,4 +1,4 @@ -package triax +package v2 import ( "context" @@ -14,7 +14,7 @@ type UpdateRequest struct { const hexDigit = "0123456789abcdef" // UpdateNode updates the name of the given node -func (c *Client) UpdateNode(ctx context.Context, mac net.HardwareAddr, req UpdateRequest) error { +func (c *backend) UpdateNode(ctx context.Context, mac net.HardwareAddr, req UpdateRequest) error { if len(mac) == 0 { return errors.New("invalid MAC address") } @@ -28,10 +28,10 @@ func (c *Client) UpdateNode(ctx context.Context, mac net.HardwareAddr, req Updat buf = append(buf, hexDigit[b&0xF]) } - err := c.apiRequest(ctx, http.MethodPut, "config/nodes/node_"+string(buf)+"/", req, nil) + err := c.ApiRequest(ctx, http.MethodPut, "config/nodes/node_"+string(buf)+"/", req, nil) if err != nil { return err } - return c.apiRequest(ctx, http.MethodPost, "config/nodes/commit/", nil, nil) + return c.ApiRequest(ctx, http.MethodPost, "config/nodes/commit/", nil, nil) } diff --git a/backend/v2/collect.go b/backend/v2/collect.go new file mode 100644 index 0000000..b4a3119 --- /dev/null +++ b/backend/v2/collect.go @@ -0,0 +1,86 @@ +package v2 + +import ( + "context" + "fmt" + "strconv" + + "github.com/digineo/triax-eoc-exporter/types" + "github.com/prometheus/client_golang/prometheus" +) + +func (b *backend) Collect(ctx context.Context, ch chan<- prometheus.Metric) error { + const C, G = prometheus.CounterValue, prometheus.GaugeValue + + metric := func(desc *prometheus.Desc, typ prometheus.ValueType, v float64, label ...string) { + ch <- prometheus.MustNewConstMetric(desc, typ, v, label...) + } + counterMetric := func(counters *Counters, node, ifname string) { + metric(types.CounterBytes, C, float64(counters.RxByte), node, ifname, "rx") + metric(types.CounterBytes, C, float64(counters.TxByte), node, ifname, "tx") + metric(types.CounterPackets, C, float64(counters.RxPacket), node, ifname, "rx") + metric(types.CounterPackets, C, float64(counters.TxPacket), node, ifname, "tx") + metric(types.CounterErrors, C, float64(counters.RxErr), node, ifname, "rx") + metric(types.CounterErrors, C, float64(counters.TxErr), node, ifname, "tx") + } + + board, err := b.Board(ctx) + if err != nil { + return err + } + metric(types.CtrlInfo, C, 1, board.Serial, board.EthMac, board.Release.Revision) + + m, err := b.Metrics(ctx) + if err != nil { + return err + } + + metric(types.CtrlUptime, C, float64(m.Uptime)) + metric(types.CtrlLoad, G, m.Load) + + metric(types.CtrlMemoryTotal, G, float64(m.Memory.Total)) + metric(types.CtrlMemoryFree, G, float64(m.Memory.Free)) + metric(types.CtrlMemoryBuffered, G, float64(m.Memory.Buffered)) + metric(types.CtrlMemoryShared, G, float64(m.Memory.Shared)) + + if ports := m.GhnPorts; ports != nil { + for _, port := range ports { + number := strconv.Itoa(port.Number) + metric(types.CtrlGhnNumRegistered, G, float64(port.EndpointsRegistered), number) + metric(types.CtrlGhnNumOnline, G, float64(port.EndpointsOnline), number) + } + } + + if nodes := m.Endpoints; nodes != nil { + for _, node := range nodes { + metric(types.NodeStatus, G, float64(node.Status), node.Name) + + if node.Uptime != nil { + metric(types.NodeUptime, C, float64(*node.Uptime), node.Name) + } else { + metric(types.NodeOffline, C, float64(node.OfflineSince.Unix()), node.Name) + } + + // ethernet statistics + for _, stats := range node.Statistics.Ethernet { + if stats.Link { + counterMetric(&stats.Counters, node.Name, fmt.Sprintf("eth%d", stats.Port)) + } + } + + // wireless statistics + for _, stats := range node.Statistics.Wireless { + metric(types.NodeClients, G, float64(stats.Clients), node.Name, strconv.Itoa(stats.Band)) + counterMetric(&stats.Counters, node.Name, fmt.Sprintf("wifi%d", stats.Band)) + } + + // ghn statistics + if stats := node.GhnStats; stats != nil { + metric(types.GhnRxbps, G, float64(stats.Rxbps), node.Name) + metric(types.GhnTxbps, G, float64(stats.Txbps), node.Name) + } + } + } + + return nil +} diff --git a/triax/metrics.go b/backend/v2/metrics.go similarity index 90% rename from triax/metrics.go rename to backend/v2/metrics.go index 2dd3b8f..89cc7f5 100644 --- a/triax/metrics.go +++ b/backend/v2/metrics.go @@ -1,4 +1,4 @@ -package triax +package v2 import "time" @@ -7,8 +7,8 @@ type EndpointMetrics struct { MAC string Status int StatusText string - Uptime uint - Load float64 + Uptime *uint + Load *float64 GhnPortNumber int GhnPortMac string GhnStats *GhnStats diff --git a/triax/quoted_int.go b/backend/v2/quoted_int.go similarity index 97% rename from triax/quoted_int.go rename to backend/v2/quoted_int.go index 5c10754..f2962ca 100644 --- a/triax/quoted_int.go +++ b/backend/v2/quoted_int.go @@ -1,4 +1,4 @@ -package triax +package v2 import ( "bytes" diff --git a/triax/testdata/2.7/nodes.json b/backend/v2/testdata/2.7/nodes.json similarity index 100% rename from triax/testdata/2.7/nodes.json rename to backend/v2/testdata/2.7/nodes.json diff --git a/triax/testdata/2.8/board.json b/backend/v2/testdata/2.8/board.json similarity index 100% rename from triax/testdata/2.8/board.json rename to backend/v2/testdata/2.8/board.json diff --git a/triax/testdata/2.8/info.json b/backend/v2/testdata/2.8/info.json similarity index 100% rename from triax/testdata/2.8/info.json rename to backend/v2/testdata/2.8/info.json diff --git a/triax/testdata/2.8/nodes.json b/backend/v2/testdata/2.8/nodes.json similarity index 100% rename from triax/testdata/2.8/nodes.json rename to backend/v2/testdata/2.8/nodes.json diff --git a/backend/v3/api-types.go b/backend/v3/api-types.go new file mode 100644 index 0000000..05ad199 --- /dev/null +++ b/backend/v3/api-types.go @@ -0,0 +1,323 @@ +package v3 + +import "time" + +const loginPath = "cgi.lua/login" + +// request for /api/login. +type loginRequest struct { + Username string `json:"username"` + Password string `json:"password"` +} + +// response from /api/login. +type loginResponse struct { + Level int `json:"level"` + Status bool `json:"status"` + ErrorCode int `json:"errorCode"` + Message string `json:"message"` +} + +type Node struct { + Mac string `json:"mac"` + RxErrorPercent float64 `json:"rxErrorPercent"` + Modem string `json:"modem"` + TxRate float64 `json:"txRate"` + RxRate float64 `json:"rxRate"` + DeviceID int `json:"deviceId"` + RxAbortPercent float64 `json:"rxAbortPercent"` + Power struct { + Min float64 `json:"min"` + Agc int `json:"agc"` + Avg float64 `json:"avg"` + Max float64 `json:"max"` + } `json:"power"` + RxFrames int `json:"rxFrames"` + Snr struct { + Min float64 `json:"min"` + Avg float64 `json:"avg"` + Max float64 `json:"max"` + } `json:"snr"` + WireLength int `json:"wireLength"` + RxLPDUs int `json:"rxLPDUs"` +} + +const matricsPath = "cgi.lua/status?type=system,ghn,ethernet,remote" + +type MetricsResponse struct { + System System `json:"system"` + Ghn struct { + Timestamp int `json:"timestamp"` + Nodes map[string]Node `json:"nodes"` + Modems map[string]Modem `json:"modems"` + } `json:"ghn"` + Ethernet struct { + Ts int `json:"ts"` + Ports map[string]EthernetPort `json:"ports"` + } `json:"ethernet"` + Remote map[string]Remote `json:"remote"` +} + +type Counters struct { + RxBcast int `json:"rx_bcast"` + RxByte int `json:"rx_byte"` + RxDrop int `json:"rx_drop"` + RxErr int `json:"rx_err"` + RxMcast int `json:"rx_mcast"` + RxPacket int `json:"rx_packet"` + RxUcast int `json:"rx_ucast"` + TxBcast int `json:"tx_bcast"` + TxByte int `json:"tx_byte"` + TxDrop int `json:"tx_drop"` + TxErr int `json:"tx_err"` + TxMcast int `json:"tx_mcast"` + TxPacket int `json:"tx_packet"` + TxUcast int `json:"tx_ucast"` +} + +type EthernetPort struct { + Access string `json:"access"` + Autoneg bool `json:"autoneg"` + Index int `json:"index"` + Link bool `json:"link"` + RxBroadcast int `json:"rxBroadcast"` + RxBytes int `json:"rxBytes"` + RxDrops int `json:"rxDrops"` + RxErrors int `json:"rxErrors"` + RxMulticast int `json:"rxMulticast"` + RxPackets int `json:"rxPackets"` + RxRate int `json:"rxRate"` + RxUnicast int `json:"rxUnicast"` + Switch string `json:"switch"` + TxBroadcast int `json:"txBroadcast"` + TxBytes int `json:"txBytes"` + TxDrops int `json:"txDrops"` + TxErrors int `json:"txErrors"` + TxMulticast int `json:"txMulticast"` + TxPackets int `json:"txPackets"` + TxRate int `json:"txRate"` + TxUnicast int `json:"txUnicast"` +} + +type Modem struct { + TxUnicast int `json:"txUnicast"` + CPUUsage int `json:"cpuUsage"` + RxUnicast int `json:"rxUnicast"` + MemUsage int `json:"memUsage"` + CombiningGroup string `json:"combiningGroup"` + Speed float64 `json:"speed"` + OperationTime int `json:"operationTime"` + RxErrors int `json:"rxErrors"` + Noise struct { + Min float64 `json:"min"` + Agc int `json:"agc"` + Avg float64 `json:"avg"` + Max float64 `json:"max"` + } `json:"noise"` + OperationMode string `json:"operationMode"` + LinkLost int `json:"linkLost"` + RxMulticast int `json:"rxMulticast"` + Name string `json:"name"` + EndpointRegistered int `json:"endpointRegistered"` + TxMulticast int `json:"txMulticast"` + Index int `json:"index"` + RetxPercent float64 `json:"retx_percent"` + Port string `json:"port"` + Switch string `json:"switch"` + TxRate int `json:"txRate"` + DomainID string `json:"domainId"` + RxRate int `json:"rxRate"` + RxBroadcast int `json:"rxBroadcast"` + DomainName string `json:"domainName"` + TxPackets int `json:"txPackets"` + RxBytes int64 `json:"rxBytes"` + RxPackets int `json:"rxPackets"` + ResetCause string `json:"resetCause"` + FecPercent float32 `json:"fec_percent"` + TxBytes int64 `json:"txBytes"` + Firmware string `json:"firmware"` + RxBlocksError int `json:"rxBlocksError"` + Ipv4 string `json:"ipv4"` + OperationSlot int `json:"operationSlot"` + Master string `json:"master"` + EndpointCount int `json:"endpointCount"` + Mac string `json:"mac"` + RxBlocks int `json:"rxBlocks"` + TxBroadcast int `json:"txBroadcast"` + TxBlocks int `json:"txBlocks"` + TxDrops int `json:"txDrops"` + DomainMode string `json:"domainMode"` + TxBlocksResent int `json:"txBlocksResent"` + TxErrors int `json:"txErrors"` + Uptime int `json:"uptime"` + ResetMarker int `json:"resetMarker"` + RxDrops int `json:"rxDrops"` +} + +type System struct { + Description string `json:"description"` + Board string `json:"board"` + ImagesValid bool `json:"images_valid"` + Uptime int `json:"uptime"` + Version string `json:"version"` + Contact string `json:"contact"` + Memory struct { + Used int `json:"used"` + Usage float64 `json:"usage"` + Total int `json:"total"` + Free int `json:"free"` + } `json:"memory"` + Name string `json:"name"` + Clock string `json:"clock"` + Location string `json:"location"` + Timestamp int `json:"timestamp"` + Processes int `json:"processes"` + CPU struct { + Usage5Min float64 `json:"usage5min"` + Usage float64 `json:"usage"` + Usage15Min float64 `json:"usage15min"` + Usage1Min float64 `json:"usage1min"` + } `json:"cpu"` +} + +type Remote struct { + EthernetClients []any `json:"ethernet_clients"` + WirelessClients []struct { + Mac string `json:"mac"` + Per int `json:"per"` + Ssid string `json:"ssid"` + Ipaddr string `json:"ipaddr"` + Protocol string `json:"protocol"` + Radio string `json:"radio"` + Bitrate struct { + Tx int `json:"tx"` + Rx int `json:"rx"` + } `json:"bitrate"` + Band int `json:"band"` + Uptime int `json:"uptime"` + Hostname string `json:"hostname"` + Packets struct { + Tx int `json:"tx"` + RxError int `json:"rx_error"` + Rx int `json:"rx"` + TxError int `json:"tx_error"` + } `json:"packets"` + Throughput struct { + Tx int `json:"tx"` + Rx int `json:"rx"` + } `json:"throughput"` + Network string `json:"network"` + Signal int `json:"signal"` + } `json:"wireless_clients"` + Group string `json:"group"` + Loadtime int `json:"loadtime"` + Wireless []struct { + Band int `json:"band"` + Clients int `json:"clients"` + Label string `json:"label"` + Mac string `json:"mac"` + Bitrate int `json:"bitrate"` + Txpower int `json:"txpower"` + ChannelWidth string `json:"channel_width"` + Channel int `json:"channel"` + Counters Counters `json:"counters"` + Radio string `json:"radio"` + Enabled bool `json:"enabled"` + Frequency int `json:"frequency"` + } `json:"wireless"` + Status string `json:"status"` + ConfigHash string `json:"config_hash"` + Mac string `json:"mac"` + Ghn []struct { + RetxPercent float32 `json:"retx_percent"` + Clients int `json:"clients"` + Label string `json:"label"` + Snr struct { + Min float64 `json:"min"` + Avg float64 `json:"avg"` + Max float64 `json:"max"` + } `json:"snr"` + Port int `json:"port"` + Status bool `json:"status"` + FecPercent float32 `json:"fec_percent"` + Ipv4 string `json:"ipv4"` + Enabled bool `json:"enabled"` + Noise struct { + Min float64 `json:"min"` + Agc int `json:"agc"` + Avg float64 `json:"avg"` + Max float64 `json:"max"` + } `json:"noise"` + Power struct { + Min float64 `json:"min"` + Agc int `json:"agc"` + Avg float64 `json:"avg"` + Max float64 `json:"max"` + } `json:"power"` + Bitrate struct { + Tx int `json:"tx"` + Rx int `json:"rx"` + } `json:"bitrate"` + ResetCause string `json:"reset_cause"` + Master string `json:"master"` + Uptime int `json:"uptime"` + MemUsage int `json:"mem_usage"` + Counters Counters `json:"counters"` + Mac string `json:"mac"` + Speed float64 `json:"speed"` + CPUUsage int `json:"cpu_usage"` + } `json:"ghn"` + System struct { + SerialNo string `json:"serial_no"` + Description string `json:"description"` + HostMac string `json:"host_mac"` + MemStat struct { + Total int `json:"total"` + Free int `json:"free"` + } `json:"mem_stat"` + Hostname string `json:"hostname"` + MacAddress string `json:"mac_address"` + SwVersion string `json:"sw_version"` + Datetime time.Time `json:"datetime"` + Model string `json:"model"` + Uptime *uint `json:"uptime"` + MemUsage float64 `json:"mem_usage"` + Name string `json:"name"` + Timestamp int `json:"timestamp"` + GhnVersion string `json:"ghn_version"` + CPUUsage float64 `json:"cpu_usage"` + } `json:"system"` + Message string `json:"message"` + PortName string `json:"port_name"` + State int `json:"state"` + Ethernet []struct { + Enabled bool `json:"enabled"` + Port int `json:"port"` + Duplex bool `json:"duplex"` + Label string `json:"label"` + Counters Counters `json:"counters"` + Autoneg bool `json:"autoneg"` + Link bool `json:"link"` + Mac string `json:"mac"` + } `json:"ethernet"` + Seen int `json:"seen"` + Timestamp int `json:"timestamp"` + Serial string `json:"serial"` + Network struct { + Areas []struct { + Network string `json:"network"` + Ipv4 struct { + Prefix int `json:"prefix"` + Address string `json:"address"` + } `json:"ipv4,omitempty"` + } `json:"areas"` + Ipv6 struct { + Prefix int `json:"prefix"` + Address string `json:"address"` + } `json:"ipv6"` + Ipv4 struct { + Prefix int `json:"prefix"` + Address string `json:"address"` + } `json:"ipv4"` + } `json:"network"` +} diff --git a/backend/v3/backend.go b/backend/v3/backend.go new file mode 100644 index 0000000..465a1b2 --- /dev/null +++ b/backend/v3/backend.go @@ -0,0 +1,43 @@ +package v3 + +import ( + "context" + "fmt" + "net/http" + "strings" + + "github.com/digineo/triax-eoc-exporter/client" + "github.com/digineo/triax-eoc-exporter/types" +) + +func init() { + client.Register(New) +} + +type backend struct { + *client.Client +} + +func New(ctx context.Context, c *client.Client) (types.Backend, error) { + b := backend{c} + + req := loginRequest{Username: c.Username, Password: c.Password} + res := loginResponse{} + httpResponse, err := c.ApiRequestRaw(ctx, http.MethodPost, loginPath, &req, &res) + + if err != nil { + return nil, err + } + + if !res.Status { + return nil, fmt.Errorf("login failed: %s", res.Message) + } + + cookie := httpResponse.Header.Get("Set-Cookie") + if i := strings.Index(cookie, ";"); i > 0 { + cookie = cookie[:i] + } + + c.SetCookie(cookie) + return &b, nil +} diff --git a/backend/v3/collect.go b/backend/v3/collect.go new file mode 100644 index 0000000..d9ebf62 --- /dev/null +++ b/backend/v3/collect.go @@ -0,0 +1,72 @@ +package v3 + +import ( + "context" + "fmt" + "strconv" + + "github.com/digineo/triax-eoc-exporter/types" + "github.com/prometheus/client_golang/prometheus" +) + +func (b *backend) Collect(ctx context.Context, ch chan<- prometheus.Metric) error { + const C, G = prometheus.CounterValue, prometheus.GaugeValue + response := MetricsResponse{} + + if err := b.Get(ctx, matricsPath, &response); err != nil { + return err + } + + metric := func(desc *prometheus.Desc, typ prometheus.ValueType, v float64, label ...string) { + ch <- prometheus.MustNewConstMetric(desc, typ, v, label...) + } + counterMetric := func(counters *Counters, node, ifname string) { + metric(types.CounterBytes, C, float64(counters.RxByte), node, ifname, "rx") + metric(types.CounterBytes, C, float64(counters.TxByte), node, ifname, "tx") + metric(types.CounterPackets, C, float64(counters.RxPacket), node, ifname, "rx") + metric(types.CounterPackets, C, float64(counters.TxPacket), node, ifname, "tx") + metric(types.CounterErrors, C, float64(counters.RxErr), node, ifname, "rx") + metric(types.CounterErrors, C, float64(counters.TxErr), node, ifname, "tx") + } + + metric(types.CtrlUptime, C, float64(response.System.Uptime)) + metric(types.CtrlMemoryTotal, G, float64(response.System.Memory.Total)) + metric(types.CtrlMemoryFree, G, float64(response.System.Memory.Total-response.System.Memory.Used)) + + for _, modem := range response.Ghn.Modems { + number := strconv.Itoa(modem.Index + 1) + metric(types.CtrlGhnNumRegistered, G, float64(modem.EndpointRegistered), number) + metric(types.CtrlGhnNumOnline, G, float64(modem.EndpointCount), number) + } + + for _, node := range response.Remote { + name := node.System.Name + + metric(types.NodeStatus, G, float64(node.State), name) + + if uptime := node.System.Uptime; uptime != nil { + metric(types.NodeUptime, G, float64(*uptime), name) + } + + // ethernet statistics + for _, stats := range node.Ethernet { + if stats.Link { + counterMetric(&stats.Counters, name, fmt.Sprintf("eth%d", stats.Port)) + } + } + + // wireless statistics + for _, stats := range node.Wireless { + metric(types.NodeClients, G, float64(stats.Clients), name, strconv.Itoa(stats.Band)) + counterMetric(&stats.Counters, name, fmt.Sprintf("wifi%d", stats.Band)) + } + + // ghn statistics + if ghn := node.Ghn; len(ghn) > 0 { + metric(types.GhnRxbps, G, float64(ghn[0].Bitrate.Rx), name) + metric(types.GhnTxbps, G, float64(ghn[0].Bitrate.Tx), name) + } + } + + return nil +} diff --git a/client/backends.go b/client/backends.go new file mode 100644 index 0000000..dd13060 --- /dev/null +++ b/client/backends.go @@ -0,0 +1,28 @@ +package client + +import ( + "context" + "log/slog" + + "github.com/digineo/triax-eoc-exporter/types" +) + +type builderFunction func(context.Context, *Client) (types.Backend, error) + +var backends []builderFunction + +func Register(f builderFunction) { + backends = append(backends, f) +} + +func Try(ctx context.Context, client *Client) types.Backend { + for i := range backends { + backend, err := backends[i](ctx, client) + if backend != nil { + return backend + } + slog.Info("backend not working", "error", err) + } + + return nil +} diff --git a/client/client.go b/client/client.go new file mode 100644 index 0000000..6a28325 --- /dev/null +++ b/client/client.go @@ -0,0 +1,207 @@ +package client + +import ( + "bytes" + "context" + "crypto/tls" + "encoding/json" + "errors" + "fmt" + "io" + "log/slog" + "net/http" + "net/http/cookiejar" + "net/url" + "strings" + "time" + + "github.com/digineo/triax-eoc-exporter/types" + "github.com/prometheus/client_golang/prometheus" +) + +type Client struct { + endpoint *url.URL + Username string + Password string + backend types.Backend +} + +var HTTPClient = http.Client{ + Timeout: time.Second * 30, + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }, +} + +func init() { + HTTPClient.Jar, _ = cookiejar.New(nil) // error is always nil + HTTPClient.Transport = &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + } +} + +// NewClient creates a new Client instance. The URL must embed login +// credentials. +func NewClient(endpoint *url.URL) (*Client, error) { + userinfo := endpoint.User + if userinfo == nil { + return nil, types.ErrMissingCredentials + } + + pwd, _ := userinfo.Password() + client := &Client{ + endpoint: endpoint, + Username: userinfo.Username(), + Password: pwd, + } + return client, nil +} + +func (c *Client) Get(ctx context.Context, path string, res interface{}) error { + return c.ApiRequest(ctx, http.MethodGet, path, nil, res) +} + +func (c *Client) SetCookie(nameAndValue string) { + i := strings.Index(nameAndValue, "=") + if i <= 0 { + slog.Error("cannot split cookie", "value", nameAndValue) + return + } + + slog.Info("Set cookie", + "host", c.endpoint.Host, + "name", nameAndValue[:i], + "value", nameAndValue[i+1:], + ) + + // Set cookie from response + HTTPClient.Jar.SetCookies(c.endpoint, []*http.Cookie{{ + Name: nameAndValue[:i], + Value: nameAndValue[i+1:], + MaxAge: 0, + }}) +} + +func (c *Client) withBackend(ctx context.Context, f func(types.Backend) error) error { + if c.backend == nil { + err := c.setupBackend(ctx) + if err != nil { + return err + } + } + + return f(c.backend) +} + +func (c *Client) setupBackend(ctx context.Context) error { + c.backend = Try(ctx, c) + if c.backend == nil { + return errors.New("no usable backend found") + } + + return nil +} + +func (c *Client) Collect(ctx context.Context, ch chan<- prometheus.Metric) error { + return c.withBackend(ctx, func(backend types.Backend) error { + return backend.Collect(ctx, ch) + }) +} + +// calls apiRequestRaw and does a login on unauthorized status +func (c *Client) ApiRequest(ctx context.Context, method, path string, request, response interface{}) error { + return c.withBackend(ctx, func(backend types.Backend) error { + + retried := false + retry: + errStatus := &types.ErrUnexpectedStatus{} + _, err := c.ApiRequestRaw(ctx, method, path, request, response) + + if errors.As(err, &errStatus) && errStatus.Status == http.StatusUnauthorized && !retried { + if c.setupBackend(ctx) == nil { + retried = true + goto retry + } + } + + return err + }) +} + +// apiRequestRaw sends an API request to the controller. The path is constructed +// from c.base + "/api/" + path. The request parameter, if not nil, will be +// JSON encoded, and the JSON response is decoded into the response parameter. +// +// req, res := requestType{...}, responseType{...} +// err := c.ApiRequestRaw(ctx, "POST", "node/status", &req, &res) +func (c *Client) ApiRequestRaw(ctx context.Context, method, path string, request, response interface{}) (*http.Response, error) { + url := fmt.Sprintf("%s://%s/%s", c.endpoint.Scheme, c.endpoint.Host, strings.TrimPrefix(path, "/")) + + slog.Info("HTTP Request", "method", method, "url", url) + + var body io.Reader + if request != nil { + var buf bytes.Buffer + if err := json.NewEncoder(&buf).Encode(request); err != nil { + return nil, fmt.Errorf("encoding body failed: %w", err) + } + body = &buf + } + + req, err := http.NewRequestWithContext(ctx, method, url, body) + if err != nil { + return nil, fmt.Errorf("cannot construct request: %w", err) + } + + req.Header.Set("Accept", "application/json") + if request != nil { + req.Header.Set("Content-Type", "application/json") + } + + res, err := HTTPClient.Do(req) + if err != nil { + return nil, fmt.Errorf("request failed: %w", err) + } + defer res.Body.Close() + + if res.StatusCode != http.StatusOK { + data, _ := io.ReadAll(res.Body) + + return res, &types.ErrUnexpectedStatus{ + Method: method, + URL: url, + Location: res.Header.Get("Location"), + Status: res.StatusCode, + Body: data, + } + } + + jsonData, err := io.ReadAll(res.Body) + if err != nil { + return res, err + } + + /* + contentType := res.Header.Get("Content-Type") + + if contentType != "application/json" { + slog.Error("unexpected content type", "contentType", contentType, "body", string(jsonData)) + return fmt.Errorf("unexpected content-type: %s", contentType) + } + */ + + if response != nil { + err = json.Unmarshal(jsonData, &response) + + if err != nil { + slog.Error("response received", "json", string(jsonData)) + return res, fmt.Errorf("decoding response failed: %w", err) + } + slog.Debug("response received", "json", string(jsonData)) + + } + + return res, nil +} diff --git a/main.go b/cmd/main.go similarity index 100% rename from main.go rename to cmd/main.go diff --git a/exporter/collector.go b/exporter/collector.go index bab2d29..3aec4f1 100644 --- a/exporter/collector.go +++ b/exporter/collector.go @@ -2,157 +2,50 @@ package exporter import ( "context" - "fmt" "log/slog" - "strconv" - "github.com/digineo/triax-eoc-exporter/triax" + "github.com/digineo/triax-eoc-exporter/client" + "github.com/digineo/triax-eoc-exporter/types" "github.com/prometheus/client_golang/prometheus" ) type triaxCollector struct { - client *triax.Client + client *client.Client ctx context.Context } var _ prometheus.Collector = (*triaxCollector)(nil) -var ( - ctrlUp = ctrlDesc("up", "indicator whether controller is reachable") - ctrlUptime = ctrlDesc("uptime", "uptime of controller in seconds") - ctrlInfo = ctrlDesc("info", "controller infos about the installed software", "serial", "eth_mac", "version") - ctrlLoad = ctrlDesc("load", "current system load of controller") - ctrlMemoryTotal = ctrlDesc("mem_total", "total system memory of controller in bytes") - ctrlMemoryFree = ctrlDesc("mem_free", "free system memory of controller in bytes") - ctrlMemoryBuffered = ctrlDesc("mem_buffered", "buffered system memory of controller in bytes") - ctrlMemoryShared = ctrlDesc("mem_shared", "shared system memory of controller in bytes") - ctrlGhnNumOnline = ctrlDesc("ghn_endpoints_online", "number of endponts online for a G.HN port", "port") - ctrlGhnNumRegistered = ctrlDesc("ghn_endpoints_registered", "number of endponts registered for a G.HN port", "port") - - nodeLabel = []string{"name"} - nodeStatus = nodeDesc("status", "current endpoint status") - nodeUptime = nodeDesc("uptime", "uptime of endpoint in seconds") - nodeOffline = nodeDesc("offline_since", "offline since unix timestamp") - nodeLoad = nodeDesc("load", "current system load of endpoint") - nodeGhnPort = nodeDesc("ghn_port", "G.HN port number", "ghn_mac") - nodeClients = nodeDesc("clients", "number of connected WLAN clients", "band") - - counterLabel = []string{"interface", "direction"} - counterBytes = nodeDesc("interface_bytes", "total bytes transmitted or received", counterLabel...) - counterPackets = nodeDesc("interface_packets", "total packets transmitted or received", counterLabel...) - counterErrors = nodeDesc("interface_errors", "total number of errors", counterLabel...) - - ghnRxbps = nodeDesc("ghn_rxbps", "negotiated RX rate in bps") - ghnTxbps = nodeDesc("ghn_txbps", "negotiated TX rate in bps") -) - func (t *triaxCollector) Describe(ch chan<- *prometheus.Desc) { - ch <- ctrlUp - ch <- ctrlUptime - ch <- ctrlInfo - ch <- ctrlLoad - ch <- ctrlMemoryTotal - ch <- ctrlMemoryFree - ch <- ctrlMemoryBuffered - ch <- ctrlMemoryShared - ch <- ctrlGhnNumOnline - ch <- ctrlGhnNumRegistered - - ch <- nodeStatus - ch <- nodeUptime - ch <- nodeLoad - ch <- nodeGhnPort - ch <- nodeClients + ch <- types.CtrlUp + ch <- types.CtrlUptime + ch <- types.CtrlInfo + ch <- types.CtrlLoad + ch <- types.CtrlMemoryTotal + ch <- types.CtrlMemoryFree + ch <- types.CtrlMemoryBuffered + ch <- types.CtrlMemoryShared + ch <- types.CtrlGhnNumOnline + ch <- types.CtrlGhnNumRegistered + + ch <- types.NodeStatus + ch <- types.NodeUptime + ch <- types.NodeLoad + ch <- types.NodeGhnPort + ch <- types.NodeClients } func (t *triaxCollector) Collect(ch chan<- prometheus.Metric) { - err := t.collect(ch) + err := t.client.Collect(t.ctx, ch) // Write up - ch <- prometheus.MustNewConstMetric(ctrlUp, prometheus.GaugeValue, boolToFloat(err == nil)) + ch <- prometheus.MustNewConstMetric(types.CtrlUp, prometheus.GaugeValue, boolToFloat(err == nil)) if err != nil { slog.Error("fetching failed", "error", err) } } -func (t *triaxCollector) collect(ch chan<- prometheus.Metric) error { - const C, G = prometheus.CounterValue, prometheus.GaugeValue - - metric := func(desc *prometheus.Desc, typ prometheus.ValueType, v float64, label ...string) { - ch <- prometheus.MustNewConstMetric(desc, typ, v, label...) - } - - counterMetric := func(counters *triax.Counters, node, ifname string) { - metric(counterBytes, C, float64(counters.RxByte), node, ifname, "rx") - metric(counterBytes, C, float64(counters.TxByte), node, ifname, "tx") - metric(counterPackets, C, float64(counters.RxPacket), node, ifname, "rx") - metric(counterPackets, C, float64(counters.TxPacket), node, ifname, "tx") - metric(counterErrors, C, float64(counters.RxErr), node, ifname, "rx") - metric(counterErrors, C, float64(counters.TxErr), node, ifname, "tx") - } - - board, err := t.client.Board(t.ctx) - if err != nil { - return err - } - metric(ctrlInfo, C, 1, board.Serial, board.EthMac, board.Release.Revision) - - m, err := t.client.Metrics(t.ctx) - if err != nil { - return err - } - - metric(ctrlUptime, C, float64(m.Uptime)) - metric(ctrlLoad, G, m.Load) - - metric(ctrlMemoryTotal, G, float64(m.Memory.Total)) - metric(ctrlMemoryFree, G, float64(m.Memory.Free)) - metric(ctrlMemoryBuffered, G, float64(m.Memory.Buffered)) - metric(ctrlMemoryShared, G, float64(m.Memory.Shared)) - - if ports := m.GhnPorts; ports != nil { - for _, port := range ports { - number := strconv.Itoa(port.Number) - metric(ctrlGhnNumRegistered, G, float64(port.EndpointsRegistered), number) - metric(ctrlGhnNumOnline, G, float64(port.EndpointsOnline), number) - } - } - - if nodes := m.Endpoints; nodes != nil { - for _, node := range nodes { - metric(nodeStatus, G, float64(node.Status), node.Name) - - if node.Uptime > 0 { - metric(nodeUptime, C, float64(node.Uptime), node.Name) - } else { - metric(nodeOffline, C, float64(node.OfflineSince.Unix()), node.Name) - } - - // ethernet statistics - for _, stats := range node.Statistics.Ethernet { - if stats.Link { - counterMetric(&stats.Counters, node.Name, fmt.Sprintf("eth%d", stats.Port)) - } - } - - // wireless statistics - for _, stats := range node.Statistics.Wireless { - metric(nodeClients, G, float64(stats.Clients), node.Name, strconv.Itoa(stats.Band)) - counterMetric(&stats.Counters, node.Name, fmt.Sprintf("wifi%d", stats.Band)) - } - - // ghn statistics - if stats := node.GhnStats; stats != nil { - metric(ghnRxbps, G, float64(stats.Rxbps), node.Name) - metric(ghnTxbps, G, float64(stats.Txbps), node.Name) - } - } - } - - return nil -} - func boolToFloat(val bool) float64 { if val { return 1 @@ -160,13 +53,3 @@ func boolToFloat(val bool) float64 { return 0 } - -func ctrlDesc(name, help string, extraLabel ...string) *prometheus.Desc { - fqdn := prometheus.BuildFQName("triax", "eoc_controller", name) - return prometheus.NewDesc(fqdn, help, extraLabel, nil) -} - -func nodeDesc(name, help string, extraLabel ...string) *prometheus.Desc { - fqdn := prometheus.BuildFQName("triax", "eoc_endpoint", name) - return prometheus.NewDesc(fqdn, help, append(nodeLabel, extraLabel...), nil) -} diff --git a/exporter/config.go b/exporter/config.go index 3bdc082..95d7ccf 100644 --- a/exporter/config.go +++ b/exporter/config.go @@ -7,7 +7,7 @@ import ( "strconv" "github.com/BurntSushi/toml" - "github.com/digineo/triax-eoc-exporter/triax" + "github.com/digineo/triax-eoc-exporter/client" ) const defaultPort = 443 @@ -36,10 +36,10 @@ func LoadConfig(file string) (*Config, error) { } // getClient builds a client -func (cfg *Config) getClient(target string) (*triax.Client, error) { +func (cfg *Config) getClient(target string) (*client.Client, error) { for _, ctrl := range cfg.Controllers { if target == ctrl.Alias || target == ctrl.Host { - return triax.NewClient(ctrl.url()) + return client.NewClient(ctrl.url()) } } diff --git a/exporter/exporter.go b/exporter/exporter.go index ffa0427..383a9d7 100644 --- a/exporter/exporter.go +++ b/exporter/exporter.go @@ -3,17 +3,18 @@ package exporter import ( "bytes" "encoding/json" - "fmt" "io" "log/slog" - "net" "net/http" "text/template" - "github.com/digineo/triax-eoc-exporter/triax" + "github.com/digineo/triax-eoc-exporter/client" "github.com/julienschmidt/httprouter" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" + + _ "github.com/digineo/triax-eoc-exporter/backend/v2" + _ "github.com/digineo/triax-eoc-exporter/backend/v3" ) func (cfg *Config) Start(listenAddress, version string) { @@ -28,13 +29,13 @@ func (cfg *Config) Start(listenAddress, version string) { router.GET("/controllers", cfg.listControllersHandler) router.GET("/controllers/:target/metrics", cfg.targetMiddleware(cfg.metricsHandler)) router.GET("/controllers/:target/api/*path", cfg.targetMiddleware(cfg.apiHandler)) - router.PUT("/controllers/:target/nodes/:mac", cfg.targetMiddleware(cfg.updateNodeHandler)) + //router.PUT("/controllers/:target/nodes/:mac", cfg.targetMiddleware(cfg.updateNodeHandler)) slog.Info("Starting exporter", "listenAddress", listenAddress) slog.Info("Server stopped", "reason", http.ListenAndServe(listenAddress, router)) } -type targetHandler func(*triax.Client, http.ResponseWriter, *http.Request, httprouter.Params) +type targetHandler func(*client.Client, http.ResponseWriter, *http.Request, httprouter.Params) func (cfg *Config) targetMiddleware(next targetHandler) httprouter.Handle { return httprouter.Handle(func(w http.ResponseWriter, r *http.Request, params httprouter.Params) { @@ -66,7 +67,7 @@ func (cfg *Config) listControllersHandler(w http.ResponseWriter, r *http.Request json.NewEncoder(w).Encode(&result) } -func (cfg *Config) metricsHandler(client *triax.Client, w http.ResponseWriter, r *http.Request, _ httprouter.Params) { +func (cfg *Config) metricsHandler(client *client.Client, w http.ResponseWriter, r *http.Request, _ httprouter.Params) { reg := prometheus.NewRegistry() reg.MustRegister(&triaxCollector{ client: client, @@ -76,8 +77,9 @@ func (cfg *Config) metricsHandler(client *triax.Client, w http.ResponseWriter, r h.ServeHTTP(w, r) } +/* // handler for updating nodes -func (cfg *Config) updateNodeHandler(client *triax.Client, w http.ResponseWriter, r *http.Request, params httprouter.Params) { +func (cfg *Config) updateNodeHandler(client *client.Client, w http.ResponseWriter, r *http.Request, params httprouter.Params) { defer r.Body.Close() // parse MAC address parameter @@ -104,9 +106,10 @@ func (cfg *Config) updateNodeHandler(client *triax.Client, w http.ResponseWriter w.WriteHeader(http.StatusNoContent) } +*/ // proxy handler for API GET requests. -func (cfg *Config) apiHandler(client *triax.Client, w http.ResponseWriter, r *http.Request, params httprouter.Params) { +func (cfg *Config) apiHandler(client *client.Client, w http.ResponseWriter, r *http.Request, params httprouter.Params) { defer r.Body.Close() msg := json.RawMessage{} diff --git a/triax/client.go b/triax/client.go deleted file mode 100644 index 164dbaa..0000000 --- a/triax/client.go +++ /dev/null @@ -1,173 +0,0 @@ -package triax - -import ( - "bytes" - "context" - "crypto/tls" - "encoding/json" - "errors" - "fmt" - "io" - "log/slog" - "net/http" - "net/http/cookiejar" - "net/url" - "strings" - "time" -) - -type Client struct { - endpoint *url.URL - username string - password string -} - -var HTTPClient = http.Client{ - Timeout: time.Second * 30, -} - -func init() { - HTTPClient.Jar, _ = cookiejar.New(nil) // error is always nil - HTTPClient.Transport = &http.Transport{ - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: true, - }, - } -} - -const sessionCookieName = "sessionId" - -// NewClient creates a new Client instance. The URL must embed login -// credentials. -func NewClient(endpoint *url.URL) (*Client, error) { - userinfo := endpoint.User - if userinfo == nil { - return nil, ErrMissingCredentials - } - - client := &Client{ - endpoint: endpoint, - username: userinfo.Username(), - } - - client.password, _ = userinfo.Password() - - return client, nil -} - -func (c *Client) Get(ctx context.Context, path string, res interface{}) error { - return c.apiRequest(ctx, http.MethodGet, path, nil, res) -} - -func (c *Client) login(ctx context.Context) error { - // Remove cookie - HTTPClient.Jar.SetCookies(c.endpoint, []*http.Cookie{{ - Name: sessionCookieName, - MaxAge: -1, - }}) - - req := loginRequest{Username: c.username, Password: c.password} - res := loginResponse{} - err := c.apiRequestRaw(ctx, http.MethodPost, loginPath, &req, &res) - if err != nil { - return err - } - - // Check the cookie - if !strings.HasPrefix(res.Cookie, sessionCookieName+"=") { - return &genericError{fmt.Sprintf("unexpected cookie: %s", res.Cookie)} - } - - // Set cookie from response - HTTPClient.Jar.SetCookies(c.endpoint, []*http.Cookie{{ - Name: sessionCookieName, - Value: strings.TrimPrefix(res.Cookie, sessionCookieName+"="), - Raw: res.Cookie, - MaxAge: 0, - }}) - - return nil -} - -// calls apiRequestRaw and does a login on unauthorized status -func (c *Client) apiRequest(ctx context.Context, method, path string, request, response interface{}) error { - retried := false - -retry: - errStatus := &ErrUnexpectedStatus{} - err := c.apiRequestRaw(ctx, method, path, request, response) - - if errors.As(err, &errStatus) && errStatus.Status == http.StatusUnauthorized && !retried { - err = c.login(ctx) - if err == nil { - retried = true - goto retry - } - } - - return err -} - -// apiRequestRaw sends an API request to the controller. The path is constructed -// from c.base + "/api/" + path. The request parameter, if not nil, will be -// JSON encoded, and the JSON response is decoded into the response parameter. -// -// req, res := requestType{...}, responseType{...} -// err := c.apiRequestRaw(ctx, "POST", "node/status", &req, &res) -func (c *Client) apiRequestRaw(ctx context.Context, method, path string, request, response interface{}) error { - url := fmt.Sprintf("%s://%s/api/%s", c.endpoint.Scheme, c.endpoint.Host, strings.TrimPrefix(path, "/")) - - slog.Info("HTTP Request", "method", method, "url", url) - - var body io.Reader - if request != nil { - var buf bytes.Buffer - if err := json.NewEncoder(&buf).Encode(request); err != nil { - return fmt.Errorf("encoding body failed: %w", err) - } - body = &buf - } - - req, err := http.NewRequestWithContext(ctx, method, url, body) - if err != nil { - return fmt.Errorf("cannot construct request: %w", err) - } - - req.Header.Set("Accept", "application/json") - if request != nil { - req.Header.Set("Content-Type", "application/json") - } - - res, err := HTTPClient.Do(req) - if err != nil { - return fmt.Errorf("request failed: %w", err) - } - defer res.Body.Close() - - if res.StatusCode != http.StatusOK { - data, _ := io.ReadAll(res.Body) - - return &ErrUnexpectedStatus{ - Method: method, - URL: url, - Status: res.StatusCode, - Body: data, - } - } - - jsonData, err := io.ReadAll(res.Body) - if err != nil { - return err - } - - if response != nil { - err = json.Unmarshal(jsonData, &response) - if err != nil { - return fmt.Errorf("decoding response failed: %w", err) - } - - slog.Debug("response received", "json", string(jsonData)) - } - - return nil -} diff --git a/triax/client_board.go b/triax/client_board.go deleted file mode 100644 index 65d5773..0000000 --- a/triax/client_board.go +++ /dev/null @@ -1,10 +0,0 @@ -package triax - -import ( - "context" -) - -func (c *Client) Board(ctx context.Context) (board Board, err error) { - err = c.Get(ctx, boardPath, &board) - return -} diff --git a/types/backend.go b/types/backend.go new file mode 100644 index 0000000..73a3690 --- /dev/null +++ b/types/backend.go @@ -0,0 +1,11 @@ +package types + +import ( + "context" + + "github.com/prometheus/client_golang/prometheus" +) + +type Backend interface { + Collect(context.Context, chan<- prometheus.Metric) error +} diff --git a/types/collect.go b/types/collect.go new file mode 100644 index 0000000..2f4799f --- /dev/null +++ b/types/collect.go @@ -0,0 +1,42 @@ +package types + +import "github.com/prometheus/client_golang/prometheus" + +var ( + CtrlUp = CtrlDesc("up", "indicator whether controller is reachable") + CtrlUptime = CtrlDesc("uptime", "uptime of controller in seconds") + CtrlInfo = CtrlDesc("info", "controller infos about the installed software", "serial", "eth_mac", "version") + CtrlLoad = CtrlDesc("load", "current system load of controller") + CtrlMemoryTotal = CtrlDesc("mem_total", "total system memory of controller in bytes") + CtrlMemoryFree = CtrlDesc("mem_free", "free system memory of controller in bytes") + CtrlMemoryBuffered = CtrlDesc("mem_buffered", "buffered system memory of controller in bytes") + CtrlMemoryShared = CtrlDesc("mem_shared", "shared system memory of controller in bytes") + CtrlGhnNumOnline = CtrlDesc("ghn_endpoints_online", "number of endponts online for a G.HN port", "port") + CtrlGhnNumRegistered = CtrlDesc("ghn_endpoints_registered", "number of endponts registered for a G.HN port", "port") + + NodeLabel = []string{"name"} + NodeStatus = NodeDesc("status", "current endpoint status") + NodeUptime = NodeDesc("uptime", "uptime of endpoint in seconds") + NodeOffline = NodeDesc("offline_since", "offline since unix timestamp") + NodeLoad = NodeDesc("load", "current system load of endpoint") + NodeGhnPort = NodeDesc("ghn_port", "G.HN port number", "ghn_mac") + NodeClients = NodeDesc("clients", "number of connected WLAN clients", "band") + + LounterLabel = []string{"interface", "direction"} + CounterBytes = NodeDesc("interface_bytes", "total bytes transmitted or received", LounterLabel...) + CounterPackets = NodeDesc("interface_packets", "total packets transmitted or received", LounterLabel...) + CounterErrors = NodeDesc("interface_errors", "total number of errors", LounterLabel...) + + GhnRxbps = NodeDesc("ghn_rxbps", "negotiated RX rate in bps") + GhnTxbps = NodeDesc("ghn_txbps", "negotiated TX rate in bps") +) + +func CtrlDesc(name, help string, extraLabel ...string) *prometheus.Desc { + fqdn := prometheus.BuildFQName("triax", "eoc_controller", name) + return prometheus.NewDesc(fqdn, help, extraLabel, nil) +} + +func NodeDesc(name, help string, extraLabel ...string) *prometheus.Desc { + fqdn := prometheus.BuildFQName("triax", "eoc_endpoint", name) + return prometheus.NewDesc(fqdn, help, append(NodeLabel, extraLabel...), nil) +} diff --git a/triax/errors.go b/types/errors.go similarity index 72% rename from triax/errors.go rename to types/errors.go index 1961b4d..24010a3 100644 --- a/triax/errors.go +++ b/types/errors.go @@ -1,4 +1,4 @@ -package triax +package types import ( "errors" @@ -19,17 +19,18 @@ func (err *ErrInvalidEndpoint) Unwrap() error { var ErrMissingCredentials = errors.New("missing username/password") -type genericError struct{ msg string } +type GenericError struct{ Msg string } -func (err *genericError) Error() string { - return err.msg +func (err *GenericError) Error() string { + return err.Msg } type ErrUnexpectedStatus struct { - Method string - URL string - Status int - Body []byte + Method string + Location string + URL string + Status int + Body []byte } func (err *ErrUnexpectedStatus) Error() string {