Skip to content

Commit 830cb7b

Browse files
johnseekinsJohn Seekins
andauthored
Prometheus' data model requires more characters be switched (#87)
Follow prometheus' data model for metric names better, allow for flexible replace of '.'. Update README correctly and ensure periodCharacter is used in queries. Co-authored-by: John Seekins <[email protected]>
1 parent e9388db commit 830cb7b

File tree

5 files changed

+24
-9
lines changed

5 files changed

+24
-9
lines changed

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,11 @@ Geras additionally listens on a HTTP port for Prometheus `/metrics` queries and
7373
-metrics-name-response-rewriting
7474
Rewrite '.' to ':' and '-' to '_' in all responses (Prometheus
7575
remote_read won't accept these, while Thanos will) (default true)
76+
-metrics-name-response-rewriting
77+
Rewrite '.' to a defined character and other bad characters to '_' in all responses (Prometheus
78+
remote_read won't accept these, while Thanos will) (default true)
79+
-period-character-replace
80+
Rewrite '.' to a defined charater that Prometheus will handle better. (default ':')
7681
```
7782

7883
When specifying multiple labels, you will need to repeat the argument name, e.g:
@@ -86,6 +91,6 @@ When specifying multiple labels, you will need to repeat the argument name, e.g:
8691
* PromQL supports queries without `__name__`. This is not possible in OpenTSDB and no results will be returned if the query doesn't match on a metric name.
8792
* Geras periodically loads metric names from OpenTSDB and keeps them in memory to support queries like `{__name__=~"regexp"}`.
8893
* Thanos' primary timeseries backend is Prometheus, which doesn't support unquoted dots in metric names. However OpenTSDB metrics generally use `.` as a seperator within names. In order to query names containing a `.` you will need to either:
89-
* Replace all `.` with `:` in your query; or
94+
* Replace all `.` with another character (we like `:`).
9095
* Use the `__name__` label to specify the metric name, e.g. `{__name__="cpu.percent"}`
9196
* Also watch out for `-` (dashes) in your metric names

cmd/geras/main.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,8 @@ func main() {
114114
allowedMetricNamesRE := flag.String("metrics-allowed-regexp", ".*", "Regexp of metrics to allow")
115115
blockedMetricNamesRE := flag.String("metrics-blocked-regexp", "", "Regexp of metrics to block (empty disables blocking)")
116116
enableMetricSuggestions := flag.Bool("metrics-suggestions", true, "Enable metric suggestions (can be expensive)")
117-
enableMetricNameRewriting := flag.Bool("metrics-name-response-rewriting", true, "Rewrite '.' to ':' and '-' to '_' in all responses (Prometheus remote_read won't accept these, while Thanos will)")
117+
periodCharacter := flag.String("period-character-replace", ":", "Replace any periods in metric name with this character")
118+
enableMetricNameRewriting := flag.Bool("metrics-name-response-rewriting", true, "Rewrite any character not in '[^a-zA-Z0-9_:.]' to '_' in all responses and '.' to a defined charater (Prometheus remote_read won't accept these, while Thanos will)")
118119
var labels multipleStringFlags
119120
flag.Var(&labels, "label", "Label to expose on the Store API, of the form '<key>=<value>'. May be repeated.")
120121
flag.Parse()
@@ -185,7 +186,7 @@ func main() {
185186
prometheus.DefaultRegisterer.MustRegister(version.NewCollector("geras"))
186187

187188
// create openTSDBStore and expose its api on a grpc server
188-
srv := store.NewOpenTSDBStore(logger, client, prometheus.DefaultRegisterer, *refreshInterval, *refreshTimeout, storeLabels, allowedMetricNames, blockedMetricNames, *enableMetricSuggestions, *enableMetricNameRewriting, *healthcheckMetric)
189+
srv := store.NewOpenTSDBStore(logger, client, prometheus.DefaultRegisterer, *refreshInterval, *refreshTimeout, storeLabels, allowedMetricNames, blockedMetricNames, *enableMetricSuggestions, *enableMetricNameRewriting, *healthcheckMetric, *periodCharacter)
189190
grpcSrv := grpc.NewServer(
190191
grpc.StreamInterceptor(grpc_prometheus.StreamServerInterceptor),
191192
grpc.UnaryInterceptor(grpc_prometheus.UnaryServerInterceptor),

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
module github.com/G-Research/geras
22

3+
go 1.15
4+
35
require (
46
github.com/G-Research/opentsdb-goclient v0.0.0-20191219203319-f9f2aa5b2624
57
github.com/go-kit/kit v0.9.0

pkg/store/store.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ type OpenTSDBStore struct {
4141
storeLabels []storepb.Label
4242
storeLabelsMap map[string]string
4343
healthcheckMetric string
44+
periodCharacter string
4445
}
4546

4647
var (
@@ -52,6 +53,7 @@ var (
5253
storepb.Aggr_COUNTER: "avg",
5354
}
5455
downsampleToAggregate map[string]storepb.Aggr
56+
replaceChars = regexp.MustCompile("[^a-zA-Z0-9_:.]")
5557
)
5658

5759
func init() {
@@ -64,7 +66,7 @@ func init() {
6466
}
6567
}
6668

67-
func NewOpenTSDBStore(logger log.Logger, client opentsdb.ClientContext, reg prometheus.Registerer, refreshInterval, refreshTimeout time.Duration, storeLabels []storepb.Label, allowedMetricNames, blockedMetricNames *regexp.Regexp, enableMetricSuggestions, enableMetricNameRewriting bool, healthcheckMetric string) *OpenTSDBStore {
69+
func NewOpenTSDBStore(logger log.Logger, client opentsdb.ClientContext, reg prometheus.Registerer, refreshInterval, refreshTimeout time.Duration, storeLabels []storepb.Label, allowedMetricNames, blockedMetricNames *regexp.Regexp, enableMetricSuggestions, enableMetricNameRewriting bool, healthcheckMetric string, periodCharacter string) *OpenTSDBStore {
6870
// Extract the store labels into a map for faster access later
6971
storeLabelsMap := map[string]string{}
7072
for _, l := range storeLabels {
@@ -83,6 +85,7 @@ func NewOpenTSDBStore(logger log.Logger, client opentsdb.ClientContext, reg prom
8385
allowedMetricNames: allowedMetricNames,
8486
blockedMetricNames: blockedMetricNames,
8587
healthcheckMetric: healthcheckMetric,
88+
periodCharacter: periodCharacter,
8689
}
8790
if client != nil {
8891
store.updateMetrics(context.Background(), logger)
@@ -376,8 +379,8 @@ func (store *OpenTSDBStore) getMatchingMetricNames(matcher storepb.LabelMatcher)
376379
if matcher.Name != "__name__" {
377380
return nil, errors.New("getMatchingMetricNames must be called on __name__ matcher")
378381
}
379-
if matcher.Type == storepb.LabelMatcher_EQ {
380-
value := strings.Replace(matcher.Value, ":", ".", -1)
382+
if matcher.Type == storepb.LabelMatcher_EQ && store.periodCharacter != "" {
383+
value := strings.Replace(matcher.Value, store.periodCharacter, ".", -1)
381384
return []string{value}, nil
382385
} else if matcher.Type == storepb.LabelMatcher_NEQ {
383386
// we can support this, but we should not.
@@ -572,7 +575,10 @@ func (store *OpenTSDBStore) checkMetricNames(metricNames []string, fullBlock boo
572575
func (store *OpenTSDBStore) convertOpenTSDBResultsToSeriesResponse(respI *opentsdb.QueryRespItem) (*storepb.SeriesResponse, int, error) {
573576
name := respI.Metric
574577
if store.enableMetricNameRewriting {
575-
name = strings.ReplaceAll(strings.ReplaceAll(name, ".", ":"), "-", "_")
578+
name = replaceChars.ReplaceAllString(name, "_")
579+
if store.periodCharacter != "" {
580+
name = strings.ReplaceAll(name, ".", store.periodCharacter)
581+
}
576582
}
577583
seriesLabels := make([]storepb.Label, 1+len(respI.Tags)+len(store.storeLabels))
578584
i := 0

pkg/store/store_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -886,7 +886,7 @@ func TestComposeOpenTSDBQuery(t *testing.T) {
886886
allowedMetrics = test.allowedMetrics
887887
}
888888
store := NewOpenTSDBStore(
889-
log.NewJSONLogger(&testLogger{t}), nil, nil, time.Duration(0), 1*time.Minute, nil, allowedMetrics, test.blockedMetrics, false, false, "foo")
889+
log.NewJSONLogger(&testLogger{t}), nil, nil, time.Duration(0), 1*time.Minute, nil, allowedMetrics, test.blockedMetrics, false, false, "foo", ":")
890890
store.metricNames = test.knownMetrics
891891
p, _, err := store.composeOpenTSDBQuery(&test.req)
892892
if test.err != nil {
@@ -1187,7 +1187,7 @@ func TestConvertOpenTSDBResultsToSeriesResponse(t *testing.T) {
11871187
}
11881188
for i, test := range testCases {
11891189
store := NewOpenTSDBStore(
1190-
log.NewJSONLogger(&testLogger{t}), nil, nil, time.Duration(0), 1*time.Minute, test.storeLabels, nil, nil, false, true, "foo")
1190+
log.NewJSONLogger(&testLogger{t}), nil, nil, time.Duration(0), 1*time.Minute, test.storeLabels, nil, nil, false, true, "foo", ":")
11911191
converted, _, err := store.convertOpenTSDBResultsToSeriesResponse(&test.input)
11921192
if err != nil {
11931193
t.Errorf("unexpected error: %s", err.Error())
@@ -1343,6 +1343,7 @@ func TestGetMatchingMetricNames(t *testing.T) {
13431343
"tsd.rpc.received",
13441344
"tsd.rpc.unauthorized",
13451345
},
1346+
periodCharacter: ":",
13461347
}
13471348
output, err := store.getMatchingMetricNames(test.input)
13481349

0 commit comments

Comments
 (0)