Skip to content

Commit 30d4492

Browse files
author
Parsa
committed
feature: count json objects with same value
if we want to count json objects with same values we can use countbylabel type in the metric configuration Signed-off-by: Parsa <[email protected]>
1 parent 1614ee8 commit 30d4492

File tree

7 files changed

+98
-7
lines changed

7 files changed

+98
-7
lines changed

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,15 @@ metrics:
5959
count: '{.count}' # dynamic value
6060
boolean: '{.some_boolean}'
6161

62+
- name: example_count
63+
type: countbylabel
64+
help: Example of count json labels and
65+
path: '{.values[*].state}'
66+
labels:
67+
environment: beta # static label
68+
state: '{}' # dynamic label
69+
mincount: 1
70+
6271
headers:
6372
X-Dummy: my-test-header
6473

@@ -68,6 +77,8 @@ Serving HTTP on 0.0.0.0 port 8000 ...
6877
$ ./json_exporter --config.file examples/config.yml &
6978

7079
$ curl "http://localhost:7979/probe?target=http://localhost:8000/examples/data.json" | grep ^example
80+
example_count{environment="beta",state="ACTIVE"} 2
81+
example_count{environment="beta",state="INACTIVE"} 1
7182
example_global_value{environment="beta",location="planet-mars"} 1234
7283
example_value_active{environment="beta",id="id-A"} 1
7384
example_value_active{environment="beta",id="id-C"} 1

config/config.go

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,21 @@ import (
2222

2323
// Metric contains values that define a metric
2424
type Metric struct {
25-
Name string
26-
Path string
27-
Labels map[string]string
28-
Type MetricType
29-
Help string
30-
Values map[string]string
25+
Name string
26+
Path string
27+
Labels map[string]string
28+
Type MetricType
29+
Help string
30+
Values map[string]string
31+
Mincount int
3132
}
3233

3334
type MetricType string
3435

3536
const (
3637
ValueScrape MetricType = "value" // default
3738
ObjectScrape MetricType = "object"
39+
CountScrape MetricType = "countbylabel"
3840
)
3941

4042
// Config contains metrics and headers defining a configuration
@@ -69,6 +71,9 @@ func LoadConfig(configPath string) (Config, error) {
6971
if config.Metrics[i].Help == "" {
7072
config.Metrics[i].Help = config.Metrics[i].Name
7173
}
74+
if !(config.Metrics[i].Mincount > 0) {
75+
config.Metrics[i].Mincount = 1
76+
}
7277
}
7378

7479
return config, nil

examples/config.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,15 @@ metrics:
1919
count: '{.count}' # dynamic value
2020
boolean: '{.some_boolean}'
2121

22+
- name: example_count
23+
type: countbylabel
24+
help: Example of count json labels and
25+
path: '{.values[*].state}'
26+
labels:
27+
environment: beta # static label
28+
state: '{}' # dynamic label
29+
mincount: 1
30+
2231
headers:
2332
X-Dummy: my-test-header
2433

exporter/collector.go

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ type JSONMetric struct {
3434
KeyJSONPath string
3535
ValueJSONPath string
3636
LabelsJSONPaths []string
37+
Mincount int
3738
}
3839

3940
func (mc JSONMetricCollector) Describe(ch chan<- *prometheus.Desc) {
@@ -44,7 +45,7 @@ func (mc JSONMetricCollector) Describe(ch chan<- *prometheus.Desc) {
4445

4546
func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) {
4647
for _, m := range mc.JSONMetrics {
47-
if m.ValueJSONPath == "" { // ScrapeType is 'value'
48+
if m.ValueJSONPath == "" && m.Mincount == 0 { // ScrapeType is 'value'
4849
value, err := extractValue(mc.Logger, mc.Data, m.KeyJSONPath, false)
4950
if err != nil {
5051
level.Error(mc.Logger).Log("msg", "Failed to extract value for metric", "path", m.KeyJSONPath, "err", err, "metric", m.Desc)
@@ -63,6 +64,42 @@ func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) {
6364
level.Error(mc.Logger).Log("msg", "Failed to convert extracted value to float64", "path", m.KeyJSONPath, "value", value, "err", err, "metric", m.Desc)
6465
continue
6566
}
67+
} else if m.Mincount > 0 { // ScrapeType is 'countbylabel'
68+
values, err := extractValue(mc.Logger, mc.Data, m.KeyJSONPath, true)
69+
if err != nil {
70+
level.Error(mc.Logger).Log("msg", "Failed to extract json objects for metric", "err", err, "metric", m.Desc)
71+
continue
72+
}
73+
74+
var jsonData []interface{}
75+
counts := make(map[interface{}]int)
76+
77+
if err := json.Unmarshal([]byte(values), &jsonData); err == nil {
78+
for _, data := range jsonData {
79+
counts[data]++
80+
}
81+
for data, count := range counts {
82+
if count >= m.Mincount {
83+
jdata, err := json.Marshal(data)
84+
if err != nil {
85+
level.Error(mc.Logger).Log("msg", "Failed to marshal data to json", "path", m.ValueJSONPath, "err", err, "metric", m.Desc, "data", data)
86+
continue
87+
}
88+
if err != nil {
89+
level.Error(mc.Logger).Log("msg", "Failed to extract value for metric", "path", m.ValueJSONPath, "err", err, "metric", m.Desc)
90+
continue
91+
}
92+
93+
ch <- prometheus.MustNewConstMetric(
94+
m.Desc,
95+
prometheus.UntypedValue,
96+
float64(count),
97+
extractLabels(mc.Logger, jdata, m.LabelsJSONPaths)...,
98+
)
99+
}
100+
}
101+
}
102+
66103
} else { // ScrapeType is 'object'
67104
values, err := extractValue(mc.Logger, mc.Data, m.KeyJSONPath, true)
68105
if err != nil {

exporter/util.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,24 @@ func CreateMetricsList(c config.Config) ([]JSONMetric, error) {
104104
}
105105
metrics = append(metrics, jsonMetric)
106106
}
107+
case config.CountScrape:
108+
var variableLabels, variableLabelsValues []string
109+
for k, v := range metric.Labels {
110+
variableLabels = append(variableLabels, k)
111+
variableLabelsValues = append(variableLabelsValues, v)
112+
}
113+
jsonMetric := JSONMetric{
114+
Desc: prometheus.NewDesc(
115+
metric.Name,
116+
metric.Help,
117+
variableLabels,
118+
nil,
119+
),
120+
KeyJSONPath: metric.Path,
121+
Mincount: metric.Mincount,
122+
LabelsJSONPaths: variableLabelsValues,
123+
}
124+
metrics = append(metrics, jsonMetric)
107125
default:
108126
return nil, fmt.Errorf("Unknown metric type: '%s', for metric: '%s'", metric.Type, metric.Name)
109127
}

test/config/good.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,10 @@ metrics:
1919
count: '{.count}' # dynamic value
2020
boolean: '{.some_boolean}'
2121

22+
- name: example_count
23+
type: countbylabel
24+
help: Example of count object from a json
25+
path: '{.values[*].state}'
26+
labels:
27+
environment: beta # static label
28+
state: '{}' # dynamic label

test/response/good.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# HELP example_count Example of count object from a json
2+
# TYPE example_count untyped
3+
example_count{environment="beta",state="ACTIVE"} 2
4+
example_count{environment="beta",state="INACTIVE"} 1
15
# HELP example_global_value Example of a top-level global value scrape in the json
26
# TYPE example_global_value untyped
37
example_global_value{environment="beta",location="planet-mars"} 1234

0 commit comments

Comments
 (0)