Skip to content

Commit

Permalink
Pick timestamp from metric
Browse files Browse the repository at this point in the history
based on #97 and #80 this provides the posibility to use a metric that has a unix style timestamp as the timestamp of the scraped metric

When deserializing objects we need to take the key json path into account as well like we would do for all the values as well. This allows collections to be defined still with each entry having a separate timestamp (e.g. list of time-stamped log messages).

Update examples for timestamp

Update Readme about staleness for custom timestamps

Signed-off-by: Jan Phillip Kretzschmar <[email protected]>
  • Loading branch information
janphkre committed Jul 11, 2022
1 parent 2c1ca88 commit c0d3da9
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 24 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ $ docker run --rm -it -p 9090:9090 -v $PWD/examples/prometheus.yml:/etc/promethe
```
Then head over to http://localhost:9090/graph?g0.range_input=1h&g0.expr=example_value_active&g0.tab=1 or http://localhost:9090/targets to check the scraped metrics or the targets.

## Using custom timestamps

This exporter allows you to use a field of the metric as the (unix/epoch) timestamp for the data as an int64. However, this may lead to unexpected behaviour, as the prometheus implements a [Staleness](https://prometheus.io/docs/prometheus/latest/querying/basics/#staleness) mechanism. Effectively, this means that samples older than 5 minutes can not be scrapped and will be ignored by the prometheus instance.

## Exposing metrics through HTTPS

TLS configuration supported by this exporter can be found at [exporter-toolkit/web](https://github.com/prometheus/exporter-toolkit/blob/v0.5.1/docs/web-configuration.md)
Expand Down
15 changes: 8 additions & 7 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,14 @@ import (

// Metric contains values that define a metric
type Metric struct {
Name string
Path string
Labels map[string]string
Type ScrapeType
ValueType ValueType
Help string
Values map[string]string
Name string
Path string
Labels map[string]string
Type ScrapeType
ValueType ValueType
EpochTimestamp string
Help string
Values map[string]string
}

type ScrapeType string
Expand Down
7 changes: 6 additions & 1 deletion examples/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ modules:
labels:
environment: beta # static label
location: "planet-{.location}" # dynamic label

- name: example_timestamped_value
path: "{ .values[?(@.state == "INACTIVE")] }"
epochTimestamp: "{ .timestamp }"
help: Example of a timestamped value scrape in the json
labels:
environment: beta # static label
- name: example_value
type: object
help: Example of sub-level value scrapes from a json
Expand Down
1 change: 1 addition & 0 deletions examples/data.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"counter": 1234,
"timestamp": 1657568506,
"values": [
{
"id": "id-A",
Expand Down
39 changes: 30 additions & 9 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"
"time"

"github.com/go-kit/log"
"github.com/go-kit/log/level"
Expand All @@ -31,12 +32,13 @@ type JSONMetricCollector struct {
}

type JSONMetric struct {
Desc *prometheus.Desc
Type config.ScrapeType
KeyJSONPath string
ValueJSONPath string
LabelsJSONPaths []string
ValueType prometheus.ValueType
Desc *prometheus.Desc
Type config.ScrapeType
KeyJSONPath string
ValueJSONPath string
LabelsJSONPaths []string
ValueType prometheus.ValueType
EpochTimestampJSONPath string
}

func (mc JSONMetricCollector) Describe(ch chan<- *prometheus.Desc) {
Expand All @@ -56,13 +58,13 @@ func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) {
}

if floatValue, err := SanitizeValue(value); err == nil {

ch <- prometheus.MustNewConstMetric(
metric := prometheus.MustNewConstMetric(
m.Desc,
m.ValueType,
floatValue,
extractLabels(mc.Logger, mc.Data, m.LabelsJSONPaths)...,
)
ch <- timestampMetric(mc.Logger, m, mc.Data, metric)
} else {
level.Error(mc.Logger).Log("msg", "Failed to convert extracted value to float64", "path", m.KeyJSONPath, "value", value, "err", err, "metric", m.Desc)
continue
Expand Down Expand Up @@ -90,12 +92,13 @@ func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) {
}

if floatValue, err := SanitizeValue(value); err == nil {
ch <- prometheus.MustNewConstMetric(
metric := prometheus.MustNewConstMetric(
m.Desc,
m.ValueType,
floatValue,
extractLabels(mc.Logger, jdata, m.LabelsJSONPaths)...,
)
ch <- timestampMetric(mc.Logger, m, jdata, metric)
} else {
level.Error(mc.Logger).Log("msg", "Failed to convert extracted value to float64", "path", m.ValueJSONPath, "value", value, "err", err, "metric", m.Desc)
continue
Expand Down Expand Up @@ -157,3 +160,21 @@ func extractLabels(logger log.Logger, data []byte, paths []string) []string {
}
return labels
}

func timestampMetric(logger log.Logger, m JSONMetric, data []byte, pm prometheus.Metric) prometheus.Metric {
if m.EpochTimestampJSONPath == "" {
return pm
}
ts, err := extractValue(logger, data, m.EpochTimestampJSONPath, false)
if err != nil {
level.Error(logger).Log("msg", "Failed to extract timestamp for metric", "path", m.KeyJSONPath, "err", err, "metric", m.Desc)
return pm
}
epochTime, err := SanitizeIntValue(ts)
if err != nil {
level.Error(logger).Log("msg", "Failed to parse timestamp for metric", "path", m.KeyJSONPath, "err", err, "metric", m.Desc)
return pm
}
timestamp := time.UnixMilli(epochTime)
return prometheus.NewMetricWithTimestamp(timestamp, pm)
}
29 changes: 22 additions & 7 deletions exporter/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,19 @@ func SanitizeValue(s string) (float64, error) {
return value, fmt.Errorf(resultErr)
}

func SanitizeIntValue(s string) (int64, error) {
var err error
var value int64
var resultErr string

if value, err = strconv.ParseInt(s, 10, 64); err == nil {
return value, nil
}
resultErr = fmt.Sprintf("%s", err)

return value, fmt.Errorf(resultErr)
}

func CreateMetricsList(c config.Module) ([]JSONMetric, error) {
var (
metrics []JSONMetric
Expand Down Expand Up @@ -91,9 +104,10 @@ func CreateMetricsList(c config.Module) ([]JSONMetric, error) {
variableLabels,
nil,
),
KeyJSONPath: metric.Path,
LabelsJSONPaths: variableLabelsValues,
ValueType: valueType,
KeyJSONPath: metric.Path,
LabelsJSONPaths: variableLabelsValues,
ValueType: valueType,
EpochTimestampJSONPath: metric.EpochTimestamp,
}
metrics = append(metrics, jsonMetric)
case config.ObjectScrape:
Expand All @@ -112,10 +126,11 @@ func CreateMetricsList(c config.Module) ([]JSONMetric, error) {
variableLabels,
nil,
),
KeyJSONPath: metric.Path,
ValueJSONPath: valuePath,
LabelsJSONPaths: variableLabelsValues,
ValueType: valueType,
KeyJSONPath: metric.Path,
ValueJSONPath: valuePath,
LabelsJSONPaths: variableLabelsValues,
ValueType: valueType,
EpochTimestampJSONPath: metric.EpochTimestamp,
}
metrics = append(metrics, jsonMetric)
}
Expand Down

0 comments on commit c0d3da9

Please sign in to comment.