From 24372466450de07cd66c4c0bd79a723627bd9a86 Mon Sep 17 00:00:00 2001 From: Wangchong Zhou Date: Tue, 1 Dec 2020 16:08:13 +0800 Subject: [PATCH] feat: add deviceId to all metrics, add kasa_metadata --- exporter/exporter.go | 46 +++++++++++++++++++++++++++++++++----------- kasa/kasa.go | 10 ++++++---- kasa/system.go | 28 +++++++++++++++------------ 3 files changed, 57 insertions(+), 27 deletions(-) diff --git a/exporter/exporter.go b/exporter/exporter.go index 553e051..860b088 100644 --- a/exporter/exporter.go +++ b/exporter/exporter.go @@ -3,6 +3,7 @@ package exporter import ( "github.com/fffonion/tplink-plug-exporter/kasa" "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/common/log" ) type Exporter struct { @@ -10,6 +11,7 @@ type Exporter struct { client *kasa.KasaClient metricsUp, + metricsMetadata, metricsRelayState, metricsOnTime, metricsRssi, @@ -26,7 +28,7 @@ type ExporterTarget struct { func NewExporter(t *ExporterTarget) *Exporter { var ( constLabels = prometheus.Labels{} - labelNames = []string{"alias"} + labelNames = []string{"alias", "id"} ) e := &Exporter{ @@ -38,6 +40,14 @@ func NewExporter(t *ExporterTarget) *Exporter { "Device online.", nil, constLabels, ), + + metricsMetadata: prometheus.NewDesc("kasa_metadata", + "Device metadata.", + []string{ + "alias", "hw_ver", "sw_ver", "model", "feature", + }, constLabels, + ), + metricsRelayState: prometheus.NewDesc("kasa_relay_state", "Relay state (switch on/off).", labelNames, constLabels, @@ -48,6 +58,7 @@ func NewExporter(t *ExporterTarget) *Exporter { metricsRssi: prometheus.NewDesc("kasa_rssi", "Wifi received signal strength indicator.", labelNames, constLabels), + metricsCurrent: prometheus.NewDesc("kasa_current", "Current flowing through device in Ampere.", labelNames, constLabels), @@ -83,20 +94,26 @@ func (k *Exporter) Collect(ch chan<- prometheus.Metric) { if err != nil { ch <- prometheus.MustNewConstMetric(k.metricsUp, prometheus.GaugeValue, 0) + log.Errorln("error collecting", k.target, ":", err) return } + // "alias", "hw_ver", "sw_ver", "model", "feature", + ch <- prometheus.MustNewConstMetric(k.metricsMetadata, prometheus.GaugeValue, + 1, r.Alias, r.HardwareVersion, r.SoftwareVersion, r.Model, r.Feature) + ch <- prometheus.MustNewConstMetric(k.metricsRelayState, prometheus.GaugeValue, - float64(r.RelayState), r.Alias) + float64(r.RelayState), r.Alias, r.DeviceID) ch <- prometheus.MustNewConstMetric(k.metricsOnTime, prometheus.CounterValue, - float64(r.OnTime), r.Alias) + float64(r.OnTime), r.Alias, r.DeviceID) ch <- prometheus.MustNewConstMetric(k.metricsRssi, prometheus.GaugeValue, - float64(r.RSSI), r.Alias) + float64(r.RSSI), r.Alias, r.DeviceID) aliases := map[string]string{} emeterContexts := []*kasa.KasaRequestContext{ nil, // a nil context, represent the single plug or the parent strip } + // iterrate over every child plug in a power strip for _, children := range r.Children { aliases[children.ID] = children.Alias @@ -105,7 +122,10 @@ func (k *Exporter) Collect(ch chan<- prometheus.Metric) { }) ch <- prometheus.MustNewConstMetric(k.metricsRelayState, prometheus.GaugeValue, - float64(children.State), children.Alias) + float64(children.State), children.Alias, children.ID) + + ch <- prometheus.MustNewConstMetric(k.metricsOnTime, prometheus.CounterValue, + float64(children.OnTime), children.Alias, children.ID) } if s.EmeterSupported(r) { @@ -113,26 +133,30 @@ func (k *Exporter) Collect(ch chan<- prometheus.Metric) { m := k.client.EmeterService(ctx) re, err := m.GetRealtime() - alias := r.Alias + labels := []string{r.Alias, r.DeviceID} + // if this is a child plug in a powerstrip, set the alias and ID if ctx != nil { - alias = aliases[ctx.ChildIDs[0]] + id := ctx.ChildIDs[0] + labels[0] = aliases[id] + labels[1] = id } // TODO: only set the child up to 0 on error if err != nil { ch <- prometheus.MustNewConstMetric(k.metricsUp, prometheus.GaugeValue, 0) + log.Errorln("error collecting", k.target, ":", err) return } ch <- prometheus.MustNewConstMetric(k.metricsCurrent, prometheus.GaugeValue, - float64(re.Current), alias) + float64(re.Current), labels...) ch <- prometheus.MustNewConstMetric(k.metricsVoltage, prometheus.GaugeValue, - float64(re.Voltage), alias) + float64(re.Voltage), labels...) ch <- prometheus.MustNewConstMetric(k.metricsPowerLoad, prometheus.GaugeValue, - float64(re.Power), alias) + float64(re.Power), labels...) ch <- prometheus.MustNewConstMetric(k.metricsPowerTotal, prometheus.CounterValue, - float64(re.Total), alias) + float64(re.Total), labels...) } } diff --git a/kasa/kasa.go b/kasa/kasa.go index 81afbe2..6597f91 100644 --- a/kasa/kasa.go +++ b/kasa/kasa.go @@ -136,6 +136,8 @@ func (c *KasaClient) Request(payload interface{}) ([]byte, error) { func (c *KasaClient) RPC(service string, cmd string, ctx *KasaRequestContext, payload interface{}, out interface{}) error { + errmsgPrefix := fmt.Sprintf("%s.%s", service, cmd) + body := map[string]interface{}{ service: map[string]interface{}{ cmd: payload, @@ -147,23 +149,23 @@ func (c *KasaClient) RPC(service string, cmd string, response, err := c.Request(body) if err != nil { - return err + return fmt.Errorf("%s: %v", errmsgPrefix, err) } var outMarshal map[string]map[string]map[string]interface{} err = json.Unmarshal(response, &outMarshal) if err != nil { - return err + return fmt.Errorf("%s: %v", errmsgPrefix, err) } if outMarshal[service] == nil || outMarshal[service][cmd] == nil { - return fmt.Errorf("malformed response: %v", outMarshal) + return fmt.Errorf("%s: malformed response: %v", errmsgPrefix, outMarshal) } var r RPCResponse mapstructure.Decode(outMarshal[service][cmd], &r) if r.ErrCode != 0 { - return fmt.Errorf("rpc error: %v", outMarshal) + return fmt.Errorf("%s: rpc error: %v", errmsgPrefix, outMarshal) } mapstructure.Decode(outMarshal[service][cmd], &out) diff --git a/kasa/system.go b/kasa/system.go index 1d422f2..5a7171d 100644 --- a/kasa/system.go +++ b/kasa/system.go @@ -11,21 +11,25 @@ type GetSysInfoRequest struct { } type SysInfoChildren struct { - ID string `mapstructure:"id"` - State int `mapstructure:"state"` - Alias string `mapstructure:"alias"` + ID string `mapstructure:"id"` + State int `mapstructure:"state"` + Alias string `mapstructure:"alias"` + OnTime int `mapstructure:"on_time"` } type GetSysInfoResponse struct { - MAC string `mapstructure:"mac"` - Model string `mapstructure:"model"` - Alias string `mapstructure:"alias"` - Feature string `mapstructure:"feature"` - RelayState int `mapstructure:"relay_state"` - RSSI int `mapstructure:"rssi"` - LEDOff int `mapstructure:"led_off"` - OnTime int `mapstructure:"on_time"` - Children []SysInfoChildren `mapstructure:"children"` + MAC string `mapstructure:"mac"` + Model string `mapstructure:"model"` + Alias string `mapstructure:"alias"` + Feature string `mapstructure:"feature"` + RelayState int `mapstructure:"relay_state"` + RSSI int `mapstructure:"rssi"` + LEDOff int `mapstructure:"led_off"` + OnTime int `mapstructure:"on_time"` + DeviceID string `mapstructure:"deviceId"` + SoftwareVersion string `mapstructure:"sw_ver"` + HardwareVersion string `mapstructure:"hw_ver"` + Children []SysInfoChildren `mapstructure:"children"` } func (s *KasaClientSystemService) GetSysInfo() (*GetSysInfoResponse, error) {