Remove incorrect errors from SensorNameFilter

This commit reorganizes the logic in parseMetric to have it take in a MetricConfig so that the extractor can find the config and properly handle the case when there's no config found.
This commit is contained in:
Will Moss
2023-01-03 21:41:10 -08:00
parent 682d2c1212
commit f03d0841f4
4 changed files with 93 additions and 26 deletions

View File

@@ -1,7 +1,6 @@
package metrics package metrics
import ( import (
"errors"
"fmt" "fmt"
"github.com/hikhvar/mqtt2prometheus/pkg/config" "github.com/hikhvar/mqtt2prometheus/pkg/config"
@@ -21,7 +20,14 @@ func NewJSONObjectExtractor(p Parser) Extractor {
if rawValue == nil { if rawValue == nil {
continue continue
} }
m, err := p.parseMetric(path, deviceID, rawValue)
// Find a valid metrics config
config, found := p.findMetricConfig(path, deviceID)
if !found {
continue
}
m, err := p.parseMetric(config, rawValue)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to parse valid metric value: %w", err) return nil, fmt.Errorf("failed to parse valid metric value: %w", err)
} }
@@ -32,17 +38,21 @@ func NewJSONObjectExtractor(p Parser) Extractor {
} }
} }
func NewMetricPerTopicExtractor(p Parser, metricName *config.Regexp) Extractor { func NewMetricPerTopicExtractor(p Parser, metricNameRegex *config.Regexp) Extractor {
return func(topic string, payload []byte, deviceID string) (MetricCollection, error) { return func(topic string, payload []byte, deviceID string) (MetricCollection, error) {
mName := metricName.GroupValue(topic, config.MetricNameRegexGroup) metricName := metricNameRegex.GroupValue(topic, config.MetricNameRegexGroup)
if mName == "" { if metricName == "" {
return nil, fmt.Errorf("failed to find valid metric in topic path") return nil, fmt.Errorf("failed to find valid metric in topic path")
} }
m, err := p.parseMetric(mName, deviceID, string(payload))
// Find a valid metrics config
config, found := p.findMetricConfig(metricName, deviceID)
if !found {
return nil, nil
}
m, err := p.parseMetric(config, string(payload))
if err != nil { if err != nil {
if errors.Is(err, metricNotConfigured) {
return nil, nil
}
return nil, fmt.Errorf("failed to parse metric: %w", err) return nil, fmt.Errorf("failed to parse metric: %w", err)
} }
m.Topic = topic m.Topic = topic

View File

@@ -25,6 +25,7 @@ func TestNewJSONObjectExtractor_parseMetric(t *testing.T) {
args args args args
want Metric want Metric
wantErr bool wantErr bool
noValue bool
}{ }{
{ {
name: "string value", name: "string value",
@@ -78,6 +79,55 @@ func TestNewJSONObjectExtractor_parseMetric(t *testing.T) {
IngestTime: testNow(), IngestTime: testNow(),
Topic: "topic", Topic: "topic",
}, },
}, {
name: "metric matching SensorNameFilter",
separator: ".",
fields: fields{
map[string][]config.MetricConfig{
"temperature": []config.MetricConfig{
{
PrometheusName: "temperature",
MQTTName: "temperature",
ValueType: "gauge",
SensorNameFilter: *config.MustNewRegexp(".*22$"),
},
},
},
},
args: args{
metricPath: "topic",
deviceID: "dht22",
value: "{\"temperature\": 8.5}",
},
want: Metric{
Description: prometheus.NewDesc("temperature", "", []string{"sensor", "topic"}, nil),
ValueType: prometheus.GaugeValue,
Value: 8.5,
IngestTime: testNow(),
Topic: "topic",
},
}, {
name: "metric not matching SensorNameFilter",
separator: ".",
fields: fields{
map[string][]config.MetricConfig{
"temperature": []config.MetricConfig{
{
PrometheusName: "temperature",
MQTTName: "temperature",
ValueType: "gauge",
SensorNameFilter: *config.MustNewRegexp(".*fail$"),
},
},
},
},
args: args{
metricPath: "topic",
deviceID: "dht22",
value: "{\"temperature\": 8.5}",
},
want: Metric{},
noValue: true,
}, },
} }
for _, tt := range tests { for _, tt := range tests {
@@ -93,10 +143,15 @@ func TestNewJSONObjectExtractor_parseMetric(t *testing.T) {
t.Errorf("parseMetric() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("parseMetric() error = %v, wantErr %v", err, tt.wantErr)
return return
} }
if len(got) != 1 {
t.Errorf("parseMetric() got = %v, want %v", nil, tt.want) if len(got) == 0 {
if !tt.noValue {
t.Errorf("parseMetric() got = %v, want %v", nil, tt.want)
}
} else if !reflect.DeepEqual(got[0], tt.want) { } else if !reflect.DeepEqual(got[0], tt.want) {
t.Errorf("parseMetric() got = %v, want %v", got[0], tt.want) t.Errorf("parseMetric() got = %v, want %v", got[0], tt.want)
} else if len(got) > 1 {
t.Errorf("unexpected result got = %v, want %v", got, tt.want)
} }
}) })
} }

