Skip to content

Commit 7a7378b

Browse files
committed
Pick timestamp from metric
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]>
1 parent 2c1ca88 commit 7a7378b

File tree

6 files changed

+111
-64
lines changed

6 files changed

+111
-64
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ $ docker run --rm -it -p 9090:9090 -v $PWD/examples/prometheus.yml:/etc/promethe
8383
```
8484
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.
8585

86+
## Using custom timestamps
87+
88+
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.
89+
8690
## Exposing metrics through HTTPS
8791

8892
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)

config/config.go

Lines changed: 48 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -14,82 +14,83 @@
1414
package config
1515

1616
import (
17-
"io/ioutil"
17+
"io/ioutil"
1818

19-
pconfig "github.com/prometheus/common/config"
20-
"gopkg.in/yaml.v2"
19+
pconfig "github.com/prometheus/common/config"
20+
"gopkg.in/yaml.v2"
2121
)
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 ScrapeType
29-
ValueType ValueType
30-
Help string
31-
Values map[string]string
25+
Name string
26+
Path string
27+
Labels map[string]string
28+
EpochTimestamp string
29+
Type ScrapeType
30+
ValueType ValueType
31+
Help string
32+
Values map[string]string
3233
}
3334

3435
type ScrapeType string
3536

3637
const (
37-
ValueScrape ScrapeType = "value" // default
38-
ObjectScrape ScrapeType = "object"
38+
ValueScrape ScrapeType = "value" // default
39+
ObjectScrape ScrapeType = "object"
3940
)
4041

4142
type ValueType string
4243

4344
const (
44-
ValueTypeGauge ValueType = "gauge"
45-
ValueTypeCounter ValueType = "counter"
46-
ValueTypeUntyped ValueType = "untyped"
45+
ValueTypeGauge ValueType = "gauge"
46+
ValueTypeCounter ValueType = "counter"
47+
ValueTypeUntyped ValueType = "untyped"
4748
)
4849

4950
// Config contains multiple modules.
5051
type Config struct {
51-
Modules map[string]Module `yaml:"modules"`
52+
Modules map[string]Module `yaml:"modules"`
5253
}
5354

5455
// Module contains metrics and headers defining a configuration
5556
type Module struct {
56-
Headers map[string]string `yaml:"headers,omitempty"`
57-
Metrics []Metric `yaml:"metrics"`
58-
HTTPClientConfig pconfig.HTTPClientConfig `yaml:"http_client_config,omitempty"`
59-
Body Body `yaml:"body,omitempty"`
60-
ValidStatusCodes []int `yaml:"valid_status_codes,omitempty"`
57+
Headers map[string]string `yaml:"headers,omitempty"`
58+
Metrics []Metric `yaml:"metrics"`
59+
HTTPClientConfig pconfig.HTTPClientConfig `yaml:"http_client_config,omitempty"`
60+
Body Body `yaml:"body,omitempty"`
61+
ValidStatusCodes []int `yaml:"valid_status_codes,omitempty"`
6162
}
6263

6364
type Body struct {
64-
Content string `yaml:"content"`
65-
Templatize bool `yaml:"templatize,omitempty"`
65+
Content string `yaml:"content"`
66+
Templatize bool `yaml:"templatize,omitempty"`
6667
}
6768

6869
func LoadConfig(configPath string) (Config, error) {
69-
var config Config
70-
data, err := ioutil.ReadFile(configPath)
71-
if err != nil {
72-
return config, err
73-
}
70+
var config Config
71+
data, err := ioutil.ReadFile(configPath)
72+
if err != nil {
73+
return config, err
74+
}
7475

75-
if err := yaml.Unmarshal(data, &config); err != nil {
76-
return config, err
77-
}
76+
if err := yaml.Unmarshal(data, &config); err != nil {
77+
return config, err
78+
}
7879

79-
// Complete Defaults
80-
for _, module := range config.Modules {
81-
for i := 0; i < len(module.Metrics); i++ {
82-
if module.Metrics[i].Type == "" {
83-
module.Metrics[i].Type = ValueScrape
84-
}
85-
if module.Metrics[i].Help == "" {
86-
module.Metrics[i].Help = module.Metrics[i].Name
87-
}
88-
if module.Metrics[i].ValueType == "" {
89-
module.Metrics[i].ValueType = ValueTypeUntyped
90-
}
91-
}
92-
}
80+
// Complete Defaults
81+
for _, module := range config.Modules {
82+
for i := 0; i < len(module.Metrics); i++ {
83+
if module.Metrics[i].Type == "" {
84+
module.Metrics[i].Type = ValueScrape
85+
}
86+
if module.Metrics[i].Help == "" {
87+
module.Metrics[i].Help = module.Metrics[i].Name
88+
}
89+
if module.Metrics[i].ValueType == "" {
90+
module.Metrics[i].ValueType = ValueTypeUntyped
91+
}
92+
}
93+
}
9394

94-
return config, nil
95-
}
95+
return config, nil
96+
}

examples/config.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,12 @@ modules:
88
labels:
99
environment: beta # static label
1010
location: "planet-{.location}" # dynamic label
11-
11+
- name: example_timestamped_value
12+
path: "{ .values[?(@.state == "INACTIVE")] }"
13+
epochTimestamp: "{ .timestamp }"
14+
help: Example of a timestamped value scrape in the json
15+
labels:
16+
environment: beta # static label
1217
- name: example_value
1318
type: object
1419
help: Example of sub-level value scrapes from a json

examples/data.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"counter": 1234,
3+
"timestamp": 1657568506,
34
"values": [
45
{
56
"id": "id-A",

exporter/collector.go

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
package exporter
1515

1616
import (
17+
"time"
1718
"bytes"
1819
"encoding/json"
1920

@@ -31,12 +32,13 @@ type JSONMetricCollector struct {
3132
}
3233

3334
type JSONMetric struct {
34-
Desc *prometheus.Desc
35-
Type config.ScrapeType
36-
KeyJSONPath string
37-
ValueJSONPath string
38-
LabelsJSONPaths []string
39-
ValueType prometheus.ValueType
35+
Desc *prometheus.Desc
36+
Type config.ScrapeType
37+
KeyJSONPath string
38+
ValueJSONPath string
39+
LabelsJSONPaths []string
40+
ValueType prometheus.ValueType
41+
EpochTimestampJSONPath string
4042
}
4143

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

5860
if floatValue, err := SanitizeValue(value); err == nil {
59-
60-
ch <- prometheus.MustNewConstMetric(
61+
metric := prometheus.MustNewConstMetric(
6162
m.Desc,
6263
m.ValueType,
6364
floatValue,
6465
extractLabels(mc.Logger, mc.Data, m.LabelsJSONPaths)...,
6566
)
67+
ch <- timestampMetric(mc.Logger, m, mc.Data, metric)
6668
} else {
6769
level.Error(mc.Logger).Log("msg", "Failed to convert extracted value to float64", "path", m.KeyJSONPath, "value", value, "err", err, "metric", m.Desc)
6870
continue
@@ -90,12 +92,13 @@ func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) {
9092
}
9193

9294
if floatValue, err := SanitizeValue(value); err == nil {
93-
ch <- prometheus.MustNewConstMetric(
95+
metric := prometheus.MustNewConstMetric(
9496
m.Desc,
9597
m.ValueType,
9698
floatValue,
9799
extractLabels(mc.Logger, jdata, m.LabelsJSONPaths)...,
98100
)
101+
ch <- timestampMetric(mc.Logger, m, jdata, metric)
99102
} else {
100103
level.Error(mc.Logger).Log("msg", "Failed to convert extracted value to float64", "path", m.ValueJSONPath, "value", value, "err", err, "metric", m.Desc)
101104
continue
@@ -157,3 +160,21 @@ func extractLabels(logger log.Logger, data []byte, paths []string) []string {
157160
}
158161
return labels
159162
}
163+
164+
func timestampMetric(logger log.Logger, m JSONMetric, data []byte, pm prometheus.Metric) prometheus.Metric {
165+
if m.EpochTimestampJSONPath == "" {
166+
return pm
167+
}
168+
ts, err := extractValue(logger, data, m.EpochTimestampJSONPath, false)
169+
if err != nil {
170+
level.Error(logger).Log("msg", "Failed to extract timestamp for metric", "path", m.KeyJSONPath, "err", err, "metric", m.Desc)
171+
return pm
172+
}
173+
epochTime, err := SanitizeIntValue(ts)
174+
if err != nil {
175+
level.Error(logger).Log("msg", "Failed to parse timestamp for metric", "path", m.KeyJSONPath, "err", err, "metric", m.Desc)
176+
return pm
177+
}
178+
timestamp := time.UnixMilli(epochTime)
179+
return prometheus.NewMetricWithTimestamp(timestamp, pm)
180+
}

exporter/util.go

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,19 @@ func SanitizeValue(s string) (float64, error) {
6262
return value, fmt.Errorf(resultErr)
6363
}
6464

65+
func SanitizeIntValue(s string) (int64, error) {
66+
var err error
67+
var value int64
68+
var resultErr string
69+
70+
if value, err = strconv.ParseInt(s, 10, 64); err == nil {
71+
return value, nil
72+
}
73+
resultErr = fmt.Sprintf("%s", err)
74+
75+
return value, fmt.Errorf(resultErr)
76+
}
77+
6578
func CreateMetricsList(c config.Module) ([]JSONMetric, error) {
6679
var (
6780
metrics []JSONMetric
@@ -91,9 +104,10 @@ func CreateMetricsList(c config.Module) ([]JSONMetric, error) {
91104
variableLabels,
92105
nil,
93106
),
94-
KeyJSONPath: metric.Path,
95-
LabelsJSONPaths: variableLabelsValues,
96-
ValueType: valueType,
107+
KeyJSONPath: metric.Path,
108+
LabelsJSONPaths: variableLabelsValues,
109+
ValueType: valueType,
110+
EpochTimestampJSONPath: metric.EpochTimestamp,
97111
}
98112
metrics = append(metrics, jsonMetric)
99113
case config.ObjectScrape:
@@ -112,10 +126,11 @@ func CreateMetricsList(c config.Module) ([]JSONMetric, error) {
112126
variableLabels,
113127
nil,
114128
),
115-
KeyJSONPath: metric.Path,
116-
ValueJSONPath: valuePath,
117-
LabelsJSONPaths: variableLabelsValues,
118-
ValueType: valueType,
129+
KeyJSONPath: metric.Path,
130+
ValueJSONPath: valuePath,
131+
LabelsJSONPaths: variableLabelsValues,
132+
ValueType: valueType,
133+
EpochTimestampJSONPath: metric.EpochTimestamp,
119134
}
120135
metrics = append(metrics, jsonMetric)
121136
}

0 commit comments

Comments
 (0)