Skip to content

Commit

Permalink
feature: count json objects with same value
Browse files Browse the repository at this point in the history
if we want to count json objects with same values we can use
countbylabel type in the metric configuration
  • Loading branch information
parsa97 committed Sep 6, 2023
1 parent 7ab7efc commit 5632095
Show file tree
Hide file tree
Showing 7 changed files with 89 additions and 2 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ Serving HTTP on :: port 8000 (http://[::]:8000/) ...
## TEST with 'default' module

$ curl "http://localhost:7979/probe?module=default&target=http://localhost:8000/examples/data.json"
# HELP example_count Example of count object from a json
# TYPE example_count untyped
example_count{environment="beta",state="ACTIVE"} 2
example_count{environment="beta",state="INACTIVE"} 1
# HELP example_global_value Example of a top-level global value scrape in the json
# TYPE example_global_value untyped
example_global_value{environment="beta",location="planet-mars"} 1234
Expand Down
6 changes: 5 additions & 1 deletion config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,15 @@ type Metric struct {
EpochTimestamp string
Help string
Values map[string]string
MinimumCount int
}

type ScrapeType string

const (
ValueScrape ScrapeType = "value" // default
ObjectScrape ScrapeType = "object"
CountScrape ScrapeType = "countbylabel"
)

type ValueType string
Expand Down Expand Up @@ -89,8 +91,10 @@ func LoadConfig(configPath string) (Config, error) {
if module.Metrics[i].ValueType == "" {
module.Metrics[i].ValueType = ValueTypeUntyped
}
if !(module.Metrics[i].MinimumCount > 0) {
module.Metrics[i].MinimumCount = 1
}
}
}

return config, nil
}
10 changes: 9 additions & 1 deletion examples/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@ modules:
active: 1 # static value
count: '{.count}' # dynamic value
boolean: '{.some_boolean}'
- name: example_count
type: countbylabel
help: Example of count json labels
path: '{.values[*].state}'
labels:
environment: beta # static label
state: '{}' # dynamic label
# The minimum count of labels required to expose the metric for each label. Defaults to 1.
minimumCount: 1

animals:
metrics:
Expand Down Expand Up @@ -66,4 +75,3 @@ modules:
# content: |
# {"time_diff": "{{ duration `95` }}","anotherVar": "{{ .myVal | first }}"}
# templatize: true

37 changes: 37 additions & 0 deletions exporter/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type JSONMetric struct {
LabelsJSONPaths []string
ValueType prometheus.ValueType
EpochTimestampJSONPath string
MinimumCount int
}

func (mc JSONMetricCollector) Describe(ch chan<- *prometheus.Desc) {
Expand Down Expand Up @@ -70,6 +71,42 @@ func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) {
continue
}

case config.CountScrape:
values, err := extractValue(mc.Logger, mc.Data, m.KeyJSONPath, true)
if err != nil {
level.Error(mc.Logger).Log("msg", "Failed to extract json objects for metric", "err", err, "metric", m.Desc)
continue
}

var jsonData []interface{}
counts := make(map[interface{}]int)

if err := json.Unmarshal([]byte(values), &jsonData); err == nil {
for _, data := range jsonData {
counts[data]++
}
for data, count := range counts {
if count >= m.MinimumCount {
jdata, err := json.Marshal(data)
if err != nil {
level.Error(mc.Logger).Log("msg", "Failed to marshal data to json", "path", m.ValueJSONPath, "err", err, "metric", m.Desc, "data", data)
continue
}
if err != nil {
level.Error(mc.Logger).Log("msg", "Failed to extract value for metric", "path", m.ValueJSONPath, "err", err, "metric", m.Desc)
continue
}

ch <- prometheus.MustNewConstMetric(
m.Desc,
prometheus.UntypedValue,
float64(count),
extractLabels(mc.Logger, jdata, m.LabelsJSONPaths)...,
)
}
}
}

case config.ObjectScrape:
values, err := extractValue(mc.Logger, mc.Data, m.KeyJSONPath, true)
if err != nil {
Expand Down
22 changes: 22 additions & 0 deletions exporter/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,28 @@ func CreateMetricsList(c config.Module) ([]JSONMetric, error) {
}
metrics = append(metrics, jsonMetric)
}
case config.CountScrape:
var variableLabels, variableLabelsValues []string
for k, v := range metric.Labels {
variableLabels = append(variableLabels, k)
variableLabelsValues = append(variableLabelsValues, v)
}
jsonMetric := JSONMetric{
Type: config.CountScrape,
Desc: prometheus.NewDesc(
metric.Name,
metric.Help,
variableLabels,
nil,
),
KeyJSONPath: metric.Path,
MinimumCount: metric.MinimumCount,
LabelsJSONPaths: variableLabelsValues,
ValueType: valueType,
EpochTimestampJSONPath: metric.EpochTimestamp,
}
fmt.Println(jsonMetric)
metrics = append(metrics, jsonMetric)
default:
return nil, fmt.Errorf("Unknown metric type: '%s', for metric: '%s'", metric.Type, metric.Name)
}
Expand Down
8 changes: 8 additions & 0 deletions test/config/good.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,11 @@ modules:
active: 1 # static value
count: '{.count}' # dynamic value
boolean: '{.some_boolean}'

- name: example_count
type: countbylabel
help: Example of count object from a json
path: '{.values[*].state}'
labels:
environment: beta # static label
state: '{}' # dynamic label
4 changes: 4 additions & 0 deletions test/response/good.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# HELP example_count Example of count object from a json
# TYPE example_count untyped
example_count{environment="beta",state="ACTIVE"} 2
example_count{environment="beta",state="INACTIVE"} 1
# HELP example_global_value Example of a top-level global value scrape in the json
# TYPE example_global_value gauge
example_global_value{environment="beta",location="planet-mars"} 1234
Expand Down

0 comments on commit 5632095

Please sign in to comment.