View File

@@ -1,7 +1,6 @@
package metrics package metrics
import ( import (
"errors"
"fmt" "fmt"
"strconv" "strconv"
"time" "time"
@@ -9,12 +8,10 @@ import (
"github.com/hikhvar/mqtt2prometheus/pkg/config" "github.com/hikhvar/mqtt2prometheus/pkg/config"
) )
type metricNotConfiguredError error
var metricNotConfigured metricNotConfiguredError = errors.New("metric not configured failed to parse")
type Parser struct { type Parser struct {
separator string separator string
// Maps the mqtt metric name to a list of configs
// The first that matches SensorNameFilter will be used
metricConfigs map[string][]config.MetricConfig metricConfigs map[string][]config.MetricConfig
} }
@@ -39,7 +36,7 @@ func (p *Parser) config() map[string][]config.MetricConfig {
// validMetric returns config matching the metric and deviceID // validMetric returns config matching the metric and deviceID
// Second return value indicates if config was found. // Second return value indicates if config was found.
func (p *Parser) validMetric(metric string, deviceID string) (config.MetricConfig, bool) { func (p *Parser) findMetricConfig(metric string, deviceID string) (config.MetricConfig, bool) {
for _, c := range p.metricConfigs[metric] { for _, c := range p.metricConfigs[metric] {
if c.SensorNameFilter.Match(deviceID) { if c.SensorNameFilter.Match(deviceID) {
return c, true return c, true
@@ -50,12 +47,7 @@ func (p *Parser) validMetric(metric string, deviceID string) (config.MetricConfi
// parseMetric parses the given value according to the given deviceID and metricPath. The config allows to // parseMetric parses the given value according to the given deviceID and metricPath. The config allows to
// parse a metric value according to the device ID. // parse a metric value according to the device ID.
func (p *Parser) parseMetric(metricPath string, deviceID string, value interface{}) (Metric, error) { func (p *Parser) parseMetric(cfg config.MetricConfig, value interface{}) (Metric, error) {
cfg, cfgFound := p.validMetric(metricPath, deviceID)
if !cfgFound {
return Metric{}, metricNotConfigured
}
var metricValue float64 var metricValue float64
if boolValue, ok := value.(bool); ok { if boolValue, ok := value.(bool); ok {

View File

@@ -411,7 +411,7 @@ func TestParser_parseMetric(t *testing.T) {
args: args{ args: args{
metricPath: "enabled", metricPath: "enabled",
deviceID: "dht22", deviceID: "dht22",
value: metricNotConfigured, value: []int{3},
}, },
wantErr: true, wantErr: true,
}, },
@@ -421,7 +421,17 @@ func TestParser_parseMetric(t *testing.T) {
p := &Parser{ p := &Parser{
metricConfigs: tt.fields.metricConfigs, metricConfigs: tt.fields.metricConfigs,
} }
got, err := p.parseMetric(tt.args.metricPath, tt.args.deviceID, tt.args.value)
// Find a valid metrics config
config, found := p.findMetricConfig(tt.args.metricPath, tt.args.deviceID)
if !found {
if !tt.wantErr {
t.Errorf("MetricConfig not found")
}
return
}
got, err := p.parseMetric(config, tt.args.value)
if (err != nil) != tt.wantErr { if (err != nil) != tt.wantErr {
t.Errorf("parseMetric() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("parseMetric() error = %v, wantErr %v", err, tt.wantErr)
return return