-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
receive: Enable exemplars ingestion and querying (#4292)
* receive: Add support for ingesting exemplars Signed-off-by: Prem Saraswat <[email protected]> * receive: Expose exemplars api from Receiver Signed-off-by: Prem Saraswat <[email protected]> * Updated docs Signed-off-by: Prem Saraswat <[email protected]> * Fix exemplars API for tsdb returning null always Signed-off-by: Prem Saraswat <[email protected]> * Add tests for exemplars in receive Signed-off-by: Prem Saraswat <[email protected]> * Fix linting issues Signed-off-by: Prem Saraswat <[email protected]> * Skip querying exemplars if external labels doesn't match Signed-off-by: Prem Saraswat <[email protected]> * Add more details in tsdb.max-exemplars flag description Signed-off-by: Prem Saraswat <[email protected]> * exemplars: proxy: return error if matchers only contain external labels Signed-off-by: Prem Saraswat <[email protected]> * Parse matchers only once for MultiTSDB exemplars queries Signed-off-by: Prem Saraswat <[email protected]> * Add documentation and CHANGELOG entry Signed-off-by: Prem Saraswat <[email protected]>
- Loading branch information
Showing
13 changed files
with
679 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
// Copyright (c) The Thanos Authors. | ||
// Licensed under the Apache License 2.0. | ||
|
||
package exemplars | ||
|
||
import ( | ||
"github.com/pkg/errors" | ||
"github.com/prometheus/prometheus/promql/parser" | ||
"google.golang.org/grpc/codes" | ||
"google.golang.org/grpc/status" | ||
|
||
"github.com/thanos-io/thanos/pkg/exemplars/exemplarspb" | ||
) | ||
|
||
// MultiTSDB implements exemplarspb.ExemplarsServer that allows to fetch exemplars a MultiTSDB instance. | ||
type MultiTSDB struct { | ||
tsdbExemplarsServers func() map[string]*TSDB | ||
} | ||
|
||
// NewMultiTSDB creates new exemplars.MultiTSDB. | ||
func NewMultiTSDB(tsdbExemplarsServers func() map[string]*TSDB) *MultiTSDB { | ||
return &MultiTSDB{ | ||
tsdbExemplarsServers: tsdbExemplarsServers, | ||
} | ||
} | ||
|
||
// Exemplars returns all specified exemplars from a MultiTSDB instance. | ||
func (m *MultiTSDB) Exemplars(r *exemplarspb.ExemplarsRequest, s exemplarspb.Exemplars_ExemplarsServer) error { | ||
expr, err := parser.ParseExpr(r.Query) | ||
if err != nil { | ||
return status.Error(codes.Internal, err.Error()) | ||
} | ||
matchers := parser.ExtractSelectors(expr) | ||
|
||
for tenant, es := range m.tsdbExemplarsServers() { | ||
if err := es.Exemplars(matchers, r.Start, r.End, s); err != nil { | ||
return status.Error(codes.Aborted, errors.Wrapf(err, "get exemplars for tenant %s", tenant).Error()) | ||
} | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
// Copyright (c) The Thanos Authors. | ||
// Licensed under the Apache License 2.0. | ||
|
||
package exemplars | ||
|
||
import ( | ||
"github.com/gogo/status" | ||
"github.com/pkg/errors" | ||
"github.com/prometheus/prometheus/pkg/labels" | ||
"github.com/prometheus/prometheus/storage" | ||
"google.golang.org/grpc/codes" | ||
|
||
"github.com/thanos-io/thanos/pkg/exemplars/exemplarspb" | ||
"github.com/thanos-io/thanos/pkg/store/labelpb" | ||
) | ||
|
||
// TSDB allows fetching exemplars from a TSDB instance. | ||
type TSDB struct { | ||
db storage.ExemplarQueryable | ||
extLabels labels.Labels | ||
} | ||
|
||
// NewTSDB creates new exemplars.TSDB. | ||
func NewTSDB(db storage.ExemplarQueryable, extLabels labels.Labels) *TSDB { | ||
return &TSDB{ | ||
db: db, | ||
extLabels: extLabels, | ||
} | ||
} | ||
|
||
// Exemplars returns all specified exemplars from a TSDB instance. | ||
func (t *TSDB) Exemplars(matchers [][]*labels.Matcher, start, end int64, s exemplarspb.Exemplars_ExemplarsServer) error { | ||
match, selectors := selectorsMatchesExternalLabels(matchers, t.extLabels) | ||
|
||
if !match { | ||
return nil | ||
} | ||
|
||
if len(selectors) == 0 { | ||
return status.Error(codes.InvalidArgument, errors.New("no matchers specified (excluding external labels)").Error()) | ||
} | ||
|
||
eq, err := t.db.ExemplarQuerier(s.Context()) | ||
if err != nil { | ||
return status.Error(codes.Internal, err.Error()) | ||
} | ||
|
||
exemplars, err := eq.Select(start, end, selectors...) | ||
if err != nil { | ||
return status.Error(codes.Internal, err.Error()) | ||
} | ||
|
||
for _, e := range exemplars { | ||
exd := exemplarspb.ExemplarData{ | ||
SeriesLabels: labelpb.ZLabelSet{ | ||
Labels: labelpb.ZLabelsFromPromLabels(labelpb.ExtendSortedLabels(e.SeriesLabels, t.extLabels)), | ||
}, | ||
Exemplars: exemplarspb.ExemplarsFromPromExemplars(e.Exemplars), | ||
} | ||
if err := s.Send(exemplarspb.NewExemplarsResponse(&exd)); err != nil { | ||
return status.Error(codes.Aborted, err.Error()) | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// selectorsMatchesExternalLabels returns false if none of the selectors matches the external labels. | ||
// If true, it also returns an array of non-empty Prometheus matchers. | ||
func selectorsMatchesExternalLabels(selectors [][]*labels.Matcher, externalLabels labels.Labels) (bool, [][]*labels.Matcher) { | ||
matchedOnce := false | ||
|
||
var newSelectors [][]*labels.Matcher | ||
for _, m := range selectors { | ||
match, m := matchesExternalLabels(m, externalLabels) | ||
|
||
matchedOnce = matchedOnce || match | ||
if match && len(m) > 0 { | ||
newSelectors = append(newSelectors, m) | ||
} | ||
} | ||
|
||
return matchedOnce, newSelectors | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
// Copyright (c) The Thanos Authors. | ||
// Licensed under the Apache License 2.0. | ||
|
||
package exemplars | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/prometheus/prometheus/pkg/labels" | ||
"github.com/thanos-io/thanos/pkg/testutil" | ||
) | ||
|
||
func TestSelectorsMatchExternalLabels(t *testing.T) { | ||
t.Parallel() | ||
|
||
tests := map[string]struct { | ||
selectors [][]*labels.Matcher | ||
extLabels labels.Labels | ||
shouldMatch bool | ||
expectedSelectors [][]*labels.Matcher | ||
}{ | ||
"should return true for matching labels": { | ||
selectors: [][]*labels.Matcher{ | ||
{ | ||
labels.MustNewMatcher(labels.MatchEqual, "code", "200"), | ||
labels.MustNewMatcher(labels.MatchEqual, "receive", "true"), | ||
}, | ||
}, | ||
extLabels: labels.FromStrings("receive", "true"), | ||
shouldMatch: true, | ||
expectedSelectors: [][]*labels.Matcher{ | ||
{ | ||
labels.MustNewMatcher(labels.MatchEqual, "code", "200"), | ||
}, | ||
}, | ||
}, | ||
"should return true when the external labels are not present in input at all": { | ||
selectors: [][]*labels.Matcher{ | ||
{ | ||
labels.MustNewMatcher(labels.MatchEqual, "code", "200"), | ||
}, | ||
}, | ||
extLabels: labels.FromStrings("receive", "true"), | ||
shouldMatch: true, | ||
expectedSelectors: [][]*labels.Matcher{ | ||
{ | ||
labels.MustNewMatcher(labels.MatchEqual, "code", "200"), | ||
}, | ||
}, | ||
}, | ||
"should return true when only some of matchers slice are matching": { | ||
selectors: [][]*labels.Matcher{ | ||
{ | ||
labels.MustNewMatcher(labels.MatchEqual, "code", "200"), | ||
labels.MustNewMatcher(labels.MatchRegexp, "receive", "false"), | ||
}, | ||
{ | ||
labels.MustNewMatcher(labels.MatchEqual, "code", "500"), | ||
labels.MustNewMatcher(labels.MatchRegexp, "receive", "true"), | ||
}, | ||
}, | ||
extLabels: labels.FromStrings("receive", "true", "replica", "0"), | ||
shouldMatch: true, | ||
expectedSelectors: [][]*labels.Matcher{ | ||
{ | ||
labels.MustNewMatcher(labels.MatchEqual, "code", "500"), | ||
}, | ||
}, | ||
}, | ||
"should return false when the external labels are not matching": { | ||
selectors: [][]*labels.Matcher{ | ||
{ | ||
labels.MustNewMatcher(labels.MatchEqual, "code", "200"), | ||
labels.MustNewMatcher(labels.MatchNotEqual, "replica", "1"), | ||
}, | ||
{ | ||
labels.MustNewMatcher(labels.MatchEqual, "op", "get"), | ||
labels.MustNewMatcher(labels.MatchEqual, "replica", "0"), | ||
}, | ||
}, | ||
extLabels: labels.FromStrings("replica", "1"), | ||
shouldMatch: false, | ||
expectedSelectors: nil, | ||
}, | ||
} | ||
|
||
for name, tdata := range tests { | ||
t.Run(name, func(t *testing.T) { | ||
match, selectors := selectorsMatchesExternalLabels(tdata.selectors, tdata.extLabels) | ||
testutil.Equals(t, tdata.shouldMatch, match) | ||
|
||
testutil.Equals(t, tdata.expectedSelectors, selectors) | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.