From 9f36136fea97d8d4d28a9f95460933bdd1b9b9f0 Mon Sep 17 00:00:00 2001 From: javiermolinar Date: Wed, 13 Nov 2024 10:36:07 +0100 Subject: [PATCH 1/2] limit tags and tag values search --- integration/e2e/multi_tenant_test.go | 6 +- .../frontend/combiner/search_tag_values.go | 16 +- modules/frontend/combiner/search_tags.go | 16 +- modules/frontend/combiner/search_tags_test.go | 39 +- modules/frontend/tag_handlers.go | 49 +- modules/frontend/tag_handlers_test.go | 39 +- modules/ingester/ingester_search.go | 2 +- modules/ingester/instance_search.go | 18 +- modules/ingester/instance_search_test.go | 10 +- modules/querier/querier.go | 31 +- pkg/collector/distinct_string_collector.go | 41 +- .../distinct_string_collector_test.go | 27 +- pkg/collector/distinct_value_collector.go | 41 +- .../distinct_value_collector_test.go | 29 +- pkg/collector/scoped_distinct_string.go | 69 ++- pkg/collector/scoped_distinct_string_test.go | 43 +- pkg/tempopb/tempo.pb.go | 521 +++++++++++------- pkg/tempopb/tempo.proto | 4 + pkg/traceql/engine_test.go | 2 +- tempodb/encoding/common/interfaces.go | 1 + .../vparquet2/block_search_tags_test.go | 4 +- .../vparquet3/block_autocomplete_test.go | 8 +- .../vparquet3/block_search_tags_test.go | 4 +- .../vparquet4/block_autocomplete_test.go | 8 +- .../vparquet4/block_search_tags_test.go | 4 +- tempodb/tempodb.go | 6 +- tempodb/tempodb_search_test.go | 6 +- 27 files changed, 662 insertions(+), 382 deletions(-) diff --git a/integration/e2e/multi_tenant_test.go b/integration/e2e/multi_tenant_test.go index 9b91c3b7c13..5a5d1b0ab97 100644 --- a/integration/e2e/multi_tenant_test.go +++ b/integration/e2e/multi_tenant_test.go @@ -207,9 +207,9 @@ func assertRequestCountMetric(t *testing.T, s *e2e.HTTPService, route string, re // getAttrsAndSpanNames returns trace attrs and span names func getAttrsAndSpanNames(trace *tempopb.Trace) traceStringsMap { - rAttrsKeys := collector.NewDistinctString(0) - rAttrsValues := collector.NewDistinctString(0) - spanNames := collector.NewDistinctString(0) + rAttrsKeys := collector.NewDistinctString(0, 0) + rAttrsValues := collector.NewDistinctString(0, 0) + spanNames := collector.NewDistinctString(0, 0) for _, b := range trace.ResourceSpans { for _, ss := range b.ScopeSpans { diff --git a/modules/frontend/combiner/search_tag_values.go b/modules/frontend/combiner/search_tag_values.go index 253033dec33..3475a51322a 100644 --- a/modules/frontend/combiner/search_tag_values.go +++ b/modules/frontend/combiner/search_tag_values.go @@ -12,9 +12,9 @@ var ( _ GRPCCombiner[*tempopb.SearchTagValuesV2Response] = (*genericCombiner[*tempopb.SearchTagValuesV2Response])(nil) ) -func NewSearchTagValues(limitBytes int) Combiner { +func NewSearchTagValues(maxDataBytes int, maxTagsValues uint32) Combiner { // Distinct collector with no limit - d := collector.NewDistinctStringWithDiff(limitBytes) + d := collector.NewDistinctStringWithDiff(maxDataBytes, maxTagsValues) inspectedBytes := atomic.NewUint64(0) c := &genericCombiner[*tempopb.SearchTagValuesResponse]{ @@ -56,13 +56,13 @@ func NewSearchTagValues(limitBytes int) Combiner { return c } -func NewTypedSearchTagValues(limitBytes int) GRPCCombiner[*tempopb.SearchTagValuesResponse] { - return NewSearchTagValues(limitBytes).(GRPCCombiner[*tempopb.SearchTagValuesResponse]) +func NewTypedSearchTagValues(maxDataBytes int, maxTagsValues uint32) GRPCCombiner[*tempopb.SearchTagValuesResponse] { + return NewSearchTagValues(maxDataBytes, maxTagsValues).(GRPCCombiner[*tempopb.SearchTagValuesResponse]) } -func NewSearchTagValuesV2(limitBytes int) Combiner { +func NewSearchTagValuesV2(maxDataBytes int, maxTagsValues uint32) Combiner { // Distinct collector with no limit and diff enabled - d := collector.NewDistinctValueWithDiff(limitBytes, func(tv tempopb.TagValue) int { return len(tv.Type) + len(tv.Value) }) + d := collector.NewDistinctValueWithDiff(maxDataBytes, maxTagsValues, func(tv tempopb.TagValue) int { return len(tv.Type) + len(tv.Value) }) inspectedBytes := atomic.NewUint64(0) c := &genericCombiner[*tempopb.SearchTagValuesV2Response]{ @@ -113,6 +113,6 @@ func NewSearchTagValuesV2(limitBytes int) Combiner { return c } -func NewTypedSearchTagValuesV2(limitBytes int) GRPCCombiner[*tempopb.SearchTagValuesV2Response] { - return NewSearchTagValuesV2(limitBytes).(GRPCCombiner[*tempopb.SearchTagValuesV2Response]) +func NewTypedSearchTagValuesV2(maxDataBytes int, maxTagsValues uint32) GRPCCombiner[*tempopb.SearchTagValuesV2Response] { + return NewSearchTagValuesV2(maxDataBytes, maxTagsValues).(GRPCCombiner[*tempopb.SearchTagValuesV2Response]) } diff --git a/modules/frontend/combiner/search_tags.go b/modules/frontend/combiner/search_tags.go index 224a5706b1a..9d5238d260a 100644 --- a/modules/frontend/combiner/search_tags.go +++ b/modules/frontend/combiner/search_tags.go @@ -12,8 +12,8 @@ var ( _ GRPCCombiner[*tempopb.SearchTagsV2Response] = (*genericCombiner[*tempopb.SearchTagsV2Response])(nil) ) -func NewSearchTags(limitBytes int) Combiner { - d := collector.NewDistinctStringWithDiff(limitBytes) +func NewSearchTags(maxDataBytes int, maxTagsPerScope uint32) Combiner { + d := collector.NewDistinctStringWithDiff(maxDataBytes, maxTagsPerScope) inspectedBytes := atomic.NewUint64(0) c := &genericCombiner[*tempopb.SearchTagsResponse]{ @@ -56,13 +56,13 @@ func NewSearchTags(limitBytes int) Combiner { return c } -func NewTypedSearchTags(limitBytes int) GRPCCombiner[*tempopb.SearchTagsResponse] { - return NewSearchTags(limitBytes).(GRPCCombiner[*tempopb.SearchTagsResponse]) +func NewTypedSearchTags(maxDataBytes int, maxTagsPerScope uint32) GRPCCombiner[*tempopb.SearchTagsResponse] { + return NewSearchTags(maxDataBytes, maxTagsPerScope).(GRPCCombiner[*tempopb.SearchTagsResponse]) } -func NewSearchTagsV2(limitBytes int) Combiner { +func NewSearchTagsV2(maxDataBytes int, maxTagsPerScope uint32) Combiner { // Distinct collector map to collect scopes and scope values - distinctValues := collector.NewScopedDistinctStringWithDiff(limitBytes) + distinctValues := collector.NewScopedDistinctStringWithDiff(maxDataBytes, maxTagsPerScope) inspectedBytes := atomic.NewUint64(0) c := &genericCombiner[*tempopb.SearchTagsV2Response]{ @@ -121,6 +121,6 @@ func NewSearchTagsV2(limitBytes int) Combiner { return c } -func NewTypedSearchTagsV2(limitBytes int) GRPCCombiner[*tempopb.SearchTagsV2Response] { - return NewSearchTagsV2(limitBytes).(GRPCCombiner[*tempopb.SearchTagsV2Response]) +func NewTypedSearchTagsV2(maxDataBytes int, maxTagsPerScope uint32) GRPCCombiner[*tempopb.SearchTagsV2Response] { + return NewSearchTagsV2(maxDataBytes, maxTagsPerScope).(GRPCCombiner[*tempopb.SearchTagsV2Response]) } diff --git a/modules/frontend/combiner/search_tags_test.go b/modules/frontend/combiner/search_tags_test.go index 3fed17bc8f3..377fcbdfd4b 100644 --- a/modules/frontend/combiner/search_tags_test.go +++ b/modules/frontend/combiner/search_tags_test.go @@ -13,8 +13,8 @@ import ( func TestTagsCombiner(t *testing.T) { tests := []struct { name string - factory func(int) Combiner - limit int + factory func(int, uint32) Combiner + limitBytes int result1 proto.Message result2 proto.Message expectedResult proto.Message @@ -31,7 +31,7 @@ func TestTagsCombiner(t *testing.T) { expectedResult: &tempopb.SearchTagsResponse{TagNames: []string{"tag1", "tag2", "tag3"}, Metrics: &tempopb.MetadataMetrics{}}, actualResult: &tempopb.SearchTagsResponse{}, sort: func(m proto.Message) { sort.Strings(m.(*tempopb.SearchTagsResponse).TagNames) }, - limit: 100, + limitBytes: 100, }, { name: "SearchTagsV2", @@ -49,7 +49,7 @@ func TestTagsCombiner(t *testing.T) { return scopes[i].Name < scopes[j].Name }) }, - limit: 100, + limitBytes: 100, }, { name: "SearchTagValues", @@ -59,7 +59,7 @@ func TestTagsCombiner(t *testing.T) { expectedResult: &tempopb.SearchTagValuesResponse{TagValues: []string{"tag1", "tag2", "tag3"}, Metrics: &tempopb.MetadataMetrics{}}, actualResult: &tempopb.SearchTagValuesResponse{}, sort: func(m proto.Message) { sort.Strings(m.(*tempopb.SearchTagValuesResponse).TagValues) }, - limit: 100, + limitBytes: 100, }, { name: "SearchTagValuesV2", @@ -73,7 +73,7 @@ func TestTagsCombiner(t *testing.T) { return m.(*tempopb.SearchTagValuesV2Response).TagValues[i].Value < m.(*tempopb.SearchTagValuesV2Response).TagValues[j].Value }) }, - limit: 100, + limitBytes: 100, }, // limits { @@ -85,7 +85,7 @@ func TestTagsCombiner(t *testing.T) { actualResult: &tempopb.SearchTagsResponse{}, sort: func(m proto.Message) { sort.Strings(m.(*tempopb.SearchTagsResponse).TagNames) }, expectedShouldQuit: true, - limit: 5, + limitBytes: 5, }, { name: "SearchTagsV2 - limited", @@ -104,7 +104,7 @@ func TestTagsCombiner(t *testing.T) { }) }, expectedShouldQuit: true, - limit: 2, + limitBytes: 2, }, { name: "SearchTagValues - limited", @@ -115,7 +115,7 @@ func TestTagsCombiner(t *testing.T) { actualResult: &tempopb.SearchTagValuesResponse{}, sort: func(m proto.Message) { sort.Strings(m.(*tempopb.SearchTagValuesResponse).TagValues) }, expectedShouldQuit: true, - limit: 5, + limitBytes: 5, }, { name: "SearchTagValuesV2 - limited", @@ -130,7 +130,7 @@ func TestTagsCombiner(t *testing.T) { }) }, expectedShouldQuit: true, - limit: 10, + limitBytes: 10, }, // with metrics { @@ -141,7 +141,7 @@ func TestTagsCombiner(t *testing.T) { expectedResult: &tempopb.SearchTagsResponse{TagNames: []string{"tag1", "tag2", "tag3"}, Metrics: &tempopb.MetadataMetrics{InspectedBytes: 2}}, actualResult: &tempopb.SearchTagsResponse{}, sort: func(m proto.Message) { sort.Strings(m.(*tempopb.SearchTagsResponse).TagNames) }, - limit: 100, + limitBytes: 100, }, { name: "SearchTagsV2 - metrics", @@ -159,7 +159,7 @@ func TestTagsCombiner(t *testing.T) { return scopes[i].Name < scopes[j].Name }) }, - limit: 100, + limitBytes: 100, }, { name: "SearchTagValues - metrics", @@ -169,7 +169,7 @@ func TestTagsCombiner(t *testing.T) { expectedResult: &tempopb.SearchTagValuesResponse{TagValues: []string{"tag1", "tag2", "tag3"}, Metrics: &tempopb.MetadataMetrics{InspectedBytes: 2}}, actualResult: &tempopb.SearchTagValuesResponse{}, sort: func(m proto.Message) { sort.Strings(m.(*tempopb.SearchTagValuesResponse).TagValues) }, - limit: 100, + limitBytes: 100, }, { name: "SearchTagValuesV2 - metrics", @@ -183,12 +183,13 @@ func TestTagsCombiner(t *testing.T) { return m.(*tempopb.SearchTagValuesV2Response).TagValues[i].Value < m.(*tempopb.SearchTagValuesV2Response).TagValues[j].Value }) }, - limit: 100, + limitBytes: 100, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - combiner := tc.factory(tc.limit) + // TODO ADD LIMIT + combiner := tc.factory(tc.limitBytes, 0) err := combiner.AddResponse(toHTTPResponse(t, tc.result1, 200)) assert.NoError(t, err) @@ -228,7 +229,7 @@ func metrics(message proto.Message) *tempopb.MetadataMetrics { } func TestTagsGRPCCombiner(t *testing.T) { - c := NewTypedSearchTags(0) + c := NewTypedSearchTags(0, 0) res1 := &tempopb.SearchTagsResponse{TagNames: []string{"tag1"}, Metrics: &tempopb.MetadataMetrics{InspectedBytes: 1}} res2 := &tempopb.SearchTagsResponse{TagNames: []string{"tag1", "tag2"}, Metrics: &tempopb.MetadataMetrics{InspectedBytes: 1}} diff1 := &tempopb.SearchTagsResponse{TagNames: []string{"tag1"}, Metrics: &tempopb.MetadataMetrics{InspectedBytes: 1}} @@ -238,7 +239,7 @@ func TestTagsGRPCCombiner(t *testing.T) { } func TestTagsV2GRPCCombiner(t *testing.T) { - c := NewTypedSearchTagsV2(0) + c := NewTypedSearchTagsV2(0, 0) res1 := &tempopb.SearchTagsV2Response{Scopes: []*tempopb.SearchTagsV2Scope{{Name: "scope1", Tags: []string{"tag1"}}}, Metrics: &tempopb.MetadataMetrics{InspectedBytes: 1}} res2 := &tempopb.SearchTagsV2Response{Scopes: []*tempopb.SearchTagsV2Scope{{Name: "scope1", Tags: []string{"tag1", "tag2"}}, {Name: "scope2", Tags: []string{"tag3"}}}, Metrics: &tempopb.MetadataMetrics{InspectedBytes: 1}} diff1 := &tempopb.SearchTagsV2Response{Scopes: []*tempopb.SearchTagsV2Scope{{Name: "scope1", Tags: []string{"tag1"}}}, Metrics: &tempopb.MetadataMetrics{InspectedBytes: 1}} @@ -255,7 +256,7 @@ func TestTagsV2GRPCCombiner(t *testing.T) { } func TestTagValuesGRPCCombiner(t *testing.T) { - c := NewTypedSearchTagValues(0) + c := NewTypedSearchTagValues(0, 0) res1 := &tempopb.SearchTagValuesResponse{TagValues: []string{"tag1"}, Metrics: &tempopb.MetadataMetrics{InspectedBytes: 1}} res2 := &tempopb.SearchTagValuesResponse{TagValues: []string{"tag1", "tag2"}, Metrics: &tempopb.MetadataMetrics{InspectedBytes: 1}} diff1 := &tempopb.SearchTagValuesResponse{TagValues: []string{"tag1"}, Metrics: &tempopb.MetadataMetrics{InspectedBytes: 1}} @@ -265,7 +266,7 @@ func TestTagValuesGRPCCombiner(t *testing.T) { } func TestTagValuesV2GRPCCombiner(t *testing.T) { - c := NewTypedSearchTagValuesV2(0) + c := NewTypedSearchTagValuesV2(0, 0) res1 := &tempopb.SearchTagValuesV2Response{TagValues: []*tempopb.TagValue{{Value: "v1", Type: "string"}}, Metrics: &tempopb.MetadataMetrics{InspectedBytes: 1}} res2 := &tempopb.SearchTagValuesV2Response{TagValues: []*tempopb.TagValue{{Value: "v1", Type: "string"}, {Value: "v2", Type: "string"}}, Metrics: &tempopb.MetadataMetrics{InspectedBytes: 1}} diff1 := &tempopb.SearchTagValuesV2Response{TagValues: []*tempopb.TagValue{{Value: "v1", Type: "string"}}, Metrics: &tempopb.MetadataMetrics{InspectedBytes: 1}} diff --git a/modules/frontend/tag_handlers.go b/modules/frontend/tag_handlers.go index 404009a48ca..467b8ec3ac3 100644 --- a/modules/frontend/tag_handlers.go +++ b/modules/frontend/tag_handlers.go @@ -47,8 +47,8 @@ func newTagsStreamingGRPCHandler(cfg Config, next pipeline.AsyncRoundTripper[com } var finalResponse *tempopb.SearchTagsResponse - comb := combiner.NewTypedSearchTags(o.MaxBytesPerTagValuesQuery(tenant)) - collector := pipeline.NewGRPCCollector[*tempopb.SearchTagsResponse](next, cfg.ResponseConsumers, comb, func(res *tempopb.SearchTagsResponse) error { + comb := combiner.NewTypedSearchTags(o.MaxBytesPerTagValuesQuery(tenant), req.MaxTagsPerScope) + collector := pipeline.NewGRPCCollector(next, cfg.ResponseConsumers, comb, func(res *tempopb.SearchTagsResponse) error { finalResponse = res // to get the bytes processed for SLO calculations return srv.Send(res) }) @@ -80,8 +80,8 @@ func newTagsV2StreamingGRPCHandler(cfg Config, next pipeline.AsyncRoundTripper[c } var finalResponse *tempopb.SearchTagsV2Response - comb := combiner.NewTypedSearchTagsV2(o.MaxBytesPerTagValuesQuery(tenant)) - collector := pipeline.NewGRPCCollector[*tempopb.SearchTagsV2Response](next, cfg.ResponseConsumers, comb, func(res *tempopb.SearchTagsV2Response) error { + comb := combiner.NewTypedSearchTagsV2(o.MaxBytesPerTagValuesQuery(tenant), req.MaxTagsPerScope) + collector := pipeline.NewGRPCCollector(next, cfg.ResponseConsumers, comb, func(res *tempopb.SearchTagsV2Response) error { finalResponse = res // to get the bytes processed for SLO calculations return srv.Send(res) }) @@ -117,8 +117,8 @@ func newTagValuesStreamingGRPCHandler(cfg Config, next pipeline.AsyncRoundTrippe } var finalResponse *tempopb.SearchTagValuesResponse - comb := combiner.NewTypedSearchTagValues(o.MaxBytesPerTagValuesQuery(tenant)) - collector := pipeline.NewGRPCCollector[*tempopb.SearchTagValuesResponse](next, cfg.ResponseConsumers, comb, func(res *tempopb.SearchTagValuesResponse) error { + comb := combiner.NewTypedSearchTagValues(o.MaxBytesPerTagValuesQuery(tenant), req.MaxTagValues) + collector := pipeline.NewGRPCCollector(next, cfg.ResponseConsumers, comb, func(res *tempopb.SearchTagValuesResponse) error { finalResponse = res // to get the bytes processed for SLO calculations return srv.Send(res) }) @@ -154,8 +154,8 @@ func newTagValuesV2StreamingGRPCHandler(cfg Config, next pipeline.AsyncRoundTrip } var finalResponse *tempopb.SearchTagValuesV2Response - comb := combiner.NewTypedSearchTagValuesV2(o.MaxBytesPerTagValuesQuery(tenant)) - collector := pipeline.NewGRPCCollector[*tempopb.SearchTagValuesV2Response](next, cfg.ResponseConsumers, comb, func(res *tempopb.SearchTagValuesV2Response) error { + comb := combiner.NewTypedSearchTagValuesV2(o.MaxBytesPerTagValuesQuery(tenant), req.MaxTagValues) + collector := pipeline.NewGRPCCollector(next, cfg.ResponseConsumers, comb, func(res *tempopb.SearchTagValuesV2Response) error { finalResponse = res // to get the bytes processed for SLO calculations return srv.Send(res) }) @@ -187,9 +187,9 @@ func newTagsHTTPHandler(cfg Config, next pipeline.AsyncRoundTripper[combiner.Pip return errResp, nil } - scope, _, rangeDur := parseParams(req) + scope, _, rangeDur, maxTagsPerScope := parseParams(req) // build and use round tripper - comb := combiner.NewTypedSearchTags(o.MaxBytesPerTagValuesQuery(tenant)) + comb := combiner.NewTypedSearchTags(o.MaxBytesPerTagValuesQuery(tenant), maxTagsPerScope) rt := pipeline.NewHTTPCollector(next, cfg.ResponseConsumers, comb) start := time.Now() logTagsRequest(logger, tenant, "SearchTags", scope, rangeDur) @@ -221,9 +221,9 @@ func newTagsV2HTTPHandler(cfg Config, next pipeline.AsyncRoundTripper[combiner.P return errResp, nil } - scope, _, rangeDur := parseParams(req) + scope, _, rangeDur, maxTagsPerScope := parseParams(req) // build and use round tripper - comb := combiner.NewTypedSearchTagsV2(o.MaxBytesPerTagValuesQuery(tenant)) + comb := combiner.NewTypedSearchTagsV2(o.MaxBytesPerTagValuesQuery(tenant), maxTagsPerScope) rt := pipeline.NewHTTPCollector(next, cfg.ResponseConsumers, comb) start := time.Now() logTagsRequest(logger, tenant, "SearchTagsV2", scope, rangeDur) @@ -255,11 +255,11 @@ func newTagValuesHTTPHandler(cfg Config, next pipeline.AsyncRoundTripper[combine return errResp, nil } - _, query, rangeDur := parseParams(req) + _, query, rangeDur, maxTagsValues := parseParams(req) tagName := extractTagName(req.URL.Path, tagNameRegexV1) // build and use round tripper - comb := combiner.NewTypedSearchTagValues(o.MaxBytesPerTagValuesQuery(tenant)) + comb := combiner.NewTypedSearchTagValues(o.MaxBytesPerTagValuesQuery(tenant), maxTagsValues) rt := pipeline.NewHTTPCollector(next, cfg.ResponseConsumers, comb) start := time.Now() logTagValuesRequest(logger, tenant, "SearchTagValues", tagName, query, rangeDur) @@ -291,11 +291,11 @@ func newTagValuesV2HTTPHandler(cfg Config, next pipeline.AsyncRoundTripper[combi return errResp, nil } - _, query, rangeDur := parseParams(req) + _, query, rangeDur, maxTagsValues := parseParams(req) tagName := extractTagName(req.URL.Path, tagNameRegexV2) // build and use round tripper - comb := combiner.NewTypedSearchTagValuesV2(o.MaxBytesPerTagValuesQuery(tenant)) + comb := combiner.NewTypedSearchTagValuesV2(o.MaxBytesPerTagValuesQuery(tenant), maxTagsValues) rt := pipeline.NewHTTPCollector(next, cfg.ResponseConsumers, comb) start := time.Now() logTagValuesRequest(logger, tenant, "SearchTagValuesV2", tagName, query, rangeDur) @@ -427,21 +427,24 @@ func logTagValuesResult(logger log.Logger, tenantID, handler, tagName, query str // parseParams parses optional 'start', 'end', 'scope', and 'q' params from a http.Request // returns scope, query and duration (end - start). returns "", and 0 if these params are invalid or absent -func parseParams(req *http.Request) (string, string, uint32) { +func parseParams(req *http.Request) (scope string, q string, duration uint32, limit uint32) { query := req.URL.Query() - - scope := query.Get("scope") - q := query.Get("q") + scope = query.Get("scope") + q = query.Get("q") // ignore errors, we default to 0 as params are not always present. start, _ := strconv.ParseInt(query.Get("start"), 10, 64) end, _ := strconv.ParseInt(query.Get("end"), 10, 64) + la := query.Get("limit") + l, err := strconv.ParseUint(la, 10, 32) + if err != nil { + l = 0 + } - var duration int64 // duration only makes sense if start and end are present and end is greater than start if start > 0 && end > 0 && end > start { - duration = end - start + duration = uint32(end - start) } - return scope, q, uint32(duration) + return scope, q, duration, uint32(l) } // extractTagName extracts the tagName based on the provided regex pattern diff --git a/modules/frontend/tag_handlers_test.go b/modules/frontend/tag_handlers_test.go index 23f46147f83..615c3139730 100644 --- a/modules/frontend/tag_handlers_test.go +++ b/modules/frontend/tag_handlers_test.go @@ -543,6 +543,7 @@ func TestParseParams(t *testing.T) { expectedScope string expectedQ string expectedDuration uint32 + expectedLimit uint32 }{ { name: "all params present", @@ -550,6 +551,7 @@ func TestParseParams(t *testing.T) { expectedScope: "resource", expectedQ: "some_query", expectedDuration: 172800, + expectedLimit: 0, }, { name: "missing start", @@ -557,6 +559,7 @@ func TestParseParams(t *testing.T) { expectedScope: "resource", expectedQ: "", expectedDuration: 0, + expectedLimit: 0, }, { name: "missing end", @@ -564,6 +567,7 @@ func TestParseParams(t *testing.T) { expectedScope: "resource", expectedQ: "", expectedDuration: 0, + expectedLimit: 0, }, { name: "missing scope", @@ -571,6 +575,7 @@ func TestParseParams(t *testing.T) { expectedScope: "", expectedQ: "", expectedDuration: 172800, + expectedLimit: 0, }, { name: "missing q", @@ -578,6 +583,7 @@ func TestParseParams(t *testing.T) { expectedScope: "resource", expectedQ: "", expectedDuration: 172800, + expectedLimit: 0, }, { name: "invalid start", @@ -585,6 +591,7 @@ func TestParseParams(t *testing.T) { expectedScope: "resource", expectedQ: "", expectedDuration: 0, + expectedLimit: 0, }, { name: "invalid end", @@ -592,6 +599,7 @@ func TestParseParams(t *testing.T) { expectedScope: "resource", expectedQ: "", expectedDuration: 0, + expectedLimit: 0, }, { name: "no params", @@ -599,6 +607,7 @@ func TestParseParams(t *testing.T) { expectedScope: "", expectedQ: "", expectedDuration: 0, + expectedLimit: 0, }, { name: "negative start and end", @@ -606,6 +615,7 @@ func TestParseParams(t *testing.T) { expectedScope: "negative_case", expectedQ: "", expectedDuration: 0, + expectedLimit: 0, }, { name: "end less than start", @@ -613,6 +623,7 @@ func TestParseParams(t *testing.T) { expectedScope: "resource", expectedQ: "", expectedDuration: 0, + expectedLimit: 0, }, { name: "start and end are the same", @@ -620,6 +631,31 @@ func TestParseParams(t *testing.T) { expectedScope: "zero_duration", expectedQ: "", expectedDuration: 0, + expectedLimit: 0, + }, + { + name: "invalid limit", + queryParams: map[string]string{"limit": "-1000"}, + expectedScope: "", + expectedQ: "", + expectedDuration: 0, + expectedLimit: 0, + }, + { + name: "invalid too large limit", + queryParams: map[string]string{"limit": "1000000000000000000"}, + expectedScope: "", + expectedQ: "", + expectedDuration: 0, + expectedLimit: 0, + }, + { + name: "valid limit", + queryParams: map[string]string{"limit": "100"}, + expectedScope: "", + expectedQ: "", + expectedDuration: 0, + expectedLimit: 100, }, } @@ -633,11 +669,12 @@ func TestParseParams(t *testing.T) { u.RawQuery = query.Encode() req := &http.Request{URL: u} - scope, q, duration := parseParams(req) + scope, q, duration, limit := parseParams(req) require.Equal(t, tt.expectedScope, scope) require.Equal(t, tt.expectedQ, q) require.Equal(t, tt.expectedDuration, duration) + require.Equal(t, tt.expectedLimit, limit) }) } } diff --git a/modules/ingester/ingester_search.go b/modules/ingester/ingester_search.go index d87af8b429b..3163a060870 100644 --- a/modules/ingester/ingester_search.go +++ b/modules/ingester/ingester_search.go @@ -103,7 +103,7 @@ func (i *Ingester) SearchTagValues(ctx context.Context, req *tempopb.SearchTagVa return &tempopb.SearchTagValuesResponse{}, nil } - res, err = inst.SearchTagValues(ctx, req.TagName) + res, err = inst.SearchTagValues(ctx, req.TagName, req.MaxTagValues) if err != nil { return nil, err } diff --git a/modules/ingester/instance_search.go b/modules/ingester/instance_search.go index 2de7b25f4a6..26b11e017f3 100644 --- a/modules/ingester/instance_search.go +++ b/modules/ingester/instance_search.go @@ -182,7 +182,7 @@ func (i *instance) SearchTags(ctx context.Context, scope string) (*tempopb.Searc return nil, err } - distinctValues := collector.NewDistinctString(0) // search tags v2 enforces the limit + distinctValues := collector.NewDistinctString(0, 0) // search tags v2 enforces the limit // flatten v2 response for _, s := range v2Response.Scopes { @@ -232,8 +232,8 @@ func (i *instance) SearchTagsV2(ctx context.Context, req *tempopb.SearchTagsRequ return nil, fmt.Errorf("unknown scope: %s", scope) } - limit := i.limiter.limits.MaxBytesPerTagValuesQuery(userID) - distinctValues := collector.NewScopedDistinctString(limit) + maxBytestPerTags := i.limiter.limits.MaxBytesPerTagValuesQuery(userID) + distinctValues := collector.NewScopedDistinctString(maxBytestPerTags, req.MaxTagsPerScope) mc := collector.NewMetricsCollector() engine := traceql.NewEngine() @@ -296,7 +296,7 @@ func (i *instance) SearchTagsV2(ctx context.Context, req *tempopb.SearchTagsRequ } if distinctValues.Exceeded() { - level.Warn(log.Logger).Log("msg", "size of tags in instance exceeded limit, reduce cardinality or size of tags", "tenant", userID, "limit", limit) + level.Warn(log.Logger).Log("msg", "size of tags in instance exceeded limit, reduce cardinality or size of tags", "tenant", userID, "maxBytesPerTag", maxBytestPerTags, "maxTagsPerScope", req.MaxTagsPerScope) } collected := distinctValues.Strings() @@ -324,14 +324,14 @@ func (i *instance) SearchTagsV2(ctx context.Context, req *tempopb.SearchTagsRequ return resp, nil } -func (i *instance) SearchTagValues(ctx context.Context, tagName string) (*tempopb.SearchTagValuesResponse, error) { +func (i *instance) SearchTagValues(ctx context.Context, tagName string, limit uint32) (*tempopb.SearchTagValuesResponse, error) { userID, err := user.ExtractOrgID(ctx) if err != nil { return nil, err } - limit := i.limiter.limits.MaxBytesPerTagValuesQuery(userID) - distinctValues := collector.NewDistinctString(limit) + maxBytesPerTagValues := i.limiter.limits.MaxBytesPerTagValuesQuery(userID) + distinctValues := collector.NewDistinctString(maxBytesPerTagValues, limit) mc := collector.NewMetricsCollector() var inspectedBlocks, maxBlocks int @@ -401,7 +401,7 @@ func (i *instance) SearchTagValuesV2(ctx context.Context, req *tempopb.SearchTag defer span.End() limit := i.limiter.limits.MaxBytesPerTagValuesQuery(userID) - valueCollector := collector.NewDistinctValue[tempopb.TagValue](limit, func(v tempopb.TagValue) int { return len(v.Type) + len(v.Value) }) + valueCollector := collector.NewDistinctValue(limit, req.MaxTagValues, func(v tempopb.TagValue) int { return len(v.Type) + len(v.Value) }) mc := collector.NewMetricsCollector() // to collect bytesRead metric engine := traceql.NewEngine() @@ -512,7 +512,7 @@ func (i *instance) SearchTagValuesV2(ctx context.Context, req *tempopb.SearchTag // cache miss, search the block. We will cache the results if we find any. span.SetAttributes(attribute.Bool("cached", false)) // using local collector to collect values from the block and cache them. - localCol := collector.NewDistinctValue[tempopb.TagValue](limit, func(v tempopb.TagValue) int { return len(v.Type) + len(v.Value) }) + localCol := collector.NewDistinctValue[tempopb.TagValue](limit, req.MaxTagValues, func(v tempopb.TagValue) int { return len(v.Type) + len(v.Value) }) localErr := performSearch(ctx, b, localCol) if localErr != nil { return localErr diff --git a/modules/ingester/instance_search_test.go b/modules/ingester/instance_search_test.go index 84a20a920d2..58608fb7889 100644 --- a/modules/ingester/instance_search_test.go +++ b/modules/ingester/instance_search_test.go @@ -293,7 +293,7 @@ func testSearchTagsAndValues(t *testing.T, ctx context.Context, i *instance, tag checkSearchTags("event", true) checkSearchTags("link", true) - srv, err := i.SearchTagValues(ctx, tagName) + srv, err := i.SearchTagValues(ctx, tagName, 0) require.NoError(t, err) require.Greater(t, srv.Metrics.InspectedBytes, uint64(100)) // we scanned at-least 100 bytes @@ -462,7 +462,7 @@ func TestInstanceSearchMaxBytesPerTagValuesQueryReturnsPartial(t *testing.T) { _, _, _, _ = writeTracesForSearch(t, i, "", tagKey, tagValue, true, false) userCtx := user.InjectOrgID(context.Background(), "fake") - resp, err := i.SearchTagValues(userCtx, tagKey) + resp, err := i.SearchTagValues(userCtx, tagKey, 0) require.NoError(t, err) require.Equal(t, 2, len(resp.TagValues)) // Only two values of the form "bar123" fit in the 10 byte limit above. } @@ -502,7 +502,7 @@ func TestInstanceSearchMaxBlocksPerTagValuesQueryReturnsPartial(t *testing.T) { userCtx := user.InjectOrgID(context.Background(), "fake") - respV1, err := i.SearchTagValues(userCtx, tagKey) + respV1, err := i.SearchTagValues(userCtx, tagKey, 0) require.NoError(t, err) assert.Equal(t, 100, len(respV1.TagValues)) @@ -516,7 +516,7 @@ func TestInstanceSearchMaxBlocksPerTagValuesQueryReturnsPartial(t *testing.T) { i.limiter = NewLimiter(limits, &ringCountMock{count: 1}, 1) - respV1, err = i.SearchTagValues(userCtx, tagKey) + respV1, err = i.SearchTagValues(userCtx, tagKey, 0) require.NoError(t, err) assert.Equal(t, 200, len(respV1.TagValues)) @@ -712,7 +712,7 @@ func TestInstanceSearchDoesNotRace(t *testing.T) { go concurrent(func() { // SearchTagValues queries now require userID in ctx ctx := user.InjectOrgID(context.Background(), "test") - _, err := i.SearchTagValues(ctx, tagKey) + _, err := i.SearchTagValues(ctx, tagKey, 0) require.NoError(t, err, "error getting search tag values") }) diff --git a/modules/querier/querier.go b/modules/querier/querier.go index 513f1fd0973..5168b7c289d 100644 --- a/modules/querier/querier.go +++ b/modules/querier/querier.go @@ -494,7 +494,7 @@ func (q *Querier) SearchTagsBlocks(ctx context.Context, req *tempopb.SearchTagsB return nil, err } - distinctValues := collector.NewDistinctString(0) + distinctValues := collector.NewDistinctString(0, 0) // flatten v2 response for _, s := range v2Response.Scopes { @@ -535,8 +535,8 @@ func (q *Querier) SearchTags(ctx context.Context, req *tempopb.SearchTagsRequest return nil, fmt.Errorf("error extracting org id in Querier.SearchTags: %w", err) } - limit := q.limits.MaxBytesPerTagValuesQuery(userID) - distinctValues := collector.NewDistinctString(limit) + maxDataSize := q.limits.MaxBytesPerTagValuesQuery(userID) + distinctValues := collector.NewDistinctString(maxDataSize, req.MaxTagsPerScope) mc := collector.NewMetricsCollector() forEach := func(ctx context.Context, client tempopb.QuerierClient) error { @@ -563,7 +563,7 @@ func (q *Querier) SearchTags(ctx context.Context, req *tempopb.SearchTagsRequest } if distinctValues.Exceeded() { - level.Warn(log.Logger).Log("msg", "size of tags in instance exceeded limit, reduce cardinality or size of tags", "userID", userID, "limit", limit, "size", distinctValues.Size()) + level.Warn(log.Logger).Log("msg", "size of tags in instance exceeded limit, reduce cardinality or size of tags", "userID", userID, "maxDataSize", maxDataSize, "size", distinctValues.Size()) } return &tempopb.SearchTagsResponse{ @@ -578,8 +578,8 @@ func (q *Querier) SearchTagsV2(ctx context.Context, req *tempopb.SearchTagsReque return nil, fmt.Errorf("error extracting org id in Querier.SearchTags: %w", err) } - limit := q.limits.MaxBytesPerTagValuesQuery(userID) - distinctValues := collector.NewScopedDistinctString(limit) + maxBytesPerTag := q.limits.MaxBytesPerTagValuesQuery(userID) + distinctValues := collector.NewScopedDistinctString(maxBytesPerTag, req.MaxTagsPerScope) mc := collector.NewMetricsCollector() // Get results from all ingesters @@ -609,7 +609,7 @@ func (q *Querier) SearchTagsV2(ctx context.Context, req *tempopb.SearchTagsReque } if distinctValues.Exceeded() { - level.Warn(log.Logger).Log("msg", "size of tags in instance exceeded limit, reduce cardinality or size of tags", "userID", userID, "limit", limit) + level.Warn(log.Logger).Log("msg", "size of tags in instance exceeded limit, reduce cardinality or size of tags", "userID", userID, "maxBytesPerTag", maxBytesPerTag, "maxTagsPerScope", req.MaxTagsPerScope) } collected := distinctValues.Strings() @@ -633,8 +633,8 @@ func (q *Querier) SearchTagValues(ctx context.Context, req *tempopb.SearchTagVal return nil, fmt.Errorf("error extracting org id in Querier.SearchTagValues: %w", err) } - limit := q.limits.MaxBytesPerTagValuesQuery(userID) - distinctValues := collector.NewDistinctString(limit) + maxDataSize := q.limits.MaxBytesPerTagValuesQuery(userID) + distinctValues := collector.NewDistinctString(maxDataSize, req.MaxTagValues) mc := collector.NewMetricsCollector() // Virtual tags values. Get these first. @@ -668,7 +668,7 @@ func (q *Querier) SearchTagValues(ctx context.Context, req *tempopb.SearchTagVal } if distinctValues.Exceeded() { - level.Warn(log.Logger).Log("msg", "size of tag values in instance exceeded limit, reduce cardinality or size of tags", "tag", req.TagName, "userID", userID, "limit", limit, "size", distinctValues.Size()) + level.Warn(log.Logger).Log("msg", "size of tag values in instance exceeded limit, reduce cardinality or size of tags", "tag", req.TagName, "userID", userID, "maxDataSize", maxDataSize, "maxTagsValues", req.MaxTagValues, "size", distinctValues.Size()) } return &tempopb.SearchTagValuesResponse{ @@ -683,8 +683,8 @@ func (q *Querier) SearchTagValuesV2(ctx context.Context, req *tempopb.SearchTagV return nil, fmt.Errorf("error extracting org id in Querier.SearchTagValues: %w", err) } - limit := q.limits.MaxBytesPerTagValuesQuery(userID) - distinctValues := collector.NewDistinctValue(limit, func(v tempopb.TagValue) int { return len(v.Type) + len(v.Value) }) + maxDataSize := q.limits.MaxBytesPerTagValuesQuery(userID) + distinctValues := collector.NewDistinctValue(maxDataSize, req.MaxTagValues, func(v tempopb.TagValue) int { return len(v.Type) + len(v.Value) }) mc := collector.NewMetricsCollector() // Virtual tags values. Get these first. @@ -726,7 +726,7 @@ func (q *Querier) SearchTagValuesV2(ctx context.Context, req *tempopb.SearchTagV } if distinctValues.Exceeded() { - _ = level.Warn(log.Logger).Log("msg", "size of tag values exceeded limit, reduce cardinality or size of tags", "tag", req.TagName, "userID", userID, "limit", limit, "size", distinctValues.Size()) + _ = level.Warn(log.Logger).Log("msg", "size of tag values exceeded limit, reduce cardinality or size of tags", "tag", req.TagName, "userID", userID, "maxDataSize", maxDataSize, "maxTagsValues", req.MaxTagValues, "size", distinctValues.Size()) } return valuesToV2Response(distinctValues, mc.TotalValue()), nil @@ -981,7 +981,7 @@ func (q *Querier) internalTagsSearchBlockV2(ctx context.Context, req *tempopb.Se return resp, nil } - valueCollector := collector.NewScopedDistinctString(q.limits.MaxBytesPerTagValuesQuery(tenantID)) + valueCollector := collector.NewScopedDistinctString(q.limits.MaxBytesPerTagValuesQuery(tenantID), req.MaxTagsPerScope) mc := collector.NewMetricsCollector() fetcher := traceql.NewTagNamesFetcherWrapper(func(ctx context.Context, req traceql.FetchTagsRequest, cb traceql.FetchTagsCallback) error { @@ -1052,6 +1052,7 @@ func (q *Querier) internalTagValuesSearchBlock(ctx context.Context, req *tempopb opts := common.DefaultSearchOptions() opts.StartPage = int(req.StartPage) opts.TotalPages = int(req.PagesToSearch) + opts.Limit = req.MaxTagValues resp, err := q.store.SearchTagValues(ctx, meta, req.SearchReq.TagName, opts) if err != nil { @@ -1109,7 +1110,7 @@ func (q *Querier) internalTagValuesSearchBlockV2(ctx context.Context, req *tempo return nil, err } - valueCollector := collector.NewDistinctValue(q.limits.MaxBytesPerTagValuesQuery(tenantID), func(v tempopb.TagValue) int { return len(v.Type) + len(v.Value) }) + valueCollector := collector.NewDistinctValue(q.limits.MaxBytesPerTagValuesQuery(tenantID), req.MaxTagValues, func(v tempopb.TagValue) int { return len(v.Type) + len(v.Value) }) mc := collector.NewMetricsCollector() fetcher := traceql.NewTagValuesFetcherWrapper(func(ctx context.Context, req traceql.FetchTagValuesRequest, cb traceql.FetchTagValuesCallback) error { diff --git a/pkg/collector/distinct_string_collector.go b/pkg/collector/distinct_string_collector.go index ca881703e26..a1ad272ccdf 100644 --- a/pkg/collector/distinct_string_collector.go +++ b/pkg/collector/distinct_string_collector.go @@ -7,33 +7,39 @@ import ( ) type DistinctString struct { - values map[string]struct{} - new map[string]struct{} - maxLen int - currLen int - diffEnabled bool - limExceeded bool - mtx sync.Mutex + values map[string]struct{} + new map[string]struct{} + maxDataSize int + currDataSize int + currentValuesLen uint32 + maxValues uint32 + diffEnabled bool + limExceeded bool + mtx sync.Mutex } -// NewDistinctString with the given maximum data size. This is calculated -// as the total length of the recorded strings. For ease of use, maximum=0 -// is interpreted as unlimited. -func NewDistinctString(maxDataSize int) *DistinctString { +// NewDistinctString with the given maximum data size and max items. +// MaxDataSize is calculated as the total length of the recorded strings. +// For ease of use, maxDataSize=0 and maxItems=0 are interpreted as unlimited. +func NewDistinctString(maxDataSize int, maxValues uint32) *DistinctString { return &DistinctString{ values: make(map[string]struct{}), - maxLen: maxDataSize, + maxDataSize: maxDataSize, diffEnabled: false, // disable diff to make it faster + maxValues: maxValues, } } // NewDistinctStringWithDiff is like NewDistinctString but with diff support enabled. -func NewDistinctStringWithDiff(maxDataSize int) *DistinctString { +// MaxDataSize is calculated as the total length of the recorded strings. +// For ease of use, maxDataSize=0 and maxItems=0 are interpreted as unlimited. +func NewDistinctStringWithDiff(maxDataSize int, maxValues uint32) *DistinctString { return &DistinctString{ values: make(map[string]struct{}), new: make(map[string]struct{}), - maxLen: maxDataSize, + maxDataSize: maxDataSize, diffEnabled: true, + maxValues: maxValues, } } @@ -55,7 +61,7 @@ func (d *DistinctString) Collect(s string) (added bool) { valueLen := len(s) // Can it fit? - if d.maxLen > 0 && d.currLen+valueLen > d.maxLen { + if (d.maxDataSize > 0 && d.currDataSize+valueLen > d.maxDataSize) || (d.maxValues > 0 && d.currentValuesLen >= d.maxValues) { // No, it can't fit d.limExceeded = true return false @@ -68,7 +74,8 @@ func (d *DistinctString) Collect(s string) (added bool) { d.new[s] = struct{}{} } d.values[s] = struct{}{} - d.currLen += valueLen + d.currDataSize += valueLen + d.currentValuesLen++ return true } @@ -101,7 +108,7 @@ func (d *DistinctString) Size() int { d.mtx.Lock() defer d.mtx.Unlock() - return d.currLen + return d.currDataSize } // Diff returns all new strings collected since the last time diff was called diff --git a/pkg/collector/distinct_string_collector_test.go b/pkg/collector/distinct_string_collector_test.go index ec778e3012c..147d709dda3 100644 --- a/pkg/collector/distinct_string_collector_test.go +++ b/pkg/collector/distinct_string_collector_test.go @@ -10,7 +10,24 @@ import ( ) func TestDistinctStringCollector(t *testing.T) { - d := NewDistinctString(10) + d := NewDistinctString(10, 0) + + d.Collect("123") + d.Collect("4567") + d.Collect("890") + d.Collect("11") + + require.True(t, d.Exceeded()) + stringsSlicesEqual(t, []string{"123", "4567", "890"}, d.Strings()) + + // diff fails when diff is not enabled + res, err := d.Diff() + require.Nil(t, res) + require.Error(t, err, errDiffNotEnabled) +} + +func TestDistinctStringCollectorWithMaxItemsLimit(t *testing.T) { + d := NewDistinctString(0, 3) d.Collect("123") d.Collect("4567") @@ -27,7 +44,7 @@ func TestDistinctStringCollector(t *testing.T) { } func TestDistinctStringCollectorDiff(t *testing.T) { - d := NewDistinctStringWithDiff(0) + d := NewDistinctStringWithDiff(0, 0) d.Collect("123") d.Collect("4567") @@ -49,7 +66,7 @@ func readDistinctStringDiff(t *testing.T, d *DistinctString) []string { } func TestDistinctStringCollectorIsSafe(t *testing.T) { - d := NewDistinctString(0) // no limit + d := NewDistinctString(0, 0) // no limit var wg sync.WaitGroup for i := 0; i < 10; i++ { wg.Add(1) @@ -90,7 +107,7 @@ func BenchmarkDistinctStringCollect(b *testing.B) { for _, lim := range limits { b.Run("uniques_limit:"+strconv.Itoa(lim), func(b *testing.B) { for n := 0; n < b.N; n++ { - distinctStrings := NewDistinctString(lim) + distinctStrings := NewDistinctString(lim, 0) for _, values := range ingesterStrings { for _, v := range values { if distinctStrings.Collect(v) { @@ -103,7 +120,7 @@ func BenchmarkDistinctStringCollect(b *testing.B) { b.Run("duplicates_limit:"+strconv.Itoa(lim), func(b *testing.B) { for n := 0; n < b.N; n++ { - distinctStrings := NewDistinctString(lim) + distinctStrings := NewDistinctString(lim, 0) for i := 0; i < numIngesters; i++ { for j := 0; j < numTagValuesPerIngester; j++ { // collect first item to simulate duplicates diff --git a/pkg/collector/distinct_value_collector.go b/pkg/collector/distinct_value_collector.go index a8359667639..a45a2349133 100644 --- a/pkg/collector/distinct_value_collector.go +++ b/pkg/collector/distinct_value_collector.go @@ -8,37 +8,41 @@ import ( var errDiffNotEnabled = errors.New("diff not enabled") type DistinctValue[T comparable] struct { - values map[T]struct{} - new map[T]struct{} - len func(T) int - maxLen int - currLen int - limExceeded bool - diffEnabled bool - mtx sync.Mutex + values map[T]struct{} + new map[T]struct{} + len func(T) int + maxDataSize int + currDataSize int + currentValuesLen uint32 + maxValues uint32 + limExceeded bool + diffEnabled bool + mtx sync.Mutex } -// NewDistinctValue with the given maximum data size. This is calculated -// as the total length of the recorded strings. For ease of use, maximum=0 -// is interpreted as unlimited. +// NewDistinctValue with the given maximum data size and values limited. +// maxDataSize is calculated as the total length of the recorded strings. For ease of use, maxDataSize=0 and maxValues +// are interpreted as unlimited. // Use NewDistinctValueWithDiff to enable diff support, but that one is slightly slower. -func NewDistinctValue[T comparable](maxDataSize int, len func(T) int) *DistinctValue[T] { +func NewDistinctValue[T comparable](maxDataSize int, maxValues uint32, len func(T) int) *DistinctValue[T] { return &DistinctValue[T]{ values: make(map[T]struct{}), - maxLen: maxDataSize, + maxDataSize: maxDataSize, diffEnabled: false, // disable diff to make it faster len: len, + maxValues: maxValues, } } // NewDistinctValueWithDiff is like NewDistinctValue but with diff support enabled. -func NewDistinctValueWithDiff[T comparable](maxDataSize int, len func(T) int) *DistinctValue[T] { +func NewDistinctValueWithDiff[T comparable](maxDataSize int, maxValues uint32, len func(T) int) *DistinctValue[T] { return &DistinctValue[T]{ values: make(map[T]struct{}), new: make(map[T]struct{}), - maxLen: maxDataSize, + maxDataSize: maxDataSize, diffEnabled: true, len: len, + maxValues: maxValues, } } @@ -58,7 +62,7 @@ func (d *DistinctValue[T]) Collect(v T) (exceeded bool) { // Can it fit? // note: we will stop adding values slightly before the limit is reached - if d.maxLen > 0 && d.currLen+valueLen >= d.maxLen { + if (d.maxDataSize > 0 && d.currDataSize+valueLen >= d.maxDataSize) || (d.maxValues > 0 && d.currentValuesLen >= d.maxValues) { // No, it can't fit d.limExceeded = true return true @@ -73,7 +77,8 @@ func (d *DistinctValue[T]) Collect(v T) (exceeded bool) { } d.values[v] = struct{}{} - d.currLen += valueLen + d.currDataSize += valueLen + d.currentValuesLen++ return false } @@ -105,7 +110,7 @@ func (d *DistinctValue[T]) Size() int { d.mtx.Lock() defer d.mtx.Unlock() - return d.currLen + return d.currDataSize } // Diff returns all new strings collected since the last time diff was called diff --git a/pkg/collector/distinct_value_collector_test.go b/pkg/collector/distinct_value_collector_test.go index d0bd470db97..a5be137c2b5 100644 --- a/pkg/collector/distinct_value_collector_test.go +++ b/pkg/collector/distinct_value_collector_test.go @@ -11,7 +11,28 @@ import ( ) func TestDistinctValueCollector(t *testing.T) { - d := NewDistinctValue[string](10, func(s string) int { return len(s) }) + d := NewDistinctValue(10, 0, func(s string) int { return len(s) }) + + var stop bool + stop = d.Collect("123") + require.False(t, stop) + stop = d.Collect("4567") + require.False(t, stop) + stop = d.Collect("890") + require.True(t, stop) + + require.True(t, d.Exceeded()) + require.Equal(t, stop, d.Exceeded()) // final stop should be same as Exceeded + stringsSlicesEqual(t, []string{"123", "4567"}, d.Values()) + + // diff fails when diff is not enabled + res, err := d.Diff() + require.Nil(t, res) + require.Error(t, err, errDiffNotEnabled) +} + +func TestDistinctValueCollectorWithMaxValuesLimited(t *testing.T) { + d := NewDistinctValue(0, 2, func(s string) int { return len(s) }) var stop bool stop = d.Collect("123") @@ -32,7 +53,7 @@ func TestDistinctValueCollector(t *testing.T) { } func TestDistinctValueCollectorDiff(t *testing.T) { - d := NewDistinctValueWithDiff[string](0, func(s string) int { return len(s) }) + d := NewDistinctValueWithDiff(0, 0, func(s string) int { return len(s) }) d.Collect("123") d.Collect("4567") @@ -85,7 +106,7 @@ func BenchmarkDistinctValueCollect(b *testing.B) { for _, lim := range limits { b.Run("uniques_limit:"+strconv.Itoa(lim), func(b *testing.B) { for n := 0; n < b.N; n++ { - distinctValues := NewDistinctValue(lim, func(v tempopb.TagValue) int { return len(v.Type) + len(v.Value) }) + distinctValues := NewDistinctValue(lim, 0, func(v tempopb.TagValue) int { return len(v.Type) + len(v.Value) }) for _, tagValues := range ingesterTagValues { for _, v := range tagValues { if distinctValues.Collect(v) { @@ -98,7 +119,7 @@ func BenchmarkDistinctValueCollect(b *testing.B) { b.Run("duplicates_limit:"+strconv.Itoa(lim), func(b *testing.B) { for n := 0; n < b.N; n++ { - distinctValues := NewDistinctValue(lim, func(v tempopb.TagValue) int { return len(v.Type) + len(v.Value) }) + distinctValues := NewDistinctValue(lim, 0, func(v tempopb.TagValue) int { return len(v.Type) + len(v.Value) }) for i := 0; i < numIngesters; i++ { for j := 0; j < numTagValuesPerIngester; j++ { // collect first item to simulate duplicates diff --git a/pkg/collector/scoped_distinct_string.go b/pkg/collector/scoped_distinct_string.go index abe902122c7..72ec3ad4cec 100644 --- a/pkg/collector/scoped_distinct_string.go +++ b/pkg/collector/scoped_distinct_string.go @@ -4,31 +4,44 @@ import ( "sync" ) +const IntrinsicScope = "intrinsic" + type ScopedDistinctString struct { - cols map[string]*DistinctString - newCol func(int) *DistinctString - maxLen int - curLen int - limExceeded bool - diffEnabled bool - mtx sync.Mutex + cols map[string]*DistinctString + newCol func(int, uint32) *DistinctString + maxDataSize int + currDataSize int + limExceeded bool + diffEnabled bool + maxTagsPerScope uint32 + mtx sync.Mutex } -func NewScopedDistinctString(maxDataSize int) *ScopedDistinctString { +// NewScopedDistinctString collects the tags per scope +// MaxDataSize is calculated as the total length of the recorded strings. +// MaxTagsPerScope controls how many tags can be added per scope. The intrinsic scope is unbounded. +// For ease of use, maxDataSize=0 and maxTagsPerScope=0 are interpreted as unlimited. +func NewScopedDistinctString(maxDataSize int, maxTagsPerScope uint32) *ScopedDistinctString { return &ScopedDistinctString{ - cols: map[string]*DistinctString{}, - newCol: NewDistinctString, - maxLen: maxDataSize, - diffEnabled: false, + cols: map[string]*DistinctString{}, + newCol: NewDistinctString, + maxDataSize: maxDataSize, + diffEnabled: false, + maxTagsPerScope: maxTagsPerScope, } } -func NewScopedDistinctStringWithDiff(maxDataSize int) *ScopedDistinctString { +// NewScopedDistinctStringWithDiff collects the tags per scope with diff +// MaxDataSize is calculated as the total length of the recorded strings. +// MaxTagsPerScope controls how many tags can be added per scope. The intrinsic scope is unbounded. +// For ease of use, maxDataSize=0 and maxTagsPerScope=0 are interpreted as unlimited. +func NewScopedDistinctStringWithDiff(maxDataSize int, maxTagsPerScope uint32) *ScopedDistinctString { return &ScopedDistinctString{ - cols: map[string]*DistinctString{}, - newCol: NewDistinctStringWithDiff, - maxLen: maxDataSize, - diffEnabled: true, + cols: map[string]*DistinctString{}, + newCol: NewDistinctStringWithDiff, + maxDataSize: maxDataSize, + diffEnabled: true, + maxTagsPerScope: maxTagsPerScope, } } @@ -45,7 +58,7 @@ func (d *ScopedDistinctString) Collect(scope string, val string) (exceeded bool) valueLen := len(val) // can it fit? - if d.maxLen > 0 && d.curLen+valueLen > d.maxLen { + if d.maxDataSize > 0 && d.currDataSize+valueLen > d.maxDataSize { // No d.limExceeded = true return true @@ -54,13 +67,17 @@ func (d *ScopedDistinctString) Collect(scope string, val string) (exceeded bool) // get or create collector col, ok := d.cols[scope] if !ok { - col = d.newCol(0) + if scope == IntrinsicScope { + col = d.newCol(0, 0) + } else { + col = d.newCol(0, d.maxTagsPerScope) + } d.cols[scope] = col } // add valueLen if we successfully added the value if col.Collect(val) { - d.curLen += valueLen + d.currDataSize += valueLen } return false } @@ -80,11 +97,21 @@ func (d *ScopedDistinctString) Strings() map[string][]string { } // Exceeded indicates if some values were lost because the maximum size limit was met. +// Or because one of the scopes max tags was reached. func (d *ScopedDistinctString) Exceeded() bool { d.mtx.Lock() defer d.mtx.Unlock() - return d.limExceeded + if d.limExceeded { + return true + } + + for _, v := range d.cols { + if v.Exceeded() { + return true + } + } + return false } // Diff returns all new strings collected since the last time Diff was called diff --git a/pkg/collector/scoped_distinct_string_test.go b/pkg/collector/scoped_distinct_string_test.go index 9b090cef442..6eb237e57e7 100644 --- a/pkg/collector/scoped_distinct_string_test.go +++ b/pkg/collector/scoped_distinct_string_test.go @@ -12,10 +12,11 @@ import ( func TestScopedDistinct(t *testing.T) { tcs := []struct { - in map[string][]string - expected map[string][]string - limit int - exceeded bool + in map[string][]string + expected map[string][]string + maxBytes int + maxItemsPerScope uint32 + exceeded bool }{ { in: map[string][]string{ @@ -46,13 +47,28 @@ func TestScopedDistinct(t *testing.T) { "scope1": {"val1", "val2"}, "scope2": {"val1"}, }, - limit: 13, + maxBytes: 13, exceeded: true, }, + { + in: map[string][]string{ + "intrinsic": {"val1", "val2"}, + "scope2": {"val1", "val2"}, + "scope3": {"val1", "val2", "val3"}, + }, + expected: map[string][]string{ + "intrinsic": {"val1", "val2"}, + "scope2": {"val1"}, + "scope3": {"val1"}, + }, + maxBytes: 0, + maxItemsPerScope: 1, + exceeded: true, + }, } for _, tc := range tcs { - c := NewScopedDistinctString(tc.limit) + c := NewScopedDistinctString(tc.maxBytes, tc.maxItemsPerScope) // get and sort keys so we can deterministically add values keys := []string{} @@ -61,18 +77,15 @@ func TestScopedDistinct(t *testing.T) { } slices.Sort(keys) - var stop bool for _, k := range keys { v := tc.in[k] for _, val := range v { - stop = c.Collect(k, val) + c.Collect(k, val) } } // check if we exceeded the limit, and Collect and Exceeded return the same value require.Equal(t, tc.exceeded, c.Exceeded()) - require.Equal(t, tc.exceeded, stop) - require.Equal(t, stop, c.Exceeded()) actual := c.Strings() assertMaps(t, tc.expected, actual) @@ -80,7 +93,7 @@ func TestScopedDistinct(t *testing.T) { } func TestScopedDistinctDiff(t *testing.T) { - c := NewScopedDistinctStringWithDiff(0) + c := NewScopedDistinctStringWithDiff(0, 0) c.Collect("scope1", "val1") expected := map[string][]string{ @@ -122,7 +135,7 @@ func TestScopedDistinctDiff(t *testing.T) { assertMaps(t, map[string][]string{}, readScopedDistinctStringDiff(t, c)) // diff should error when diff is not enabled - col := NewScopedDistinctString(0) + col := NewScopedDistinctString(0, 0) col.Collect("scope1", "val1") res, err := col.Diff() require.Nil(t, res) @@ -144,7 +157,7 @@ func assertMaps(t *testing.T, expected, actual map[string][]string) { } func TestScopedDistinctStringCollectorIsSafe(t *testing.T) { - d := NewScopedDistinctString(0) // no limit + d := NewScopedDistinctString(0, 0) // no limit var wg sync.WaitGroup for i := 0; i < 10; i++ { @@ -195,7 +208,7 @@ func BenchmarkScopedDistinctStringCollect(b *testing.B) { for _, lim := range limits { b.Run("uniques_limit:"+strconv.Itoa(lim), func(b *testing.B) { for n := 0; n < b.N; n++ { - scopedDistinctStrings := NewScopedDistinctString(lim) + scopedDistinctStrings := NewScopedDistinctString(lim, 0) for _, tags := range ingesterTags { for scope, values := range tags { for _, v := range values { @@ -210,7 +223,7 @@ func BenchmarkScopedDistinctStringCollect(b *testing.B) { b.Run("duplicates_limit:"+strconv.Itoa(lim), func(b *testing.B) { for n := 0; n < b.N; n++ { - scopedDistinctStrings := NewScopedDistinctString(lim) + scopedDistinctStrings := NewScopedDistinctString(lim, 0) for i := 0; i < numIngesters; i++ { for scope := range ingesterTags[i] { // collect first item to simulate duplicates diff --git a/pkg/tempopb/tempo.pb.go b/pkg/tempopb/tempo.pb.go index dd3cd274a52..13e84e8bd61 100644 --- a/pkg/tempopb/tempo.pb.go +++ b/pkg/tempopb/tempo.pb.go @@ -1045,10 +1045,11 @@ func (m *SearchMetrics) GetInspectedSpans() uint64 { } type SearchTagsRequest struct { - Scope string `protobuf:"bytes,1,opt,name=scope,proto3" json:"scope,omitempty"` - Query string `protobuf:"bytes,2,opt,name=query,proto3" json:"query,omitempty"` - Start uint32 `protobuf:"varint,3,opt,name=start,proto3" json:"start,omitempty"` - End uint32 `protobuf:"varint,4,opt,name=end,proto3" json:"end,omitempty"` + Scope string `protobuf:"bytes,1,opt,name=scope,proto3" json:"scope,omitempty"` + Query string `protobuf:"bytes,2,opt,name=query,proto3" json:"query,omitempty"` + Start uint32 `protobuf:"varint,3,opt,name=start,proto3" json:"start,omitempty"` + End uint32 `protobuf:"varint,4,opt,name=end,proto3" json:"end,omitempty"` + MaxTagsPerScope uint32 `protobuf:"varint,5,opt,name=maxTagsPerScope,proto3" json:"maxTagsPerScope,omitempty"` } func (m *SearchTagsRequest) Reset() { *m = SearchTagsRequest{} } @@ -1112,6 +1113,13 @@ func (m *SearchTagsRequest) GetEnd() uint32 { return 0 } +func (m *SearchTagsRequest) GetMaxTagsPerScope() uint32 { + if m != nil { + return m.MaxTagsPerScope + } + return 0 +} + // SearchTagsBlockRequest takes SearchTagsRequest parameters as well as all information necessary // to search a block in the backend. type SearchTagsBlockRequest struct { @@ -1127,6 +1135,7 @@ type SearchTagsBlockRequest struct { Size_ uint64 `protobuf:"varint,10,opt,name=size,proto3" json:"size,omitempty"` FooterSize uint32 `protobuf:"varint,11,opt,name=footerSize,proto3" json:"footerSize,omitempty"` DedicatedColumns []*DedicatedColumn `protobuf:"bytes,12,rep,name=dedicatedColumns,proto3" json:"dedicatedColumns,omitempty"` + MaxTagsPerScope uint32 `protobuf:"varint,13,opt,name=maxTagsPerScope,proto3" json:"maxTagsPerScope,omitempty"` } func (m *SearchTagsBlockRequest) Reset() { *m = SearchTagsBlockRequest{} } @@ -1246,6 +1255,13 @@ func (m *SearchTagsBlockRequest) GetDedicatedColumns() []*DedicatedColumn { return nil } +func (m *SearchTagsBlockRequest) GetMaxTagsPerScope() uint32 { + if m != nil { + return m.MaxTagsPerScope + } + return 0 +} + type SearchTagValuesBlockRequest struct { SearchReq *SearchTagValuesRequest `protobuf:"bytes,1,opt,name=searchReq,proto3" json:"searchReq,omitempty"` BlockID string `protobuf:"bytes,2,opt,name=blockID,proto3" json:"blockID,omitempty"` @@ -1259,6 +1275,7 @@ type SearchTagValuesBlockRequest struct { Size_ uint64 `protobuf:"varint,10,opt,name=size,proto3" json:"size,omitempty"` FooterSize uint32 `protobuf:"varint,11,opt,name=footerSize,proto3" json:"footerSize,omitempty"` DedicatedColumns []*DedicatedColumn `protobuf:"bytes,12,rep,name=dedicatedColumns,proto3" json:"dedicatedColumns,omitempty"` + MaxTagValues uint32 `protobuf:"varint,13,opt,name=maxTagValues,proto3" json:"maxTagValues,omitempty"` } func (m *SearchTagValuesBlockRequest) Reset() { *m = SearchTagValuesBlockRequest{} } @@ -1378,6 +1395,13 @@ func (m *SearchTagValuesBlockRequest) GetDedicatedColumns() []*DedicatedColumn { return nil } +func (m *SearchTagValuesBlockRequest) GetMaxTagValues() uint32 { + if m != nil { + return m.MaxTagValues + } + return 0 +} + type SearchTagsResponse struct { TagNames []string `protobuf:"bytes,1,rep,name=tagNames,proto3" json:"tagNames,omitempty"` Metrics *MetadataMetrics `protobuf:"bytes,2,opt,name=metrics,proto3" json:"metrics,omitempty"` @@ -1535,10 +1559,11 @@ func (m *SearchTagsV2Scope) GetTags() []string { } type SearchTagValuesRequest struct { - TagName string `protobuf:"bytes,1,opt,name=tagName,proto3" json:"tagName,omitempty"` - Query string `protobuf:"bytes,2,opt,name=query,proto3" json:"query,omitempty"` - Start uint32 `protobuf:"varint,4,opt,name=start,proto3" json:"start,omitempty"` - End uint32 `protobuf:"varint,5,opt,name=end,proto3" json:"end,omitempty"` + TagName string `protobuf:"bytes,1,opt,name=tagName,proto3" json:"tagName,omitempty"` + Query string `protobuf:"bytes,2,opt,name=query,proto3" json:"query,omitempty"` + Start uint32 `protobuf:"varint,4,opt,name=start,proto3" json:"start,omitempty"` + End uint32 `protobuf:"varint,5,opt,name=end,proto3" json:"end,omitempty"` + MaxTagValues uint32 `protobuf:"varint,6,opt,name=maxTagValues,proto3" json:"maxTagValues,omitempty"` } func (m *SearchTagValuesRequest) Reset() { *m = SearchTagValuesRequest{} } @@ -1602,6 +1627,13 @@ func (m *SearchTagValuesRequest) GetEnd() uint32 { return 0 } +func (m *SearchTagValuesRequest) GetMaxTagValues() uint32 { + if m != nil { + return m.MaxTagValues + } + return 0 +} + type SearchTagValuesResponse struct { TagValues []string `protobuf:"bytes,1,rep,name=tagValues,proto3" json:"tagValues,omitempty"` Metrics *MetadataMetrics `protobuf:"bytes,2,opt,name=metrics,proto3" json:"metrics,omitempty"` @@ -3534,187 +3566,190 @@ func init() { func init() { proto.RegisterFile("pkg/tempopb/tempo.proto", fileDescriptor_f22805646f4f62b6) } var fileDescriptor_f22805646f4f62b6 = []byte{ - // 2876 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x5a, 0x4b, 0x6f, 0x23, 0xc7, - 0xf1, 0xd7, 0x88, 0xef, 0x22, 0x29, 0x51, 0xbd, 0xb2, 0xcc, 0xe5, 0xae, 0x25, 0x79, 0xbc, 0xf0, - 0x5f, 0x7f, 0x3f, 0x28, 0x2d, 0xbd, 0x8b, 0x78, 0xed, 0xc4, 0x81, 0xb4, 0x62, 0xd6, 0xb2, 0xf5, - 0x72, 0x93, 0x96, 0x8d, 0xc0, 0x80, 0x30, 0x22, 0x7b, 0xb9, 0x03, 0x91, 0x33, 0xf4, 0xcc, 0x50, - 0x5e, 0xe5, 0x60, 0x24, 0x01, 0x72, 0x08, 0x90, 0x43, 0x0e, 0xc9, 0x21, 0xc7, 0x9c, 0x82, 0x9c, - 0x72, 0x48, 0xbe, 0x41, 0x80, 0xc0, 0x41, 0x10, 0xc3, 0x40, 0x2e, 0x46, 0x0e, 0x46, 0x60, 0x1f, - 0x92, 0x8f, 0x11, 0x74, 0x75, 0xf7, 0xbc, 0x25, 0x79, 0xbd, 0x32, 0xe2, 0x83, 0x4f, 0xea, 0xae, - 0xf9, 0x75, 0x75, 0x75, 0x55, 0x75, 0x3d, 0x9a, 0x82, 0x27, 0xc7, 0xc7, 0x83, 0x55, 0x8f, 0x8d, - 0xc6, 0xf6, 0xf8, 0x48, 0xfc, 0x6d, 0x8e, 0x1d, 0xdb, 0xb3, 0x49, 0x41, 0x12, 0x1b, 0x0b, 0x3d, - 0x7b, 0x34, 0xb2, 0xad, 0xd5, 0x93, 0x9b, 0xab, 0x62, 0x24, 0x00, 0x8d, 0x17, 0x07, 0xa6, 0xf7, - 0x60, 0x72, 0xd4, 0xec, 0xd9, 0xa3, 0xd5, 0x81, 0x3d, 0xb0, 0x57, 0x91, 0x7c, 0x34, 0xb9, 0x8f, - 0x33, 0x9c, 0xe0, 0x48, 0xc2, 0xe7, 0x3d, 0xc7, 0xe8, 0x31, 0xce, 0x05, 0x07, 0x82, 0xaa, 0xff, - 0x49, 0x83, 0x5a, 0x97, 0xcf, 0x37, 0x4e, 0xb7, 0x36, 0x29, 0x7b, 0x7f, 0xc2, 0x5c, 0x8f, 0xd4, - 0xa1, 0x80, 0x98, 0xad, 0xcd, 0xba, 0xb6, 0xac, 0xad, 0x54, 0xa8, 0x9a, 0x92, 0x45, 0x80, 0xa3, - 0xa1, 0xdd, 0x3b, 0xee, 0x78, 0x86, 0xe3, 0xd5, 0xa7, 0x97, 0xb5, 0x95, 0x12, 0x0d, 0x51, 0x48, - 0x03, 0x8a, 0x38, 0x6b, 0x5b, 0xfd, 0x7a, 0x06, 0xbf, 0xfa, 0x73, 0x72, 0x1d, 0x4a, 0xef, 0x4f, - 0x98, 0x73, 0xba, 0x63, 0xf7, 0x59, 0x3d, 0x87, 0x1f, 0x03, 0x02, 0x79, 0x01, 0xe6, 0x8c, 0xe1, - 0xd0, 0xfe, 0x60, 0xdf, 0x70, 0x3c, 0xd3, 0x18, 0xa2, 0x4c, 0xf5, 0xfc, 0xb2, 0xb6, 0x52, 0xa4, - 0xc9, 0x0f, 0xfa, 0x7f, 0x34, 0x98, 0x0b, 0x89, 0xed, 0x8e, 0x6d, 0xcb, 0x65, 0xe4, 0x06, 0xe4, - 0x50, 0x50, 0x94, 0xba, 0xdc, 0x9a, 0x69, 0x4a, 0x15, 0x36, 0x11, 0x4a, 0xc5, 0x47, 0xf2, 0x12, - 0x14, 0x46, 0xcc, 0x73, 0xcc, 0x9e, 0x8b, 0x07, 0x28, 0xb7, 0xae, 0x46, 0x71, 0x9c, 0xe5, 0x8e, - 0x00, 0x50, 0x85, 0x24, 0x77, 0x20, 0xef, 0x7a, 0x86, 0x37, 0x71, 0xf1, 0x58, 0x33, 0xad, 0xa7, - 0x93, 0x6b, 0x94, 0x18, 0xcd, 0x0e, 0x02, 0xa9, 0x5c, 0xc0, 0xb5, 0x39, 0x62, 0xae, 0x6b, 0x0c, - 0x58, 0x3d, 0x8b, 0xa7, 0x56, 0x53, 0xfd, 0x19, 0xc8, 0x0b, 0x2c, 0xa9, 0x40, 0xf1, 0xee, 0xde, - 0xce, 0xfe, 0x76, 0xbb, 0xdb, 0xae, 0x4d, 0x91, 0x32, 0x14, 0xf6, 0xd7, 0x69, 0x77, 0x6b, 0x7d, - 0xbb, 0xa6, 0xe9, 0x24, 0x64, 0x20, 0x29, 0x96, 0xfe, 0xf1, 0x34, 0x54, 0x3b, 0xcc, 0x70, 0x7a, - 0x0f, 0x94, 0xc9, 0x5e, 0x81, 0x6c, 0xd7, 0x18, 0xb8, 0x75, 0x6d, 0x39, 0xb3, 0x52, 0x6e, 0x2d, - 0xfb, 0xd2, 0x45, 0x50, 0x4d, 0x0e, 0x69, 0x5b, 0x9e, 0x73, 0xba, 0x91, 0xfd, 0xe8, 0xb3, 0xa5, - 0x29, 0x8a, 0x6b, 0xc8, 0x0d, 0xa8, 0xee, 0x98, 0xd6, 0xe6, 0xc4, 0x31, 0x3c, 0xd3, 0xb6, 0x76, - 0x84, 0x5a, 0xaa, 0x34, 0x4a, 0x44, 0x94, 0xf1, 0x30, 0x84, 0xca, 0x48, 0x54, 0x98, 0x48, 0xe6, - 0x21, 0xb7, 0x6d, 0x8e, 0x4c, 0x0f, 0x8f, 0x5a, 0xa5, 0x62, 0xc2, 0xa9, 0x2e, 0x7a, 0x4c, 0x4e, - 0x50, 0x71, 0x42, 0x6a, 0x90, 0x61, 0x56, 0x1f, 0x8d, 0x5c, 0xa5, 0x7c, 0xc8, 0x71, 0x6f, 0x71, - 0x8f, 0xa8, 0x17, 0x51, 0x51, 0x62, 0x42, 0x56, 0x60, 0xb6, 0x33, 0x36, 0x2c, 0x77, 0x9f, 0x39, - 0xfc, 0x6f, 0x87, 0x79, 0xf5, 0x12, 0xae, 0x89, 0x93, 0x1b, 0xdf, 0x81, 0x92, 0x7f, 0x44, 0xce, - 0xfe, 0x98, 0x9d, 0xa2, 0x2f, 0x94, 0x28, 0x1f, 0x72, 0xf6, 0x27, 0xc6, 0x70, 0xc2, 0xa4, 0xe3, - 0x8a, 0xc9, 0x2b, 0xd3, 0x2f, 0x6b, 0xfa, 0x5f, 0x32, 0x40, 0x84, 0xaa, 0x36, 0xb8, 0xbb, 0x2a, - 0xad, 0xde, 0x82, 0x92, 0xab, 0x14, 0x28, 0x9d, 0x6a, 0x21, 0x5d, 0xb5, 0x34, 0x00, 0x72, 0x83, - 0xa3, 0xd3, 0x6f, 0x6d, 0xca, 0x8d, 0xd4, 0x94, 0x5f, 0x01, 0x3c, 0xfa, 0x3e, 0x77, 0x06, 0xa1, - 0xbf, 0x80, 0xc0, 0x35, 0x3c, 0x36, 0x06, 0xcc, 0xed, 0xda, 0x82, 0xb5, 0xd4, 0x61, 0x94, 0xc8, - 0xaf, 0x18, 0xb3, 0x7a, 0x76, 0xdf, 0xb4, 0x06, 0xf2, 0x16, 0xf9, 0x73, 0xce, 0xc1, 0xb4, 0xfa, - 0xec, 0x21, 0x67, 0xd7, 0x31, 0x7f, 0xc4, 0xa4, 0x6e, 0xa3, 0x44, 0xa2, 0x43, 0xc5, 0xb3, 0x3d, - 0x63, 0x48, 0x59, 0xcf, 0x76, 0xfa, 0x6e, 0xbd, 0x80, 0xa0, 0x08, 0x8d, 0x63, 0xfa, 0x86, 0x67, - 0xb4, 0xd5, 0x4e, 0xc2, 0x20, 0x11, 0x1a, 0x3f, 0xe7, 0x09, 0x73, 0x5c, 0xd3, 0xb6, 0xd0, 0x1e, - 0x25, 0xaa, 0xa6, 0x84, 0x40, 0xd6, 0xe5, 0xdb, 0xc3, 0xb2, 0xb6, 0x92, 0xa5, 0x38, 0xe6, 0xa1, - 0xe3, 0xbe, 0x6d, 0x7b, 0xcc, 0x41, 0xc1, 0xca, 0xb8, 0x67, 0x88, 0x42, 0x36, 0xa1, 0xd6, 0x67, - 0x7d, 0xb3, 0x67, 0x78, 0xac, 0x7f, 0xd7, 0x1e, 0x4e, 0x46, 0x96, 0x5b, 0xaf, 0xa0, 0x37, 0xd7, - 0x7d, 0x95, 0x6f, 0x46, 0x01, 0x34, 0xb1, 0x42, 0xff, 0xb3, 0x06, 0xb3, 0x31, 0x14, 0xb9, 0x05, - 0x39, 0xb7, 0x67, 0x8f, 0x99, 0xbc, 0xba, 0x8b, 0x67, 0xb1, 0x6b, 0x76, 0x38, 0x8a, 0x0a, 0x30, - 0x3f, 0x83, 0x65, 0x8c, 0x94, 0xaf, 0xe0, 0x98, 0xdc, 0x84, 0xac, 0x77, 0x3a, 0x16, 0xf1, 0x65, - 0xa6, 0xf5, 0xd4, 0x99, 0x8c, 0xba, 0xa7, 0x63, 0x46, 0x11, 0xaa, 0x2f, 0x41, 0x0e, 0xd9, 0x92, - 0x22, 0x64, 0x3b, 0xfb, 0xeb, 0xbb, 0xb5, 0x29, 0x7e, 0xd9, 0x69, 0xbb, 0xb3, 0xf7, 0x36, 0xbd, - 0xdb, 0xc6, 0xfb, 0x9d, 0xe5, 0x70, 0x02, 0x90, 0xef, 0x74, 0xe9, 0xd6, 0xee, 0xbd, 0xda, 0x94, - 0xfe, 0x10, 0x66, 0x94, 0x77, 0xc9, 0xd0, 0x76, 0x0b, 0xf2, 0x18, 0xbd, 0xd4, 0x0d, 0xbf, 0x1e, - 0x8d, 0x3f, 0x02, 0xbd, 0xc3, 0x3c, 0x83, 0x5b, 0x88, 0x4a, 0x2c, 0x59, 0x8b, 0x87, 0xba, 0xb8, - 0xf7, 0xc6, 0xe3, 0x9c, 0xfe, 0x8f, 0x0c, 0x5c, 0x49, 0xe1, 0x18, 0x4f, 0x09, 0xa5, 0x20, 0x25, - 0xac, 0xc0, 0xac, 0x63, 0xdb, 0x5e, 0x87, 0x39, 0x27, 0x66, 0x8f, 0xed, 0x06, 0x2a, 0x8b, 0x93, - 0xb9, 0x77, 0x72, 0x12, 0xb2, 0x47, 0x9c, 0xc8, 0x10, 0x51, 0x22, 0x4f, 0x04, 0x78, 0x25, 0xba, - 0xe6, 0x88, 0xbd, 0x6d, 0x99, 0x0f, 0x77, 0x0d, 0xcb, 0xc6, 0x9b, 0x90, 0xa5, 0xc9, 0x0f, 0xdc, - 0xab, 0xfa, 0x41, 0x48, 0x12, 0xe1, 0x25, 0x44, 0x21, 0xcf, 0x41, 0xc1, 0x95, 0x31, 0x23, 0x8f, - 0x1a, 0xa8, 0x05, 0x1a, 0x10, 0x74, 0xaa, 0x00, 0xe4, 0x05, 0x28, 0xca, 0x21, 0xbf, 0x13, 0x99, - 0x54, 0xb0, 0x8f, 0x20, 0x14, 0x2a, 0xae, 0x38, 0x1c, 0x8f, 0xe1, 0x6e, 0xbd, 0x88, 0x2b, 0x9a, - 0xe7, 0xd9, 0xa5, 0xd9, 0x09, 0x2d, 0xc0, 0x20, 0x45, 0x23, 0x3c, 0x1a, 0x07, 0x30, 0x97, 0x80, - 0xa4, 0xc4, 0xb1, 0xe7, 0xc3, 0x71, 0xac, 0xdc, 0x7a, 0x22, 0x64, 0xd4, 0x60, 0x71, 0x38, 0xbc, - 0x6d, 0x43, 0x25, 0xfc, 0x09, 0xe3, 0xd0, 0xd8, 0xb0, 0xee, 0xda, 0x13, 0xcb, 0x43, 0xc6, 0x3c, - 0x0e, 0x29, 0x02, 0xd7, 0x29, 0x73, 0x1c, 0xdb, 0x11, 0x9f, 0x45, 0x32, 0x08, 0x51, 0xf4, 0x9f, - 0x69, 0x50, 0x90, 0xfa, 0x20, 0xcf, 0x40, 0x8e, 0x2f, 0x54, 0x6e, 0x59, 0x8d, 0x28, 0x8c, 0x8a, - 0x6f, 0x98, 0x01, 0x0d, 0xaf, 0xf7, 0x80, 0xf5, 0x25, 0x37, 0x35, 0x25, 0xaf, 0x02, 0x18, 0x9e, - 0xe7, 0x98, 0x47, 0x13, 0x8f, 0xf1, 0x8c, 0xc2, 0x79, 0x5c, 0xf3, 0x79, 0xc8, 0x72, 0xe7, 0xe4, - 0x66, 0xf3, 0x4d, 0x76, 0x7a, 0xc0, 0x4f, 0x43, 0x43, 0x70, 0x7e, 0xd7, 0xb3, 0x7c, 0x1b, 0xb2, - 0x00, 0x79, 0xbe, 0x91, 0xef, 0x9b, 0x72, 0x96, 0x7a, 0x85, 0x53, 0xdd, 0x2b, 0x73, 0x96, 0x7b, - 0xdd, 0x80, 0xaa, 0x72, 0x26, 0x3e, 0x77, 0xa5, 0x23, 0x46, 0x89, 0xb1, 0x53, 0xe4, 0x1e, 0xed, - 0x14, 0xbf, 0xf1, 0x73, 0xb9, 0xbc, 0x8c, 0xfc, 0x46, 0x99, 0x96, 0x3b, 0x66, 0x3d, 0x8f, 0xf5, - 0xbb, 0xea, 0xd2, 0x63, 0xbe, 0x8b, 0x91, 0xc9, 0xb3, 0x30, 0xe3, 0x93, 0x36, 0x4e, 0xf9, 0xe6, - 0xd3, 0x28, 0x5f, 0x8c, 0x4a, 0x96, 0xa1, 0x8c, 0xd1, 0x1d, 0x93, 0x9b, 0xca, 0xdc, 0x61, 0x12, - 0x3f, 0x68, 0xcf, 0x1e, 0x8d, 0x87, 0xcc, 0x63, 0xfd, 0x37, 0xec, 0x23, 0x57, 0xe5, 0x9e, 0x08, - 0x91, 0xfb, 0x0d, 0x2e, 0x42, 0x84, 0xb8, 0x6c, 0x01, 0x81, 0xcb, 0x1d, 0xb0, 0x14, 0xe2, 0xe4, - 0x51, 0x9c, 0x38, 0x39, 0x22, 0x37, 0xe6, 0x70, 0xcc, 0x41, 0x61, 0xb9, 0x91, 0xaa, 0x0f, 0xf8, - 0x7d, 0xe0, 0xaa, 0xe1, 0x59, 0x5d, 0x25, 0xe5, 0x79, 0x15, 0xce, 0x85, 0xb1, 0x65, 0xb8, 0x9e, - 0x87, 0x1c, 0x16, 0x93, 0x2a, 0xb7, 0xe3, 0x24, 0x28, 0x3c, 0x32, 0x29, 0x85, 0x47, 0xd6, 0x2f, - 0x3c, 0xf4, 0x8f, 0x33, 0xb0, 0x10, 0xec, 0x14, 0xa9, 0x01, 0x5e, 0x4e, 0xd6, 0x00, 0x8d, 0x58, - 0x14, 0x0d, 0x49, 0xf7, 0x6d, 0x1d, 0xf0, 0xcd, 0xa8, 0x03, 0x3e, 0xcd, 0xc0, 0x35, 0xdf, 0x38, - 0x78, 0xe9, 0xa2, 0x56, 0xfd, 0x5e, 0xd2, 0xaa, 0x4b, 0x49, 0xab, 0x8a, 0x85, 0xdf, 0x9a, 0xf6, - 0x1b, 0x65, 0xda, 0xbe, 0x2a, 0xd5, 0xc5, 0xb5, 0x93, 0x05, 0x52, 0x03, 0x8a, 0x9e, 0x31, 0xe0, - 0x15, 0x84, 0xc8, 0x45, 0x25, 0xea, 0xcf, 0x49, 0x2b, 0x5e, 0x06, 0x05, 0xdb, 0xa9, 0xd4, 0x9c, - 0x28, 0x84, 0x3e, 0x84, 0xf9, 0x60, 0x97, 0x83, 0x96, 0xbf, 0x4f, 0x0b, 0xf2, 0x18, 0x70, 0x54, - 0xc6, 0x4b, 0x8b, 0x05, 0x07, 0x2d, 0x51, 0x49, 0x4a, 0xe4, 0x57, 0xda, 0xff, 0xd5, 0x70, 0xe8, - 0x93, 0x0c, 0xfd, 0x84, 0xa6, 0x85, 0x12, 0x1a, 0x81, 0xac, 0xc7, 0x3b, 0xbf, 0x69, 0x3c, 0x34, - 0x8e, 0xf5, 0x71, 0x28, 0x9a, 0x45, 0x7c, 0x18, 0xeb, 0x38, 0xa1, 0x16, 0xbf, 0x8e, 0x13, 0xd3, - 0x8b, 0x02, 0x68, 0x36, 0x25, 0x80, 0xe6, 0x82, 0x00, 0x7a, 0x0c, 0x4f, 0x26, 0x76, 0x94, 0x1a, - 0xe3, 0x49, 0x43, 0x11, 0xa5, 0x69, 0x02, 0xc2, 0x57, 0xd2, 0xcd, 0x2d, 0x28, 0xaa, 0x6d, 0xf0, - 0xf8, 0xa7, 0x7e, 0x32, 0xc0, 0x71, 0x7a, 0x9f, 0xa7, 0xff, 0x58, 0x83, 0xab, 0x31, 0x19, 0x43, - 0x76, 0x5d, 0x8d, 0x4b, 0x59, 0x6e, 0xcd, 0x05, 0xb5, 0x9c, 0xfc, 0xf2, 0xb8, 0x82, 0xff, 0x55, - 0x83, 0xd9, 0xd8, 0xc7, 0x94, 0x1c, 0xae, 0xa5, 0xe6, 0xf0, 0x48, 0xee, 0x9d, 0x8e, 0xe7, 0xde, - 0x44, 0xfe, 0xce, 0xa4, 0xe5, 0xef, 0x58, 0x1d, 0x90, 0x4d, 0xd6, 0x01, 0x29, 0x39, 0x3c, 0x97, - 0x9a, 0xc3, 0xf5, 0x5d, 0xc8, 0x61, 0x15, 0x42, 0xda, 0x50, 0x75, 0x98, 0x6b, 0x4f, 0x9c, 0x1e, - 0xeb, 0x84, 0x4a, 0xc1, 0x20, 0x9c, 0x8a, 0xf7, 0xa6, 0x93, 0x9b, 0x4d, 0x1a, 0x86, 0xd1, 0xe8, - 0x2a, 0x7d, 0x17, 0x2a, 0xfb, 0x13, 0x37, 0xe8, 0x78, 0x5e, 0x83, 0x2a, 0xd6, 0x9c, 0xee, 0xc6, - 0x69, 0x57, 0x3e, 0xea, 0x64, 0x56, 0x66, 0x42, 0x5a, 0xe6, 0xe8, 0x36, 0x47, 0x50, 0x66, 0xb8, - 0xb6, 0x45, 0xa3, 0x70, 0xfd, 0xb7, 0x1a, 0xd4, 0x38, 0x04, 0xa5, 0x55, 0xee, 0xff, 0xa2, 0xdf, - 0x46, 0xf1, 0xeb, 0x52, 0xd9, 0x78, 0xe2, 0xa3, 0xcf, 0x96, 0xa6, 0xfe, 0xf9, 0xd9, 0x52, 0x75, - 0xdf, 0x61, 0xc6, 0x70, 0x68, 0xf7, 0x04, 0x5a, 0xf5, 0x4f, 0xff, 0x07, 0x19, 0xb3, 0x2f, 0xea, - 0xd2, 0x33, 0xb1, 0x1c, 0x41, 0x6e, 0x03, 0x88, 0xe4, 0xb0, 0x69, 0x78, 0x46, 0x3d, 0x7b, 0x1e, - 0x3e, 0x04, 0xd4, 0x77, 0x84, 0x88, 0x42, 0x1f, 0x52, 0xc4, 0x3b, 0x50, 0x38, 0xc2, 0xea, 0xf8, - 0x4b, 0x2b, 0x52, 0xe1, 0xf5, 0x1b, 0x00, 0xf2, 0xa9, 0x88, 0x3b, 0xcc, 0x42, 0xa4, 0x65, 0xac, - 0xa8, 0x43, 0xe9, 0xaf, 0x41, 0x69, 0xdb, 0xb4, 0x8e, 0x3b, 0x43, 0xb3, 0xc7, 0x3b, 0xda, 0xdc, - 0xd0, 0xb4, 0x8e, 0xd5, 0x5e, 0xd7, 0x92, 0x7b, 0xf1, 0x3d, 0x9a, 0x7c, 0x01, 0x15, 0x48, 0xfd, - 0xa7, 0x1a, 0x10, 0x4e, 0x54, 0xde, 0x1d, 0x94, 0x65, 0x22, 0x7e, 0x68, 0xe1, 0xf8, 0x51, 0x87, - 0xc2, 0xc0, 0xb1, 0x27, 0xe3, 0x0d, 0x15, 0x57, 0xd4, 0x94, 0xe3, 0x87, 0xf8, 0x52, 0x24, 0x8a, - 0x6f, 0x31, 0xf9, 0xd2, 0xf1, 0xe6, 0xe7, 0xfc, 0x32, 0x07, 0x42, 0x74, 0x26, 0xa3, 0x91, 0xe1, - 0x9c, 0xfe, 0x6f, 0x64, 0xf9, 0xbd, 0x06, 0x57, 0x22, 0x0a, 0x09, 0x02, 0x1f, 0x73, 0x3d, 0x73, - 0xc4, 0x93, 0x17, 0x4a, 0x52, 0xa4, 0x01, 0x21, 0xda, 0x83, 0x89, 0xb2, 0x3d, 0xd4, 0x83, 0x3d, - 0x0b, 0x33, 0xe8, 0xce, 0x1d, 0x1f, 0x22, 0x44, 0x8b, 0x51, 0x49, 0x33, 0x88, 0x42, 0x59, 0xb4, - 0xe0, 0x7c, 0xa4, 0x03, 0x4b, 0x44, 0xa0, 0xef, 0x42, 0x85, 0x1a, 0x1f, 0xbc, 0x6e, 0xba, 0x9e, - 0x3d, 0x70, 0x8c, 0x11, 0x77, 0x92, 0xa3, 0x49, 0xef, 0x98, 0x79, 0x32, 0xea, 0xc8, 0x19, 0x3f, - 0x7b, 0x2f, 0x24, 0x99, 0x98, 0xe8, 0x6f, 0x40, 0x51, 0xf5, 0x30, 0x29, 0x6d, 0xe9, 0x0b, 0xd1, - 0xb6, 0x74, 0x21, 0xda, 0x0a, 0xbf, 0xb5, 0xcd, 0x7b, 0x4f, 0xb3, 0xa7, 0xc2, 0xf1, 0xaf, 0x34, - 0x28, 0x87, 0x44, 0x24, 0x1b, 0x30, 0x37, 0x34, 0x3c, 0x66, 0xf5, 0x4e, 0x0f, 0x1f, 0x28, 0xf1, - 0xa4, 0x57, 0x06, 0x0d, 0x6e, 0x58, 0x76, 0x5a, 0x93, 0xf8, 0xe0, 0x34, 0xff, 0x0f, 0x79, 0x97, - 0x39, 0xa6, 0xbc, 0xde, 0xe1, 0x08, 0xee, 0xb7, 0x5e, 0x12, 0xc0, 0x0f, 0x2e, 0xe2, 0x85, 0x54, - 0xac, 0x9c, 0xe9, 0x7f, 0x8f, 0x7a, 0xb7, 0x74, 0xac, 0x64, 0xc7, 0x7c, 0x81, 0xb5, 0xa6, 0x53, - 0xad, 0x15, 0xc8, 0x97, 0xb9, 0x48, 0xbe, 0x1a, 0x64, 0xc6, 0x77, 0xee, 0xc8, 0x7e, 0x93, 0x0f, - 0x05, 0xe5, 0xb6, 0x0c, 0xc7, 0x7c, 0x28, 0x28, 0x6b, 0xb2, 0xc9, 0xe2, 0x43, 0xa4, 0xdc, 0x5e, - 0x93, 0xdd, 0x14, 0x1f, 0xea, 0xef, 0x40, 0x23, 0xed, 0x9e, 0x48, 0x17, 0xbd, 0x03, 0x25, 0x17, - 0x49, 0x26, 0x4b, 0x86, 0x80, 0x94, 0x75, 0x01, 0x5a, 0xff, 0xb5, 0x06, 0xd5, 0x88, 0x61, 0x23, - 0xa9, 0x38, 0x27, 0x53, 0x71, 0x05, 0x34, 0x0b, 0x95, 0x91, 0xa1, 0x9a, 0xc5, 0x67, 0xf7, 0x51, - 0xdf, 0x1a, 0xd5, 0xee, 0xf3, 0x99, 0x2b, 0x9f, 0xc4, 0x35, 0x97, 0xcf, 0x8e, 0xf0, 0x70, 0x45, - 0xaa, 0x1d, 0xf1, 0x59, 0x5f, 0x1e, 0x4c, 0xeb, 0x63, 0x83, 0x2f, 0x5e, 0xdf, 0x0b, 0xc8, 0x5b, - 0x3d, 0xad, 0x13, 0xc8, 0x1e, 0x9b, 0x56, 0x1f, 0x4b, 0xd7, 0x1c, 0xc5, 0xb1, 0xce, 0xc4, 0x6b, - 0xb1, 0x14, 0x9c, 0x87, 0x59, 0x5e, 0x97, 0x3a, 0xcc, 0x9d, 0x0c, 0xbd, 0x6e, 0x50, 0x29, 0x84, - 0x28, 0xbc, 0xa6, 0x13, 0x33, 0xe9, 0x36, 0x8d, 0xd4, 0x3b, 0x84, 0x08, 0x2a, 0x91, 0x3c, 0x0a, - 0xce, 0x25, 0xbe, 0x72, 0x37, 0x19, 0x1a, 0x47, 0x6c, 0x18, 0x2a, 0xb0, 0x02, 0x02, 0x97, 0x03, - 0x27, 0x07, 0xa1, 0xe2, 0x24, 0x44, 0x21, 0xab, 0x30, 0xed, 0x29, 0xd7, 0x58, 0x3a, 0x5b, 0x86, - 0x7d, 0xdb, 0xb4, 0x3c, 0x3a, 0xed, 0xb9, 0xfc, 0x0e, 0x2d, 0xa4, 0x7f, 0x46, 0x63, 0x98, 0x52, - 0x88, 0x2a, 0xc5, 0x31, 0xf7, 0x8e, 0x13, 0x63, 0x88, 0x1b, 0x6b, 0x94, 0x0f, 0x79, 0xba, 0x67, - 0x0f, 0xd9, 0x68, 0x3c, 0x34, 0x9c, 0xae, 0x7c, 0xde, 0xcb, 0xe0, 0x2f, 0x3e, 0x71, 0x32, 0x79, - 0x0e, 0x6a, 0x8a, 0xa4, 0x9e, 0xfb, 0xa5, 0x73, 0x26, 0xe8, 0x7a, 0x07, 0xae, 0xe0, 0xcb, 0xfd, - 0x96, 0xe5, 0x7a, 0x86, 0xe5, 0x9d, 0x1f, 0x95, 0xfd, 0x28, 0x2b, 0x23, 0x4d, 0x24, 0xca, 0x8a, - 0xbb, 0x89, 0x51, 0xf6, 0x21, 0xcc, 0x47, 0x99, 0x4a, 0x17, 0x6e, 0xfa, 0x77, 0x4a, 0xf8, 0x6f, - 0x10, 0x76, 0x24, 0xb2, 0x83, 0x5f, 0xfd, 0x8b, 0xf5, 0xe8, 0x6f, 0xa2, 0x3f, 0xd1, 0xa0, 0x1a, - 0xe1, 0x45, 0xee, 0x40, 0x1e, 0xcd, 0x96, 0xbc, 0x33, 0xc9, 0xc7, 0x1e, 0xf9, 0x53, 0x8b, 0x5c, - 0x10, 0xad, 0x4d, 0x35, 0x19, 0x0c, 0xc9, 0x12, 0x94, 0xc7, 0x8e, 0x3d, 0x3a, 0x94, 0x5c, 0xc5, - 0xc3, 0x28, 0x70, 0xd2, 0x36, 0x52, 0xf4, 0x3f, 0x64, 0x60, 0x0e, 0x8f, 0x4f, 0x0d, 0x6b, 0xc0, - 0x2e, 0x45, 0xa3, 0xd8, 0xc2, 0x79, 0x6c, 0x2c, 0xcd, 0x88, 0xe3, 0xe8, 0x8f, 0x74, 0x85, 0xf8, - 0x8f, 0x74, 0xa1, 0xb6, 0xb7, 0x78, 0x4e, 0xdb, 0x5b, 0xba, 0xb0, 0xed, 0x85, 0xb4, 0xb6, 0x37, - 0xd4, 0x6c, 0x96, 0xa3, 0xcd, 0x66, 0xb8, 0x21, 0xae, 0xc4, 0x1a, 0x62, 0xd5, 0x88, 0x56, 0xcf, - 0x6c, 0x44, 0x67, 0xbe, 0x54, 0x23, 0x3a, 0xfb, 0xa8, 0x8d, 0x28, 0xe6, 0x77, 0xe9, 0xfa, 0x6e, - 0xbd, 0x26, 0xce, 0xec, 0x13, 0x74, 0x17, 0x48, 0xd8, 0x60, 0xd2, 0x5b, 0x9f, 0x8f, 0x79, 0xeb, - 0x95, 0x20, 0x49, 0x9a, 0x23, 0xf6, 0xd8, 0xae, 0xfa, 0x21, 0x14, 0xdb, 0x52, 0x82, 0xcb, 0x77, - 0xd2, 0xa7, 0xa1, 0xc2, 0xc3, 0x88, 0xeb, 0x19, 0xa3, 0xf1, 0xe1, 0x48, 0x78, 0x69, 0x86, 0x96, - 0x7d, 0xda, 0x8e, 0xab, 0xaf, 0x43, 0xbe, 0x63, 0xf0, 0x8e, 0x23, 0x01, 0x9e, 0x4e, 0x80, 0x83, - 0x5d, 0xb4, 0xd0, 0x2e, 0xfa, 0x27, 0x1a, 0x40, 0xa0, 0x8b, 0xc7, 0x39, 0xc5, 0x2a, 0x14, 0x5c, - 0x14, 0x46, 0x95, 0x03, 0xb3, 0x81, 0xfa, 0x90, 0x2e, 0xf1, 0x0a, 0x75, 0xe1, 0x2d, 0x24, 0xb7, - 0xc3, 0x16, 0xcf, 0xc6, 0x52, 0xb8, 0x52, 0xbc, 0xe4, 0x1a, 0x20, 0x9f, 0x7b, 0x0f, 0x66, 0x63, - 0xcd, 0x0a, 0xa9, 0x40, 0x71, 0x77, 0xef, 0xb0, 0x4d, 0xe9, 0x1e, 0xad, 0x4d, 0x91, 0x2b, 0x30, - 0xbb, 0xb3, 0xfe, 0xee, 0xe1, 0xf6, 0xd6, 0x41, 0xfb, 0xb0, 0x4b, 0xd7, 0xef, 0xb6, 0x3b, 0x35, - 0x8d, 0x13, 0x71, 0x7c, 0xd8, 0xdd, 0xdb, 0x3b, 0xdc, 0x5e, 0xa7, 0xf7, 0xda, 0xb5, 0x69, 0x32, - 0x07, 0xd5, 0xb7, 0x77, 0xdf, 0xdc, 0xdd, 0x7b, 0x67, 0x57, 0x2e, 0xce, 0xb4, 0x7e, 0xa1, 0x41, - 0x9e, 0xb3, 0x67, 0x0e, 0xf9, 0x3e, 0x94, 0xfc, 0x96, 0x87, 0x5c, 0x8d, 0x74, 0x4a, 0xe1, 0x36, - 0xa8, 0xf1, 0x44, 0xe4, 0x93, 0x72, 0x4e, 0x7d, 0x8a, 0xac, 0x43, 0xd9, 0x07, 0x1f, 0xb4, 0xbe, - 0x0a, 0x8b, 0xd6, 0xbf, 0x35, 0xa8, 0x49, 0xbf, 0xbc, 0xc7, 0x2c, 0xe6, 0x18, 0x9e, 0xed, 0x0b, - 0x86, 0xfd, 0x4a, 0x8c, 0x6b, 0xb8, 0xf9, 0x39, 0x5b, 0xb0, 0x2d, 0x80, 0x7b, 0xcc, 0x53, 0xb5, - 0xe2, 0xb5, 0xf4, 0xe4, 0x28, 0x78, 0x5c, 0x3f, 0x23, 0x73, 0x2a, 0x56, 0xf7, 0x00, 0x82, 0x8b, - 0x49, 0x82, 0x5c, 0x9f, 0x08, 0xaf, 0x8d, 0x6b, 0xa9, 0xdf, 0xfc, 0x93, 0xfe, 0x2e, 0x0b, 0x05, - 0xfe, 0xc1, 0x64, 0x0e, 0x79, 0x1d, 0xaa, 0x3f, 0x30, 0xad, 0xbe, 0xff, 0x4b, 0x3d, 0xb9, 0x9a, - 0xf6, 0x0f, 0x02, 0x82, 0x6d, 0xe3, 0xec, 0xff, 0x1d, 0x40, 0x13, 0x54, 0xd4, 0x6f, 0x7f, 0x3d, - 0x66, 0x79, 0xe4, 0x8c, 0x1f, 0x9c, 0x1b, 0x4f, 0x26, 0xe8, 0x3e, 0x8b, 0x36, 0x94, 0x43, 0x3f, - 0x66, 0x87, 0xb5, 0x95, 0xf8, 0x89, 0xfb, 0x3c, 0x36, 0xf7, 0x00, 0x82, 0x27, 0x28, 0x72, 0xce, - 0xa3, 0x77, 0xe3, 0x5a, 0xea, 0x37, 0x9f, 0xd1, 0x9b, 0xea, 0x48, 0xe2, 0x2d, 0xeb, 0x5c, 0x56, - 0x4f, 0xa5, 0xbe, 0xa7, 0x85, 0x98, 0x1d, 0xc0, 0x6c, 0xec, 0x15, 0x87, 0x5c, 0xf4, 0x72, 0xdb, - 0x58, 0x3e, 0x1b, 0xe0, 0xf3, 0xfd, 0x61, 0xe8, 0xc1, 0x4d, 0xbd, 0x0e, 0x5d, 0xcc, 0x59, 0x3f, - 0x0b, 0x10, 0x96, 0xb9, 0xf5, 0xb7, 0x2c, 0xd4, 0x3a, 0x9e, 0xc3, 0x8c, 0x91, 0x69, 0x0d, 0x94, - 0xcb, 0xbc, 0x0a, 0x79, 0x99, 0xf8, 0x1e, 0xd5, 0xc4, 0x6b, 0x1a, 0xbf, 0x0f, 0x97, 0x62, 0x9b, - 0x35, 0x8d, 0xec, 0x5c, 0xa2, 0x75, 0xd6, 0x34, 0xf2, 0xee, 0xd7, 0x63, 0x9f, 0x35, 0x8d, 0xbc, - 0xf7, 0xf5, 0x59, 0x68, 0x4d, 0x23, 0xfb, 0x30, 0x27, 0x63, 0xc5, 0xa5, 0x44, 0x87, 0x35, 0x8d, - 0x1c, 0xc0, 0x95, 0x30, 0x47, 0x59, 0x42, 0x92, 0xeb, 0xd1, 0x75, 0xd1, 0x22, 0x39, 0xa4, 0xe1, - 0xb4, 0x6a, 0x97, 0xf3, 0x6d, 0xfd, 0x51, 0x83, 0x82, 0x8a, 0x84, 0x87, 0xa9, 0xdd, 0xaa, 0x7e, - 0x5e, 0x0f, 0x27, 0x37, 0x7a, 0xe6, 0x5c, 0xcc, 0xa5, 0x47, 0xcb, 0x8d, 0xfa, 0x47, 0x9f, 0x2f, - 0x6a, 0x9f, 0x7c, 0xbe, 0xa8, 0xfd, 0xeb, 0xf3, 0x45, 0xed, 0x97, 0x5f, 0x2c, 0x4e, 0x7d, 0xf2, - 0xc5, 0xe2, 0xd4, 0xa7, 0x5f, 0x2c, 0x4e, 0x1d, 0xe5, 0xf1, 0x5f, 0xd1, 0x5e, 0xfa, 0x6f, 0x00, - 0x00, 0x00, 0xff, 0xff, 0x3d, 0xde, 0x49, 0x19, 0x0b, 0x27, 0x00, 0x00, + // 2918 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x3a, 0xcd, 0x6f, 0x24, 0x47, + 0xf5, 0x6e, 0xcf, 0xf7, 0x9b, 0x19, 0x7b, 0x5c, 0xeb, 0x6c, 0x66, 0x67, 0x37, 0xb6, 0xd3, 0x59, + 0xe5, 0xe7, 0x5f, 0x3e, 0xc6, 0xde, 0xc9, 0xae, 0xc8, 0x26, 0x10, 0x64, 0xaf, 0x87, 0x8d, 0x13, + 0x7f, 0xa5, 0x66, 0xe2, 0x44, 0x28, 0x92, 0xd5, 0x9e, 0xa9, 0x9d, 0x6d, 0x79, 0xa6, 0x7b, 0xd2, + 0xdd, 0xe3, 0xd8, 0x1c, 0x22, 0x40, 0xe2, 0x80, 0xc4, 0x01, 0xa4, 0x70, 0xe0, 0xc8, 0x09, 0x71, + 0xe2, 0x00, 0xff, 0x01, 0x12, 0x0a, 0x42, 0xa0, 0x48, 0x5c, 0x10, 0x87, 0x08, 0x25, 0x07, 0xf8, + 0x13, 0x38, 0x70, 0x40, 0xf5, 0xaa, 0xaa, 0xbf, 0x6d, 0x67, 0x13, 0x47, 0xe4, 0x90, 0xd3, 0x54, + 0xbd, 0x7a, 0xf5, 0xea, 0xd5, 0xfb, 0x7e, 0xd5, 0x03, 0x8f, 0x8f, 0x8f, 0x06, 0x2b, 0x1e, 0x1b, + 0x8d, 0xed, 0xf1, 0xa1, 0xf8, 0x6d, 0x8e, 0x1d, 0xdb, 0xb3, 0x49, 0x41, 0x02, 0x1b, 0x57, 0x7b, + 0xf6, 0x68, 0x64, 0x5b, 0x2b, 0xc7, 0xb7, 0x56, 0xc4, 0x48, 0x20, 0x34, 0x9e, 0x1f, 0x98, 0xde, + 0xc3, 0xc9, 0x61, 0xb3, 0x67, 0x8f, 0x56, 0x06, 0xf6, 0xc0, 0x5e, 0x41, 0xf0, 0xe1, 0xe4, 0x01, + 0xce, 0x70, 0x82, 0x23, 0x89, 0x3e, 0xef, 0x39, 0x46, 0x8f, 0x71, 0x2a, 0x38, 0x10, 0x50, 0xfd, + 0x77, 0x1a, 0xd4, 0xba, 0x7c, 0xbe, 0x7e, 0xba, 0xb9, 0x41, 0xd9, 0xbb, 0x13, 0xe6, 0x7a, 0xa4, + 0x0e, 0x05, 0xc4, 0xd9, 0xdc, 0xa8, 0x6b, 0x4b, 0xda, 0x72, 0x85, 0xaa, 0x29, 0x59, 0x00, 0x38, + 0x1c, 0xda, 0xbd, 0xa3, 0x8e, 0x67, 0x38, 0x5e, 0x7d, 0x7a, 0x49, 0x5b, 0x2e, 0xd1, 0x10, 0x84, + 0x34, 0xa0, 0x88, 0xb3, 0xb6, 0xd5, 0xaf, 0x67, 0x70, 0xd5, 0x9f, 0x93, 0x1b, 0x50, 0x7a, 0x77, + 0xc2, 0x9c, 0xd3, 0x6d, 0xbb, 0xcf, 0xea, 0x39, 0x5c, 0x0c, 0x00, 0xe4, 0x39, 0x98, 0x33, 0x86, + 0x43, 0xfb, 0xbd, 0x3d, 0xc3, 0xf1, 0x4c, 0x63, 0x88, 0x3c, 0xd5, 0xf3, 0x4b, 0xda, 0x72, 0x91, + 0x26, 0x17, 0xf4, 0x7f, 0x69, 0x30, 0x17, 0x62, 0xdb, 0x1d, 0xdb, 0x96, 0xcb, 0xc8, 0x4d, 0xc8, + 0x21, 0xa3, 0xc8, 0x75, 0xb9, 0x35, 0xd3, 0x94, 0x22, 0x6c, 0x22, 0x2a, 0x15, 0x8b, 0xe4, 0x05, + 0x28, 0x8c, 0x98, 0xe7, 0x98, 0x3d, 0x17, 0x2f, 0x50, 0x6e, 0x5d, 0x8b, 0xe2, 0x71, 0x92, 0xdb, + 0x02, 0x81, 0x2a, 0x4c, 0x72, 0x17, 0xf2, 0xae, 0x67, 0x78, 0x13, 0x17, 0xaf, 0x35, 0xd3, 0x7a, + 0x32, 0xb9, 0x47, 0xb1, 0xd1, 0xec, 0x20, 0x22, 0x95, 0x1b, 0xb8, 0x34, 0x47, 0xcc, 0x75, 0x8d, + 0x01, 0xab, 0x67, 0xf1, 0xd6, 0x6a, 0xaa, 0x3f, 0x05, 0x79, 0x81, 0x4b, 0x2a, 0x50, 0xbc, 0xb7, + 0xbb, 0xbd, 0xb7, 0xd5, 0xee, 0xb6, 0x6b, 0x53, 0xa4, 0x0c, 0x85, 0xbd, 0x35, 0xda, 0xdd, 0x5c, + 0xdb, 0xaa, 0x69, 0x3a, 0x09, 0x29, 0x48, 0xb2, 0xa5, 0xff, 0x65, 0x1a, 0xaa, 0x1d, 0x66, 0x38, + 0xbd, 0x87, 0x4a, 0x65, 0x2f, 0x41, 0xb6, 0x6b, 0x0c, 0xdc, 0xba, 0xb6, 0x94, 0x59, 0x2e, 0xb7, + 0x96, 0x7c, 0xee, 0x22, 0x58, 0x4d, 0x8e, 0xd2, 0xb6, 0x3c, 0xe7, 0x74, 0x3d, 0xfb, 0xe1, 0xc7, + 0x8b, 0x53, 0x14, 0xf7, 0x90, 0x9b, 0x50, 0xdd, 0x36, 0xad, 0x8d, 0x89, 0x63, 0x78, 0xa6, 0x6d, + 0x6d, 0x0b, 0xb1, 0x54, 0x69, 0x14, 0x88, 0x58, 0xc6, 0x49, 0x08, 0x2b, 0x23, 0xb1, 0xc2, 0x40, + 0x32, 0x0f, 0xb9, 0x2d, 0x73, 0x64, 0x7a, 0x78, 0xd5, 0x2a, 0x15, 0x13, 0x0e, 0x75, 0xd1, 0x62, + 0x72, 0x02, 0x8a, 0x13, 0x52, 0x83, 0x0c, 0xb3, 0xfa, 0xa8, 0xe4, 0x2a, 0xe5, 0x43, 0x8e, 0xf7, + 0x06, 0xb7, 0x88, 0x7a, 0x11, 0x05, 0x25, 0x26, 0x64, 0x19, 0x66, 0x3b, 0x63, 0xc3, 0x72, 0xf7, + 0x98, 0xc3, 0x7f, 0x3b, 0xcc, 0xab, 0x97, 0x70, 0x4f, 0x1c, 0xdc, 0xf8, 0x06, 0x94, 0xfc, 0x2b, + 0x72, 0xf2, 0x47, 0xec, 0x14, 0x6d, 0xa1, 0x44, 0xf9, 0x90, 0x93, 0x3f, 0x36, 0x86, 0x13, 0x26, + 0x0d, 0x57, 0x4c, 0x5e, 0x9a, 0x7e, 0x51, 0xd3, 0xff, 0x90, 0x01, 0x22, 0x44, 0xb5, 0xce, 0xcd, + 0x55, 0x49, 0xf5, 0x36, 0x94, 0x5c, 0x25, 0x40, 0x69, 0x54, 0x57, 0xd3, 0x45, 0x4b, 0x03, 0x44, + 0xae, 0x70, 0x34, 0xfa, 0xcd, 0x0d, 0x79, 0x90, 0x9a, 0x72, 0x17, 0xc0, 0xab, 0xef, 0x71, 0x63, + 0x10, 0xf2, 0x0b, 0x00, 0x5c, 0xc2, 0x63, 0x63, 0xc0, 0xdc, 0xae, 0x2d, 0x48, 0x4b, 0x19, 0x46, + 0x81, 0xdc, 0xc5, 0x98, 0xd5, 0xb3, 0xfb, 0xa6, 0x35, 0x90, 0x5e, 0xe4, 0xcf, 0x39, 0x05, 0xd3, + 0xea, 0xb3, 0x13, 0x4e, 0xae, 0x63, 0x7e, 0x8f, 0x49, 0xd9, 0x46, 0x81, 0x44, 0x87, 0x8a, 0x67, + 0x7b, 0xc6, 0x90, 0xb2, 0x9e, 0xed, 0xf4, 0xdd, 0x7a, 0x01, 0x91, 0x22, 0x30, 0x8e, 0xd3, 0x37, + 0x3c, 0xa3, 0xad, 0x4e, 0x12, 0x0a, 0x89, 0xc0, 0xf8, 0x3d, 0x8f, 0x99, 0xe3, 0x9a, 0xb6, 0x85, + 0xfa, 0x28, 0x51, 0x35, 0x25, 0x04, 0xb2, 0x2e, 0x3f, 0x1e, 0x96, 0xb4, 0xe5, 0x2c, 0xc5, 0x31, + 0x0f, 0x1d, 0x0f, 0x6c, 0xdb, 0x63, 0x0e, 0x32, 0x56, 0xc6, 0x33, 0x43, 0x10, 0xb2, 0x01, 0xb5, + 0x3e, 0xeb, 0x9b, 0x3d, 0xc3, 0x63, 0xfd, 0x7b, 0xf6, 0x70, 0x32, 0xb2, 0xdc, 0x7a, 0x05, 0xad, + 0xb9, 0xee, 0x8b, 0x7c, 0x23, 0x8a, 0x40, 0x13, 0x3b, 0xf4, 0xdf, 0x6b, 0x30, 0x1b, 0xc3, 0x22, + 0xb7, 0x21, 0xe7, 0xf6, 0xec, 0x31, 0x93, 0xae, 0xbb, 0x70, 0x16, 0xb9, 0x66, 0x87, 0x63, 0x51, + 0x81, 0xcc, 0xef, 0x60, 0x19, 0x23, 0x65, 0x2b, 0x38, 0x26, 0xb7, 0x20, 0xeb, 0x9d, 0x8e, 0x45, + 0x7c, 0x99, 0x69, 0x3d, 0x71, 0x26, 0xa1, 0xee, 0xe9, 0x98, 0x51, 0x44, 0xd5, 0x17, 0x21, 0x87, + 0x64, 0x49, 0x11, 0xb2, 0x9d, 0xbd, 0xb5, 0x9d, 0xda, 0x14, 0x77, 0x76, 0xda, 0xee, 0xec, 0xbe, + 0x49, 0xef, 0xb5, 0xd1, 0xbf, 0xb3, 0x1c, 0x9d, 0x00, 0xe4, 0x3b, 0x5d, 0xba, 0xb9, 0x73, 0xbf, + 0x36, 0xa5, 0x9f, 0xc0, 0x8c, 0xb2, 0x2e, 0x19, 0xda, 0x6e, 0x43, 0x1e, 0xa3, 0x97, 0xf2, 0xf0, + 0x1b, 0xd1, 0xf8, 0x23, 0xb0, 0xb7, 0x99, 0x67, 0x70, 0x0d, 0x51, 0x89, 0x4b, 0x56, 0xe3, 0xa1, + 0x2e, 0x6e, 0xbd, 0xf1, 0x38, 0xa7, 0xff, 0x35, 0x03, 0x57, 0x52, 0x28, 0xc6, 0x53, 0x42, 0x29, + 0x48, 0x09, 0xcb, 0x30, 0xeb, 0xd8, 0xb6, 0xd7, 0x61, 0xce, 0xb1, 0xd9, 0x63, 0x3b, 0x81, 0xc8, + 0xe2, 0x60, 0x6e, 0x9d, 0x1c, 0x84, 0xe4, 0x11, 0x4f, 0x64, 0x88, 0x28, 0x90, 0x27, 0x02, 0x74, + 0x89, 0xae, 0x39, 0x62, 0x6f, 0x5a, 0xe6, 0xc9, 0x8e, 0x61, 0xd9, 0xe8, 0x09, 0x59, 0x9a, 0x5c, + 0xe0, 0x56, 0xd5, 0x0f, 0x42, 0x92, 0x08, 0x2f, 0x21, 0x08, 0x79, 0x06, 0x0a, 0xae, 0x8c, 0x19, + 0x79, 0x94, 0x40, 0x2d, 0x90, 0x80, 0x80, 0x53, 0x85, 0x40, 0x9e, 0x83, 0xa2, 0x1c, 0x72, 0x9f, + 0xc8, 0xa4, 0x22, 0xfb, 0x18, 0x84, 0x42, 0xc5, 0x15, 0x97, 0xe3, 0x31, 0xdc, 0xad, 0x17, 0x71, + 0x47, 0xf3, 0x3c, 0xbd, 0x34, 0x3b, 0xa1, 0x0d, 0x18, 0xa4, 0x68, 0x84, 0x46, 0x63, 0x1f, 0xe6, + 0x12, 0x28, 0x29, 0x71, 0xec, 0xd9, 0x70, 0x1c, 0x2b, 0xb7, 0x1e, 0x0b, 0x29, 0x35, 0xd8, 0x1c, + 0x0e, 0x6f, 0x5b, 0x50, 0x09, 0x2f, 0x61, 0x1c, 0x1a, 0x1b, 0xd6, 0x3d, 0x7b, 0x62, 0x79, 0x48, + 0x98, 0xc7, 0x21, 0x05, 0xe0, 0x32, 0x65, 0x8e, 0x63, 0x3b, 0x62, 0x59, 0x24, 0x83, 0x10, 0x44, + 0xff, 0x91, 0x06, 0x05, 0x29, 0x0f, 0xf2, 0x14, 0xe4, 0xf8, 0x46, 0x65, 0x96, 0xd5, 0x88, 0xc0, + 0xa8, 0x58, 0xc3, 0x0c, 0x68, 0x78, 0xbd, 0x87, 0xac, 0x2f, 0xa9, 0xa9, 0x29, 0x79, 0x19, 0xc0, + 0xf0, 0x3c, 0xc7, 0x3c, 0x9c, 0x78, 0x8c, 0x67, 0x14, 0x4e, 0xe3, 0xba, 0x4f, 0x43, 0x96, 0x3b, + 0xc7, 0xb7, 0x9a, 0xaf, 0xb3, 0xd3, 0x7d, 0x7e, 0x1b, 0x1a, 0x42, 0xe7, 0xbe, 0x9e, 0xe5, 0xc7, + 0x90, 0xab, 0x90, 0xe7, 0x07, 0xf9, 0xb6, 0x29, 0x67, 0xa9, 0x2e, 0x9c, 0x6a, 0x5e, 0x99, 0xb3, + 0xcc, 0xeb, 0x26, 0x54, 0x95, 0x31, 0xf1, 0xb9, 0x2b, 0x0d, 0x31, 0x0a, 0x8c, 0xdd, 0x22, 0xf7, + 0x68, 0xb7, 0xf8, 0x85, 0x9f, 0xcb, 0xa5, 0x33, 0x72, 0x8f, 0x32, 0x2d, 0x77, 0xcc, 0x7a, 0x1e, + 0xeb, 0x77, 0x95, 0xd3, 0x63, 0xbe, 0x8b, 0x81, 0xc9, 0xd3, 0x30, 0xe3, 0x83, 0xd6, 0x4f, 0xf9, + 0xe1, 0xd3, 0xc8, 0x5f, 0x0c, 0x4a, 0x96, 0xa0, 0x8c, 0xd1, 0x1d, 0x93, 0x9b, 0xca, 0xdc, 0x61, + 0x10, 0xbf, 0x68, 0xcf, 0x1e, 0x8d, 0x87, 0xcc, 0x63, 0xfd, 0xd7, 0xec, 0x43, 0x57, 0xe5, 0x9e, + 0x08, 0x90, 0xdb, 0x0d, 0x6e, 0x42, 0x0c, 0xe1, 0x6c, 0x01, 0x80, 0xf3, 0x1d, 0x90, 0x14, 0xec, + 0xe4, 0x91, 0x9d, 0x38, 0x38, 0xc2, 0x37, 0xe6, 0x70, 0xcc, 0x41, 0x61, 0xbe, 0x11, 0xaa, 0xff, + 0x4c, 0xe3, 0x0e, 0xc1, 0x65, 0xc3, 0xd3, 0xba, 0xca, 0xca, 0xf3, 0x2a, 0x9e, 0x0b, 0x6d, 0xcb, + 0x78, 0x3d, 0x0f, 0x39, 0xac, 0x26, 0x55, 0x72, 0xc7, 0x49, 0x50, 0x79, 0x64, 0x52, 0x2a, 0x8f, + 0x6c, 0x50, 0x79, 0x2c, 0xc3, 0xec, 0xc8, 0x38, 0xe1, 0xa7, 0xf0, 0x72, 0x02, 0xa9, 0x8b, 0xfb, + 0xc5, 0xc1, 0xfa, 0xbf, 0x33, 0x70, 0x35, 0xe0, 0x29, 0x52, 0x2e, 0xbc, 0x98, 0x2c, 0x17, 0x1a, + 0xb1, 0x80, 0x1b, 0xba, 0xc7, 0xd7, 0x25, 0xc3, 0x57, 0xa2, 0x64, 0x48, 0x53, 0x7d, 0x35, 0x5d, + 0xf5, 0xff, 0xc9, 0xc0, 0x75, 0x5f, 0x8d, 0xe8, 0xc9, 0x51, 0xfd, 0x7f, 0x2b, 0xa9, 0xff, 0xc5, + 0xa4, 0xfe, 0xc5, 0xc6, 0xaf, 0x8d, 0xe0, 0x2b, 0x65, 0x04, 0x3a, 0x54, 0x84, 0xb6, 0x85, 0x76, + 0xa4, 0x05, 0x44, 0x60, 0x7a, 0x5f, 0xf5, 0x08, 0xc2, 0x89, 0x65, 0x65, 0xd6, 0x80, 0xa2, 0x67, + 0x0c, 0x78, 0xe9, 0x22, 0x92, 0x60, 0x89, 0xfa, 0x73, 0xd2, 0x8a, 0xd7, 0x5f, 0x01, 0x4b, 0xaa, + 0x26, 0x48, 0x54, 0x60, 0xef, 0xc3, 0x7c, 0x70, 0xca, 0x7e, 0xcb, 0x3f, 0xa7, 0x05, 0x79, 0x0c, + 0x74, 0x2a, 0xd5, 0xa6, 0x45, 0x96, 0xfd, 0x96, 0x28, 0x61, 0x25, 0xe6, 0xe7, 0x3a, 0xff, 0xe5, + 0x70, 0xc8, 0x95, 0x04, 0xfd, 0x4c, 0xaa, 0x85, 0x32, 0x29, 0x81, 0xac, 0xc7, 0x5b, 0xce, 0x69, + 0xbc, 0x34, 0x8e, 0xf5, 0x0f, 0xb4, 0x50, 0x70, 0x8c, 0x18, 0x3a, 0x56, 0x90, 0x42, 0x2e, 0x7e, + 0x05, 0x29, 0xa6, 0x17, 0x45, 0xee, 0x6c, 0x4a, 0xe4, 0xce, 0x05, 0x91, 0x3b, 0xae, 0xb9, 0x7c, + 0x8a, 0xe6, 0x8e, 0xe0, 0xf1, 0x04, 0x57, 0x52, 0xac, 0x3c, 0xa5, 0xf9, 0x7b, 0x85, 0xfe, 0x02, + 0xc0, 0xe7, 0x12, 0xe0, 0x6d, 0x28, 0xaa, 0x63, 0x50, 0x46, 0xa7, 0x7e, 0xa6, 0xc2, 0x71, 0x7a, + 0x17, 0xaa, 0x7f, 0x5f, 0x83, 0x6b, 0x31, 0x1e, 0x43, 0xca, 0x5f, 0x89, 0x73, 0x59, 0x6e, 0xcd, + 0x05, 0x95, 0xa6, 0x5c, 0xf9, 0xa2, 0x8c, 0xff, 0x51, 0x83, 0xd9, 0xd8, 0x62, 0x4a, 0x85, 0xa1, + 0xa5, 0x56, 0x18, 0x91, 0xca, 0x60, 0x3a, 0x5e, 0x19, 0x24, 0xaa, 0x8b, 0x4c, 0x5a, 0x75, 0x11, + 0xab, 0x52, 0xb2, 0xc9, 0x2a, 0x25, 0xa5, 0xc2, 0xc8, 0xa5, 0x56, 0x18, 0xfa, 0x0e, 0xe4, 0xb0, + 0x46, 0x22, 0x6d, 0xa8, 0x3a, 0xcc, 0xb5, 0x27, 0x4e, 0x8f, 0x75, 0x42, 0x85, 0x6a, 0x10, 0x97, + 0xc5, 0x6b, 0xd8, 0xf1, 0xad, 0x26, 0x0d, 0xa3, 0xd1, 0xe8, 0x2e, 0x7d, 0x07, 0x2a, 0x7b, 0x13, + 0x37, 0xe8, 0xc7, 0x5e, 0x81, 0x2a, 0x56, 0xc4, 0xee, 0xfa, 0x69, 0x57, 0x3e, 0x39, 0x65, 0x96, + 0x67, 0x42, 0x52, 0xe6, 0xd8, 0x6d, 0x8e, 0x41, 0x99, 0xe1, 0xda, 0x16, 0x8d, 0xa2, 0xeb, 0xbf, + 0xd4, 0xa0, 0xc6, 0x51, 0x90, 0x5b, 0xe5, 0x22, 0xcf, 0xfb, 0x4d, 0x1e, 0xf7, 0xa9, 0xca, 0xfa, + 0x63, 0x1f, 0x7e, 0xbc, 0x38, 0xf5, 0xf7, 0x8f, 0x17, 0xab, 0x7b, 0x0e, 0x33, 0x86, 0x43, 0xbb, + 0x27, 0xb0, 0x55, 0x77, 0xf7, 0x7f, 0x90, 0x31, 0xfb, 0xa2, 0x6a, 0x3e, 0x13, 0x97, 0x63, 0x90, + 0x3b, 0x00, 0x22, 0xcb, 0x6c, 0x18, 0x9e, 0x51, 0xcf, 0x9e, 0x87, 0x1f, 0x42, 0xd4, 0xb7, 0x05, + 0x8b, 0x42, 0x1e, 0x92, 0xc5, 0xbb, 0x50, 0x38, 0xc4, 0xda, 0xfd, 0x33, 0x0b, 0x52, 0xe1, 0xeb, + 0x37, 0x01, 0xe4, 0x43, 0x16, 0x37, 0x98, 0xab, 0x91, 0x86, 0xb6, 0xa2, 0x2e, 0xa5, 0xbf, 0x02, + 0xa5, 0x2d, 0xd3, 0x3a, 0xea, 0x0c, 0xcd, 0x1e, 0xef, 0xb7, 0x73, 0x43, 0xd3, 0x3a, 0x52, 0x67, + 0x5d, 0x4f, 0x9e, 0xc5, 0xcf, 0x68, 0xf2, 0x0d, 0x54, 0x60, 0xea, 0x3f, 0xd4, 0x80, 0x70, 0xa0, + 0xb2, 0xee, 0xa0, 0x66, 0x14, 0x31, 0x46, 0x0b, 0xc7, 0x98, 0x3a, 0x14, 0x06, 0x8e, 0x3d, 0x19, + 0xaf, 0xab, 0xd8, 0xa3, 0xa6, 0x1c, 0x7f, 0x88, 0xef, 0x58, 0xa2, 0x35, 0x10, 0x93, 0xcf, 0x1a, + 0x93, 0xf4, 0x1f, 0x73, 0x67, 0x0e, 0x98, 0xe8, 0x4c, 0x46, 0x23, 0xc3, 0x39, 0xfd, 0xdf, 0xf0, + 0xf2, 0x6b, 0x0d, 0xae, 0x44, 0x04, 0x12, 0x04, 0x3e, 0xe6, 0x7a, 0xe6, 0x88, 0x67, 0x41, 0xe4, + 0xa4, 0x48, 0x03, 0x40, 0xb4, 0x43, 0x14, 0x4d, 0x45, 0xa8, 0x43, 0x7c, 0x1a, 0x66, 0xd0, 0x9c, + 0x3b, 0x3e, 0x8a, 0x60, 0x2d, 0x06, 0x25, 0xcd, 0x20, 0x0a, 0x65, 0x51, 0x83, 0xf3, 0x91, 0xfe, + 0x30, 0x11, 0x81, 0xbe, 0x09, 0x15, 0x6a, 0xbc, 0xf7, 0xaa, 0xe9, 0x7a, 0xf6, 0xc0, 0x31, 0x46, + 0xdc, 0x48, 0x0e, 0x27, 0xbd, 0x23, 0xe6, 0xc9, 0xa8, 0x23, 0x67, 0xfc, 0xee, 0xbd, 0x10, 0x67, + 0x62, 0xa2, 0xbf, 0x06, 0x45, 0xd5, 0x61, 0xa5, 0x34, 0xcd, 0xcf, 0x45, 0x9b, 0xe6, 0xab, 0xd1, + 0x46, 0xfd, 0x8d, 0x2d, 0xde, 0x19, 0x9b, 0x3d, 0x15, 0x8e, 0x3f, 0xd0, 0xa0, 0x1c, 0x62, 0x91, + 0xac, 0xc3, 0xdc, 0xd0, 0xf0, 0x98, 0xd5, 0x3b, 0x3d, 0x78, 0xa8, 0xd8, 0x93, 0x56, 0x19, 0xb4, + 0xdf, 0x61, 0xde, 0x69, 0x4d, 0xe2, 0x07, 0xb7, 0xf9, 0x7f, 0xc8, 0xbb, 0xcc, 0x31, 0xa5, 0x7b, + 0x87, 0x23, 0xb8, 0xdf, 0x18, 0x4a, 0x04, 0x7e, 0x71, 0x11, 0x2f, 0xa4, 0x60, 0xe5, 0x4c, 0xff, + 0x73, 0xd4, 0xba, 0xa5, 0x61, 0x25, 0xfb, 0xf9, 0x0b, 0xb4, 0x35, 0x9d, 0xaa, 0xad, 0x80, 0xbf, + 0xcc, 0x45, 0xfc, 0xd5, 0x20, 0x33, 0xbe, 0x7b, 0x57, 0x76, 0xc3, 0x7c, 0x28, 0x20, 0x77, 0x64, + 0x38, 0xe6, 0x43, 0x01, 0x59, 0x95, 0x2d, 0x20, 0x1f, 0x22, 0xe4, 0xce, 0xaa, 0xec, 0xf5, 0xf8, + 0x50, 0x7f, 0x0b, 0x1a, 0x69, 0x7e, 0x22, 0x4d, 0xf4, 0x2e, 0x94, 0x5c, 0x04, 0x99, 0x2c, 0x19, + 0x02, 0x52, 0xf6, 0x05, 0xd8, 0xfa, 0xcf, 0x35, 0xa8, 0x46, 0x14, 0x1b, 0x49, 0xc5, 0x39, 0x99, + 0x8a, 0x2b, 0xa0, 0x59, 0x28, 0x8c, 0x0c, 0xd5, 0x2c, 0x3e, 0x7b, 0x80, 0xf2, 0xd6, 0xa8, 0xf6, + 0x80, 0xcf, 0x5c, 0xf9, 0x60, 0xaf, 0xb9, 0x7c, 0x76, 0x88, 0x97, 0x2b, 0x52, 0xed, 0x90, 0xcf, + 0xfa, 0xf2, 0x62, 0x5a, 0x1f, 0x9f, 0x1f, 0xc4, 0xb7, 0x81, 0x02, 0xd2, 0x56, 0x0f, 0xff, 0x04, + 0xb2, 0x47, 0xa6, 0xd5, 0xc7, 0x1a, 0x38, 0x47, 0x71, 0xac, 0x33, 0xf1, 0x96, 0x2d, 0x19, 0xe7, + 0x61, 0x96, 0x17, 0xb8, 0x0e, 0x73, 0x27, 0x43, 0xaf, 0x1b, 0x54, 0x0a, 0x21, 0x08, 0x2f, 0xfc, + 0xc4, 0x4c, 0x9a, 0x4d, 0x23, 0xd5, 0x87, 0x10, 0x83, 0x4a, 0x4c, 0x1e, 0x05, 0xe7, 0x12, 0xab, + 0xdc, 0x4c, 0x86, 0xc6, 0x21, 0x1b, 0x86, 0x8a, 0xb0, 0x00, 0xc0, 0xf9, 0xc0, 0xc9, 0x7e, 0xa8, + 0x38, 0x09, 0x41, 0xc8, 0x0a, 0x4c, 0x7b, 0xca, 0x34, 0x16, 0xcf, 0xe6, 0x61, 0xcf, 0x36, 0x2d, + 0x8f, 0x4e, 0x7b, 0xb2, 0x18, 0x4c, 0x5d, 0x46, 0x65, 0x98, 0x92, 0x89, 0x2a, 0xc5, 0x31, 0xb7, + 0x8e, 0x63, 0x63, 0x88, 0x07, 0x6b, 0x94, 0x0f, 0x79, 0xba, 0x67, 0x27, 0x6c, 0x34, 0x1e, 0x1a, + 0x4e, 0x57, 0x3e, 0x3e, 0x66, 0xf0, 0x7b, 0x54, 0x1c, 0x4c, 0x9e, 0x81, 0x9a, 0x02, 0xa9, 0x8f, + 0x11, 0xd2, 0x38, 0x13, 0x70, 0xbd, 0x03, 0x57, 0xf0, 0xbb, 0xc2, 0xa6, 0xe5, 0x7a, 0x86, 0xe5, + 0x9d, 0x1f, 0x95, 0xfd, 0x28, 0x2b, 0x23, 0x4d, 0x24, 0xca, 0x0a, 0xdf, 0xc4, 0x28, 0x7b, 0x02, + 0xf3, 0x51, 0xa2, 0xd2, 0x84, 0x9b, 0xbe, 0x4f, 0x09, 0xfb, 0x0d, 0xc2, 0x8e, 0xc4, 0xec, 0xe0, + 0xaa, 0xef, 0x58, 0x8f, 0xfe, 0x62, 0xfb, 0x03, 0x0d, 0xaa, 0x11, 0x5a, 0xe4, 0x2e, 0xe4, 0x51, + 0x6d, 0x49, 0x9f, 0x49, 0x3e, 0x45, 0xc9, 0x0f, 0x41, 0x72, 0x43, 0xb4, 0x36, 0xd5, 0x64, 0x30, + 0x24, 0x8b, 0x50, 0x1e, 0x3b, 0xf6, 0xe8, 0x40, 0x52, 0x15, 0xcf, 0xb6, 0xc0, 0x41, 0x5b, 0x08, + 0xd1, 0x7f, 0x93, 0x81, 0x39, 0xbc, 0x3e, 0x35, 0xac, 0x01, 0xbb, 0x14, 0x89, 0x62, 0x2f, 0xe8, + 0xb1, 0xb1, 0x54, 0x23, 0x8e, 0xa3, 0x9f, 0x10, 0x0b, 0xf1, 0x4f, 0x88, 0xa1, 0xfe, 0xb9, 0x78, + 0x4e, 0xff, 0x5c, 0xba, 0xb0, 0x7f, 0x86, 0xb4, 0xfe, 0x39, 0xd4, 0xb5, 0x96, 0xa3, 0x5d, 0x6b, + 0xb8, 0xb3, 0xae, 0xc4, 0x3a, 0x6b, 0xd5, 0xd1, 0x56, 0xcf, 0xec, 0x68, 0x67, 0x3e, 0x53, 0x47, + 0x3b, 0xfb, 0xc8, 0x1d, 0x2d, 0xcf, 0xef, 0xd2, 0xf4, 0xdd, 0x7a, 0x4d, 0xdc, 0xd9, 0x07, 0xe8, + 0x2e, 0x90, 0xb0, 0xc2, 0xa4, 0xb5, 0x3e, 0x1b, 0xb3, 0xd6, 0x2b, 0x41, 0x92, 0x34, 0x47, 0xec, + 0x0b, 0x9b, 0xea, 0xfb, 0x50, 0x6c, 0x4b, 0x0e, 0x2e, 0xdf, 0x48, 0x9f, 0x84, 0x0a, 0x0f, 0x23, + 0xae, 0x67, 0x8c, 0xc6, 0x07, 0x23, 0x61, 0xa5, 0x19, 0x5a, 0xf6, 0x61, 0xdb, 0xae, 0xbe, 0x06, + 0xf9, 0x8e, 0xc1, 0x3b, 0x8e, 0x04, 0xf2, 0x74, 0x02, 0x39, 0x38, 0x45, 0x0b, 0x9d, 0xa2, 0x7f, + 0xa4, 0x01, 0x04, 0xb2, 0xf8, 0x22, 0xb7, 0x58, 0x81, 0x82, 0x8b, 0xcc, 0xa8, 0x72, 0x60, 0x36, + 0x10, 0x1f, 0xc2, 0x25, 0xbe, 0xc2, 0xba, 0xd0, 0x0b, 0xc9, 0x9d, 0xb0, 0xc6, 0xb3, 0xb1, 0x14, + 0xae, 0x04, 0x2f, 0xa9, 0x06, 0x98, 0xcf, 0xbc, 0x03, 0xb3, 0xb1, 0x66, 0x85, 0x54, 0xa0, 0xb8, + 0xb3, 0x7b, 0xd0, 0xa6, 0x74, 0x97, 0xd6, 0xa6, 0xc8, 0x15, 0x98, 0xdd, 0x5e, 0x7b, 0xfb, 0x60, + 0x6b, 0x73, 0xbf, 0x7d, 0xd0, 0xa5, 0x6b, 0xf7, 0xda, 0x9d, 0x9a, 0xc6, 0x81, 0x38, 0x3e, 0xe8, + 0xee, 0xee, 0x1e, 0x6c, 0xad, 0xd1, 0xfb, 0xed, 0xda, 0x34, 0x99, 0x83, 0xea, 0x9b, 0x3b, 0xaf, + 0xef, 0xec, 0xbe, 0xb5, 0x23, 0x37, 0x67, 0x5a, 0x3f, 0xd1, 0x20, 0xcf, 0xc9, 0x33, 0x87, 0x7c, + 0x1b, 0x4a, 0x7e, 0xcb, 0x43, 0xae, 0x45, 0x3a, 0xa5, 0x70, 0x1b, 0xd4, 0x78, 0x2c, 0xb2, 0xa4, + 0x8c, 0x53, 0x9f, 0x22, 0x6b, 0x50, 0xf6, 0x91, 0xf7, 0x5b, 0x9f, 0x87, 0x44, 0xeb, 0x9f, 0x1a, + 0xd4, 0xa4, 0x5d, 0xde, 0x67, 0x16, 0x73, 0x0c, 0xcf, 0xf6, 0x19, 0xc3, 0x7e, 0x25, 0x46, 0x35, + 0xdc, 0xfc, 0x9c, 0xcd, 0xd8, 0x26, 0xc0, 0x7d, 0xe6, 0xa9, 0x5a, 0xf1, 0x7a, 0x7a, 0x72, 0x14, + 0x34, 0x6e, 0x9c, 0x91, 0x39, 0x15, 0xa9, 0xfb, 0x00, 0x81, 0x63, 0x92, 0x20, 0xd7, 0x27, 0xc2, + 0x6b, 0xe3, 0x7a, 0xea, 0x9a, 0x7f, 0xd3, 0x5f, 0x65, 0xa1, 0xc0, 0x17, 0x4c, 0xe6, 0x90, 0x57, + 0xa1, 0xfa, 0x1d, 0xd3, 0xea, 0xfb, 0xff, 0x23, 0x20, 0xd7, 0xd2, 0xfe, 0xbe, 0x20, 0xc8, 0x36, + 0xce, 0xfe, 0x67, 0x03, 0xaa, 0xa0, 0xa2, 0xbe, 0x4c, 0xf6, 0x98, 0xe5, 0x91, 0x33, 0x3e, 0x87, + 0x37, 0x1e, 0x4f, 0xc0, 0x7d, 0x12, 0x6d, 0x28, 0x87, 0x3e, 0xb5, 0x87, 0xa5, 0x95, 0xf8, 0x00, + 0x7f, 0x1e, 0x99, 0xfb, 0x00, 0xc1, 0x3b, 0x15, 0x39, 0xe7, 0x9d, 0xbd, 0x71, 0x3d, 0x75, 0xcd, + 0x27, 0xf4, 0xba, 0xba, 0x92, 0x78, 0xf0, 0x3a, 0x97, 0xd4, 0x13, 0xa9, 0x8f, 0x6e, 0x21, 0x62, + 0xfb, 0x30, 0x1b, 0x7b, 0xc5, 0x21, 0x17, 0x3d, 0x01, 0x37, 0x96, 0xce, 0x46, 0xf0, 0xe9, 0x7e, + 0x37, 0xf4, 0x2a, 0xa7, 0x5e, 0x87, 0x2e, 0xa6, 0xac, 0x9f, 0x85, 0x10, 0xe6, 0xb9, 0xf5, 0xa7, + 0x2c, 0xd4, 0x3a, 0x9e, 0xc3, 0x8c, 0x91, 0x69, 0x0d, 0x94, 0xc9, 0xbc, 0x0c, 0x79, 0x99, 0xf8, + 0x1e, 0x55, 0xc5, 0xab, 0x1a, 0xf7, 0x87, 0x4b, 0xd1, 0xcd, 0xaa, 0x46, 0xb6, 0x2f, 0x51, 0x3b, + 0xab, 0x1a, 0x79, 0xfb, 0xcb, 0xd1, 0xcf, 0xaa, 0x46, 0xde, 0xf9, 0xf2, 0x34, 0xb4, 0xaa, 0x91, + 0x3d, 0x98, 0x93, 0xb1, 0xe2, 0x52, 0xa2, 0xc3, 0xaa, 0x46, 0xf6, 0xe1, 0x4a, 0x98, 0xa2, 0x2c, + 0x21, 0xc9, 0x8d, 0xe8, 0xbe, 0x68, 0x91, 0x1c, 0x92, 0x70, 0x5a, 0xb5, 0xcb, 0xe9, 0xb6, 0x7e, + 0xab, 0x41, 0x41, 0x45, 0xc2, 0x83, 0xd4, 0x6e, 0x55, 0x3f, 0xaf, 0x87, 0x93, 0x07, 0x3d, 0x75, + 0x2e, 0xce, 0xa5, 0x47, 0xcb, 0xf5, 0xfa, 0x87, 0x9f, 0x2c, 0x68, 0x1f, 0x7d, 0xb2, 0xa0, 0xfd, + 0xe3, 0x93, 0x05, 0xed, 0xa7, 0x9f, 0x2e, 0x4c, 0x7d, 0xf4, 0xe9, 0xc2, 0xd4, 0xdf, 0x3e, 0x5d, + 0x98, 0x3a, 0xcc, 0xe3, 0x1f, 0xe5, 0x5e, 0xf8, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x68, 0x6b, + 0x4b, 0x9d, 0xa9, 0x27, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -5606,6 +5641,11 @@ func (m *SearchTagsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.MaxTagsPerScope != 0 { + i = encodeVarintTempo(dAtA, i, uint64(m.MaxTagsPerScope)) + i-- + dAtA[i] = 0x28 + } if m.End != 0 { i = encodeVarintTempo(dAtA, i, uint64(m.End)) i-- @@ -5653,6 +5693,11 @@ func (m *SearchTagsBlockRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) _ = i var l int _ = l + if m.MaxTagsPerScope != 0 { + i = encodeVarintTempo(dAtA, i, uint64(m.MaxTagsPerScope)) + i-- + dAtA[i] = 0x68 + } if len(m.DedicatedColumns) > 0 { for iNdEx := len(m.DedicatedColumns) - 1; iNdEx >= 0; iNdEx-- { { @@ -5760,6 +5805,11 @@ func (m *SearchTagValuesBlockRequest) MarshalToSizedBuffer(dAtA []byte) (int, er _ = i var l int _ = l + if m.MaxTagValues != 0 { + i = encodeVarintTempo(dAtA, i, uint64(m.MaxTagValues)) + i-- + dAtA[i] = 0x68 + } if len(m.DedicatedColumns) > 0 { for iNdEx := len(m.DedicatedColumns) - 1; iNdEx >= 0; iNdEx-- { { @@ -5999,6 +6049,11 @@ func (m *SearchTagValuesRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) _ = i var l int _ = l + if m.MaxTagValues != 0 { + i = encodeVarintTempo(dAtA, i, uint64(m.MaxTagValues)) + i-- + dAtA[i] = 0x30 + } if m.End != 0 { i = encodeVarintTempo(dAtA, i, uint64(m.End)) i-- @@ -7887,6 +7942,9 @@ func (m *SearchTagsRequest) Size() (n int) { if m.End != 0 { n += 1 + sovTempo(uint64(m.End)) } + if m.MaxTagsPerScope != 0 { + n += 1 + sovTempo(uint64(m.MaxTagsPerScope)) + } return n } @@ -7940,6 +7998,9 @@ func (m *SearchTagsBlockRequest) Size() (n int) { n += 1 + l + sovTempo(uint64(l)) } } + if m.MaxTagsPerScope != 0 { + n += 1 + sovTempo(uint64(m.MaxTagsPerScope)) + } return n } @@ -7993,6 +8054,9 @@ func (m *SearchTagValuesBlockRequest) Size() (n int) { n += 1 + l + sovTempo(uint64(l)) } } + if m.MaxTagValues != 0 { + n += 1 + sovTempo(uint64(m.MaxTagValues)) + } return n } @@ -8073,6 +8137,9 @@ func (m *SearchTagValuesRequest) Size() (n int) { if m.End != 0 { n += 1 + sovTempo(uint64(m.End)) } + if m.MaxTagValues != 0 { + n += 1 + sovTempo(uint64(m.MaxTagValues)) + } return n } @@ -11203,6 +11270,25 @@ func (m *SearchTagsRequest) Unmarshal(dAtA []byte) error { break } } + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MaxTagsPerScope", wireType) + } + m.MaxTagsPerScope = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTempo + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MaxTagsPerScope |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipTempo(dAtA[iNdEx:]) @@ -11565,6 +11651,25 @@ func (m *SearchTagsBlockRequest) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 13: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MaxTagsPerScope", wireType) + } + m.MaxTagsPerScope = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTempo + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MaxTagsPerScope |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipTempo(dAtA[iNdEx:]) @@ -11927,6 +12032,25 @@ func (m *SearchTagValuesBlockRequest) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 13: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MaxTagValues", wireType) + } + m.MaxTagValues = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTempo + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MaxTagValues |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipTempo(dAtA[iNdEx:]) @@ -12431,6 +12555,25 @@ func (m *SearchTagValuesRequest) Unmarshal(dAtA []byte) error { break } } + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MaxTagValues", wireType) + } + m.MaxTagValues = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTempo + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MaxTagValues |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipTempo(dAtA[iNdEx:]) diff --git a/pkg/tempopb/tempo.proto b/pkg/tempopb/tempo.proto index 1ef043877ca..82207753e18 100644 --- a/pkg/tempopb/tempo.proto +++ b/pkg/tempopb/tempo.proto @@ -163,6 +163,7 @@ message SearchTagsRequest { string query = 2; uint32 start = 3; uint32 end = 4; + uint32 maxTagsPerScope = 5; } // SearchTagsBlockRequest takes SearchTagsRequest parameters as well as all information necessary @@ -180,6 +181,7 @@ message SearchTagsBlockRequest { uint64 size = 10; // total size of data file uint32 footerSize = 11; // size of file footer (parquet) repeated DedicatedColumn dedicatedColumns = 12; + uint32 maxTagsPerScope = 13; // Limit of tags per scope } message SearchTagValuesBlockRequest { @@ -195,6 +197,7 @@ message SearchTagValuesBlockRequest { uint64 size = 10; // total size of data file uint32 footerSize = 11; // size of file footer (parquet) repeated DedicatedColumn dedicatedColumns = 12; + uint32 maxTagValues = 13; // Limit of tags values } @@ -218,6 +221,7 @@ message SearchTagValuesRequest { string query = 2; // TraceQL query uint32 start = 4; uint32 end = 5; + uint32 maxTagValues = 6; } message SearchTagValuesResponse { diff --git a/pkg/traceql/engine_test.go b/pkg/traceql/engine_test.go index f7f0599b4e4..0614e055d38 100644 --- a/pkg/traceql/engine_test.go +++ b/pkg/traceql/engine_test.go @@ -668,7 +668,7 @@ func TestExecuteTagValues(t *testing.T) { }, } { t.Run(tc.name, func(t *testing.T) { - distinctValues := collector.NewDistinctValue[tempopb.TagValue](100_000, func(v tempopb.TagValue) int { return len(v.Type) + len(v.Value) }) + distinctValues := collector.NewDistinctValue[tempopb.TagValue](100_000, 0, func(v tempopb.TagValue) int { return len(v.Type) + len(v.Value) }) // Ugly hack to make the mock fetcher work with a bad query fetcherQuery := tc.query diff --git a/tempodb/encoding/common/interfaces.go b/tempodb/encoding/common/interfaces.go index ef3cd6b9969..0454bcaf2e8 100644 --- a/tempodb/encoding/common/interfaces.go +++ b/tempodb/encoding/common/interfaces.go @@ -43,6 +43,7 @@ type SearchOptions struct { ReadBufferCount int ReadBufferSize int BlockReplicationFactor int // Only blocks with this replication factor will be searched. Set to 1 to search generator blocks (RF=1). + Limit uint32 } // DefaultSearchOptions is used in a lot of places such as local ingester searches. It is important diff --git a/tempodb/encoding/vparquet2/block_search_tags_test.go b/tempodb/encoding/vparquet2/block_search_tags_test.go index a32e1bcb0f5..a7037ec06ce 100644 --- a/tempodb/encoding/vparquet2/block_search_tags_test.go +++ b/tempodb/encoding/vparquet2/block_search_tags_test.go @@ -177,7 +177,7 @@ func BenchmarkBackendBlockSearchTags(b *testing.B) { block := newBackendBlock(meta, rr) opts := common.DefaultSearchOptions() - d := collector.NewDistinctString(1_000_000) + d := collector.NewDistinctString(1_000_000, 0) mc := collector.NewMetricsCollector() b.ResetTimer() @@ -212,7 +212,7 @@ func BenchmarkBackendBlockSearchTagValues(b *testing.B) { for _, tc := range testCases { b.Run(tc, func(b *testing.B) { - d := collector.NewDistinctString(1_000_000) + d := collector.NewDistinctString(1_000_000, 0) mc := collector.NewMetricsCollector() b.ResetTimer() for i := 0; i < b.N; i++ { diff --git a/tempodb/encoding/vparquet3/block_autocomplete_test.go b/tempodb/encoding/vparquet3/block_autocomplete_test.go index b4964c45f11..ed5332fe2a0 100644 --- a/tempodb/encoding/vparquet3/block_autocomplete_test.go +++ b/tempodb/encoding/vparquet3/block_autocomplete_test.go @@ -211,7 +211,7 @@ func TestFetchTagNames(t *testing.T) { } t.Run(fmt.Sprintf("query: %s %s-%s", tc.name, tc.query, scope), func(t *testing.T) { - distinctAttrNames := collector.NewScopedDistinctString(0) + distinctAttrNames := collector.NewScopedDistinctString(0, 0) req, err := traceql.ExtractFetchSpansRequest(tc.query) require.NoError(t, err) @@ -494,7 +494,7 @@ func TestFetchTagValues(t *testing.T) { for _, tc := range testCases { t.Run(fmt.Sprintf("tag: %s, query: %s", tc.tag, tc.query), func(t *testing.T) { - distinctValues := collector.NewDistinctValue[tempopb.TagValue](1_000_000, func(v tempopb.TagValue) int { return len(v.Type) + len(v.Value) }) + distinctValues := collector.NewDistinctValue[tempopb.TagValue](1_000_000, 0, func(v tempopb.TagValue) int { return len(v.Type) + len(v.Value) }) req, err := traceql.ExtractFetchSpansRequest(tc.query) require.NoError(t, err) @@ -596,7 +596,7 @@ func BenchmarkFetchTagValues(b *testing.B) { for _, tc := range testCases { b.Run(fmt.Sprintf("tag: %s, query: %s", tc.tag, tc.query), func(b *testing.B) { - distinctValues := collector.NewDistinctValue[tempopb.TagValue](1_000_000, func(v tempopb.TagValue) int { return len(v.Type) + len(v.Value) }) + distinctValues := collector.NewDistinctValue[tempopb.TagValue](1_000_000, 0, func(v tempopb.TagValue) int { return len(v.Type) + len(v.Value) }) req, err := traceql.ExtractFetchSpansRequest(tc.query) require.NoError(b, err) @@ -678,7 +678,7 @@ func BenchmarkFetchTags(b *testing.B) { for _, tc := range testCases { for _, scope := range []traceql.AttributeScope{traceql.AttributeScopeSpan, traceql.AttributeScopeResource, traceql.AttributeScopeNone} { b.Run(fmt.Sprintf("query: %s %s", tc.query, scope), func(b *testing.B) { - distinctStrings := collector.NewScopedDistinctString(1_000_000) + distinctStrings := collector.NewScopedDistinctString(1_000_000, 0) req, err := traceql.ExtractFetchSpansRequest(tc.query) require.NoError(b, err) diff --git a/tempodb/encoding/vparquet3/block_search_tags_test.go b/tempodb/encoding/vparquet3/block_search_tags_test.go index 31e1ae2a704..8f2967bd33b 100644 --- a/tempodb/encoding/vparquet3/block_search_tags_test.go +++ b/tempodb/encoding/vparquet3/block_search_tags_test.go @@ -203,7 +203,7 @@ func BenchmarkBackendBlockSearchTags(b *testing.B) { block := newBackendBlock(meta, rr) opts := common.DefaultSearchOptions() - d := collector.NewDistinctString(1_000_000) + d := collector.NewDistinctString(1_000_000, 0) mc := collector.NewMetricsCollector() b.ResetTimer() @@ -238,7 +238,7 @@ func BenchmarkBackendBlockSearchTagValues(b *testing.B) { for _, tc := range testCases { b.Run(tc, func(b *testing.B) { - d := collector.NewDistinctString(1_000_000) + d := collector.NewDistinctString(1_000_000, 0) mc := collector.NewMetricsCollector() b.ResetTimer() diff --git a/tempodb/encoding/vparquet4/block_autocomplete_test.go b/tempodb/encoding/vparquet4/block_autocomplete_test.go index d0f87aa82af..e9706d41637 100644 --- a/tempodb/encoding/vparquet4/block_autocomplete_test.go +++ b/tempodb/encoding/vparquet4/block_autocomplete_test.go @@ -318,7 +318,7 @@ func TestFetchTagNames(t *testing.T) { } t.Run(fmt.Sprintf("query: %s %s-%s", tc.name, tc.query, scope), func(t *testing.T) { - distinctAttrNames := collector.NewScopedDistinctString(0) + distinctAttrNames := collector.NewScopedDistinctString(0, 0) req, err := traceql.ExtractFetchSpansRequest(tc.query) require.NoError(t, err) @@ -617,7 +617,7 @@ func TestFetchTagValues(t *testing.T) { for _, tc := range testCases { t.Run(fmt.Sprintf("tag: %s, query: %s", tc.tag, tc.query), func(t *testing.T) { - distinctValues := collector.NewDistinctValue[tempopb.TagValue](1_000_000, func(v tempopb.TagValue) int { return len(v.Type) + len(v.Value) }) + distinctValues := collector.NewDistinctValue[tempopb.TagValue](1_000_000, 0, func(v tempopb.TagValue) int { return len(v.Type) + len(v.Value) }) req, err := traceql.ExtractFetchSpansRequest(tc.query) require.NoError(t, err) @@ -720,7 +720,7 @@ func BenchmarkFetchTagValues(b *testing.B) { for _, tc := range testCases { b.Run(fmt.Sprintf("tag: %s, query: %s", tc.tag, tc.query), func(b *testing.B) { - distinctValues := collector.NewDistinctValue[tempopb.TagValue](1_000_000, func(v tempopb.TagValue) int { return len(v.Type) + len(v.Value) }) + distinctValues := collector.NewDistinctValue[tempopb.TagValue](1_000_000, 0, func(v tempopb.TagValue) int { return len(v.Type) + len(v.Value) }) req, err := traceql.ExtractFetchSpansRequest(tc.query) require.NoError(b, err) @@ -802,7 +802,7 @@ func BenchmarkFetchTags(b *testing.B) { for _, tc := range testCases { for _, scope := range []traceql.AttributeScope{traceql.AttributeScopeSpan, traceql.AttributeScopeResource, traceql.AttributeScopeNone} { b.Run(fmt.Sprintf("query: %s %s", tc.query, scope), func(b *testing.B) { - distinctStrings := collector.NewScopedDistinctString(1_000_000) + distinctStrings := collector.NewScopedDistinctString(1_000_000, 0) req, err := traceql.ExtractFetchSpansRequest(tc.query) require.NoError(b, err) diff --git a/tempodb/encoding/vparquet4/block_search_tags_test.go b/tempodb/encoding/vparquet4/block_search_tags_test.go index 2c734da42b1..2a319667322 100644 --- a/tempodb/encoding/vparquet4/block_search_tags_test.go +++ b/tempodb/encoding/vparquet4/block_search_tags_test.go @@ -203,7 +203,7 @@ func BenchmarkBackendBlockSearchTags(b *testing.B) { block := newBackendBlock(meta, rr) opts := common.DefaultSearchOptions() - d := collector.NewDistinctString(1_000_000) + d := collector.NewDistinctString(1_000_000, 0) mc := collector.NewMetricsCollector() b.ResetTimer() @@ -238,7 +238,7 @@ func BenchmarkBackendBlockSearchTagValues(b *testing.B) { for _, tc := range testCases { b.Run(tc, func(b *testing.B) { - d := collector.NewDistinctString(1_000_000) + d := collector.NewDistinctString(1_000_000, 0) mc := collector.NewMetricsCollector() b.ResetTimer() for i := 0; i < b.N; i++ { diff --git a/tempodb/tempodb.go b/tempodb/tempodb.go index 86ceebed9bc..a562d953e58 100644 --- a/tempodb/tempodb.go +++ b/tempodb/tempodb.go @@ -381,7 +381,7 @@ func (rw *readerWriter) SearchTags(ctx context.Context, meta *backend.BlockMeta, return nil, err } - distinctValues := collector.NewScopedDistinctString(0) // todo: propagate limit? + distinctValues := collector.NewScopedDistinctString(0, opts.Limit) // todo: propagate limit? mc := collector.NewMetricsCollector() rw.cfg.Search.ApplyToOptions(&opts) @@ -414,7 +414,7 @@ func (rw *readerWriter) SearchTagValues(ctx context.Context, meta *backend.Block return &tempopb.SearchTagValuesResponse{}, err } - dv := collector.NewDistinctString(0) + dv := collector.NewDistinctString(0, opts.Limit) mc := collector.NewMetricsCollector() rw.cfg.Search.ApplyToOptions(&opts) err = block.SearchTagValues(ctx, tag, dv.Collect, mc.Add, opts) @@ -436,7 +436,7 @@ func (rw *readerWriter) SearchTagValuesV2(ctx context.Context, meta *backend.Blo return nil, err } - dv := collector.NewDistinctValue[tempopb.TagValue](0, func(v tempopb.TagValue) int { return len(v.Type) + len(v.Value) }) + dv := collector.NewDistinctValue[tempopb.TagValue](0, opts.Limit, func(v tempopb.TagValue) int { return len(v.Type) + len(v.Value) }) mc := collector.NewMetricsCollector() rw.cfg.Search.ApplyToOptions(&opts) err = block.SearchTagValuesV2(ctx, tag, traceql.MakeCollectTagValueFunc(dv.Collect), mc.Add, opts) diff --git a/tempodb/tempodb_search_test.go b/tempodb/tempodb_search_test.go index 58d72e8ba08..d521d892e8e 100644 --- a/tempodb/tempodb_search_test.go +++ b/tempodb/tempodb_search_test.go @@ -1383,7 +1383,7 @@ func tagValuesRunner(t *testing.T, _ *tempopb.Trace, _ *tempopb.TraceSearchMetad for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { - valueCollector := collector.NewDistinctValue[tempopb.TagValue](0, func(_ tempopb.TagValue) int { return 0 }) + valueCollector := collector.NewDistinctValue[tempopb.TagValue](0, 0, func(_ tempopb.TagValue) int { return 0 }) mc := collector.NewMetricsCollector() fetcher := traceql.NewTagValuesFetcherWrapper(func(ctx context.Context, req traceql.FetchTagValuesRequest, cb traceql.FetchTagValuesCallback) error { return bb.FetchTagValues(ctx, req, cb, mc.Add, common.DefaultSearchOptions()) @@ -1459,7 +1459,7 @@ func tagNamesRunner(t *testing.T, _ *tempopb.Trace, _ *tempopb.TraceSearchMetada return bb.FetchTagNames(ctx, req, cb, mc.Add, common.DefaultSearchOptions()) }) - valueCollector := collector.NewScopedDistinctString(0) + valueCollector := collector.NewScopedDistinctString(0, 0) err := e.ExecuteTagNames(ctx, traceql.AttributeScopeFromString(tc.scope), tc.query, func(tag string, scope traceql.AttributeScope) bool { return valueCollector.Collect(scope.String(), tag) }, fetcher) @@ -2372,7 +2372,7 @@ func TestSearchForTagsAndTagValues(t *testing.T) { require.Equal(t, expected, tagValues.TagValues) require.NotZero(t, tagValues.Metrics.InspectedBytes) - valueCollector := collector.NewDistinctValue[tempopb.TagValue](0, func(_ tempopb.TagValue) int { return 0 }) + valueCollector := collector.NewDistinctValue[tempopb.TagValue](0, 0, func(_ tempopb.TagValue) int { return 0 }) mc := collector.NewMetricsCollector() f := traceql.NewTagValuesFetcherWrapper(func(ctx context.Context, req traceql.FetchTagValuesRequest, cb traceql.FetchTagValuesCallback) error { return r.FetchTagValues(ctx, block.BlockMeta(), req, cb, mc.Add, common.DefaultSearchOptions()) From 573695fcf979eb347c1783c570b39e4f47118255 Mon Sep 17 00:00:00 2001 From: javiermolinar Date: Wed, 13 Nov 2024 10:57:15 +0100 Subject: [PATCH 2/2] added documentation --- docs/sources/tempo/api_docs/_index.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/docs/sources/tempo/api_docs/_index.md b/docs/sources/tempo/api_docs/_index.md index 346e85cf3d2..d26022ae4bd 100644 --- a/docs/sources/tempo/api_docs/_index.md +++ b/docs/sources/tempo/api_docs/_index.md @@ -360,6 +360,8 @@ Parameters: Optional. Along with `end`, defines a time range from which tags should be returned. - `end = (unix epoch seconds)` Optional. Along with `start`, defines a time range from which tags should be returned. Providing both `start` and `end` includes blocks for the specified time range only. +- `limit = (integer)` + Optional. Limits the maximum number of tags ### Search tags V2 @@ -385,6 +387,8 @@ Parameters: Optional. Along with `end` define a time range from which tags should be returned. - `end = (unix epoch seconds)` Optional. Along with `start` define a time range from which tags should be returned. Providing both `start` and `end` includes blocks for the specified time range only. +- `limit = (integer)` + Optional. Limits the maximum number of tags per scope #### Example @@ -515,6 +519,8 @@ Parameters: Optional. Along with `end`, defines a time range from which tags should be returned. - `end = (unix epoch seconds)` Optional. Along with `start`, defines a time range from which tags should be returned. Providing both `start` and `end` includes blocks for the specified time range only. +- `limit = (integer)` + Optional. Limits the maximum number of tags values ### Search tag values V2 @@ -561,7 +567,16 @@ $ curl -G -s http://localhost:3200/api/v2/search/tag/.service.name/values | jq } } ``` -This endpoint can also receive `start` and `end` optional parameters. These parameters define the time range from which the tags are fetched +Parameters: +- `start = (unix epoch seconds)` + Optional. Along with `end`, defines a time range from which tags values should be returned. +- `end = (unix epoch seconds)` + Optional. Along with `start`, defines a time range from which tags values should be returned. Providing both `start` and `end` includes blocks for the specified time range only. +- `q = (traceql query)` + Optional. A TraceQL query to filter tag values by. Currently only works for a single spanset of `&&`ed conditions. For example: `{ span.foo = "bar" && resource.baz = "bat" ...}`. See also [Filtered tag values](#filtered-tag-values). +- `limit = (integer)` + Optional. Limits the maximum number of tags values + #### Filtered tag values