Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Value Conversions #172

Merged
merged 9 commits into from
Oct 5, 2022
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,18 @@ modules:
active: 1 # static value
count: '{.count}' # dynamic value
boolean: '{.some_boolean}'

- name: example_convert
type: object
path: '{.values[0,1]}'
labels:
state: '{.state}'
values:
state: '{.state}'
valueconverter:
'{.state}': #convert value 'state' into a number
active: 1
inactive: 2

headers:
X-Dummy: my-test-header
Expand All @@ -70,6 +82,8 @@ Serving HTTP on 0.0.0.0 port 8000 ...
$ ./json_exporter --config.file examples/config.yml &

$ curl "http://localhost:7979/probe?module=default&target=http://localhost:8000/examples/data.json" | grep ^example
example_convert_state{state="ACTIVE"} 1
example_convert_state{state="INACTIVE"} 2
example_global_value{environment="beta",location="planet-mars"} 1234
example_value_active{environment="beta",id="id-A"} 1
example_value_active{environment="beta",id="id-C"} 1
Expand Down
3 changes: 3 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,11 @@ type Metric struct {
EpochTimestamp string
Help string
Values map[string]string
ValueConverter ValueConverterType
}

type ValueConverterType map[string]map[string]string

type ScrapeType string

const (
Expand Down
13 changes: 13 additions & 0 deletions examples/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,19 @@ modules:
active: 1 # static value
count: '{.count}' # dynamic value
boolean: '{.some_boolean}'

- name: example_convert
type: object
path: '{.values[0,1]}'
labels:
state: '{.state}'
values:
state: '{.state}'
valueconverter:
'{.state}': #convert value 'state' in JSON into a number
active: 1
inactive: 2

headers:
X-Dummy: my-test-header

Expand Down
18 changes: 18 additions & 0 deletions exporter/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package exporter
import (
"bytes"
"encoding/json"
"strings"
"time"

"github.com/go-kit/log"
Expand All @@ -38,6 +39,7 @@ type JSONMetric struct {
ValueJSONPath string
LabelsJSONPaths []string
ValueType prometheus.ValueType
ValueConverter config.ValueConverterType
EpochTimestampJSONPath string
}

Expand Down Expand Up @@ -86,11 +88,14 @@ func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) {
continue
}
value, err := extractValue(mc.Logger, jdata, m.ValueJSONPath, false)

if err != nil {
level.Error(mc.Logger).Log("msg", "Failed to extract value for metric", "path", m.ValueJSONPath, "err", err, "metric", m.Desc)
continue
}

value = convertValueIfNeeded(m, value)

if floatValue, err := SanitizeValue(value); err == nil {
metric := prometheus.MustNewConstMetric(
m.Desc,
Expand Down Expand Up @@ -161,6 +166,19 @@ func extractLabels(logger log.Logger, data []byte, paths []string) []string {
return labels
}

// Returns the conversion of the dynamic value- if it exists in the ValueConverter configuration
func convertValueIfNeeded(m JSONMetric, value string) string {
if m.ValueConverter != nil {
if valueMappings, hasPathKey := m.ValueConverter[m.ValueJSONPath]; hasPathKey {
value = strings.ToLower(value)

if _, hasValueKey := valueMappings[value]; hasValueKey {
value = valueMappings[value]
}
}
}
return value
}
func timestampMetric(logger log.Logger, m JSONMetric, data []byte, pm prometheus.Metric) prometheus.Metric {
if m.EpochTimestampJSONPath == "" {
return pm
Expand Down
23 changes: 23 additions & 0 deletions exporter/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,9 @@ func CreateMetricsList(c config.Module) ([]JSONMetric, error) {
variableLabels = append(variableLabels, k)
variableLabelsValues = append(variableLabelsValues, v)
}

var valueConverters config.ValueConverterType = initializeValueConverter(metric)

jsonMetric := JSONMetric{
Type: config.ObjectScrape,
Desc: prometheus.NewDesc(
Expand All @@ -130,6 +133,7 @@ func CreateMetricsList(c config.Module) ([]JSONMetric, error) {
ValueJSONPath: valuePath,
LabelsJSONPaths: variableLabelsValues,
ValueType: valueType,
ValueConverter: valueConverters,
EpochTimestampJSONPath: metric.EpochTimestamp,
}
metrics = append(metrics, jsonMetric)
Expand Down Expand Up @@ -245,3 +249,22 @@ func renderBody(logger log.Logger, body config.Body, tplValues url.Values) (meth
}
return
}

// Initializes and returns a ValueConverter object. nil if there aren't any conversions
func initializeValueConverter(metric config.Metric) config.ValueConverterType {
var valueConverters config.ValueConverterType

//convert all keys to lowercase
ngrebels marked this conversation as resolved.
Show resolved Hide resolved
if metric.ValueConverter != nil {
valueConverters = make(config.ValueConverterType)
for valuesKey, innerMap := range metric.ValueConverter {
//make the mappings for each value key lowercase
valueConverters[valuesKey] = make(map[string]string)
for conversionFrom, conversionTo := range innerMap {
valueConverters[valuesKey][strings.ToLower(conversionFrom)] = conversionTo
}
}
}

return valueConverters
}
116 changes: 116 additions & 0 deletions test/config/test-converter.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
---
#the following tests use ./valueconverter.json

modules:
default:
metrics:
#State should be converted to 1 for the metric value
#Test uppercase key
- name: test1
path: "{$}"
help: Testing Single Value Converter on Type Object
type: object
labels:
name: '{.name}'
values:
state: '{.state}'
valueconverter:
'{.state}':
ACTIVE: 1

#State should be converted to 1 for the metric value
#Test lowercase key
- name: test2
path: "{$}"
help: Testing Single Value Converter on Type Object
type: object
labels:
name: '{.name}'
values:
state: '{.state}'
valueconverter:
'{.state}':
active: 1

#There should be two JSONs returned: a metric for 'state' with value 1, and a metric with value 12
- name: test3
path: "{$}"
help: Testing Multi Diff Value Converter on Type Object
type: object
labels:
name: '{.name}'
values:
state: '{.state}'
active: 12
valueconverter:
'{.state}':
ACTIVE: 1

#Nothing should be returned. This should be an error since 'state' can't be converted to an int
- name: test4
path: "{$}"
help: Testing Value with missing conversion
type: object
labels:
name: '{.name}'
values:
state: '{.state}'

#Test nested JSON. It should return with both values but with 12 as the metric value
- name: test5
path: '{.values[*]}'
help: Testing Conversion with Missing value
type: object
labels:
name: '{.name}'
values:
active: 12
valueconverter:
'{.state}':
ACTIVE: 1

#Test nested JSON.
#It should return with both values but 'state' should be converted
- name: test6
path: '{.values[*]}'
help: Testing Value with Multiple Conversions
type: object
labels:
name: '{.name}'
values:
state: '{.state}'
valueconverter:
'{.state}':
ACTIVE: 1
inactive: 2

#Test nested JSON.
#However, it should only return with state value
#There is a missing key: 'down' in valueconverter
- name: test7
path: '{.values[*]}'
help: Testing Value with Multiple Conversions and Missing Key, Value
type: object
labels:
name: '{.name}'
values:
state: '{.state}'
valueconverter:
'{.state}':
ACTIVE: 1

#Two metrics should be returned
#State should be mapped to 1.
#ResponseCode should be 2 because it already has a value in the JSON.
- name: test8
path: "{$}"
help: Testing Multi Diff Value Converter on Type Object
type: object
labels:
name: '{.name}'
values:
stat: '{.state}'
active: '{.responseCode}'
valueconverter:
'{.state}':
ACTIVE: 1
29 changes: 29 additions & 0 deletions test/response/test-converter.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# HELP test1_state Testing Single Value Converter on Type Object
# TYPE test1_state untyped
test1_state{name="Test Converter"} 1
# HELP test2_state Testing Single Value Converter on Type Object
# TYPE test2_state untyped
test2_state{name="Test Converter"} 1
# HELP test3_active Testing Multi Diff Value Converter on Type Object
# TYPE test3_active untyped
test3_active{name="Test Converter"} 12
# HELP test3_state Testing Multi Diff Value Converter on Type Object
# TYPE test3_state untyped
test3_state{name="Test Converter"} 1
# HELP test5_active Testing Conversion with Missing value
# TYPE test5_active untyped
test5_active{name="id-A"} 12
test5_active{name="id-B"} 12
# HELP test6_state Testing Value with Multiple Conversions
# TYPE test6_state untyped
test6_state{name="id-A"} 1
test6_state{name="id-B"} 2
# HELP test7_state Testing Value with Multiple Conversions and Missing Key, Value
# TYPE test7_state untyped
test7_state{name="id-A"} 1
# HELP test8_active Testing Multi Diff Value Converter on Type Object
# TYPE test8_active untyped
test8_active{name="Test Converter"} 2
# HELP test8_stat Testing Multi Diff Value Converter on Type Object
# TYPE test8_stat untyped
test8_stat{name="Test Converter"} 1
16 changes: 16 additions & 0 deletions test/serve/test-converter.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "Test Converter",
"state": "ACTIVE",
"responseCode": 2,
"values": [
{
"name": "id-A",
"state": "ACTIVE"
},
{
"name": "id-B",
"state": "INACTIVE"
}
]
}