From 17e342387ddd598a1faa2e95fca5430aab436afa Mon Sep 17 00:00:00 2001 From: Oleg Zaytsev Date: Fri, 23 Jul 2021 14:00:22 +0200 Subject: [PATCH] LabelNames API with matchers (#3) * Upgrade prometheus to b1ed4a0a663d0c62526312311c7529471abbc565 Updated Prometheus to latest commit that includes changes in the LabelQuerier.LabelNames method signature: https://github.com/prometheus/prometheus/pull/9083 Signed-off-by: Oleg Zaytsev * Add matchers to all LabelNames implementations Signed-off-by: Oleg Zaytsev * Pass matchers from distributor to ingester's block tsdb Signed-off-by: Oleg Zaytsev * Ingester.LabelNames(w/matchers) for chunks too Signed-off-by: Oleg Zaytsev * Use the old distributor query path when cfg is disabled Signed-off-by: Oleg Zaytsev * Sort label names in old implementations Signed-off-by: Oleg Zaytsev * BlockStoreQueryable querying both paths Signed-off-by: Oleg Zaytsev * Fix pkg/querier/querier_test.go Signed-off-by: Oleg Zaytsev * Use the provided ctx Signed-off-by: Oleg Zaytsev * Make doc * Test distributorQueryable.LabelNames And fix missing matchers in the call to Distributor Signed-off-by: Oleg Zaytsev * Test Distributor.LabelNames Signed-off-by: Oleg Zaytsev * Test Ingester.LabelNames with matchers Signed-off-by: Oleg Zaytsev * Add matchers to querier test Signed-off-by: Oleg Zaytsev * Add CHANGELOG.md entry Signed-off-by: Oleg Zaytsev * Move changes to mimir/unreleased changelog section Signed-off-by: Oleg Zaytsev * Add another label to testcase to improve test If multiple matchers didn't work, we wouldn't realize as the result set was the same as for a single matcher. Now the multiple matcher doesn't match the series with status=500 which has a reason=broken label Signed-off-by: Oleg Zaytsev * Update docs, add `enabled` suffix to the config. Signed-off-by: Oleg Zaytsev * Remove fixme comment and feature flag from blocks_store_queryable All the StoreGateways (actually, one) are already supporting matchers in the call so we don't need to use a feature flag on this. Signed-off-by: Oleg Zaytsev * Update span name Signed-off-by: Oleg Zaytsev Co-authored-by: Marco Pracucci * Remove feature flag from blocks_store_queryable Signed-off-by: Oleg Zaytsev * Fixup changelog entry Signed-off-by: Oleg Zaytsev * Remove fingerprint collision testcase At least until we find a collision between two different label names. Signed-off-by: Oleg Zaytsev * Handle matchers in tenantfederation/mergeQuerier.LabelNames Signed-off-by: Oleg Zaytsev * Opinionated refactor of filterValuesByMatchers Since there are three options, IMO, a switch is clearer than if/continue/if/elseif structure. Signed-off-by: Oleg Zaytsev Co-authored-by: Marco Pracucci --- CHANGELOG.md | 2 + docs/blocks-storage/querier.md | 7 + docs/configuration/config-file-reference.md | 7 + docs/configuration/v1-guarantees.md | 3 + go.mod | 2 +- go.sum | 4 +- pkg/distributor/distributor.go | 9 +- pkg/distributor/distributor_test.go | 127 ++++++++++ pkg/ingester/client/compat.go | 29 +++ pkg/ingester/client/compat_test.go | 21 ++ pkg/ingester/client/ingester.pb.go | 227 ++++++++++++------ pkg/ingester/client/ingester.proto | 1 + pkg/ingester/ingester.go | 29 ++- pkg/ingester/ingester_test.go | 64 +++++ pkg/ingester/ingester_v2.go | 9 +- pkg/ingester/ingester_v2_test.go | 32 ++- pkg/querier/blocks_store_queryable.go | 31 ++- pkg/querier/chunk_store_queryable.go | 2 +- pkg/querier/distributor_queryable.go | 57 ++++- pkg/querier/distributor_queryable_test.go | 56 ++++- pkg/querier/duplicates_test.go | 2 +- pkg/querier/error_translate_queryable.go | 8 +- pkg/querier/error_translate_queryable_test.go | 2 +- pkg/querier/lazyquery/lazyquery.go | 4 +- pkg/querier/querier.go | 16 +- pkg/querier/querier_test.go | 18 +- pkg/querier/queryrange/promql_test.go | 6 +- pkg/querier/queryrange/queryable.go | 2 +- pkg/querier/queryrange/test_utils.go | 2 +- pkg/querier/remote_read_test.go | 2 +- .../tenantfederation/merge_queryable.go | 52 ++-- .../tenantfederation/merge_queryable_test.go | 89 ++++++- .../prometheus/storage/interface.go | 5 +- .../prometheus/prometheus/storage/merge.go | 4 +- .../prometheus/prometheus/storage/noop.go | 4 +- .../prometheus/storage/remote/read.go | 2 +- .../prometheus/storage/secondary.go | 4 +- .../prometheus/prometheus/tsdb/block.go | 26 +- .../prometheus/prometheus/tsdb/head.go | 38 ++- .../prometheus/prometheus/tsdb/index/index.go | 77 +++++- .../prometheus/prometheus/tsdb/querier.go | 21 +- .../prometheus/prometheus/web/api/v1/api.go | 26 +- vendor/modules.txt | 2 +- 43 files changed, 911 insertions(+), 220 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a17c8283d8..f7fc855fd85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,12 @@ * [CHANGE] Changed `-alertmanager.storage.type` default value from `configdb` to `local`. #15 * [CHANGE] Prevent path traversal attack from users able to control the HTTP header `X-Scope-OrgID`. (CVE-2021-36157) #20 * Users only have control of the HTTP header when Mimir is not frontend by an auth proxy validating the tenant IDs +* [ENHANCEMENT] Querier now can use the `LabelNames` call with matchers, if matchers are provided in the `/labels` API call, instead of using the more expensive `MetricsForLabelMatchers` call as before. This can be enabled by enabling the `-querier.query-label-names-with-matchers-enabled` flag once the ingesters are updated to this version. In the future this is expected to become the default behavior. #3 * [BUGFIX] Upgrade Prometheus. TSDB now waits for pending readers before truncating Head block, fixing the `chunk not found` error and preventing wrong query results. #16 ## master / unreleased + * [FEATURE] Ruler: Add new `-ruler.query-stats-enabled` which when enabled will report the `cortex_ruler_query_seconds_total` as a per-user metric that tracks the sum of the wall time of executing queries in the ruler in seconds. #4317 * [FEATURE] Query Frontend: Add `cortex_query_fetched_series_total` and `cortex_query_fetched_chunks_bytes_total` per-user counters to expose the number of series and bytes fetched as part of queries. These metrics can be enabled with the `-frontend.query-stats-enabled` flag (or its respective YAML config option `query_stats_enabled`). #4343 * [CHANGE] Update Go version to 1.16.6. #4362 diff --git a/docs/blocks-storage/querier.md b/docs/blocks-storage/querier.md index a7080d4eb2a..e15b973f5f1 100644 --- a/docs/blocks-storage/querier.md +++ b/docs/blocks-storage/querier.md @@ -136,6 +136,13 @@ querier: # CLI flag: -querier.at-modifier-enabled [at_modifier_enabled: | default = false] + # True to enable queriers to use an optimized implementation which passes down + # to ingesters the label matchers when running the label names API. Can be + # enabled once all ingesters run a version >= the one where this option has + # been introduced. + # CLI flag: -querier.query-label-names-with-matchers-enabled + [query_label_names_with_matchers_enabled: | default = false] + # The time after which a metric should be queried from storage and not just # ingesters. 0 means all queries are sent to store. When running the blocks # storage, if this option is enabled, the time range of the query sent to the diff --git a/docs/configuration/config-file-reference.md b/docs/configuration/config-file-reference.md index a3ab133f6c8..e8bfdc89167 100644 --- a/docs/configuration/config-file-reference.md +++ b/docs/configuration/config-file-reference.md @@ -862,6 +862,13 @@ The `querier_config` configures the Cortex querier. # CLI flag: -querier.at-modifier-enabled [at_modifier_enabled: | default = false] +# True to enable queriers to use an optimized implementation which passes down +# to ingesters the label matchers when running the label names API. Can be +# enabled once all ingesters run a version >= the one where this option has been +# introduced. +# CLI flag: -querier.query-label-names-with-matchers-enabled +[query_label_names_with_matchers_enabled: | default = false] + # The time after which a metric should be queried from storage and not just # ingesters. 0 means all queries are sent to store. When running the blocks # storage, if this option is enabled, the time range of the query sent to the diff --git a/docs/configuration/v1-guarantees.md b/docs/configuration/v1-guarantees.md index 3af5dd4cd14..57ce7bb33f4 100644 --- a/docs/configuration/v1-guarantees.md +++ b/docs/configuration/v1-guarantees.md @@ -95,3 +95,6 @@ Currently experimental features are: - `-alertmanager.sharding-ring.heartbeat-period=0` - `-compactor.ring.heartbeat-period=0` - `-store-gateway.sharding-ring.heartbeat-period=0` +- `LabelNames` calls using matchers + - `-querier.query-label-names-with-matchers-enabled` + diff --git a/go.mod b/go.mod index c2522558931..7e3d6451e4d 100644 --- a/go.mod +++ b/go.mod @@ -46,7 +46,7 @@ require ( github.com/prometheus/client_golang v1.11.0 github.com/prometheus/client_model v0.2.0 github.com/prometheus/common v0.29.0 - github.com/prometheus/prometheus v1.8.2-0.20210720084720-59d02b5ef003 + github.com/prometheus/prometheus v1.8.2-0.20210720123808-b1ed4a0a663d github.com/segmentio/fasthash v0.0.0-20180216231524-a72b379d632e github.com/sony/gobreaker v0.4.1 github.com/spf13/afero v1.2.2 diff --git a/go.sum b/go.sum index 3732020612f..6fab515c075 100644 --- a/go.sum +++ b/go.sum @@ -1520,8 +1520,8 @@ github.com/prometheus/prometheus v1.8.2-0.20210215121130-6f488061dfb4/go.mod h1: github.com/prometheus/prometheus v1.8.2-0.20210315220929-1cba1741828b/go.mod h1:MS/bpdil77lPbfQeKk6OqVQ9OLnpN3Rszd0hka0EOWE= github.com/prometheus/prometheus v1.8.2-0.20210324152458-c7a62b95cea0/go.mod h1:sf7j/iAbhZahjeC0s3wwMmp5dksrJ/Za1UKdR+j6Hmw= github.com/prometheus/prometheus v1.8.2-0.20210421143221-52df5ef7a3be/go.mod h1:WbIKsp4vWCoPHis5qQfd0QimLOR7qe79roXN5O8U8bs= -github.com/prometheus/prometheus v1.8.2-0.20210720084720-59d02b5ef003 h1:MYbsDV+OIFLkqwea5oC3jIUp/HxFyXQrvXpw/hooMRQ= -github.com/prometheus/prometheus v1.8.2-0.20210720084720-59d02b5ef003/go.mod h1:o6V+A4iPEWjLG0rSEKeev3OzfBZwP+ay+4iS4dkfLI4= +github.com/prometheus/prometheus v1.8.2-0.20210720123808-b1ed4a0a663d h1:UnqZFF2qXa+ctCfbss/J4yn9rTVoTiuawjrokqwt4Hg= +github.com/prometheus/prometheus v1.8.2-0.20210720123808-b1ed4a0a663d/go.mod h1:o6V+A4iPEWjLG0rSEKeev3OzfBZwP+ay+4iS4dkfLI4= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rafaeljusto/redigomock v0.0.0-20190202135759-257e089e14a1/go.mod h1:JaY6n2sDr+z2WTsXkOmNRUfDy6FN0L6Nk7x06ndm4tY= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= diff --git a/pkg/distributor/distributor.go b/pkg/distributor/distributor.go index ca9039dc542..e48b61f7d7c 100644 --- a/pkg/distributor/distributor.go +++ b/pkg/distributor/distributor.go @@ -881,16 +881,17 @@ func (d *Distributor) LabelValuesForLabelName(ctx context.Context, from, to mode } // LabelNames returns all of the label names. -func (d *Distributor) LabelNames(ctx context.Context, from, to model.Time) ([]string, error) { +func (d *Distributor) LabelNames(ctx context.Context, from, to model.Time, matchers ...*labels.Matcher) ([]string, error) { replicationSet, err := d.GetIngestersForMetadata(ctx) if err != nil { return nil, err } - req := &ingester_client.LabelNamesRequest{ - StartTimestampMs: int64(from), - EndTimestampMs: int64(to), + req, err := ingester_client.ToLabelNamesRequest(from, to, matchers) + if err != nil { + return nil, err } + resps, err := d.ForReplicationSet(ctx, replicationSet, func(ctx context.Context, client ingester_client.IngesterClient) (interface{}, error) { return client.LabelNames(ctx, req) }) diff --git a/pkg/distributor/distributor_test.go b/pkg/distributor/distributor_test.go index 2b0fab3c4f2..b3402130bda 100644 --- a/pkg/distributor/distributor_test.go +++ b/pkg/distributor/distributor_test.go @@ -1808,6 +1808,105 @@ func TestDistributor_MetricsForLabelMatchers(t *testing.T) { } } +func TestDistributor_LabelNames(t *testing.T) { + const numIngesters = 5 + + fixtures := []struct { + lbls labels.Labels + value float64 + timestamp int64 + }{ + {labels.Labels{{Name: labels.MetricName, Value: "test_1"}, {Name: "status", Value: "200"}}, 1, 100000}, + {labels.Labels{{Name: labels.MetricName, Value: "test_1"}, {Name: "status", Value: "500"}, {Name: "reason", Value: "broken"}}, 1, 110000}, + {labels.Labels{{Name: labels.MetricName, Value: "test_2"}}, 2, 200000}, + } + + tests := map[string]struct { + shuffleShardEnabled bool + shuffleShardSize int + matchers []*labels.Matcher + expectedResult []string + expectedIngesters int + }{ + "should return an empty response if no metric match": { + matchers: []*labels.Matcher{ + mustNewMatcher(labels.MatchEqual, model.MetricNameLabel, "unknown"), + }, + expectedResult: []string{}, + expectedIngesters: numIngesters, + }, + "should filter metrics by single matcher": { + matchers: []*labels.Matcher{ + mustNewMatcher(labels.MatchEqual, model.MetricNameLabel, "test_1"), + }, + expectedResult: []string{labels.MetricName, "reason", "status"}, + expectedIngesters: numIngesters, + }, + "should filter metrics by multiple matchers": { + matchers: []*labels.Matcher{ + mustNewMatcher(labels.MatchEqual, "status", "200"), + mustNewMatcher(labels.MatchEqual, model.MetricNameLabel, "test_1"), + }, + expectedResult: []string{labels.MetricName, "status"}, + expectedIngesters: numIngesters, + }, + "should query only ingesters belonging to tenant's subring if shuffle sharding is enabled": { + shuffleShardEnabled: true, + shuffleShardSize: 3, + matchers: []*labels.Matcher{ + mustNewMatcher(labels.MatchEqual, model.MetricNameLabel, "test_1"), + }, + expectedResult: []string{labels.MetricName, "reason", "status"}, + expectedIngesters: 3, + }, + "should query all ingesters if shuffle sharding is enabled but shard size is 0": { + shuffleShardEnabled: true, + shuffleShardSize: 0, + matchers: []*labels.Matcher{ + mustNewMatcher(labels.MatchEqual, model.MetricNameLabel, "test_1"), + }, + expectedResult: []string{labels.MetricName, "reason", "status"}, + expectedIngesters: numIngesters, + }, + } + + for testName, testData := range tests { + t.Run(testName, func(t *testing.T) { + now := model.Now() + + // Create distributor + ds, ingesters, r, _ := prepare(t, prepConfig{ + numIngesters: numIngesters, + happyIngesters: numIngesters, + numDistributors: 1, + shardByAllLabels: true, + shuffleShardEnabled: testData.shuffleShardEnabled, + shuffleShardSize: testData.shuffleShardSize, + }) + defer stopAll(ds, r) + + // Push fixtures + ctx := user.InjectOrgID(context.Background(), "test") + + for _, series := range fixtures { + req := mockWriteRequest(series.lbls, series.value, series.timestamp) + _, err := ds[0].Push(ctx, req) + require.NoError(t, err) + } + + names, err := ds[0].LabelNames(ctx, now, now, testData.matchers...) + require.NoError(t, err) + assert.ElementsMatch(t, testData.expectedResult, names) + + // Check how many ingesters have been queried. + // Due to the quorum the distributor could cancel the last request towards ingesters + // if all other ones are successful, so we're good either has been queried X or X-1 + // ingesters. + assert.Contains(t, []int{testData.expectedIngesters, testData.expectedIngesters - 1}, countMockIngestersCalls(ingesters, "LabelNames")) + }) + } +} + func TestDistributor_MetricsMetadata(t *testing.T) { const numIngesters = 5 @@ -2346,6 +2445,34 @@ func (i *mockIngester) MetricsForLabelMatchers(ctx context.Context, req *client. return &response, nil } +func (i *mockIngester) LabelNames(ctx context.Context, req *client.LabelNamesRequest, opts ...grpc.CallOption) (*client.LabelNamesResponse, error) { + i.Lock() + defer i.Unlock() + + i.trackCall("LabelNames") + + if !i.happy { + return nil, errFail + } + + _, _, matchers, err := client.FromLabelNamesRequest(req) + if err != nil { + return nil, err + } + + response := client.LabelNamesResponse{} + for _, ts := range i.timeseries { + if match(ts.Labels, matchers) { + for _, lbl := range ts.Labels { + response.LabelNames = append(response.LabelNames, lbl.Name) + } + } + } + sort.Strings(response.LabelNames) + + return &response, nil +} + func (i *mockIngester) MetricsMetadata(ctx context.Context, req *client.MetricsMetadataRequest, opts ...grpc.CallOption) (*client.MetricsMetadataResponse, error) { i.Lock() defer i.Unlock() diff --git a/pkg/ingester/client/compat.go b/pkg/ingester/client/compat.go index 7350adcd3b2..d627435c9d5 100644 --- a/pkg/ingester/client/compat.go +++ b/pkg/ingester/client/compat.go @@ -172,6 +172,35 @@ func FromLabelValuesRequest(req *LabelValuesRequest) (string, int64, int64, []*l return req.LabelName, req.StartTimestampMs, req.EndTimestampMs, matchers, nil } +// ToLabelNamesRequest builds a LabelNamesRequest proto +func ToLabelNamesRequest(from, to model.Time, matchers []*labels.Matcher) (*LabelNamesRequest, error) { + ms, err := toLabelMatchers(matchers) + if err != nil { + return nil, err + } + + return &LabelNamesRequest{ + StartTimestampMs: int64(from), + EndTimestampMs: int64(to), + Matchers: &LabelMatchers{Matchers: ms}, + }, nil +} + +// FromLabelNamesRequest unpacks a LabelNamesRequest proto +func FromLabelNamesRequest(req *LabelNamesRequest) (int64, int64, []*labels.Matcher, error) { + var err error + var matchers []*labels.Matcher + + if req.Matchers != nil { + matchers, err = FromLabelMatchers(req.Matchers.Matchers) + if err != nil { + return 0, 0, nil, err + } + } + + return req.StartTimestampMs, req.EndTimestampMs, matchers, nil +} + func toLabelMatchers(matchers []*labels.Matcher) ([]*LabelMatcher, error) { result := make([]*LabelMatcher, 0, len(matchers)) for _, matcher := range matchers { diff --git a/pkg/ingester/client/compat_test.go b/pkg/ingester/client/compat_test.go index 0928bd09156..fb644d536ac 100644 --- a/pkg/ingester/client/compat_test.go +++ b/pkg/ingester/client/compat_test.go @@ -7,6 +7,9 @@ import ( "strconv" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/prometheus/common/model" "github.com/prometheus/prometheus/pkg/labels" ) @@ -59,6 +62,24 @@ func TestQueryRequest(t *testing.T) { } } +func TestLabelNamesRequest(t *testing.T) { + const ( + mint, maxt = 0, 10 + ) + + matchers := []*labels.Matcher{labels.MustNewMatcher(labels.MatchEqual, "foo", "bar")} + + req, err := ToLabelNamesRequest(mint, maxt, matchers) + require.NoError(t, err) + + actualMinT, actualMaxT, actualMatchers, err := FromLabelNamesRequest(req) + require.NoError(t, err) + + assert.Equal(t, int64(mint), actualMinT) + assert.Equal(t, int64(maxt), actualMaxT) + assert.Equal(t, matchers, actualMatchers) +} + func buildTestMatrix(numSeries int, samplesPerSeries int, offset int) model.Matrix { m := make(model.Matrix, 0, numSeries) for i := 0; i < numSeries; i++ { diff --git a/pkg/ingester/client/ingester.pb.go b/pkg/ingester/client/ingester.pb.go index 8949becf7f1..8487a274476 100644 --- a/pkg/ingester/client/ingester.pb.go +++ b/pkg/ingester/client/ingester.pb.go @@ -516,8 +516,9 @@ func (m *LabelValuesResponse) GetLabelValues() []string { } type LabelNamesRequest struct { - StartTimestampMs int64 `protobuf:"varint,1,opt,name=start_timestamp_ms,json=startTimestampMs,proto3" json:"start_timestamp_ms,omitempty"` - EndTimestampMs int64 `protobuf:"varint,2,opt,name=end_timestamp_ms,json=endTimestampMs,proto3" json:"end_timestamp_ms,omitempty"` + StartTimestampMs int64 `protobuf:"varint,1,opt,name=start_timestamp_ms,json=startTimestampMs,proto3" json:"start_timestamp_ms,omitempty"` + EndTimestampMs int64 `protobuf:"varint,2,opt,name=end_timestamp_ms,json=endTimestampMs,proto3" json:"end_timestamp_ms,omitempty"` + Matchers *LabelMatchers `protobuf:"bytes,3,opt,name=matchers,proto3" json:"matchers,omitempty"` } func (m *LabelNamesRequest) Reset() { *m = LabelNamesRequest{} } @@ -566,6 +567,13 @@ func (m *LabelNamesRequest) GetEndTimestampMs() int64 { return 0 } +func (m *LabelNamesRequest) GetMatchers() *LabelMatchers { + if m != nil { + return m.Matchers + } + return nil +} + type LabelNamesResponse struct { LabelNames []string `protobuf:"bytes,1,rep,name=label_names,json=labelNames,proto3" json:"label_names,omitempty"` } @@ -1348,86 +1356,86 @@ func init() { func init() { proto.RegisterFile("ingester.proto", fileDescriptor_60f6df4f3586b478) } var fileDescriptor_60f6df4f3586b478 = []byte{ - // 1253 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x57, 0xcd, 0x6f, 0x13, 0x57, - 0x10, 0xdf, 0x17, 0x7f, 0x10, 0x8f, 0x1d, 0xe3, 0xbc, 0x00, 0x31, 0x4b, 0xd9, 0xd0, 0x95, 0x68, - 0xad, 0xb6, 0x38, 0x90, 0x7e, 0x08, 0xaa, 0x56, 0xc8, 0x81, 0x00, 0x29, 0x98, 0xc0, 0xc6, 0xb4, - 0x55, 0xa5, 0x6a, 0xb5, 0xb6, 0x5f, 0x9c, 0x2d, 0xfb, 0xc5, 0xbe, 0xb7, 0x15, 0xdc, 0x2a, 0xf5, - 0x0f, 0x68, 0xd5, 0x53, 0xaf, 0xbd, 0xf5, 0xdc, 0x4b, 0x6f, 0x3d, 0xf5, 0xc0, 0x91, 0x23, 0xea, - 0x01, 0x15, 0x73, 0xe9, 0x91, 0xfe, 0x07, 0xd5, 0xbe, 0x7d, 0xbb, 0xde, 0xdd, 0xd8, 0x40, 0x24, - 0xd2, 0x9b, 0x77, 0xe6, 0x37, 0xf3, 0x7e, 0x6f, 0x66, 0xde, 0xcc, 0x18, 0xea, 0xa6, 0x33, 0x22, - 0x94, 0x11, 0xbf, 0xed, 0xf9, 0x2e, 0x73, 0x71, 0x79, 0xe0, 0xfa, 0x8c, 0xdc, 0x97, 0xcf, 0x8c, + // 1256 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x57, 0xcd, 0x8f, 0xd3, 0x46, + 0x14, 0xf7, 0x6c, 0x3e, 0xd8, 0xbc, 0x64, 0x43, 0x76, 0x16, 0xd8, 0x60, 0x8a, 0x97, 0x5a, 0xa2, + 0x8d, 0xda, 0x92, 0x85, 0xed, 0x87, 0xa0, 0x6a, 0x85, 0xb2, 0xb0, 0xc0, 0x16, 0xc2, 0x82, 0x37, + 0xb4, 0x55, 0xa5, 0xca, 0x72, 0x92, 0xd9, 0xac, 0x8b, 0xbf, 0xf0, 0x8c, 0x2b, 0xb8, 0x55, 0xea, + 0x1f, 0xd0, 0xaa, 0xa7, 0x9e, 0x2a, 0xf5, 0xd6, 0x73, 0x2f, 0xbd, 0xf5, 0xd4, 0x03, 0x47, 0x8e, + 0xa8, 0x07, 0x54, 0xc2, 0xa5, 0x47, 0xfa, 0x1f, 0x54, 0x1e, 0x8f, 0x1d, 0xdb, 0x9b, 0xc0, 0x22, + 0x01, 0xb7, 0xf8, 0xbd, 0xdf, 0x7b, 0xf3, 0x7b, 0x1f, 0x33, 0xef, 0x05, 0xea, 0xa6, 0x33, 0x22, + 0x94, 0x11, 0xbf, 0xed, 0xf9, 0x2e, 0x73, 0x71, 0x79, 0xe0, 0xfa, 0x8c, 0xdc, 0x95, 0x4f, 0x8d, 0x4c, 0xb6, 0x1b, 0xf4, 0xdb, 0x03, 0xd7, 0x5e, 0x1d, 0xb9, 0x23, 0x77, 0x95, 0xab, 0xfb, 0xc1, - 0x0e, 0xff, 0xe2, 0x1f, 0xfc, 0x57, 0x64, 0x26, 0x5f, 0x48, 0xc1, 0x23, 0x0f, 0x9e, 0xef, 0x7e, - 0x43, 0x06, 0x4c, 0x7c, 0xad, 0x7a, 0x77, 0x47, 0xb1, 0xa2, 0x2f, 0x7e, 0x44, 0xa6, 0xea, 0xa7, - 0x50, 0xd5, 0x88, 0x31, 0xd4, 0xc8, 0xbd, 0x80, 0x50, 0x86, 0xdb, 0x70, 0xe8, 0x5e, 0x40, 0x7c, - 0x93, 0xd0, 0x26, 0x3a, 0x55, 0x68, 0x55, 0xd7, 0x8e, 0xb4, 0x05, 0xfc, 0x76, 0x40, 0xfc, 0x07, - 0x02, 0xa6, 0xc5, 0x20, 0xf5, 0x22, 0xd4, 0x22, 0x73, 0xea, 0xb9, 0x0e, 0x25, 0x78, 0x15, 0x0e, - 0xf9, 0x84, 0x06, 0x16, 0x8b, 0xed, 0x8f, 0xe6, 0xec, 0x23, 0x9c, 0x16, 0xa3, 0xd4, 0x9f, 0x11, + 0x0e, 0xff, 0xe2, 0x1f, 0xfc, 0x57, 0x64, 0x26, 0x9f, 0x4b, 0xc1, 0x23, 0x0f, 0x9e, 0xef, 0x7e, + 0x43, 0x06, 0x4c, 0x7c, 0xad, 0x7a, 0xb7, 0x47, 0xb1, 0xa2, 0x2f, 0x7e, 0x44, 0xa6, 0xea, 0xa7, + 0x50, 0xd5, 0x88, 0x31, 0xd4, 0xc8, 0x9d, 0x80, 0x50, 0x86, 0xdb, 0x70, 0xe0, 0x4e, 0x40, 0x7c, + 0x93, 0xd0, 0x26, 0x3a, 0x51, 0x68, 0x55, 0xd7, 0x0e, 0xb5, 0x05, 0xfc, 0x66, 0x40, 0xfc, 0x7b, + 0x02, 0xa6, 0xc5, 0x20, 0xf5, 0x3c, 0xd4, 0x22, 0x73, 0xea, 0xb9, 0x0e, 0x25, 0x78, 0x15, 0x0e, + 0xf8, 0x84, 0x06, 0x16, 0x8b, 0xed, 0x0f, 0xe7, 0xec, 0x23, 0x9c, 0x16, 0xa3, 0xd4, 0x9f, 0x11, 0xd4, 0xd2, 0xae, 0xf1, 0x7b, 0x80, 0x29, 0x33, 0x7c, 0xa6, 0x33, 0xd3, 0x26, 0x94, 0x19, 0xb6, 0xa7, 0xdb, 0xa1, 0x33, 0xd4, 0x2a, 0x68, 0x0d, 0xae, 0xe9, 0xc5, 0x8a, 0x2e, 0xc5, 0x2d, 0x68, - 0x10, 0x67, 0x98, 0xc5, 0xce, 0x71, 0x6c, 0x9d, 0x38, 0xc3, 0x34, 0xf2, 0x2c, 0xcc, 0xdb, 0x06, - 0x1b, 0xec, 0x12, 0x9f, 0x36, 0x0b, 0xd9, 0xab, 0xdd, 0x30, 0xfa, 0xc4, 0xea, 0x46, 0x4a, 0x2d, - 0x41, 0xa9, 0xbf, 0x20, 0x38, 0xb2, 0x71, 0x9f, 0xd8, 0x9e, 0x65, 0xf8, 0xff, 0x0b, 0xc5, 0x73, - 0x7b, 0x28, 0x1e, 0x9d, 0x46, 0x91, 0xa6, 0x38, 0x5e, 0x87, 0x85, 0x4c, 0x60, 0xf1, 0xc7, 0x00, - 0xfc, 0xa4, 0x69, 0x39, 0xf4, 0xfa, 0xed, 0xf0, 0xb8, 0x6d, 0xae, 0x5b, 0x2f, 0x3e, 0x7c, 0xb2, - 0x22, 0x69, 0x29, 0xb4, 0xfa, 0x13, 0x82, 0x25, 0xee, 0x6d, 0x9b, 0xf9, 0xc4, 0xb0, 0x13, 0x9f, - 0x17, 0xa1, 0x3a, 0xd8, 0x0d, 0x9c, 0xbb, 0x19, 0xa7, 0xcb, 0x31, 0xb5, 0x89, 0xcb, 0x4b, 0x21, - 0x48, 0xf8, 0x4d, 0x5b, 0xe4, 0x48, 0xcd, 0xed, 0x8b, 0xd4, 0x36, 0x1c, 0xcd, 0x25, 0xe1, 0x35, - 0xdc, 0xf4, 0x0f, 0x04, 0x98, 0x87, 0xf4, 0x73, 0xc3, 0x0a, 0x08, 0x8d, 0x13, 0x7b, 0x12, 0xc0, - 0x0a, 0xa5, 0xba, 0x63, 0xd8, 0x84, 0x27, 0xb4, 0xa2, 0x55, 0xb8, 0xe4, 0xa6, 0x61, 0x93, 0x19, - 0x79, 0x9f, 0xdb, 0x47, 0xde, 0x0b, 0x2f, 0xcd, 0x7b, 0xf1, 0x14, 0x7a, 0x95, 0xbc, 0x9f, 0x87, - 0xa5, 0x0c, 0x7f, 0x11, 0x93, 0x37, 0xa1, 0x16, 0x5d, 0xe0, 0x5b, 0x2e, 0xe7, 0x51, 0xa9, 0x68, - 0x55, 0x6b, 0x02, 0x55, 0xef, 0xc2, 0xe2, 0x8d, 0xf8, 0x46, 0xf4, 0x80, 0x2b, 0x5a, 0xfd, 0x50, - 0x84, 0x59, 0x1c, 0x26, 0x58, 0xae, 0x40, 0x75, 0x12, 0xe6, 0x98, 0x24, 0x24, 0x71, 0xa6, 0x2a, - 0x86, 0xc6, 0x1d, 0x4a, 0xfc, 0x6d, 0x66, 0xb0, 0x98, 0xa2, 0xfa, 0x3b, 0x82, 0xc5, 0x94, 0x50, - 0xb8, 0x3a, 0x1d, 0xb7, 0x50, 0xd3, 0x75, 0x74, 0xdf, 0x60, 0x51, 0xd6, 0x90, 0xb6, 0x90, 0x48, - 0x35, 0x83, 0x91, 0x30, 0xb1, 0x4e, 0x60, 0xeb, 0x49, 0x01, 0xa2, 0x56, 0x51, 0xab, 0x38, 0x81, - 0x1d, 0x15, 0x48, 0x78, 0x7d, 0xc3, 0x33, 0xf5, 0x9c, 0xa7, 0x02, 0xf7, 0xd4, 0x30, 0x3c, 0x73, - 0x33, 0xe3, 0xac, 0x0d, 0x4b, 0x7e, 0x60, 0x91, 0x3c, 0xbc, 0xc8, 0xe1, 0x8b, 0xa1, 0x2a, 0x83, - 0x57, 0xbf, 0x86, 0xa5, 0x90, 0xf8, 0xe6, 0xe5, 0x2c, 0xf5, 0x65, 0x38, 0x14, 0x50, 0xe2, 0xeb, - 0xe6, 0x50, 0x54, 0x5a, 0x39, 0xfc, 0xdc, 0x1c, 0xe2, 0x33, 0x50, 0x1c, 0x1a, 0xcc, 0xe0, 0x34, - 0xab, 0x6b, 0xc7, 0xe3, 0x52, 0xd8, 0x73, 0x79, 0x8d, 0xc3, 0xd4, 0xab, 0x80, 0x43, 0x15, 0xcd, - 0x7a, 0x3f, 0x07, 0x25, 0x1a, 0x0a, 0xc4, 0xc3, 0x38, 0x91, 0xf6, 0x92, 0x63, 0xa2, 0x45, 0x48, - 0xf5, 0x37, 0x04, 0x4a, 0x97, 0x30, 0xdf, 0x1c, 0xd0, 0x2b, 0xae, 0x9f, 0xad, 0xbc, 0x03, 0xee, - 0x7c, 0xe7, 0xa1, 0x16, 0x97, 0xb6, 0x4e, 0x09, 0x7b, 0x71, 0xf7, 0xab, 0xc6, 0xd0, 0x6d, 0xc2, - 0xd4, 0xeb, 0xb0, 0x32, 0x93, 0xb3, 0x08, 0x45, 0x0b, 0xca, 0x36, 0x87, 0x88, 0x58, 0x34, 0x26, - 0x4d, 0x22, 0x32, 0xd5, 0x84, 0x5e, 0x6d, 0xc2, 0x31, 0xe1, 0xac, 0x4b, 0x98, 0x11, 0x46, 0x37, - 0xae, 0xbe, 0x2d, 0x58, 0xde, 0xa3, 0x11, 0xee, 0x3f, 0x80, 0x79, 0x5b, 0xc8, 0xc4, 0x01, 0xcd, - 0xfc, 0x01, 0x89, 0x4d, 0x82, 0x54, 0xff, 0x45, 0x70, 0x38, 0xd7, 0x39, 0xc3, 0x78, 0xed, 0xf8, - 0xae, 0xad, 0xc7, 0x4b, 0xc1, 0xa4, 0x34, 0xea, 0xa1, 0x7c, 0x53, 0x88, 0x37, 0x87, 0xe9, 0xda, - 0x99, 0xcb, 0xd4, 0x8e, 0x03, 0x65, 0xfe, 0x8e, 0xe2, 0x01, 0xb2, 0x34, 0xa1, 0xc2, 0x83, 0x73, - 0xcb, 0x30, 0xfd, 0xf5, 0x4e, 0xd8, 0x0f, 0xff, 0x7a, 0xb2, 0xb2, 0xaf, 0xb5, 0x21, 0xb2, 0xef, - 0x0c, 0x0d, 0x8f, 0x11, 0x5f, 0x13, 0xa7, 0xe0, 0x77, 0xa1, 0x1c, 0x35, 0xfa, 0x66, 0x91, 0x9f, - 0xb7, 0x10, 0xa7, 0x2c, 0x3d, 0x0b, 0x04, 0x44, 0xfd, 0x01, 0x41, 0x29, 0xba, 0xe9, 0x41, 0xd5, - 0x91, 0x0c, 0xf3, 0xc4, 0x19, 0xb8, 0x43, 0xd3, 0x19, 0xf1, 0xe7, 0x5b, 0xd2, 0x92, 0x6f, 0x8c, - 0xc5, 0xb3, 0x0a, 0xdf, 0x69, 0x4d, 0xbc, 0x9d, 0x26, 0x1c, 0xeb, 0xf9, 0x86, 0x43, 0x77, 0x88, - 0xcf, 0x89, 0x25, 0x45, 0xa3, 0x76, 0x60, 0x21, 0x53, 0x4d, 0x99, 0xfd, 0x01, 0xbd, 0xd2, 0xfe, - 0xa0, 0x43, 0x2d, 0xad, 0xc1, 0xa7, 0xa1, 0xc8, 0x1e, 0x78, 0x51, 0x87, 0xaa, 0xaf, 0x2d, 0xc6, - 0xd6, 0x5c, 0xdd, 0x7b, 0xe0, 0x11, 0x8d, 0xab, 0x43, 0x9e, 0x7c, 0xfc, 0x44, 0x89, 0xe5, 0xbf, - 0xf1, 0x11, 0x28, 0xf1, 0x8e, 0xce, 0x2f, 0x55, 0xd1, 0xa2, 0x0f, 0xf5, 0x7b, 0x04, 0xf5, 0x49, - 0x0d, 0x5d, 0x31, 0x2d, 0xf2, 0x3a, 0x4a, 0x48, 0x86, 0xf9, 0x1d, 0xd3, 0x22, 0x9c, 0x43, 0x74, - 0x5c, 0xf2, 0x3d, 0x2d, 0x86, 0xef, 0x7c, 0x06, 0x95, 0xe4, 0x0a, 0xb8, 0x02, 0xa5, 0x8d, 0xdb, - 0x77, 0x3a, 0x37, 0x1a, 0x12, 0x5e, 0x80, 0xca, 0xcd, 0xad, 0x9e, 0x1e, 0x7d, 0x22, 0x7c, 0x18, - 0xaa, 0xda, 0xc6, 0xd5, 0x8d, 0x2f, 0xf5, 0x6e, 0xa7, 0x77, 0xe9, 0x5a, 0x63, 0x0e, 0x63, 0xa8, - 0x47, 0x82, 0x9b, 0x5b, 0x42, 0x56, 0x58, 0xfb, 0xb3, 0x0c, 0xf3, 0x31, 0x47, 0x7c, 0x01, 0x8a, - 0xb7, 0x02, 0xba, 0x8b, 0x8f, 0x4d, 0x6a, 0xf8, 0x0b, 0xdf, 0x64, 0x44, 0xbc, 0x49, 0x79, 0x79, - 0x8f, 0x5c, 0xe4, 0x4e, 0xc2, 0x1f, 0x41, 0x89, 0x2f, 0x0b, 0x78, 0xea, 0xfa, 0x2a, 0x4f, 0x5f, - 0x4a, 0x55, 0x09, 0x5f, 0x86, 0x6a, 0x6a, 0x01, 0x9a, 0x61, 0x7d, 0x22, 0x23, 0xcd, 0xee, 0x4a, - 0xaa, 0x74, 0x16, 0xe1, 0x2d, 0xa8, 0x73, 0x55, 0xbc, 0xb7, 0x50, 0xfc, 0x46, 0x6c, 0x32, 0x6d, - 0x9f, 0x94, 0x4f, 0xce, 0xd0, 0x26, 0xb4, 0xae, 0x41, 0x35, 0x35, 0xed, 0xb1, 0x9c, 0x29, 0xbc, - 0xcc, 0x0a, 0x33, 0x21, 0x37, 0x65, 0x3d, 0x50, 0x25, 0xbc, 0x01, 0x30, 0x19, 0xc8, 0xf8, 0x78, - 0x06, 0x9c, 0xde, 0x08, 0x64, 0x79, 0x9a, 0x2a, 0x71, 0xb3, 0x0e, 0x95, 0x64, 0x1c, 0xe1, 0xe6, - 0x94, 0x09, 0x15, 0x39, 0x99, 0x3d, 0xbb, 0x54, 0x09, 0x5f, 0x81, 0x5a, 0xc7, 0xb2, 0x5e, 0xc5, - 0x8d, 0x9c, 0xd6, 0xd0, 0xbc, 0x1f, 0x2b, 0x69, 0xcd, 0xf9, 0x09, 0x80, 0xdf, 0x4a, 0xde, 0xd8, - 0x0b, 0xc7, 0x9a, 0xfc, 0xf6, 0x4b, 0x71, 0xc9, 0x69, 0x3d, 0x38, 0x9c, 0x1b, 0x04, 0x58, 0xc9, - 0x59, 0xe7, 0x66, 0x87, 0xbc, 0x32, 0x53, 0x9f, 0x78, 0xed, 0x42, 0x3d, 0xdb, 0x87, 0xf0, 0xac, - 0xf5, 0x5a, 0x4e, 0x4e, 0x9b, 0xd1, 0xb8, 0xa4, 0x16, 0x5a, 0xff, 0xe4, 0xd1, 0x53, 0x45, 0x7a, - 0xfc, 0x54, 0x91, 0x9e, 0x3f, 0x55, 0xd0, 0x77, 0x63, 0x05, 0xfd, 0x3a, 0x56, 0xd0, 0xc3, 0xb1, - 0x82, 0x1e, 0x8d, 0x15, 0xf4, 0xf7, 0x58, 0x41, 0xff, 0x8c, 0x15, 0xe9, 0xf9, 0x58, 0x41, 0x3f, - 0x3e, 0x53, 0xa4, 0x47, 0xcf, 0x14, 0xe9, 0xf1, 0x33, 0x45, 0xfa, 0xaa, 0x3c, 0xb0, 0x4c, 0xe2, - 0xb0, 0x7e, 0x99, 0xff, 0x33, 0x7c, 0xff, 0xbf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x0e, 0x4f, 0x5c, - 0xe0, 0x9d, 0x0e, 0x00, 0x00, + 0x10, 0x67, 0x98, 0xc5, 0xce, 0x71, 0x6c, 0x9d, 0x38, 0xc3, 0x34, 0xf2, 0x34, 0xcc, 0xdb, 0x06, + 0x1b, 0xec, 0x12, 0x9f, 0x36, 0x0b, 0xd9, 0xd0, 0xae, 0x19, 0x7d, 0x62, 0x75, 0x23, 0xa5, 0x96, + 0xa0, 0xd4, 0x5f, 0x11, 0x1c, 0xda, 0xb8, 0x4b, 0x6c, 0xcf, 0x32, 0xfc, 0xd7, 0x42, 0xf1, 0xcc, + 0x1e, 0x8a, 0x87, 0xa7, 0x51, 0xa4, 0x29, 0x8e, 0x57, 0x61, 0x21, 0x93, 0x58, 0xfc, 0x31, 0x00, + 0x3f, 0x69, 0x5a, 0x0d, 0xbd, 0x7e, 0x3b, 0x3c, 0x6e, 0x9b, 0xeb, 0xd6, 0x8b, 0xf7, 0x1f, 0xad, + 0x48, 0x5a, 0x0a, 0xad, 0xfe, 0x84, 0x60, 0x89, 0x7b, 0xdb, 0x66, 0x3e, 0x31, 0xec, 0xc4, 0xe7, + 0x79, 0xa8, 0x0e, 0x76, 0x03, 0xe7, 0x76, 0xc6, 0xe9, 0x72, 0x4c, 0x6d, 0xe2, 0xf2, 0x42, 0x08, + 0x12, 0x7e, 0xd3, 0x16, 0x39, 0x52, 0x73, 0x2f, 0x44, 0x6a, 0x1b, 0x0e, 0xe7, 0x8a, 0xf0, 0x12, + 0x22, 0xfd, 0x13, 0x01, 0xe6, 0x29, 0xfd, 0xdc, 0xb0, 0x02, 0x42, 0xe3, 0xc2, 0x1e, 0x07, 0xb0, + 0x42, 0xa9, 0xee, 0x18, 0x36, 0xe1, 0x05, 0xad, 0x68, 0x15, 0x2e, 0xb9, 0x6e, 0xd8, 0x64, 0x46, + 0xdd, 0xe7, 0x5e, 0xa0, 0xee, 0x85, 0xe7, 0xd6, 0xbd, 0x78, 0x02, 0xed, 0xa7, 0xee, 0x67, 0x61, + 0x29, 0xc3, 0x5f, 0xe4, 0xe4, 0x4d, 0xa8, 0x45, 0x01, 0x7c, 0xcb, 0xe5, 0x3c, 0x2b, 0x15, 0xad, + 0x6a, 0x4d, 0xa0, 0xea, 0x2f, 0x08, 0x16, 0xaf, 0xc5, 0x21, 0xd1, 0xd7, 0xdb, 0xd2, 0xfb, 0x0a, + 0xed, 0x43, 0x51, 0x1a, 0xc1, 0x4f, 0x44, 0xb6, 0x02, 0xd5, 0x49, 0x69, 0xe2, 0xc0, 0x20, 0xa9, + 0x0d, 0x55, 0x31, 0x34, 0x6e, 0x51, 0xe2, 0x6f, 0x33, 0x83, 0xc5, 0x51, 0xa9, 0x7f, 0x20, 0x58, + 0x4c, 0x09, 0x85, 0xab, 0x93, 0xf1, 0xb3, 0x6b, 0xba, 0x8e, 0xee, 0x1b, 0x2c, 0xaa, 0x34, 0xd2, + 0x16, 0x12, 0xa9, 0x66, 0x30, 0x12, 0x36, 0x83, 0x13, 0xd8, 0x7a, 0xd2, 0xb4, 0xa8, 0x55, 0xd4, + 0x2a, 0x4e, 0x60, 0x47, 0x4d, 0x15, 0x66, 0xcc, 0xf0, 0x4c, 0x3d, 0xe7, 0xa9, 0xc0, 0x3d, 0x35, + 0x0c, 0xcf, 0xdc, 0xcc, 0x38, 0x6b, 0xc3, 0x92, 0x1f, 0x58, 0x24, 0x0f, 0x2f, 0x72, 0xf8, 0x62, + 0xa8, 0xca, 0xe0, 0xd5, 0xaf, 0x61, 0x29, 0x24, 0xbe, 0x79, 0x31, 0x4b, 0x7d, 0x19, 0x0e, 0x04, + 0x94, 0xf8, 0xba, 0x39, 0x14, 0xdd, 0x59, 0x0e, 0x3f, 0x37, 0x87, 0xf8, 0x14, 0x14, 0x87, 0x06, + 0x33, 0x38, 0xcd, 0xea, 0xda, 0xd1, 0x38, 0xc7, 0x7b, 0x82, 0xd7, 0x38, 0x4c, 0xbd, 0x0c, 0x38, + 0x54, 0xd1, 0xac, 0xf7, 0x33, 0x50, 0xa2, 0xa1, 0x40, 0x5c, 0xa6, 0x63, 0x69, 0x2f, 0x39, 0x26, + 0x5a, 0x84, 0x54, 0x7f, 0x47, 0xa0, 0x74, 0x09, 0xf3, 0xcd, 0x01, 0xbd, 0xe4, 0xfa, 0xd9, 0x92, + 0xbe, 0xe2, 0xd6, 0x3a, 0x0b, 0xb5, 0xb8, 0x67, 0x74, 0x4a, 0xd8, 0xb3, 0x5f, 0xcc, 0x6a, 0x0c, + 0xdd, 0x26, 0x4c, 0xbd, 0x0a, 0x2b, 0x33, 0x39, 0x8b, 0x54, 0xb4, 0xa0, 0x6c, 0x73, 0x88, 0xc8, + 0x45, 0x63, 0xf2, 0xb0, 0x44, 0xa6, 0x9a, 0xd0, 0xab, 0x4d, 0x38, 0x22, 0x9c, 0x75, 0x09, 0x33, + 0xc2, 0xec, 0xc6, 0xdd, 0xb7, 0x05, 0xcb, 0x7b, 0x34, 0xc2, 0xfd, 0x07, 0x30, 0x6f, 0x0b, 0x99, + 0x38, 0xa0, 0x99, 0x3f, 0x20, 0xb1, 0x49, 0x90, 0xea, 0x7f, 0x08, 0x0e, 0xe6, 0x5e, 0xdb, 0x30, + 0x5f, 0x3b, 0xbe, 0x6b, 0xeb, 0xf1, 0x22, 0x31, 0x69, 0x8d, 0x7a, 0x28, 0xdf, 0x14, 0xe2, 0xcd, + 0x61, 0xba, 0x77, 0xe6, 0x32, 0xbd, 0xe3, 0x40, 0x99, 0xdf, 0xa3, 0x78, 0xe8, 0x2c, 0x4d, 0xa8, + 0xf0, 0xe4, 0xdc, 0x30, 0x4c, 0x7f, 0xbd, 0x13, 0xbe, 0xa1, 0x7f, 0x3f, 0x5a, 0x79, 0xa1, 0x55, + 0x23, 0xb2, 0xef, 0x0c, 0x0d, 0x8f, 0x11, 0x5f, 0x13, 0xa7, 0xe0, 0x77, 0xa1, 0x1c, 0x0d, 0x87, + 0x66, 0x91, 0x9f, 0xb7, 0x10, 0x97, 0x2c, 0x3d, 0x3f, 0x04, 0x44, 0xfd, 0x01, 0x41, 0x29, 0x8a, + 0xf4, 0x55, 0xf5, 0x91, 0x0c, 0xf3, 0xc4, 0x19, 0xb8, 0x43, 0xd3, 0x19, 0xf1, 0xeb, 0x5b, 0xd2, + 0x92, 0x6f, 0x8c, 0xc5, 0xb5, 0x0a, 0xef, 0x69, 0x4d, 0xdc, 0x9d, 0x26, 0x1c, 0xe9, 0xf9, 0x86, + 0x43, 0x77, 0x88, 0xcf, 0x89, 0x25, 0x4d, 0xa3, 0x76, 0x60, 0x21, 0xd3, 0x4d, 0x99, 0x9d, 0x03, + 0xed, 0x6b, 0xe7, 0xd0, 0xa1, 0x96, 0xd6, 0xe0, 0x93, 0x50, 0x64, 0xf7, 0xbc, 0xe8, 0x85, 0xaa, + 0xaf, 0x2d, 0xc6, 0xd6, 0x5c, 0xdd, 0xbb, 0xe7, 0x11, 0x8d, 0xab, 0x43, 0x9e, 0x7c, 0x64, 0x45, + 0x85, 0xe5, 0xbf, 0xf1, 0x21, 0x28, 0xf1, 0x29, 0xc0, 0x83, 0xaa, 0x68, 0xd1, 0x87, 0xfa, 0x3d, + 0x82, 0xfa, 0xa4, 0x87, 0x2e, 0x99, 0x16, 0x79, 0x19, 0x2d, 0x24, 0xc3, 0xfc, 0x8e, 0x69, 0x11, + 0xce, 0x21, 0x3a, 0x2e, 0xf9, 0x9e, 0x96, 0xc3, 0x77, 0x3e, 0x83, 0x4a, 0x12, 0x02, 0xae, 0x40, + 0x69, 0xe3, 0xe6, 0xad, 0xce, 0xb5, 0x86, 0x84, 0x17, 0xa0, 0x72, 0x7d, 0xab, 0xa7, 0x47, 0x9f, + 0x08, 0x1f, 0x84, 0xaa, 0xb6, 0x71, 0x79, 0xe3, 0x4b, 0xbd, 0xdb, 0xe9, 0x5d, 0xb8, 0xd2, 0x98, + 0xc3, 0x18, 0xea, 0x91, 0xe0, 0xfa, 0x96, 0x90, 0x15, 0xd6, 0xfe, 0x2a, 0xc3, 0x7c, 0xcc, 0x11, + 0x9f, 0x83, 0xe2, 0x8d, 0x80, 0xee, 0xe2, 0x23, 0x93, 0x1e, 0xfe, 0xc2, 0x37, 0x19, 0x11, 0x77, + 0x52, 0x5e, 0xde, 0x23, 0x17, 0xb5, 0x93, 0xf0, 0x47, 0x50, 0xe2, 0x0b, 0x06, 0x9e, 0xba, 0xf2, + 0xca, 0xd3, 0x17, 0x59, 0x55, 0xc2, 0x17, 0xa1, 0x9a, 0x5a, 0x9a, 0x66, 0x58, 0x1f, 0xcb, 0x48, + 0xb3, 0xfb, 0x95, 0x2a, 0x9d, 0x46, 0x78, 0x0b, 0xea, 0x5c, 0x15, 0xef, 0x3a, 0x14, 0xbf, 0x11, + 0x9b, 0x4c, 0xdb, 0x41, 0xe5, 0xe3, 0x33, 0xb4, 0x09, 0xad, 0x2b, 0x50, 0x4d, 0x6d, 0x08, 0x58, + 0xce, 0x34, 0x5e, 0x66, 0xed, 0x99, 0x90, 0x9b, 0xb2, 0x52, 0xa8, 0x12, 0xde, 0x00, 0x98, 0x0c, + 0x64, 0x7c, 0x34, 0x03, 0x4e, 0x2f, 0x11, 0xb2, 0x3c, 0x4d, 0x95, 0xb8, 0x59, 0x87, 0x4a, 0x32, + 0x8e, 0x70, 0x73, 0xca, 0x84, 0x8a, 0x9c, 0xcc, 0x9e, 0x5d, 0xaa, 0x84, 0x2f, 0x41, 0xad, 0x63, + 0x59, 0xfb, 0x71, 0x23, 0xa7, 0x35, 0x34, 0xef, 0xc7, 0x4a, 0x9e, 0xe6, 0xfc, 0x04, 0xc0, 0x6f, + 0x25, 0x77, 0xec, 0x99, 0x63, 0x4d, 0x7e, 0xfb, 0xb9, 0xb8, 0xe4, 0xb4, 0x1e, 0x1c, 0xcc, 0x0d, + 0x02, 0xac, 0xe4, 0xac, 0x73, 0xb3, 0x43, 0x5e, 0x99, 0xa9, 0x4f, 0xbc, 0x76, 0xa1, 0x9e, 0x7d, + 0x87, 0xf0, 0xac, 0x95, 0x5c, 0x4e, 0x4e, 0x9b, 0xf1, 0x70, 0x49, 0x2d, 0xb4, 0xfe, 0xc9, 0x83, + 0xc7, 0x8a, 0xf4, 0xf0, 0xb1, 0x22, 0x3d, 0x7d, 0xac, 0xa0, 0xef, 0xc6, 0x0a, 0xfa, 0x6d, 0xac, + 0xa0, 0xfb, 0x63, 0x05, 0x3d, 0x18, 0x2b, 0xe8, 0x9f, 0xb1, 0x82, 0xfe, 0x1d, 0x2b, 0xd2, 0xd3, + 0xb1, 0x82, 0x7e, 0x7c, 0xa2, 0x48, 0x0f, 0x9e, 0x28, 0xd2, 0xc3, 0x27, 0x8a, 0xf4, 0x55, 0x79, + 0x60, 0x99, 0xc4, 0x61, 0xfd, 0x32, 0xff, 0x37, 0xf9, 0xfe, 0xff, 0x01, 0x00, 0x00, 0xff, 0xff, + 0xf2, 0xe0, 0xb5, 0x58, 0xd1, 0x0e, 0x00, 0x00, } func (x MatchType) String() string { @@ -1747,6 +1755,9 @@ func (this *LabelNamesRequest) Equal(that interface{}) bool { if this.EndTimestampMs != that1.EndTimestampMs { return false } + if !this.Matchers.Equal(that1.Matchers) { + return false + } return true } func (this *LabelNamesResponse) Equal(that interface{}) bool { @@ -2327,10 +2338,13 @@ func (this *LabelNamesRequest) GoString() string { if this == nil { return "nil" } - s := make([]string, 0, 6) + s := make([]string, 0, 7) s = append(s, "&client.LabelNamesRequest{") s = append(s, "StartTimestampMs: "+fmt.Sprintf("%#v", this.StartTimestampMs)+",\n") s = append(s, "EndTimestampMs: "+fmt.Sprintf("%#v", this.EndTimestampMs)+",\n") + if this.Matchers != nil { + s = append(s, "Matchers: "+fmt.Sprintf("%#v", this.Matchers)+",\n") + } s = append(s, "}") return strings.Join(s, "") } @@ -3426,6 +3440,18 @@ func (m *LabelNamesRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.Matchers != nil { + { + size, err := m.Matchers.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintIngester(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } if m.EndTimestampMs != 0 { i = encodeVarintIngester(dAtA, i, uint64(m.EndTimestampMs)) i-- @@ -4210,6 +4236,10 @@ func (m *LabelNamesRequest) Size() (n int) { if m.EndTimestampMs != 0 { n += 1 + sovIngester(uint64(m.EndTimestampMs)) } + if m.Matchers != nil { + l = m.Matchers.Size() + n += 1 + l + sovIngester(uint64(l)) + } return n } @@ -4621,6 +4651,7 @@ func (this *LabelNamesRequest) String() string { s := strings.Join([]string{`&LabelNamesRequest{`, `StartTimestampMs:` + fmt.Sprintf("%v", this.StartTimestampMs) + `,`, `EndTimestampMs:` + fmt.Sprintf("%v", this.EndTimestampMs) + `,`, + `Matchers:` + strings.Replace(this.Matchers.String(), "LabelMatchers", "LabelMatchers", 1) + `,`, `}`, }, "") return s @@ -5857,6 +5888,42 @@ func (m *LabelNamesRequest) Unmarshal(dAtA []byte) error { break } } + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Matchers", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowIngester + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthIngester + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthIngester + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Matchers == nil { + m.Matchers = &LabelMatchers{} + } + if err := m.Matchers.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipIngester(dAtA[iNdEx:]) diff --git a/pkg/ingester/client/ingester.proto b/pkg/ingester/client/ingester.proto index 0314139fcec..0c6c2745e91 100644 --- a/pkg/ingester/client/ingester.proto +++ b/pkg/ingester/client/ingester.proto @@ -76,6 +76,7 @@ message LabelValuesResponse { message LabelNamesRequest { int64 start_timestamp_ms = 1; int64 end_timestamp_ms = 2; + LabelMatchers matchers = 3; } message LabelNamesResponse { diff --git a/pkg/ingester/ingester.go b/pkg/ingester/ingester.go index 7936a6d3f65..c23f5001e47 100644 --- a/pkg/ingester/ingester.go +++ b/pkg/ingester/ingester.go @@ -6,6 +6,7 @@ import ( "fmt" "net/http" "os" + "sort" "strings" "sync" "time" @@ -968,9 +969,35 @@ func (i *Ingester) LabelNames(ctx context.Context, req *client.LabelNamesRequest return &client.LabelNamesResponse{}, nil } + _, _, matchers, err := client.FromLabelNamesRequest(req) + if err != nil { + return nil, err + } + resp := &client.LabelNamesResponse{} - resp.LabelNames = append(resp.LabelNames, state.index.LabelNames()...) + if len(matchers) > 0 { + namesMap := make(map[string]struct{}) + if err := state.forSeriesMatching(ctx, matchers, func(ctx context.Context, fp model.Fingerprint, series *memorySeries) error { + for _, lbl := range series.metric { + namesMap[lbl.Name] = struct{}{} + } + return nil + }, nil, 0); err != nil { + return nil, err + } + + names := make([]string, 0, len(namesMap)) + for name := range namesMap { + names = append(names, name) + } + resp.LabelNames = names + sort.Strings(resp.LabelNames) + + return resp, nil + } + + resp.LabelNames = append(resp.LabelNames, state.index.LabelNames()...) return resp, nil } diff --git a/pkg/ingester/ingester_test.go b/pkg/ingester/ingester_test.go index f8d4c43246b..420ccdab12c 100644 --- a/pkg/ingester/ingester_test.go +++ b/pkg/ingester/ingester_test.go @@ -788,6 +788,70 @@ func TestIngesterValidation(t *testing.T) { } } +func TestIngesterLabelNames(t *testing.T) { + _, ing := newDefaultTestStore(t) + defer func() { require.NoError(t, services.StopAndAwaitTerminated(context.Background(), ing)) }() + + const ( + userID = "1" + numSeries = 5 + samplesPerSeries = 1 + ) + ctx := user.InjectOrgID(context.Background(), userID) + + // Create test samples. + m := make(model.Matrix, 0) + for i := 0; i < numSeries; i++ { + ss := model.SampleStream{ + Metric: model.Metric{ + model.MetricNameLabel: model.LabelValue(fmt.Sprintf("testmetric_%d", i)), + model.JobLabel: model.LabelValue(fmt.Sprintf("testjob%d", i%2)), + }, + Values: make([]model.SamplePair, 0, samplesPerSeries), + } + // Each testmetric_N has extra label names [lbl_0, ..., lbl_N] + for j := 0; j <= i; j++ { + ss.Metric[model.LabelName(fmt.Sprintf("lbl_%d", j))] = "foo" + } + for j := 0; j < samplesPerSeries; j++ { + ss.Values = append(ss.Values, model.SamplePair{ + Timestamp: model.Time(i + j), + Value: model.SampleValue(i + j), + }) + } + m = append(m, &ss) + } + sort.Sort(m) + + // Append samples. + _, err := ing.Push(ctx, cortexpb.ToWriteRequest(matrixToLables(m), matrixToSamples(m), nil, cortexpb.API)) + require.NoError(t, err) + + t.Run("without matchers", func(t *testing.T) { + var matchers []*labels.Matcher + req, err := client.ToLabelNamesRequest(0, model.Latest, matchers) + require.NoError(t, err) + + resp, err := ing.LabelNames(ctx, req) + require.NoError(t, err) + + assert.Equal(t, []string{"__name__", "job", "lbl_0", "lbl_1", "lbl_2", "lbl_3", "lbl_4"}, resp.LabelNames) + }) + + t.Run("with matchers", func(t *testing.T) { + matchers := []*labels.Matcher{ + labels.MustNewMatcher(labels.MatchRegexp, model.MetricNameLabel, "testmetric_[0-3]"), + } + req, err := client.ToLabelNamesRequest(0, model.Latest, matchers) + require.NoError(t, err) + + resp, err := ing.LabelNames(ctx, req) + require.NoError(t, err) + + assert.Equal(t, []string{"__name__", "job", "lbl_0", "lbl_1", "lbl_2", "lbl_3"}, resp.LabelNames) + }) +} + func BenchmarkIngesterSeriesCreationLocking(b *testing.B) { for i := 1; i <= 32; i++ { b.Run(strconv.Itoa(i), func(b *testing.B) { diff --git a/pkg/ingester/ingester_v2.go b/pkg/ingester/ingester_v2.go index d0f2e43c6b3..5f41a9623b2 100644 --- a/pkg/ingester/ingester_v2.go +++ b/pkg/ingester/ingester_v2.go @@ -1130,7 +1130,12 @@ func (i *Ingester) v2LabelNames(ctx context.Context, req *client.LabelNamesReque return &client.LabelNamesResponse{}, nil } - mint, maxt, err := metadataQueryRange(req.StartTimestampMs, req.EndTimestampMs, db) + mint, maxt, matchers, err := client.FromLabelNamesRequest(req) + if err != nil { + return nil, err + } + + mint, maxt, err = metadataQueryRange(mint, maxt, db) if err != nil { return nil, err } @@ -1141,7 +1146,7 @@ func (i *Ingester) v2LabelNames(ctx context.Context, req *client.LabelNamesReque } defer q.Close() - names, _, err := q.LabelNames() + names, _, err := q.LabelNames(matchers...) if err != nil { return nil, err } diff --git a/pkg/ingester/ingester_v2_test.go b/pkg/ingester/ingester_v2_test.go index 1cd19d55c8c..622740fa427 100644 --- a/pkg/ingester/ingester_v2_test.go +++ b/pkg/ingester/ingester_v2_test.go @@ -1106,10 +1106,9 @@ func Test_Ingester_v2LabelNames(t *testing.T) { {labels.Labels{{Name: labels.MetricName, Value: "test_1"}, {Name: "status", Value: "200"}, {Name: "route", Value: "get_user"}}, 1, 100000}, {labels.Labels{{Name: labels.MetricName, Value: "test_1"}, {Name: "status", Value: "500"}, {Name: "route", Value: "get_user"}}, 1, 110000}, {labels.Labels{{Name: labels.MetricName, Value: "test_2"}}, 2, 200000}, + {labels.Labels{{Name: labels.MetricName, Value: "test_3"}, {Name: "status", Value: "500"}}, 2, 200000}, } - expected := []string{"__name__", "status", "route"} - // Create ingester i, err := prepareIngesterWithBlocksStorage(t, defaultIngesterTestConfig(), nil) require.NoError(t, err) @@ -1130,10 +1129,31 @@ func Test_Ingester_v2LabelNames(t *testing.T) { require.NoError(t, err) } - // Get label names - res, err := i.v2LabelNames(ctx, &client.LabelNamesRequest{}) - require.NoError(t, err) - assert.ElementsMatch(t, expected, res.LabelNames) + t.Run("without matchers", func(t *testing.T) { + expected := []string{"__name__", "status", "route"} + + // Get label names + res, err := i.v2LabelNames(ctx, &client.LabelNamesRequest{}) + require.NoError(t, err) + assert.ElementsMatch(t, expected, res.LabelNames) + }) + + t.Run("with matchers", func(t *testing.T) { + // test_2 and test_3 are selected in this test, they don't have the "route" label + expected := []string{"__name__", "status"} + + matchers := []*labels.Matcher{ + labels.MustNewMatcher(labels.MatchNotEqual, "route", "get_user"), + } + + req, err := client.ToLabelNamesRequest(0, model.Latest, matchers) + require.NoError(t, err) + + // Get label names + res, err := i.v2LabelNames(ctx, req) + require.NoError(t, err) + assert.ElementsMatch(t, expected, res.LabelNames) + }) } func Test_Ingester_v2LabelValues(t *testing.T) { diff --git a/pkg/querier/blocks_store_queryable.go b/pkg/querier/blocks_store_queryable.go index 02c50b5b9d1..69584f01baf 100644 --- a/pkg/querier/blocks_store_queryable.go +++ b/pkg/querier/blocks_store_queryable.go @@ -136,7 +136,15 @@ type BlocksStoreQueryable struct { subservicesWatcher *services.FailureWatcher } -func NewBlocksStoreQueryable(stores BlocksStoreSet, finder BlocksFinder, consistency *BlocksConsistencyChecker, limits BlocksStoreLimits, queryStoreAfter time.Duration, logger log.Logger, reg prometheus.Registerer) (*BlocksStoreQueryable, error) { +func NewBlocksStoreQueryable( + stores BlocksStoreSet, + finder BlocksFinder, + consistency *BlocksConsistencyChecker, + limits BlocksStoreLimits, + queryStoreAfter time.Duration, + logger log.Logger, + reg prometheus.Registerer, +) (*BlocksStoreQueryable, error) { manager, err := services.NewManager(stores, finder) if err != nil { return nil, errors.Wrap(err, "register blocks storage queryable subservices") @@ -317,20 +325,21 @@ func (q *blocksStoreQuerier) Select(_ bool, sp *storage.SelectHints, matchers .. return q.selectSorted(sp, matchers...) } -func (q *blocksStoreQuerier) LabelNames() ([]string, storage.Warnings, error) { +func (q *blocksStoreQuerier) LabelNames(matchers ...*labels.Matcher) ([]string, storage.Warnings, error) { spanLog, spanCtx := spanlogger.New(q.ctx, "blocksStoreQuerier.LabelNames") defer spanLog.Span.Finish() minT, maxT := q.minT, q.maxT var ( - resMtx sync.Mutex - resNameSets = [][]string{} - resWarnings = storage.Warnings(nil) + resMtx sync.Mutex + resNameSets = [][]string{} + resWarnings = storage.Warnings(nil) + convertedMatchers = convertMatchersToLabelMatcher(matchers) ) queryFunc := func(clients map[BlocksStoreClient][]ulid.ULID, minT, maxT int64) ([]ulid.ULID, error) { - nameSets, warnings, queriedBlocks, err := q.fetchLabelNamesFromStore(spanCtx, clients, minT, maxT) + nameSets, warnings, queriedBlocks, err := q.fetchLabelNamesFromStore(spanCtx, clients, minT, maxT, convertedMatchers) if err != nil { return nil, err } @@ -693,6 +702,7 @@ func (q *blocksStoreQuerier) fetchLabelNamesFromStore( clients map[BlocksStoreClient][]ulid.ULID, minT int64, maxT int64, + matchers []storepb.LabelMatcher, ) ([][]string, storage.Warnings, []ulid.ULID, error) { var ( reqCtx = grpc_metadata.AppendToOutgoingContext(ctx, cortex_tsdb.TenantIDExternalLabel, q.userID) @@ -711,7 +721,7 @@ func (q *blocksStoreQuerier) fetchLabelNamesFromStore( blockIDs := blockIDs g.Go(func() error { - req, err := createLabelNamesRequest(minT, maxT, blockIDs) + req, err := createLabelNamesRequest(minT, maxT, blockIDs, matchers) if err != nil { return errors.Wrapf(err, "failed to create label names request") } @@ -870,10 +880,11 @@ func createSeriesRequest(minT, maxT int64, matchers []storepb.LabelMatcher, skip }, nil } -func createLabelNamesRequest(minT, maxT int64, blockIDs []ulid.ULID) (*storepb.LabelNamesRequest, error) { +func createLabelNamesRequest(minT, maxT int64, blockIDs []ulid.ULID, matchers []storepb.LabelMatcher) (*storepb.LabelNamesRequest, error) { req := &storepb.LabelNamesRequest{ - Start: minT, - End: maxT, + Start: minT, + End: maxT, + Matchers: matchers, } // Selectively query only specific blocks. diff --git a/pkg/querier/chunk_store_queryable.go b/pkg/querier/chunk_store_queryable.go index 6707598e1ae..ba323215872 100644 --- a/pkg/querier/chunk_store_queryable.go +++ b/pkg/querier/chunk_store_queryable.go @@ -85,7 +85,7 @@ func (q *chunkStoreQuerier) LabelValues(name string, labels ...*labels.Matcher) return nil, nil, nil } -func (q *chunkStoreQuerier) LabelNames() ([]string, storage.Warnings, error) { +func (q *chunkStoreQuerier) LabelNames(matchers ...*labels.Matcher) ([]string, storage.Warnings, error) { return nil, nil, nil } diff --git a/pkg/querier/distributor_queryable.go b/pkg/querier/distributor_queryable.go index 6b13b0a95d0..61b43d25f33 100644 --- a/pkg/querier/distributor_queryable.go +++ b/pkg/querier/distributor_queryable.go @@ -30,17 +30,18 @@ type Distributor interface { QueryStream(ctx context.Context, from, to model.Time, matchers ...*labels.Matcher) (*client.QueryStreamResponse, error) QueryExemplars(ctx context.Context, from, to model.Time, matchers ...[]*labels.Matcher) (*client.ExemplarQueryResponse, error) LabelValuesForLabelName(ctx context.Context, from, to model.Time, label model.LabelName, matchers ...*labels.Matcher) ([]string, error) - LabelNames(context.Context, model.Time, model.Time) ([]string, error) + LabelNames(ctx context.Context, from model.Time, to model.Time, matchers ...*labels.Matcher) ([]string, error) MetricsForLabelMatchers(ctx context.Context, from, through model.Time, matchers ...*labels.Matcher) ([]metric.Metric, error) MetricsMetadata(ctx context.Context) ([]scrape.MetricMetadata, error) } -func newDistributorQueryable(distributor Distributor, streaming bool, iteratorFn chunkIteratorFunc, queryIngestersWithin time.Duration) QueryableWithFilter { +func newDistributorQueryable(distributor Distributor, streaming bool, iteratorFn chunkIteratorFunc, queryIngestersWithin time.Duration, queryLabelNamesWithMatchers bool) QueryableWithFilter { return distributorQueryable{ - distributor: distributor, - streaming: streaming, - iteratorFn: iteratorFn, - queryIngestersWithin: queryIngestersWithin, + distributor: distributor, + streaming: streaming, + iteratorFn: iteratorFn, + queryIngestersWithin: queryIngestersWithin, + queryLabelNamesWithMatchers: queryLabelNamesWithMatchers, } } @@ -49,6 +50,8 @@ type distributorQueryable struct { streaming bool iteratorFn chunkIteratorFunc queryIngestersWithin time.Duration + + queryLabelNamesWithMatchers bool } func (d distributorQueryable) Querier(ctx context.Context, mint, maxt int64) (storage.Querier, error) { @@ -60,6 +63,8 @@ func (d distributorQueryable) Querier(ctx context.Context, mint, maxt int64) (st streaming: d.streaming, chunkIterFn: d.iteratorFn, queryIngestersWithin: d.queryIngestersWithin, + + queryLabelNamesWithMatchers: d.queryLabelNamesWithMatchers, }, nil } @@ -75,6 +80,8 @@ type distributorQuerier struct { streaming bool chunkIterFn chunkIteratorFunc queryIngestersWithin time.Duration + + queryLabelNamesWithMatchers bool } // Select implements storage.Querier interface. @@ -192,11 +199,45 @@ func (q *distributorQuerier) LabelValues(name string, matchers ...*labels.Matche return lvs, nil, err } -func (q *distributorQuerier) LabelNames() ([]string, storage.Warnings, error) { - ln, err := q.distributor.LabelNames(q.ctx, model.Time(q.mint), model.Time(q.maxt)) +func (q *distributorQuerier) LabelNames(matchers ...*labels.Matcher) ([]string, storage.Warnings, error) { + log, ctx := spanlogger.New(q.ctx, "distributorQuerier.LabelNames") + defer log.Span.Finish() + + if len(matchers) > 0 && !q.queryLabelNamesWithMatchers { + return q.legacyLabelNamesWithMatchersThroughMetricsCall(ctx, matchers...) + } + + ln, err := q.distributor.LabelNames(ctx, model.Time(q.mint), model.Time(q.maxt), matchers...) return ln, nil, err } +// legacyLabelNamesWithMatchersThroughMetricsCall performs the LabelNames call in _the old way_, by calling ingester's MetricsForLabelMatchers method +// this is used when the LabelNames with matchers feature is first deployed, and some ingesters may have not been updated yet, so they could be ignoring +// the matchers, leading to wrong results. +func (q *distributorQuerier) legacyLabelNamesWithMatchersThroughMetricsCall(ctx context.Context, matchers ...*labels.Matcher) ([]string, storage.Warnings, error) { + log, ctx := spanlogger.New(ctx, "distributorQuerier.legacyLabelNamesWithMatchersThroughMetricsCall") + defer log.Span.Finish() + ms, err := q.distributor.MetricsForLabelMatchers(ctx, model.Time(q.mint), model.Time(q.maxt), matchers...) + if err != nil { + return nil, nil, err + } + namesMap := make(map[string]struct{}) + + for _, m := range ms { + for name := range m.Metric { + namesMap[string(name)] = struct{}{} + } + } + + names := make([]string, 0, len(namesMap)) + for name := range namesMap { + names = append(names, name) + } + sort.Strings(names) + + return names, nil, nil +} + func (q *distributorQuerier) Close() error { return nil } diff --git a/pkg/querier/distributor_queryable_test.go b/pkg/querier/distributor_queryable_test.go index 1b664a4b8b3..c90aaa66156 100644 --- a/pkg/querier/distributor_queryable_test.go +++ b/pkg/querier/distributor_queryable_test.go @@ -46,7 +46,7 @@ func TestDistributorQuerier(t *testing.T) { }, nil) - queryable := newDistributorQueryable(d, false, nil, 0) + queryable := newDistributorQueryable(d, false, nil, 0, true) querier, err := queryable.Querier(context.Background(), mint, maxt) require.NoError(t, err) @@ -123,7 +123,7 @@ func TestDistributorQuerier_SelectShouldHonorQueryIngestersWithin(t *testing.T) distributor.On("MetricsForLabelMatchers", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]metric.Metric{}, nil) ctx := user.InjectOrgID(context.Background(), "test") - queryable := newDistributorQueryable(distributor, streamingEnabled, nil, testData.queryIngestersWithin) + queryable := newDistributorQueryable(distributor, streamingEnabled, nil, testData.queryIngestersWithin, true) querier, err := queryable.Querier(ctx, testData.queryMinT, testData.queryMaxT) require.NoError(t, err) @@ -150,7 +150,7 @@ func TestDistributorQuerier_SelectShouldHonorQueryIngestersWithin(t *testing.T) func TestDistributorQueryableFilter(t *testing.T) { d := &mockDistributor{} - dq := newDistributorQueryable(d, false, nil, 1*time.Hour) + dq := newDistributorQueryable(d, false, nil, 1*time.Hour, true) now := time.Now() @@ -196,7 +196,7 @@ func TestIngesterStreaming(t *testing.T) { nil) ctx := user.InjectOrgID(context.Background(), "0") - queryable := newDistributorQueryable(d, true, mergeChunks, 0) + queryable := newDistributorQueryable(d, true, mergeChunks, 0, true) querier, err := queryable.Querier(ctx, mint, maxt) require.NoError(t, err) @@ -272,7 +272,7 @@ func TestIngesterStreamingMixedResults(t *testing.T) { nil) ctx := user.InjectOrgID(context.Background(), "0") - queryable := newDistributorQueryable(d, true, mergeChunks, 0) + queryable := newDistributorQueryable(d, true, mergeChunks, 0, true) querier, err := queryable.Querier(ctx, mint, maxt) require.NoError(t, err) @@ -292,6 +292,48 @@ func TestIngesterStreamingMixedResults(t *testing.T) { require.NoError(t, seriesSet.Err()) } +func TestDistributorQuerier_LabelNames(t *testing.T) { + someMatchers := []*labels.Matcher{labels.MustNewMatcher(labels.MatchEqual, "foo", "bar")} + labelNames := []string{"foo", "job"} + + t.Run("with matchers", func(t *testing.T) { + t.Run("queryLabelNamesWithMatchers=true", func(t *testing.T) { + d := &mockDistributor{} + d.On("LabelNames", mock.Anything, model.Time(mint), model.Time(maxt), someMatchers). + Return(labelNames, nil) + + queryable := newDistributorQueryable(d, false, nil, 0, true) + querier, err := queryable.Querier(context.Background(), mint, maxt) + require.NoError(t, err) + + names, warnings, err := querier.LabelNames(someMatchers...) + require.NoError(t, err) + assert.Empty(t, warnings) + assert.Equal(t, labelNames, names) + }) + + t.Run("queryLabelNamesWithMatchers=false", func(t *testing.T) { + metrics := []metric.Metric{ + {Metric: model.Metric{"foo": "bar"}}, + {Metric: model.Metric{"job": "baz"}}, + {Metric: model.Metric{"job": "baz", "foo": "boom"}}, + } + d := &mockDistributor{} + d.On("MetricsForLabelMatchers", mock.Anything, model.Time(mint), model.Time(maxt), someMatchers). + Return(metrics, nil) + + queryable := newDistributorQueryable(d, false, nil, 0, false) + querier, err := queryable.Querier(context.Background(), mint, maxt) + require.NoError(t, err) + + names, warnings, err := querier.LabelNames(someMatchers...) + require.NoError(t, err) + assert.Empty(t, warnings) + assert.Equal(t, labelNames, names) + }) + }) +} + func verifySeries(t *testing.T, series storage.Series, l labels.Labels, samples []cortexpb.Sample) { require.Equal(t, l, series.Labels()) @@ -347,8 +389,8 @@ func (m *mockDistributor) LabelValuesForLabelName(ctx context.Context, from, to args := m.Called(ctx, from, to, lbl, matchers) return args.Get(0).([]string), args.Error(1) } -func (m *mockDistributor) LabelNames(ctx context.Context, from, to model.Time) ([]string, error) { - args := m.Called(ctx, from, to) +func (m *mockDistributor) LabelNames(ctx context.Context, from, to model.Time, matchers ...*labels.Matcher) ([]string, error) { + args := m.Called(ctx, from, to, matchers) return args.Get(0).([]string), args.Error(1) } func (m *mockDistributor) MetricsForLabelMatchers(ctx context.Context, from, to model.Time, matchers ...*labels.Matcher) ([]metric.Metric, error) { diff --git a/pkg/querier/duplicates_test.go b/pkg/querier/duplicates_test.go index 09ea1f42837..d400380fa10 100644 --- a/pkg/querier/duplicates_test.go +++ b/pkg/querier/duplicates_test.go @@ -128,7 +128,7 @@ func (m testQuerier) LabelValues(name string, matchers ...*labels.Matcher) ([]st return nil, nil, nil } -func (m testQuerier) LabelNames() ([]string, storage.Warnings, error) { +func (m testQuerier) LabelNames(matchers ...*labels.Matcher) ([]string, storage.Warnings, error) { return nil, nil, nil } diff --git a/pkg/querier/error_translate_queryable.go b/pkg/querier/error_translate_queryable.go index ce9289b18bb..1225690c12b 100644 --- a/pkg/querier/error_translate_queryable.go +++ b/pkg/querier/error_translate_queryable.go @@ -125,8 +125,8 @@ func (e errorTranslateQuerier) LabelValues(name string, matchers ...*labels.Matc return values, warnings, e.fn(err) } -func (e errorTranslateQuerier) LabelNames() ([]string, storage.Warnings, error) { - values, warnings, err := e.q.LabelNames() +func (e errorTranslateQuerier) LabelNames(matchers ...*labels.Matcher) ([]string, storage.Warnings, error) { + values, warnings, err := e.q.LabelNames(matchers...) return values, warnings, e.fn(err) } @@ -149,8 +149,8 @@ func (e errorTranslateChunkQuerier) LabelValues(name string, matchers ...*labels return values, warnings, e.fn(err) } -func (e errorTranslateChunkQuerier) LabelNames() ([]string, storage.Warnings, error) { - values, warnings, err := e.q.LabelNames() +func (e errorTranslateChunkQuerier) LabelNames(matchers ...*labels.Matcher) ([]string, storage.Warnings, error) { + values, warnings, err := e.q.LabelNames(matchers...) return values, warnings, e.fn(err) } diff --git a/pkg/querier/error_translate_queryable_test.go b/pkg/querier/error_translate_queryable_test.go index 35e882212aa..ee17c9f07cd 100644 --- a/pkg/querier/error_translate_queryable_test.go +++ b/pkg/querier/error_translate_queryable_test.go @@ -192,7 +192,7 @@ func (t errorTestQuerier) LabelValues(name string, matchers ...*labels.Matcher) return nil, nil, t.err } -func (t errorTestQuerier) LabelNames() ([]string, storage.Warnings, error) { +func (t errorTestQuerier) LabelNames(matchers ...*labels.Matcher) ([]string, storage.Warnings, error) { return nil, nil, t.err } diff --git a/pkg/querier/lazyquery/lazyquery.go b/pkg/querier/lazyquery/lazyquery.go index da96049fddf..1ca6f4c2b5e 100644 --- a/pkg/querier/lazyquery/lazyquery.go +++ b/pkg/querier/lazyquery/lazyquery.go @@ -63,8 +63,8 @@ func (l LazyQuerier) LabelValues(name string, matchers ...*labels.Matcher) ([]st } // LabelNames implements Storage.Querier -func (l LazyQuerier) LabelNames() ([]string, storage.Warnings, error) { - return l.next.LabelNames() +func (l LazyQuerier) LabelNames(matchers ...*labels.Matcher) ([]string, storage.Warnings, error) { + return l.next.LabelNames(matchers...) } // Close implements Storage.Querier diff --git a/pkg/querier/querier.go b/pkg/querier/querier.go index cb3b35f32e0..2c9da343d28 100644 --- a/pkg/querier/querier.go +++ b/pkg/querier/querier.go @@ -46,6 +46,11 @@ type Config struct { QueryStoreForLabels bool `yaml:"query_store_for_labels_enabled"` AtModifierEnabled bool `yaml:"at_modifier_enabled"` + // QueryLabelNamesWithMatchers enables the usage of matchers in the LabelNames call. + // Can be enabled once this code is deployed on all ingesters, so they correctly read that request param. + // When disabled, the MetricsForLabelMatchers method is used to retrieve label names when matchers are provided. + QueryLabelNamesWithMatchers bool `yaml:"query_label_names_with_matchers_enabled"` + // QueryStoreAfter the time after which queries should also be sent to the store and not just ingesters. QueryStoreAfter time.Duration `yaml:"query_store_after"` MaxQueryIntoFuture time.Duration `yaml:"max_query_into_future"` @@ -92,6 +97,7 @@ func (cfg *Config) RegisterFlags(f *flag.FlagSet) { f.DurationVar(&cfg.QueryIngestersWithin, "querier.query-ingesters-within", 0, "Maximum lookback beyond which queries are not sent to ingester. 0 means all queries are sent to ingester.") f.BoolVar(&cfg.QueryStoreForLabels, "querier.query-store-for-labels-enabled", false, "Query long-term store for series, label values and label names APIs. Works only with blocks engine.") f.BoolVar(&cfg.AtModifierEnabled, "querier.at-modifier-enabled", false, "Enable the @ modifier in PromQL.") + f.BoolVar(&cfg.QueryLabelNamesWithMatchers, "querier.query-label-names-with-matchers-enabled", false, "True to enable queriers to use an optimized implementation which passes down to ingesters the label matchers when running the label names API. Can be enabled once all ingesters run a version >= the one where this option has been introduced.") f.DurationVar(&cfg.MaxQueryIntoFuture, "querier.max-query-into-future", 10*time.Minute, "Maximum duration into the future you can query. 0 to disable.") f.DurationVar(&cfg.DefaultEvaluationInterval, "querier.default-evaluation-interval", time.Minute, "The default evaluation interval or step size for subqueries.") f.DurationVar(&cfg.QueryStoreAfter, "querier.query-store-after", 0, "The time after which a metric should be queried from storage and not just ingesters. 0 means all queries are sent to store. When running the blocks storage, if this option is enabled, the time range of the query sent to the store will be manipulated to ensure the query end is not more recent than 'now - query-store-after'.") @@ -147,7 +153,7 @@ func NewChunkStoreQueryable(cfg Config, chunkStore chunkstore.ChunkStore) storag func New(cfg Config, limits *validation.Overrides, distributor Distributor, stores []QueryableWithFilter, tombstonesLoader *purger.TombstonesLoader, reg prometheus.Registerer, logger log.Logger) (storage.SampleAndChunkQueryable, storage.ExemplarQueryable, *promql.Engine) { iteratorFunc := getChunksIteratorFunction(cfg) - distributorQueryable := newDistributorQueryable(distributor, cfg.IngesterStreaming, iteratorFunc, cfg.QueryIngestersWithin) + distributorQueryable := newDistributorQueryable(distributor, cfg.IngesterStreaming, iteratorFunc, cfg.QueryIngestersWithin, cfg.QueryLabelNamesWithMatchers) ns := make([]QueryableWithFilter, len(stores)) for ix, s := range stores { @@ -431,13 +437,13 @@ func (q querier) LabelValues(name string, matchers ...*labels.Matcher) ([]string return strutil.MergeSlices(sets...), warnings, nil } -func (q querier) LabelNames() ([]string, storage.Warnings, error) { +func (q querier) LabelNames(matchers ...*labels.Matcher) ([]string, storage.Warnings, error) { if !q.queryStoreForLabels { - return q.metadataQuerier.LabelNames() + return q.metadataQuerier.LabelNames(matchers...) } if len(q.queriers) == 1 { - return q.queriers[0].LabelNames() + return q.queriers[0].LabelNames(matchers...) } var ( @@ -453,7 +459,7 @@ func (q querier) LabelNames() ([]string, storage.Warnings, error) { querier := querier g.Go(func() error { // NB: Names are sorted in Cortex already. - myNames, myWarnings, err := querier.LabelNames() + myNames, myWarnings, err := querier.LabelNames(matchers...) if err != nil { return err } diff --git a/pkg/querier/querier_test.go b/pkg/querier/querier_test.go index bc6d3819b67..61ecd391796 100644 --- a/pkg/querier/querier_test.go +++ b/pkg/querier/querier_test.go @@ -559,6 +559,7 @@ func TestQuerier_ValidateQueryTimeRange_MaxQueryLookback(t *testing.T) { var cfg Config flagext.DefaultValues(&cfg) cfg.IngesterStreaming = ingesterStreaming + cfg.QueryLabelNamesWithMatchers = true limits := defaultLimitsConfig() limits.MaxQueryLookback = testData.maxQueryLookback @@ -631,14 +632,17 @@ func TestQuerier_ValidateQueryTimeRange_MaxQueryLookback(t *testing.T) { }) t.Run("label names", func(t *testing.T) { + matchers := []*labels.Matcher{ + labels.MustNewMatcher(labels.MatchNotEqual, "route", "get_user"), + } distributor := &mockDistributor{} - distributor.On("LabelNames", mock.Anything, mock.Anything, mock.Anything).Return([]string{}, nil) + distributor.On("LabelNames", mock.Anything, mock.Anything, mock.Anything, matchers).Return([]string{}, nil) queryable, _, _ := New(cfg, overrides, distributor, queryables, purger.NewTombstonesLoader(nil, nil), nil, log.NewNopLogger()) q, err := queryable.Querier(ctx, util.TimeToMillis(testData.queryStartTime), util.TimeToMillis(testData.queryEndTime)) require.NoError(t, err) - _, _, err = q.LabelNames() + _, _, err = q.LabelNames(matchers...) require.NoError(t, err) if !testData.expectedSkipped { @@ -646,8 +650,10 @@ func TestQuerier_ValidateQueryTimeRange_MaxQueryLookback(t *testing.T) { delta := float64(5000) require.Len(t, distributor.Calls, 1) assert.Equal(t, "LabelNames", distributor.Calls[0].Method) - assert.InDelta(t, util.TimeToMillis(testData.expectedMetadataStartTime), int64(distributor.Calls[0].Arguments.Get(1).(model.Time)), delta) - assert.InDelta(t, util.TimeToMillis(testData.expectedMetadataEndTime), int64(distributor.Calls[0].Arguments.Get(2).(model.Time)), delta) + args := distributor.Calls[0].Arguments + assert.InDelta(t, util.TimeToMillis(testData.expectedMetadataStartTime), int64(args.Get(1).(model.Time)), delta) + assert.InDelta(t, util.TimeToMillis(testData.expectedMetadataEndTime), int64(args.Get(2).(model.Time)), delta) + assert.Equal(t, matchers, args.Get(3).([]*labels.Matcher)) } else { // Ensure no query has been executed executed (because skipped). assert.Len(t, distributor.Calls, 0) @@ -752,7 +758,7 @@ func (m *errDistributor) QueryExemplars(ctx context.Context, from, to model.Time func (m *errDistributor) LabelValuesForLabelName(context.Context, model.Time, model.Time, model.LabelName, ...*labels.Matcher) ([]string, error) { return nil, errDistributorError } -func (m *errDistributor) LabelNames(context.Context, model.Time, model.Time) ([]string, error) { +func (m *errDistributor) LabelNames(context.Context, model.Time, model.Time, ...*labels.Matcher) ([]string, error) { return nil, errDistributorError } func (m *errDistributor) MetricsForLabelMatchers(ctx context.Context, from, through model.Time, matchers ...*labels.Matcher) ([]metric.Metric, error) { @@ -799,7 +805,7 @@ func (d *emptyDistributor) LabelValuesForLabelName(context.Context, model.Time, return nil, nil } -func (d *emptyDistributor) LabelNames(context.Context, model.Time, model.Time) ([]string, error) { +func (d *emptyDistributor) LabelNames(context.Context, model.Time, model.Time, ...*labels.Matcher) ([]string, error) { return nil, nil } diff --git a/pkg/querier/queryrange/promql_test.go b/pkg/querier/queryrange/promql_test.go index 3a4f39fb37d..05ec6168c7c 100644 --- a/pkg/querier/queryrange/promql_test.go +++ b/pkg/querier/queryrange/promql_test.go @@ -614,8 +614,10 @@ func (m *testMatrix) Select(_ bool, selectParams *storage.SelectHints, matchers func (m *testMatrix) LabelValues(name string, matchers ...*labels.Matcher) ([]string, storage.Warnings, error) { return nil, nil, nil } -func (m *testMatrix) LabelNames() ([]string, storage.Warnings, error) { return nil, nil, nil } -func (m *testMatrix) Close() error { return nil } +func (m *testMatrix) LabelNames(matchers ...*labels.Matcher) ([]string, storage.Warnings, error) { + return nil, nil, nil +} +func (m *testMatrix) Close() error { return nil } func newSeries(metric labels.Labels, generator func(float64) float64) *promql.StorageSeries { sort.Sort(metric) diff --git a/pkg/querier/queryrange/queryable.go b/pkg/querier/queryrange/queryable.go index a9fe27e0382..7da6cb63fd9 100644 --- a/pkg/querier/queryrange/queryable.go +++ b/pkg/querier/queryrange/queryable.go @@ -136,7 +136,7 @@ func (q *ShardedQuerier) LabelValues(name string, matchers ...*labels.Matcher) ( } // LabelNames returns all the unique label names present in the block in sorted order. -func (q *ShardedQuerier) LabelNames() ([]string, storage.Warnings, error) { +func (q *ShardedQuerier) LabelNames(matchers ...*labels.Matcher) ([]string, storage.Warnings, error) { return nil, nil, errors.Errorf("unimplemented") } diff --git a/pkg/querier/queryrange/test_utils.go b/pkg/querier/queryrange/test_utils.go index d6e9b2dff71..8f87b3f8376 100644 --- a/pkg/querier/queryrange/test_utils.go +++ b/pkg/querier/queryrange/test_utils.go @@ -175,7 +175,7 @@ func (q *MockShardedQueryable) LabelValues(name string, matchers ...*labels.Matc } // LabelNames returns all the unique label names present in the block in sorted order. -func (q *MockShardedQueryable) LabelNames() ([]string, storage.Warnings, error) { +func (q *MockShardedQueryable) LabelNames(matchers ...*labels.Matcher) ([]string, storage.Warnings, error) { return nil, nil, errors.Errorf("unimplemented") } diff --git a/pkg/querier/remote_read_test.go b/pkg/querier/remote_read_test.go index 45ff6576c11..35fd08929fa 100644 --- a/pkg/querier/remote_read_test.go +++ b/pkg/querier/remote_read_test.go @@ -101,7 +101,7 @@ func (m mockQuerier) LabelValues(name string, matchers ...*labels.Matcher) ([]st return nil, nil, nil } -func (m mockQuerier) LabelNames() ([]string, storage.Warnings, error) { +func (m mockQuerier) LabelNames(matchers ...*labels.Matcher) ([]string, storage.Warnings, error) { return nil, nil, nil } diff --git a/pkg/querier/tenantfederation/merge_queryable.go b/pkg/querier/tenantfederation/merge_queryable.go index fa1f5ce7272..9efba31d135 100644 --- a/pkg/querier/tenantfederation/merge_queryable.go +++ b/pkg/querier/tenantfederation/merge_queryable.go @@ -166,12 +166,15 @@ func (m *mergeQuerier) LabelValues(name string, matchers ...*labels.Matcher) ([] // LabelNames returns all the unique label names present in the underlying // queriers. It also adds the `idLabelName` and if present in the original // results the original `idLabelName`. -func (m *mergeQuerier) LabelNames() ([]string, storage.Warnings, error) { +func (m *mergeQuerier) LabelNames(matchers ...*labels.Matcher) ([]string, storage.Warnings, error) { log, _ := spanlogger.New(m.ctx, "mergeQuerier.LabelNames") defer log.Span.Finish() - labelNames, warnings, err := m.mergeDistinctStringSlice(func(ctx context.Context, q storage.Querier) ([]string, storage.Warnings, error) { - return q.LabelNames() - }) + + matchedTenants, filteredMatchers := filterValuesByMatchers(m.idLabelName, m.ids, matchers...) + + labelNames, warnings, err := m.mergeDistinctStringSliceWithTenants(func(ctx context.Context, q storage.Querier) ([]string, storage.Warnings, error) { + return q.LabelNames(filteredMatchers...) + }, matchedTenants) if err != nil { return nil, nil, err } @@ -276,12 +279,6 @@ func (m *mergeQuerier) mergeDistinctStringSliceWithTenants(f stringSliceFunc, te return result, warnings, nil } -// mergeDistinctStringSlice aggregates results from all stringSliceFunc calls -// for all queriers, in parallel. -func (m *mergeQuerier) mergeDistinctStringSlice(f stringSliceFunc) ([]string, storage.Warnings, error) { - return m.mergeDistinctStringSliceWithTenants(f, nil) -} - // Close releases the resources of the Querier. func (m *mergeQuerier) Close() error { errs := tsdb_errors.NewMulti() @@ -354,7 +351,7 @@ func (m *mergeQuerier) Select(sortSeries bool, hints *storage.SelectHints, match // are considered and the forwarded matchers do not contain matchers on the // `idLabelName`. func filterValuesByMatchers(idLabelName string, ids []string, matchers ...*labels.Matcher) (matchedIDs map[string]struct{}, unrelatedMatchers []*labels.Matcher) { - // this contains the matchers which are not related to labelName + // this contains the matchers which are not related to idLabelName unrelatedMatchers = make([]*labels.Matcher, 0, len(matchers)) // build map of values to consider for the matchers @@ -364,24 +361,25 @@ func filterValuesByMatchers(idLabelName string, ids []string, matchers ...*label } for _, m := range matchers { - if m.Name != idLabelName { - // check if has the retained label name - if m.Name == retainExistingPrefix+idLabelName { - // rewrite label to the original name, by copying matcher and - // replacing the label name - rewrittenM := *m - rewrittenM.Name = idLabelName - unrelatedMatchers = append(unrelatedMatchers, &rewrittenM) - } else { - unrelatedMatchers = append(unrelatedMatchers, m) + switch m.Name { + // matcher has idLabelName to target a specific tenant(s) + case idLabelName: + for value := range matchedIDs { + if !m.Matches(value) { + delete(matchedIDs, value) + } } - continue - } - for value := range matchedIDs { - if !m.Matches(value) { - delete(matchedIDs, value) - } + // check if has the retained label name + case retainExistingPrefix + idLabelName: + // rewrite label to the original name, by copying matcher and + // replacing the label name + rewrittenM := *m + rewrittenM.Name = idLabelName + unrelatedMatchers = append(unrelatedMatchers, &rewrittenM) + + default: + unrelatedMatchers = append(unrelatedMatchers, m) } } diff --git a/pkg/querier/tenantfederation/merge_queryable_test.go b/pkg/querier/tenantfederation/merge_queryable_test.go index 113be825401..fd1441daa07 100644 --- a/pkg/querier/tenantfederation/merge_queryable_test.go +++ b/pkg/querier/tenantfederation/merge_queryable_test.go @@ -31,6 +31,10 @@ const ( // originalDefaultTenantLabel is the default tenant label with a prefix. // It is used to prevent matcher clashes for timeseries that happen to have a label with the same name as the default tenant label. originalDefaultTenantLabel = retainExistingPrefix + defaultTenantLabel + // seriesWithLabelNames can be used as matcher's label name in LabelNames call. + // If it's the only matcher provided then the result label names are the pipe-split value of the matcher. + // Matcher type is ignored. + seriesWithLabelNames = "series_with_label_names" ) // mockTenantQueryableWithFilter is a storage.Queryable that can be use to return specific warnings or errors by tenant. @@ -203,7 +207,20 @@ func (m mockTenantQuerier) LabelValues(name string, matchers ...*labels.Matcher) // LabelNames implements the storage.LabelQuerier interface. // It returns a sorted slice of all label names in the querier. -func (m mockTenantQuerier) LabelNames() ([]string, storage.Warnings, error) { +// If only one matcher is provided with label Name=seriesWithLabelNames then the resulting set will have the values of that matchers pipe-split appended. +// I.e. querying for {seriesWithLabelNames="foo|bar|baz"} will have as result [bar, baz, foo, ] +func (m mockTenantQuerier) LabelNames(matchers ...*labels.Matcher) ([]string, storage.Warnings, error) { + var results []string + + if len(matchers) == 1 && matchers[0].Name == seriesWithLabelNames { + if matchers[0].Value == "" { + return nil, m.warnings, nil + } + results = strings.Split(matchers[0].Value, "|") + } else if len(matchers) > 1 { + m.warnings = append(m.warnings, errors.New(mockMatchersNotImplemented)) + } + if m.queryErr != nil { return nil, nil, m.queryErr } @@ -214,7 +231,7 @@ func (m mockTenantQuerier) LabelNames() ([]string, storage.Warnings, error) { labelValues[string(k)] = struct{}{} } } - var results []string + for k := range labelValues { results = append(results, k) } @@ -276,6 +293,8 @@ type selectScenario struct { type labelNamesTestCase struct { // name is a description of the test case. name string + // matchers is a slice of label matchers used to filter series in the test case. + matchers []*labels.Matcher // expectedLabelNames are the expected label names returned from the queryable. expectedLabelNames []string // expectedWarnings is a slice of storage.Warnings messages expected when querying. @@ -497,6 +516,14 @@ func TestMergeQueryable_LabelNames(t *testing.T) { expectedLabelNames: []string{"instance", "tenant-team-a"}, }, }, + { + mergeQueryableScenario: singleTenantScenario, + labelNamesTestCase: labelNamesTestCase{ + name: "should not return the __tenant_id__ label as the MergeQueryable has been bypassed with matchers", + matchers: []*labels.Matcher{labels.MustNewMatcher(labels.MatchRegexp, seriesWithLabelNames, "bar|foo")}, + expectedLabelNames: []string{"bar", "foo", "instance", "tenant-team-a"}, + }, + }, { mergeQueryableScenario: singleTenantNoBypassScenario, labelNamesTestCase: labelNamesTestCase{ @@ -536,13 +563,69 @@ func TestMergeQueryable_LabelNames(t *testing.T) { expectedQueryErr: errors.New("error querying tenant_id team-b: failure xyz"), }, }, + { + mergeQueryableScenario: threeTenantsWithWarningsScenario, + labelNamesTestCase: labelNamesTestCase{ + name: "should propagate non-tenant matchers to downstream queriers", + matchers: []*labels.Matcher{labels.MustNewMatcher(labels.MatchRegexp, seriesWithLabelNames, "bar|foo")}, + expectedLabelNames: []string{defaultTenantLabel, "bar", "foo", "instance", "tenant-team-a", "tenant-team-b", "tenant-team-c"}, + expectedWarnings: []string{ + `warning querying tenant_id team-b: don't like them`, + `warning querying tenant_id team-c: out of office`, + }, + }, + }, + { + mergeQueryableScenario: threeTenantsWithWarningsScenario, + labelNamesTestCase: labelNamesTestCase{ + + name: "should only query tenant-b when there is an equals matcher for team-b tenant", + matchers: []*labels.Matcher{ + {Name: defaultTenantLabel, Value: "team-b", Type: labels.MatchEqual}, + labels.MustNewMatcher(labels.MatchRegexp, seriesWithLabelNames, "bar|foo"), + }, + expectedLabelNames: []string{defaultTenantLabel, "bar", "foo", "instance", "tenant-team-b"}, + expectedWarnings: []string{ + `warning querying tenant_id team-b: don't like them`, + }, + }, + }, + { + mergeQueryableScenario: threeTenantsWithWarningsScenario, + labelNamesTestCase: labelNamesTestCase{ + name: "should only query tenant-b and tenant-c when there is an regex matcher for team-b|team-c tenant", + matchers: []*labels.Matcher{ + labels.MustNewMatcher(labels.MatchRegexp, defaultTenantLabel, "team-b|team-c"), + labels.MustNewMatcher(labels.MatchRegexp, seriesWithLabelNames, "bar|foo"), + }, + expectedLabelNames: []string{defaultTenantLabel, "bar", "foo", "instance", "tenant-team-b", "tenant-team-c"}, + expectedWarnings: []string{ + `warning querying tenant_id team-b: don't like them`, + `warning querying tenant_id team-c: out of office`, + }, + }, + }, + { + mergeQueryableScenario: threeTenantsWithWarningsScenario, + labelNamesTestCase: labelNamesTestCase{ + name: "only tenant-b is selected and it already has a defaultTenantLabel which is prepended with original_ prefix", + matchers: []*labels.Matcher{ + {Name: defaultTenantLabel, Value: "team-b", Type: labels.MatchEqual}, + labels.MustNewMatcher(labels.MatchRegexp, seriesWithLabelNames, defaultTenantLabel), + }, + expectedLabelNames: []string{defaultTenantLabel, "instance", originalDefaultTenantLabel, "tenant-team-b"}, + expectedWarnings: []string{ + `warning querying tenant_id team-b: don't like them`, + }, + }, + }, } { t.Run(scenario.mergeQueryableScenario.name, func(t *testing.T) { querier, err := scenario.init() require.NoError(t, err) t.Run(scenario.labelNamesTestCase.name, func(t *testing.T) { - labelNames, warnings, err := querier.LabelNames() + labelNames, warnings, err := querier.LabelNames(scenario.labelNamesTestCase.matchers...) if scenario.labelNamesTestCase.expectedQueryErr != nil { require.EqualError(t, err, scenario.labelNamesTestCase.expectedQueryErr.Error()) } else { diff --git a/vendor/github.com/prometheus/prometheus/storage/interface.go b/vendor/github.com/prometheus/prometheus/storage/interface.go index 92ad15b8c08..d3dab0e21e2 100644 --- a/vendor/github.com/prometheus/prometheus/storage/interface.go +++ b/vendor/github.com/prometheus/prometheus/storage/interface.go @@ -113,8 +113,9 @@ type LabelQuerier interface { LabelValues(name string, matchers ...*labels.Matcher) ([]string, Warnings, error) // LabelNames returns all the unique label names present in the block in sorted order. - // TODO(yeya24): support matchers or hints. - LabelNames() ([]string, Warnings, error) + // If matchers are specified the returned result set is reduced + // to label names of metrics matching the matchers. + LabelNames(matchers ...*labels.Matcher) ([]string, Warnings, error) // Close releases the resources of the Querier. Close() error diff --git a/vendor/github.com/prometheus/prometheus/storage/merge.go b/vendor/github.com/prometheus/prometheus/storage/merge.go index 81e45d55de4..1c08a537f4b 100644 --- a/vendor/github.com/prometheus/prometheus/storage/merge.go +++ b/vendor/github.com/prometheus/prometheus/storage/merge.go @@ -218,13 +218,13 @@ func mergeStrings(a, b []string) []string { } // LabelNames returns all the unique label names present in all queriers in sorted order. -func (q *mergeGenericQuerier) LabelNames() ([]string, Warnings, error) { +func (q *mergeGenericQuerier) LabelNames(matchers ...*labels.Matcher) ([]string, Warnings, error) { var ( labelNamesMap = make(map[string]struct{}) warnings Warnings ) for _, querier := range q.queriers { - names, wrn, err := querier.LabelNames() + names, wrn, err := querier.LabelNames(matchers...) if wrn != nil { // TODO(bwplotka): We could potentially wrap warnings. warnings = append(warnings, wrn...) diff --git a/vendor/github.com/prometheus/prometheus/storage/noop.go b/vendor/github.com/prometheus/prometheus/storage/noop.go index 3f800e76cec..c63353b92f9 100644 --- a/vendor/github.com/prometheus/prometheus/storage/noop.go +++ b/vendor/github.com/prometheus/prometheus/storage/noop.go @@ -32,7 +32,7 @@ func (noopQuerier) LabelValues(string, ...*labels.Matcher) ([]string, Warnings, return nil, nil, nil } -func (noopQuerier) LabelNames() ([]string, Warnings, error) { +func (noopQuerier) LabelNames(...*labels.Matcher) ([]string, Warnings, error) { return nil, nil, nil } @@ -55,7 +55,7 @@ func (noopChunkQuerier) LabelValues(string, ...*labels.Matcher) ([]string, Warni return nil, nil, nil } -func (noopChunkQuerier) LabelNames() ([]string, Warnings, error) { +func (noopChunkQuerier) LabelNames(...*labels.Matcher) ([]string, Warnings, error) { return nil, nil, nil } diff --git a/vendor/github.com/prometheus/prometheus/storage/remote/read.go b/vendor/github.com/prometheus/prometheus/storage/remote/read.go index 94eab01cf81..7f1d749e639 100644 --- a/vendor/github.com/prometheus/prometheus/storage/remote/read.go +++ b/vendor/github.com/prometheus/prometheus/storage/remote/read.go @@ -212,7 +212,7 @@ func (q *querier) LabelValues(string, ...*labels.Matcher) ([]string, storage.War } // LabelNames implements storage.Querier and is a noop. -func (q *querier) LabelNames() ([]string, storage.Warnings, error) { +func (q *querier) LabelNames(...*labels.Matcher) ([]string, storage.Warnings, error) { // TODO: Implement: https://github.com/prometheus/prometheus/issues/3351 return nil, nil, errors.New("not implemented") } diff --git a/vendor/github.com/prometheus/prometheus/storage/secondary.go b/vendor/github.com/prometheus/prometheus/storage/secondary.go index 2586a77440e..64a83b5e7c2 100644 --- a/vendor/github.com/prometheus/prometheus/storage/secondary.go +++ b/vendor/github.com/prometheus/prometheus/storage/secondary.go @@ -55,8 +55,8 @@ func (s *secondaryQuerier) LabelValues(name string, matchers ...*labels.Matcher) return vals, w, nil } -func (s *secondaryQuerier) LabelNames() ([]string, Warnings, error) { - names, w, err := s.genericQuerier.LabelNames() +func (s *secondaryQuerier) LabelNames(matchers ...*labels.Matcher) ([]string, Warnings, error) { + names, w, err := s.genericQuerier.LabelNames(matchers...) if err != nil { return nil, append([]error{err}, w...), nil } diff --git a/vendor/github.com/prometheus/prometheus/tsdb/block.go b/vendor/github.com/prometheus/prometheus/tsdb/block.go index 23a075c4f66..42a91ff5930 100644 --- a/vendor/github.com/prometheus/prometheus/tsdb/block.go +++ b/vendor/github.com/prometheus/prometheus/tsdb/block.go @@ -85,13 +85,17 @@ type IndexReader interface { Series(ref uint64, lset *labels.Labels, chks *[]chunks.Meta) error // LabelNames returns all the unique label names present in the index in sorted order. - LabelNames() ([]string, error) + LabelNames(matchers ...*labels.Matcher) ([]string, error) // LabelValueFor returns label value for the given label name in the series referred to by ID. // If the series couldn't be found or the series doesn't have the requested label a // storage.ErrNotFound is returned as error. LabelValueFor(id uint64, label string) (string, error) + // LabelNamesFor returns all the label names for the series referred to by IDs. + // The names returned are sorted. + LabelNamesFor(ids ...uint64) ([]string, error) + // Close releases the underlying resources of the reader. Close() error } @@ -443,7 +447,15 @@ func (r blockIndexReader) LabelValues(name string, matchers ...*labels.Matcher) return st, errors.Wrapf(err, "block: %s", r.b.Meta().ULID) } - return labelValuesWithMatchers(r, name, matchers...) + return labelValuesWithMatchers(r.ir, name, matchers...) +} + +func (r blockIndexReader) LabelNames(matchers ...*labels.Matcher) ([]string, error) { + if len(matchers) == 0 { + return r.b.LabelNames() + } + + return labelNamesWithMatchers(r.ir, matchers...) } func (r blockIndexReader) Postings(name string, values ...string) (index.Postings, error) { @@ -465,10 +477,6 @@ func (r blockIndexReader) Series(ref uint64, lset *labels.Labels, chks *[]chunks return nil } -func (r blockIndexReader) LabelNames() ([]string, error) { - return r.b.LabelNames() -} - func (r blockIndexReader) Close() error { r.b.pendingReaders.Done() return nil @@ -479,6 +487,12 @@ func (r blockIndexReader) LabelValueFor(id uint64, label string) (string, error) return r.ir.LabelValueFor(id, label) } +// LabelNamesFor returns all the label names for the series referred to by IDs. +// The names returned are sorted. +func (r blockIndexReader) LabelNamesFor(ids ...uint64) ([]string, error) { + return r.ir.LabelNamesFor(ids...) +} + type blockTombstoneReader struct { tombstones.Reader b *Block diff --git a/vendor/github.com/prometheus/prometheus/tsdb/head.go b/vendor/github.com/prometheus/prometheus/tsdb/head.go index 2f1896dee60..92ad7b53ccd 100644 --- a/vendor/github.com/prometheus/prometheus/tsdb/head.go +++ b/vendor/github.com/prometheus/prometheus/tsdb/head.go @@ -2019,18 +2019,21 @@ func (h *headIndexReader) LabelValues(name string, matchers ...*labels.Matcher) // LabelNames returns all the unique label names present in the head // that are within the time range mint to maxt. -func (h *headIndexReader) LabelNames() ([]string, error) { - h.head.symMtx.RLock() +func (h *headIndexReader) LabelNames(matchers ...*labels.Matcher) ([]string, error) { if h.maxt < h.head.MinTime() || h.mint > h.head.MaxTime() { - h.head.symMtx.RUnlock() return []string{}, nil } - labelNames := h.head.postings.LabelNames() - h.head.symMtx.RUnlock() + if len(matchers) == 0 { + h.head.symMtx.RLock() + labelNames := h.head.postings.LabelNames() + h.head.symMtx.RUnlock() + + sort.Strings(labelNames) + return labelNames, nil + } - sort.Strings(labelNames) - return labelNames, nil + return labelNamesWithMatchers(h, matchers...) } // Postings returns the postings list iterator for the label pairs. @@ -2122,6 +2125,27 @@ func (h *headIndexReader) LabelValueFor(id uint64, label string) (string, error) return value, nil } +// LabelNamesFor returns all the label names for the series referred to by IDs. +// The names returned are sorted. +func (h *headIndexReader) LabelNamesFor(ids ...uint64) ([]string, error) { + namesMap := make(map[string]struct{}) + for _, id := range ids { + memSeries := h.head.series.getByID(id) + if memSeries == nil { + return nil, storage.ErrNotFound + } + for _, lbl := range memSeries.lset { + namesMap[lbl.Name] = struct{}{} + } + } + names := make([]string, 0, len(namesMap)) + for name := range namesMap { + names = append(names, name) + } + sort.Strings(names) + return names, nil +} + func (h *Head) getOrCreate(hash uint64, lset labels.Labels) (*memSeries, bool, error) { // Just using `getOrCreateWithID` below would be semantically sufficient, but we'd create // a new series on every sample inserted via Add(), which causes allocations diff --git a/vendor/github.com/prometheus/prometheus/tsdb/index/index.go b/vendor/github.com/prometheus/prometheus/tsdb/index/index.go index a6ade9455ea..a7b9e57ef40 100644 --- a/vendor/github.com/prometheus/prometheus/tsdb/index/index.go +++ b/vendor/github.com/prometheus/prometheus/tsdb/index/index.go @@ -523,9 +523,15 @@ func (w *Writer) AddSymbol(sym string) error { } func (w *Writer) finishSymbols() error { + symbolTableSize := w.f.pos - w.toc.Symbols - 4 + // The symbol table's part is 4 bytes. So the total symbol table size must be less than or equal to 2^32-1 + if symbolTableSize > 4294967295 { + return errors.Errorf("symbol table size exceeds 4 bytes: %d", symbolTableSize) + } + // Write out the length and symbol count. w.buf1.Reset() - w.buf1.PutBE32int(int(w.f.pos - w.toc.Symbols - 4)) + w.buf1.PutBE32int(int(symbolTableSize)) w.buf1.PutBE32int(int(w.numSymbols)) if err := w.writeAt(w.buf1.Get(), w.toc.Symbols); err != nil { return err @@ -1511,6 +1517,49 @@ func (r *Reader) LabelValues(name string, matchers ...*labels.Matcher) ([]string return values, nil } +// LabelNamesFor returns all the label names for the series referred to by IDs. +// The names returned are sorted. +func (r *Reader) LabelNamesFor(ids ...uint64) ([]string, error) { + // Gather offsetsMap the name offsetsMap in the symbol table first + offsetsMap := make(map[uint32]struct{}) + for _, id := range ids { + offset := id + // In version 2 series IDs are no longer exact references but series are 16-byte padded + // and the ID is the multiple of 16 of the actual position. + if r.version == FormatV2 { + offset = id * 16 + } + + d := encoding.NewDecbufUvarintAt(r.b, int(offset), castagnoliTable) + buf := d.Get() + if d.Err() != nil { + return nil, errors.Wrap(d.Err(), "get buffer for series") + } + + offsets, err := r.dec.LabelNamesOffsetsFor(buf) + if err != nil { + return nil, errors.Wrap(err, "get label name offsets") + } + for _, off := range offsets { + offsetsMap[off] = struct{}{} + } + } + + // Lookup the unique symbols. + names := make([]string, 0, len(offsetsMap)) + for off := range offsetsMap { + name, err := r.lookupSymbol(off) + if err != nil { + return nil, errors.Wrap(err, "lookup symbol in LabelNamesFor") + } + names = append(names, name) + } + + sort.Strings(names) + + return names, nil +} + // LabelValueFor returns label value for the given label name in the series referred to by ID. func (r *Reader) LabelValueFor(id uint64, label string) (string, error) { offset := id @@ -1664,7 +1713,12 @@ func (r *Reader) Size() int64 { } // LabelNames returns all the unique label names present in the index. -func (r *Reader) LabelNames() ([]string, error) { +// TODO(twilkie) implement support for matchers +func (r *Reader) LabelNames(matchers ...*labels.Matcher) ([]string, error) { + if len(matchers) > 0 { + return nil, errors.Errorf("matchers parameter is not implemented: %+v", matchers) + } + labelNames := make([]string, 0, len(r.postings)) for name := range r.postings { if name == allPostingsKey.Name { @@ -1715,6 +1769,25 @@ func (dec *Decoder) Postings(b []byte) (int, Postings, error) { return n, newBigEndianPostings(l), d.Err() } +// LabelNamesOffsetsFor decodes the offsets of the name symbols for a given series. +// They are returned in the same order they're stored, which should be sorted lexicographically. +func (dec *Decoder) LabelNamesOffsetsFor(b []byte) ([]uint32, error) { + d := encoding.Decbuf{B: b} + k := d.Uvarint() + + offsets := make([]uint32, k) + for i := 0; i < k; i++ { + offsets[i] = uint32(d.Uvarint()) + _ = d.Uvarint() // skip the label value + + if d.Err() != nil { + return nil, errors.Wrap(d.Err(), "read series label offsets") + } + } + + return offsets, d.Err() +} + // LabelValueFor decodes a label for a given series. func (dec *Decoder) LabelValueFor(b []byte, label string) (string, error) { d := encoding.Decbuf{B: b} diff --git a/vendor/github.com/prometheus/prometheus/tsdb/querier.go b/vendor/github.com/prometheus/prometheus/tsdb/querier.go index af5007fc18b..18a1fd20a5f 100644 --- a/vendor/github.com/prometheus/prometheus/tsdb/querier.go +++ b/vendor/github.com/prometheus/prometheus/tsdb/querier.go @@ -88,8 +88,8 @@ func (q *blockBaseQuerier) LabelValues(name string, matchers ...*labels.Matcher) return res, nil, err } -func (q *blockBaseQuerier) LabelNames() ([]string, storage.Warnings, error) { - res, err := q.index.LabelNames() +func (q *blockBaseQuerier) LabelNames(matchers ...*labels.Matcher) ([]string, storage.Warnings, error) { + res, err := q.index.LabelNames(matchers...) return res, nil, err } @@ -407,6 +407,23 @@ func labelValuesWithMatchers(r IndexReader, name string, matchers ...*labels.Mat return values, nil } +func labelNamesWithMatchers(r IndexReader, matchers ...*labels.Matcher) ([]string, error) { + p, err := PostingsForMatchers(r, matchers...) + if err != nil { + return nil, err + } + + var postings []uint64 + for p.Next() { + postings = append(postings, p.At()) + } + if p.Err() != nil { + return nil, errors.Wrapf(p.Err(), "postings for label names with matchers") + } + + return r.LabelNamesFor(postings...) +} + // blockBaseSeriesSet allows to iterate over all series in the single block. // Iterated series are trimmed with given min and max time as well as tombstones. // See newBlockSeriesSet and newBlockChunkSeriesSet to use it for either sample or chunk iterating. diff --git a/vendor/github.com/prometheus/prometheus/web/api/v1/api.go b/vendor/github.com/prometheus/prometheus/web/api/v1/api.go index 8cbc915b500..745a28c8d78 100644 --- a/vendor/github.com/prometheus/prometheus/web/api/v1/api.go +++ b/vendor/github.com/prometheus/prometheus/web/api/v1/api.go @@ -559,26 +559,18 @@ func (api *API) labelNames(r *http.Request) apiFuncResult { warnings storage.Warnings ) if len(matcherSets) > 0 { - hints := &storage.SelectHints{ - Start: timestamp.FromTime(start), - End: timestamp.FromTime(end), - Func: "series", // There is no series function, this token is used for lookups that don't need samples. - } - labelNamesSet := make(map[string]struct{}) - // Get all series which match matchers. - for _, mset := range matcherSets { - s := q.Select(false, hints, mset...) - for s.Next() { - series := s.At() - for _, lb := range series.Labels() { - labelNamesSet[lb.Name] = struct{}{} - } - } - warnings = append(warnings, s.Warnings()...) - if err := s.Err(); err != nil { + + for _, matchers := range matcherSets { + vals, callWarnings, err := q.LabelNames(matchers...) + if err != nil { return apiFuncResult{nil, &apiError{errorExec, err}, warnings, nil} } + + warnings = append(warnings, callWarnings...) + for _, val := range vals { + labelNamesSet[val] = struct{}{} + } } // Convert the map to an array. diff --git a/vendor/modules.txt b/vendor/modules.txt index 671e6bb40bc..41ba4635970 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -466,7 +466,7 @@ github.com/prometheus/node_exporter/https github.com/prometheus/procfs github.com/prometheus/procfs/internal/fs github.com/prometheus/procfs/internal/util -# github.com/prometheus/prometheus v1.8.2-0.20210720084720-59d02b5ef003 +# github.com/prometheus/prometheus v1.8.2-0.20210720123808-b1ed4a0a663d ## explicit github.com/prometheus/prometheus/config github.com/prometheus/prometheus/discovery