From b592810fc5c50203a1dd1e2a8f5713825b695935 Mon Sep 17 00:00:00 2001 From: Tolya Korniltsev Date: Mon, 10 Jun 2024 12:17:50 +0800 Subject: [PATCH 1/9] fix(ingest): line numbers for pyspy (#3337) * fix(ingest): rewrite pprof to include line numbers in function names for pyspy --- cmd/pyroscope/frontend.Dockerfile | 13 ++++++ pkg/og/convert/pprof/profile.go | 47 +++++++++++++++---- pkg/og/convert/pprof/profile_test.go | 57 +++++++++++++++++++++++ pkg/test/integration/ingest_pprof_test.go | 51 ++++++++++++++++++++ 4 files changed, 159 insertions(+), 9 deletions(-) create mode 100644 cmd/pyroscope/frontend.Dockerfile diff --git a/cmd/pyroscope/frontend.Dockerfile b/cmd/pyroscope/frontend.Dockerfile new file mode 100644 index 0000000000..d516e956d1 --- /dev/null +++ b/cmd/pyroscope/frontend.Dockerfile @@ -0,0 +1,13 @@ +FROM node:18 as builder +RUN apt-get update && apt-get install -y libpango1.0-dev libcairo2-dev +WORKDIR /pyroscope +COPY yarn.lock package.json tsconfig.json ./ +RUN yarn --frozen-lockfile +COPY scripts/webpack ./scripts/webpack/ +COPY public/app ./public/app +COPY public/templates ./public/templates +RUN yarn build + +# Usage: docker build -f cmd/pyroscope/frontend.Dockerfile --output=public/build . +FROM scratch +COPY --from=builder /pyroscope/public/build / \ No newline at end of file diff --git a/pkg/og/convert/pprof/profile.go b/pkg/og/convert/pprof/profile.go index 7337174985..047d42862c 100644 --- a/pkg/og/convert/pprof/profile.go +++ b/pkg/og/convert/pprof/profile.go @@ -245,26 +245,55 @@ func isScriptingSpy(md ingestion.Metadata) bool { return md.SpyName == "pyspy" || md.SpyName == "rbspy" || md.SpyName == "scripting" } +// FixFunctionNamesForScriptingLanguages modifies the function names in the provided profile +// to include line numbers. This is a workaround for frontend limitations in rendering line numbers. +// The function is specifically designed for profiles generated by scripting languages. +// Note: This function modifies the provided profile in place. func FixFunctionNamesForScriptingLanguages(p *pprof.Profile, md ingestion.Metadata) { if !needFunctionNameRewrite(md) { return } smap := map[string]int{} - for _, fn := range p.Function { - // obtaining correct line number will require rewriting functions and slices - // lets not do it and wait until we render line numbers on frontend - const lineNumber = -1 - name := fmt.Sprintf("%s:%d - %s", - p.StringTable[fn.Filename], - lineNumber, - p.StringTable[fn.Name]) + addString := func(name string) int { sid := smap[name] if sid == 0 { sid = len(p.StringTable) p.StringTable = append(p.StringTable, name) smap[name] = sid } - fn.Name = int64(sid) + return sid + } + funcId2Index := map[uint64]int64{} + newFunctions := map[string]*profilev1.Function{} + maxId := uint64(0) + for index, fn := range p.Function { + funcId2Index[fn.Id] = int64(index) + if fn.Id > maxId { + maxId = fn.Id + } + } + for _, location := range p.Location { + for _, line := range location.Line { + fn := p.Function[funcId2Index[line.FunctionId]] + name := fmt.Sprintf("%s:%d - %s", + p.StringTable[fn.Filename], + line.Line, + p.StringTable[fn.Name]) + newFunc, ok := newFunctions[name] + if !ok { + maxId++ + newFunc = &profilev1.Function{ + Id: maxId, + Name: int64(addString(name)), + Filename: fn.Filename, + SystemName: fn.SystemName, + StartLine: fn.StartLine, + } + newFunctions[name] = newFunc + p.Function = append(p.Function, newFunc) + } + line.FunctionId = newFunc.Id + } } } diff --git a/pkg/og/convert/pprof/profile_test.go b/pkg/og/convert/pprof/profile_test.go index 86390faa44..effed00957 100644 --- a/pkg/og/convert/pprof/profile_test.go +++ b/pkg/og/convert/pprof/profile_test.go @@ -1,6 +1,10 @@ package pprof import ( + profilev1 "github.com/grafana/pyroscope/api/gen/proto/go/google/v1" + "github.com/grafana/pyroscope/pkg/og/convert/pprof/bench" + "github.com/grafana/pyroscope/pkg/pprof" + "github.com/stretchr/testify/assert" "testing" "github.com/grafana/pyroscope/pkg/og/ingestion" @@ -22,3 +26,56 @@ func TestEmptyPPROF(t *testing.T) { require.NoError(t, err) require.Equal(t, 0, len(profile.Series)) } + +func TestFixFunctionNamesForScriptingLanguages(t *testing.T) { + profile := pprof.RawFromProto(&profilev1.Profile{ + StringTable: []string{"", "main", "func1", "func2", "qwe.py"}, + Function: []*profilev1.Function{ + {Id: 1, Name: 1, Filename: 4, SystemName: 1, StartLine: 239}, + {Id: 2, Name: 2, Filename: 4, SystemName: 2, StartLine: 42}, + {Id: 3, Name: 3, Filename: 4, SystemName: 3, StartLine: 7}, + }, + Location: []*profilev1.Location{ + {Id: 1, Line: []*profilev1.Line{{FunctionId: 1, Line: 242}}}, + {Id: 2, Line: []*profilev1.Line{{FunctionId: 2, Line: 50}}}, + {Id: 3, Line: []*profilev1.Line{{FunctionId: 3, Line: 8}}}, + }, + Sample: []*profilev1.Sample{ + {LocationId: []uint64{2, 1}, Value: []int64{10, 1000}}, + {LocationId: []uint64{3, 1}, Value: []int64{13, 1300}}, + }, + }) + functionNameFromLocation := func(locID uint64) string { + for _, loc := range profile.Location { + if loc.Id == locID { + for _, fun := range profile.Function { + if fun.Id == loc.Line[0].FunctionId { + return profile.StringTable[fun.Name] + } + } + } + } + return "" + } + + md := ingestion.Metadata{ + SpyName: "pyspy", + } + + FixFunctionNamesForScriptingLanguages(profile, md) + + assert.Len(t, profile.Function, 6) // we do not remove unreferenced functions for now, let the distributor do it + assert.Len(t, profile.Location, 3) + assert.Len(t, profile.Sample, 2) + + collapsed := bench.StackCollapseProto(profile.Profile, 0, 1) + expected := []string{ + "qwe.py:242 - main;qwe.py:50 - func1 10", + "qwe.py:242 - main;qwe.py:8 - func2 13", + } + assert.Equal(t, expected, collapsed) + + assert.Equal(t, "qwe.py:242 - main", functionNameFromLocation(profile.Location[0].Id)) + assert.Equal(t, "qwe.py:50 - func1", functionNameFromLocation(profile.Location[1].Id)) + assert.Equal(t, "qwe.py:8 - func2", functionNameFromLocation(profile.Location[2].Id)) +} diff --git a/pkg/test/integration/ingest_pprof_test.go b/pkg/test/integration/ingest_pprof_test.go index f410ca3f0a..0cc8133444 100644 --- a/pkg/test/integration/ingest_pprof_test.go +++ b/pkg/test/integration/ingest_pprof_test.go @@ -267,6 +267,57 @@ func TestIngest(t *testing.T) { } } +func TestIngestPPROFFixPythonLinenumbers(t *testing.T) { + p := PyroscopeTest{} + p.Start(t) + defer p.Stop(t) + profile := pprof.RawFromProto(&profilev1.Profile{ + SampleType: []*profilev1.ValueType{{ + Type: 5, + Unit: 6, + }}, + PeriodType: &profilev1.ValueType{ + Type: 5, Unit: 6, + }, + StringTable: []string{"", "main", "func1", "func2", "qwe.py", "cpu", "nanoseconds"}, + Period: 1000000000, + Function: []*profilev1.Function{ + {Id: 1, Name: 1, Filename: 4, SystemName: 1, StartLine: 239}, + {Id: 2, Name: 2, Filename: 4, SystemName: 2, StartLine: 42}, + {Id: 3, Name: 3, Filename: 4, SystemName: 3, StartLine: 7}, + }, + Location: []*profilev1.Location{ + {Id: 1, Line: []*profilev1.Line{{FunctionId: 1, Line: 242}}}, + {Id: 2, Line: []*profilev1.Line{{FunctionId: 2, Line: 50}}}, + {Id: 3, Line: []*profilev1.Line{{FunctionId: 3, Line: 8}}}, + }, + Sample: []*profilev1.Sample{ + {LocationId: []uint64{2, 1}, Value: []int64{10}}, + {LocationId: []uint64{3, 1}, Value: []int64{13}}, + }, + }) + + tempProfileFile, err := os.CreateTemp("", "profile") + require.NoError(t, err) + _, err = profile.WriteTo(tempProfileFile) + assert.NoError(t, err) + tempProfileFile.Close() + defer os.Remove(tempProfileFile.Name()) + + rb := p.NewRequestBuilder(t). + Spy("pyspy") + req := rb.IngestPPROFRequest(tempProfileFile.Name(), "", "") + p.Ingest(t, req, 200) + + renderedProfile := rb.SelectMergeProfile("process_cpu:cpu:nanoseconds:cpu:nanoseconds", nil) + actual := bench.StackCollapseProto(renderedProfile.Msg, 0, 1) + expected := []string{ + "qwe.py:242 - main;qwe.py:50 - func1 10", + "qwe.py:242 - main;qwe.py:8 - func2 13", + } + assert.Equal(t, expected, actual) +} + func TestPush(t *testing.T) { p := new(PyroscopeTest) p.Start(t) From fc023dadceb1e2811026dfaeb0bd4baf2b6c9d89 Mon Sep 17 00:00:00 2001 From: Anton Kolesnikov Date: Mon, 10 Jun 2024 16:41:01 +0800 Subject: [PATCH 2/9] feat: optimize series order (#3345) * feat: optimize series order * Remove additional ordering as part of TSDB index I am also adding a test, to ensure the profile table is ordered as expected. * feat: make generate * feat: preserve labels order in profilecli admin tsdb series --------- Co-authored-by: Christian Simon --- cmd/profilecli/tsdb.go | 2 +- cmd/pyroscope/help-all.txt.tmpl | 2 + cmd/pyroscope/help.txt.tmpl | 2 + .../index.md | 4 + pkg/distributor/distributor.go | 13 +- pkg/distributor/distributor_test.go | 3 +- pkg/model/labels.go | 108 ++++++++++---- pkg/model/labels_test.go | 140 +++++++++++++++++- pkg/phlaredb/head.go | 5 +- pkg/phlaredb/head_test.go | 84 +++++++++++ pkg/phlaredb/labels/labels.go | 17 ++- pkg/phlaredb/labels/labels_test.go | 18 +-- pkg/phlaredb/schemas/v1/testhelper/profile.go | 2 +- pkg/phlaredb/tsdb/index.go | 1 - pkg/validation/limits.go | 6 + 15 files changed, 347 insertions(+), 60 deletions(-) diff --git a/cmd/profilecli/tsdb.go b/cmd/profilecli/tsdb.go index 4d12ec4649..6c0d7264f6 100644 --- a/cmd/profilecli/tsdb.go +++ b/cmd/profilecli/tsdb.go @@ -44,7 +44,7 @@ func tsdbSeries(ctx context.Context, path string) error { return fmt.Errorf("error retrieving seriesRef: %w", err) } - line.Labels, err = lbls.ToPrometheusLabels().MarshalJSON() + line.Labels, err = json.Marshal(lbls) if err != nil { return fmt.Errorf("error marshalling labels: %w", err) } diff --git a/cmd/pyroscope/help-all.txt.tmpl b/cmd/pyroscope/help-all.txt.tmpl index 0b0c0de789..bf47cbe097 100644 --- a/cmd/pyroscope/help-all.txt.tmpl +++ b/cmd/pyroscope/help-all.txt.tmpl @@ -1047,6 +1047,8 @@ Usage of ./pyroscope: [experimental] Set to true to enable profiling integration. -usage-stats.enabled Enable anonymous usage reporting. (default true) + -validation.enforce-labels-order + Enforce labels order optimization. -validation.max-label-names-per-series int Maximum number of label names per series. (default 30) -validation.max-length-label-name int diff --git a/cmd/pyroscope/help.txt.tmpl b/cmd/pyroscope/help.txt.tmpl index e949ba0ea2..ad077e714e 100644 --- a/cmd/pyroscope/help.txt.tmpl +++ b/cmd/pyroscope/help.txt.tmpl @@ -381,6 +381,8 @@ Usage of ./pyroscope: Set to false to disable tracing. (default true) -usage-stats.enabled Enable anonymous usage reporting. (default true) + -validation.enforce-labels-order + Enforce labels order optimization. -validation.max-label-names-per-series int Maximum number of label names per series. (default 30) -validation.max-length-label-name int diff --git a/docs/sources/configure-server/reference-configuration-parameters/index.md b/docs/sources/configure-server/reference-configuration-parameters/index.md index 59748393f6..070f09b853 100644 --- a/docs/sources/configure-server/reference-configuration-parameters/index.md +++ b/docs/sources/configure-server/reference-configuration-parameters/index.md @@ -1815,6 +1815,10 @@ The `limits` block configures default and per-tenant limits imposed by component # CLI flag: -validation.max-sessions-per-series [max_sessions_per_series: | default = 0] +# Enforce labels order optimization. +# CLI flag: -validation.enforce-labels-order +[enforce_labels_order: | default = false] + # Maximum size of a profile in bytes. This is based off the uncompressed size. 0 # to disable. # CLI flag: -validation.max-profile-size-bytes diff --git a/pkg/distributor/distributor.go b/pkg/distributor/distributor.go index 8c1611f80e..a1065a6328 100644 --- a/pkg/distributor/distributor.go +++ b/pkg/distributor/distributor.go @@ -122,6 +122,7 @@ type Limits interface { MaxProfileStacktraceDepth(tenantID string) int MaxProfileSymbolValueLength(tenantID string) int MaxSessionsPerSeries(tenantID string) int + EnforceLabelsOrder(tenantID string) bool validation.ProfileValidationLimits aggregator.Limits } @@ -393,8 +394,9 @@ func (d *Distributor) sendAggregatedProfile(ctx context.Context, req *distributo func (d *Distributor) sendRequests(ctx context.Context, req *distributormodel.PushRequest, tenantID string) (resp *connect.Response[pushv1.PushResponse], err error) { // Reduce cardinality of session_id label. + maxSessionsPerSeries := d.limits.MaxSessionsPerSeries(tenantID) for _, series := range req.Series { - series.Labels = d.limitMaxSessionsPerSeries(tenantID, series.Labels) + series.Labels = d.limitMaxSessionsPerSeries(maxSessionsPerSeries, series.Labels) } // Next we split profiles by labels. @@ -414,7 +416,13 @@ func (d *Distributor) sendRequests(ctx context.Context, req *distributormodel.Pu // Validate the labels again and generate tokens for shuffle sharding. keys := make([]uint32, len(profileSeries)) + enforceLabelsOrder := d.limits.EnforceLabelsOrder(tenantID) for i, series := range profileSeries { + if enforceLabelsOrder { + labels := phlaremodel.Labels(series.Labels) + labels.Insert(phlaremodel.LabelNameOrder, phlaremodel.LabelOrderEnforced) + series.Labels = labels + } if err = validation.ValidateLabels(d.limits, tenantID, series.Labels); err != nil { validation.DiscardedProfiles.WithLabelValues(string(validation.ReasonOf(err)), tenantID).Add(float64(req.TotalProfiles)) validation.DiscardedBytes.WithLabelValues(string(validation.ReasonOf(err)), tenantID).Add(float64(req.TotalBytesUncompressed)) @@ -666,8 +674,7 @@ func extractSampleSeries(req *distributormodel.PushRequest) []*distributormodel. return profileSeries } -func (d *Distributor) limitMaxSessionsPerSeries(tenantID string, labels phlaremodel.Labels) phlaremodel.Labels { - maxSessionsPerSeries := d.limits.MaxSessionsPerSeries(tenantID) +func (d *Distributor) limitMaxSessionsPerSeries(maxSessionsPerSeries int, labels phlaremodel.Labels) phlaremodel.Labels { if maxSessionsPerSeries == 0 { return labels.Delete(phlaremodel.LabelNameSessionID) } diff --git a/pkg/distributor/distributor_test.go b/pkg/distributor/distributor_test.go index f78464adb7..acc0f9e5dd 100644 --- a/pkg/distributor/distributor_test.go +++ b/pkg/distributor/distributor_test.go @@ -415,7 +415,8 @@ func Test_Sessions_Limit(t *testing.T) { }), nil, log.NewLogfmtLogger(os.Stdout)) require.NoError(t, err) - assert.Equal(t, tc.expectedLabels, d.limitMaxSessionsPerSeries("user-1", tc.seriesLabels)) + limit := d.limits.MaxSessionsPerSeries("user-1") + assert.Equal(t, tc.expectedLabels, d.limitMaxSessionsPerSeries(limit, tc.seriesLabels)) }) } } diff --git a/pkg/model/labels.go b/pkg/model/labels.go index 191a77e7d8..f66de3a9ab 100644 --- a/pkg/model/labels.go +++ b/pkg/model/labels.go @@ -22,21 +22,24 @@ import ( var seps = []byte{'\xff'} const ( - LabelNameProfileType = "__profile_type__" - LabelNameType = "__type__" - LabelNameUnit = "__unit__" - LabelNamePeriodType = "__period_type__" - LabelNamePeriodUnit = "__period_unit__" - LabelNameDelta = "__delta__" - LabelNameProfileName = pmodel.MetricNameLabel - LabelNameSessionID = "__session_id__" + LabelNameProfileType = "__profile_type__" + LabelNameServiceNamePrivate = "__service_name__" + LabelNameDelta = "__delta__" + LabelNameProfileName = pmodel.MetricNameLabel + LabelNamePeriodType = "__period_type__" + LabelNamePeriodUnit = "__period_unit__" + LabelNameSessionID = "__session_id__" + LabelNameType = "__type__" + LabelNameUnit = "__unit__" + LabelNameServiceGitRef = "service_git_ref" LabelNameServiceName = "service_name" LabelNameServiceRepository = "service_repository" - LabelNameServiceGitRef = "service_git_ref" - LabelNamePyroscopeSpy = "pyroscope_spy" - LabelNameServiceNameK8s = "__meta_kubernetes_pod_annotation_pyroscope_io_service_name" + LabelNameOrder = "__order__" + LabelOrderEnforced = "enforced" + + LabelNamePyroscopeSpy = "pyroscope_spy" labelSep = '\xfe' ) @@ -49,6 +52,30 @@ func (ls Labels) Len() int { return len(ls) } func (ls Labels) Swap(i, j int) { ls[i], ls[j] = ls[j], ls[i] } func (ls Labels) Less(i, j int) bool { return ls[i].Name < ls[j].Name } +// LabelsEnforcedOrder is a sort order of labels, where profile type and +// service name labels always go first. This is crucial for query performance +// as labels determine the physical order of the profiling data. +type LabelsEnforcedOrder []*typesv1.LabelPair + +func (ls LabelsEnforcedOrder) Len() int { return len(ls) } +func (ls LabelsEnforcedOrder) Swap(i, j int) { ls[i], ls[j] = ls[j], ls[i] } + +func (ls LabelsEnforcedOrder) Less(i, j int) bool { + if ls[i].Name[0] == '_' || ls[j].Name[0] == '_' { + leftType := ls[i].Name == LabelNameProfileType + rightType := ls[j].Name == LabelNameProfileType + if leftType || rightType { + return leftType || !rightType + } + leftService := ls[i].Name == LabelNameServiceNamePrivate + rightService := ls[j].Name == LabelNameServiceNamePrivate + if leftService || rightService { + return leftService || !rightService + } + } + return ls[i].Name < ls[j].Name +} + // Hash returns a hash value for the label set. func (ls Labels) Hash() uint64 { // Use xxhash.Sum64(b) for fast path as it's faster. @@ -192,14 +219,41 @@ func (ls Labels) GetLabel(name string) (*typesv1.LabelPair, bool) { return nil, false } -// Delete removes the first label encountered with the name given. -// A copy of the label set without the label is returned. +// Delete removes the first label encountered with the name given in place. func (ls Labels) Delete(name string) Labels { return slices.RemoveInPlace(ls, func(pair *typesv1.LabelPair, i int) bool { return pair.Name == name }) } +// Insert adds the given label to the set of labels. +// It assumes the labels are ordered +func (ls *Labels) Insert(name, value string) { + // Find the index where the new label should be inserted + index := -1 + for i, label := range *ls { + if label.Name > name { + index = i + break + } + if label.Name == name { + label.Value = value + return + } + } + // Insert the new label at the found index + l := &typesv1.LabelPair{ + Name: name, + Value: value, + } + *ls = append(*ls, l) + if index == -1 { + return + } + copy((*ls)[index+1:], (*ls)[index:]) + (*ls)[index] = l +} + func (ls Labels) Clone() Labels { result := make(Labels, len(ls)) for i, l := range ls { @@ -277,19 +331,7 @@ func LabelsFromStrings(ss ...string) Labels { return res } -// CloneLabelPairs clones the label pairs. -func CloneLabelPairs(lbs []*typesv1.LabelPair) []*typesv1.LabelPair { - result := make([]*typesv1.LabelPair, len(lbs)) - for i, l := range lbs { - result[i] = &typesv1.LabelPair{ - Name: l.Name, - Value: l.Value, - } - } - return result -} - -// Compare compares the two label sets. +// CompareLabelPairs compares the two label sets. // The result will be 0 if a==b, <0 if a < b, and >0 if a > b. func CompareLabelPairs(a []*typesv1.LabelPair, b []*typesv1.LabelPair) int { l := len(a) @@ -377,6 +419,16 @@ func (b *LabelsBuilder) Set(n, v string) *LabelsBuilder { // Labels returns the labels from the builder. If no modifications // were made, the original labels are returned. func (b *LabelsBuilder) Labels() Labels { + res := b.LabelsUnsorted() + sort.Sort(res) + return res +} + +// LabelsUnsorted returns the labels from the builder. If no modifications +// were made, the original labels are returned. +// +// The order is not deterministic. +func (b *LabelsBuilder) LabelsUnsorted() Labels { if len(b.del) == 0 && len(b.add) == 0 { return b.base } @@ -398,10 +450,8 @@ Outer: } res = append(res, l) } - res = append(res, b.add...) - sort.Sort(res) - return res + return append(res, b.add...) } // StableHash is a labels hashing implementation which is guaranteed to not change over time. diff --git a/pkg/model/labels_test.go b/pkg/model/labels_test.go index 3395f238df..89380d1d79 100644 --- a/pkg/model/labels_test.go +++ b/pkg/model/labels_test.go @@ -6,6 +6,8 @@ import ( "testing" "github.com/stretchr/testify/assert" + + typesv1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1" ) func TestLabelsUnique(t *testing.T) { @@ -67,21 +69,20 @@ func TestLabelsUnique(t *testing.T) { } func TestLabels_SessionID_Order(t *testing.T) { - const serviceNameLabel = "__service_name__" input := []Labels{ { {Name: LabelNameSessionID, Value: "session-a"}, {Name: LabelNameProfileType, Value: "cpu"}, - {Name: serviceNameLabel, Value: "service-name"}, + {Name: LabelNameServiceNamePrivate, Value: "service-name"}, }, { {Name: LabelNameSessionID, Value: "session-b"}, {Name: LabelNameProfileType, Value: "cpu"}, - {Name: serviceNameLabel, Value: "service-name"}, + {Name: LabelNameServiceNamePrivate, Value: "service-name"}, }, } for _, x := range input { - sort.Sort(x) + sort.Sort(LabelsEnforcedOrder(x)) } sort.Slice(input, func(i, j int) bool { return CompareLabelPairs(input[i], input[j]) < 0 @@ -90,11 +91,11 @@ func TestLabels_SessionID_Order(t *testing.T) { expectedOrder := []Labels{ { {Name: LabelNameProfileType, Value: "cpu"}, - {Name: serviceNameLabel, Value: "service-name"}, + {Name: LabelNameServiceNamePrivate, Value: "service-name"}, {Name: LabelNameSessionID, Value: "session-a"}, }, { {Name: LabelNameProfileType, Value: "cpu"}, - {Name: serviceNameLabel, Value: "service-name"}, + {Name: LabelNameServiceNamePrivate, Value: "service-name"}, {Name: LabelNameSessionID, Value: "session-b"}, }, } @@ -133,3 +134,130 @@ func Test_SessionID_Parse(t *testing.T) { _, err = ParseSessionID("not-a-session-id-either") assert.NotNil(t, err) } + +func TestLabels_LabelsEnforcedOrder(t *testing.T) { + labels := []*typesv1.LabelPair{ + {Name: "foo", Value: "bar"}, + {Name: LabelNameProfileType, Value: "cpu"}, + {Name: "__request_id__", Value: "mess"}, + {Name: LabelNameServiceNamePrivate, Value: "service"}, + {Name: "Alarm", Value: "Order"}, + } + + expected := Labels{ + {Name: LabelNameProfileType, Value: "cpu"}, + {Name: LabelNameServiceNamePrivate, Value: "service"}, + {Name: "Alarm", Value: "Order"}, + {Name: "__request_id__", Value: "mess"}, + {Name: "foo", Value: "bar"}, + } + + permute(labels, func(x []*typesv1.LabelPair) { + sort.Sort(LabelsEnforcedOrder(x)) + assert.Equal(t, LabelPairsString(expected), LabelPairsString(labels)) + }) +} + +func permute[T any](s []T, f func([]T)) { + n := len(s) + stack := make([]int, n) + f(s) + i := 0 + for i < n { + if stack[i] < i { + if i%2 == 0 { + s[0], s[i] = s[i], s[0] + } else { + s[stack[i]], s[i] = s[i], s[stack[i]] + } + f(s) + stack[i]++ + i = 0 + } else { + stack[i] = 0 + i++ + } + } +} + +func TestInsert(t *testing.T) { + tests := []struct { + name string + labels Labels + insertName string + insertValue string + expected Labels + }{ + { + name: "Insert into empty slice", + labels: Labels{}, + insertName: "foo", + insertValue: "bar", + expected: Labels{ + {Name: "foo", Value: "bar"}, + }, + }, + { + name: "Insert at the beginning", + labels: Labels{ + {Name: "baz", Value: "qux"}, + {Name: "quux", Value: "corge"}, + }, + insertName: "alice", + insertValue: "bob", + expected: Labels{ + {Name: "alice", Value: "bob"}, + {Name: "baz", Value: "qux"}, + {Name: "quux", Value: "corge"}, + }, + }, + { + name: "Insert in the middle", + labels: Labels{ + {Name: "baz", Value: "qux"}, + {Name: "quux", Value: "corge"}, + }, + insertName: "foo", + insertValue: "bar", + expected: Labels{ + {Name: "baz", Value: "qux"}, + {Name: "foo", Value: "bar"}, + {Name: "quux", Value: "corge"}, + }, + }, + { + name: "Insert at the end", + labels: Labels{ + {Name: "baz", Value: "qux"}, + {Name: "quux", Value: "corge"}, + }, + insertName: "xyz", + insertValue: "123", + expected: Labels{ + {Name: "baz", Value: "qux"}, + {Name: "quux", Value: "corge"}, + {Name: "xyz", Value: "123"}, + }, + }, + { + name: "Update existing label", + labels: Labels{ + {Name: "baz", Value: "qux"}, + {Name: "quux", Value: "corge"}, + }, + insertName: "baz", + insertValue: "updated_value", + expected: Labels{ + {Name: "baz", Value: "updated_value"}, + {Name: "quux", Value: "corge"}, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + test.labels.Insert(test.insertName, test.insertValue) + assert.Equal(t, test.expected, test.labels) + }) + } +} diff --git a/pkg/phlaredb/head.go b/pkg/phlaredb/head.go index b82d5c4904..d5f18b3e2b 100644 --- a/pkg/phlaredb/head.go +++ b/pkg/phlaredb/head.go @@ -187,7 +187,10 @@ func (h *Head) Ingest(ctx context.Context, p *profilev1.Profile, id uuid.UUID, e delta := phlaremodel.Labels(externalLabels).Get(phlaremodel.LabelNameDelta) != "false" externalLabels = phlaremodel.Labels(externalLabels).Delete(phlaremodel.LabelNameDelta) - lbls, seriesFingerprints := phlarelabels.CreateProfileLabels(p, externalLabels...) + enforceLabelOrder := phlaremodel.Labels(externalLabels).Get(phlaremodel.LabelNameOrder) == phlaremodel.LabelOrderEnforced + externalLabels = phlaremodel.Labels(externalLabels).Delete(phlaremodel.LabelNameOrder) + + lbls, seriesFingerprints := phlarelabels.CreateProfileLabels(enforceLabelOrder, p, externalLabels...) for i, fp := range seriesFingerprints { if err := h.limiter.AllowProfile(fp, lbls[i], p.TimeNanos); err != nil { diff --git a/pkg/phlaredb/head_test.go b/pkg/phlaredb/head_test.go index 7f2c5cbd5b..8d7232de77 100644 --- a/pkg/phlaredb/head_test.go +++ b/pkg/phlaredb/head_test.go @@ -12,6 +12,7 @@ import ( "connectrpc.com/connect" "github.com/google/uuid" "github.com/oklog/ulid" + "github.com/parquet-go/parquet-go" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/common/model" "github.com/stretchr/testify/assert" @@ -525,6 +526,89 @@ func TestIsStale(t *testing.T) { require.False(t, head.isStale(now.Add(2*StaleGracePeriod).UnixNano(), now.Add(2*StaleGracePeriod))) } +func profileWithID(id int) (*profilev1.Profile, uuid.UUID) { + p := newProfileFoo() + p.TimeNanos = int64(id) + return p, uuid.MustParse(fmt.Sprintf("00000000-0000-0000-0000-%012d", id)) +} + +func TestHead_ProfileOrder(t *testing.T) { + head := newTestHead(t) + + p, u := profileWithID(1) + require.NoError(t, head.Ingest( + context.Background(), + p, + u, + &typesv1.LabelPair{Name: phlaremodel.LabelNameProfileName, Value: "memory"}, + &typesv1.LabelPair{Name: phlaremodel.LabelNameOrder, Value: phlaremodel.LabelOrderEnforced}, + &typesv1.LabelPair{Name: phlaremodel.LabelNameServiceName, Value: "service-a"}, + )) + + p, u = profileWithID(2) + require.NoError(t, head.Ingest( + context.Background(), + p, + u, + &typesv1.LabelPair{Name: phlaremodel.LabelNameProfileName, Value: "memory"}, + &typesv1.LabelPair{Name: phlaremodel.LabelNameOrder, Value: phlaremodel.LabelOrderEnforced}, + &typesv1.LabelPair{Name: phlaremodel.LabelNameServiceName, Value: "service-b"}, + &typesv1.LabelPair{Name: "____Label", Value: "important"}, + )) + + p, u = profileWithID(3) + require.NoError(t, head.Ingest( + context.Background(), + p, + u, + &typesv1.LabelPair{Name: phlaremodel.LabelNameProfileName, Value: "memory"}, + &typesv1.LabelPair{Name: phlaremodel.LabelNameOrder, Value: phlaremodel.LabelOrderEnforced}, + &typesv1.LabelPair{Name: phlaremodel.LabelNameServiceName, Value: "service-c"}, + &typesv1.LabelPair{Name: "AAALabel", Value: "important"}, + )) + + p, u = profileWithID(4) + require.NoError(t, head.Ingest( + context.Background(), + p, + u, + &typesv1.LabelPair{Name: phlaremodel.LabelNameProfileName, Value: "cpu"}, + &typesv1.LabelPair{Name: phlaremodel.LabelNameOrder, Value: phlaremodel.LabelOrderEnforced}, + &typesv1.LabelPair{Name: phlaremodel.LabelNameServiceName, Value: "service-a"}, + &typesv1.LabelPair{Name: "000Label", Value: "important"}, + )) + + p, u = profileWithID(5) + require.NoError(t, head.Ingest( + context.Background(), + p, + u, + &typesv1.LabelPair{Name: phlaremodel.LabelNameProfileName, Value: "cpu"}, + &typesv1.LabelPair{Name: phlaremodel.LabelNameOrder, Value: phlaremodel.LabelOrderEnforced}, + &typesv1.LabelPair{Name: phlaremodel.LabelNameServiceName, Value: "service-b"}, + )) + + p, u = profileWithID(6) + require.NoError(t, head.Ingest( + context.Background(), + p, + u, + &typesv1.LabelPair{Name: phlaremodel.LabelNameProfileName, Value: "cpu"}, + &typesv1.LabelPair{Name: phlaremodel.LabelNameOrder, Value: phlaremodel.LabelOrderEnforced}, + &typesv1.LabelPair{Name: phlaremodel.LabelNameServiceName, Value: "service-b"}, + )) + + head.Flush(context.Background()) + + // test that the profiles are ordered correctly + type row struct{ TimeNanos uint64 } + rows, err := parquet.ReadFile[row](filepath.Join(head.headPath, "profiles.parquet")) + require.NoError(t, err) + require.Equal(t, []row{ + {4}, {5}, {6}, {1}, {2}, {3}, + }, rows) +} + func BenchmarkHeadIngestProfiles(t *testing.B) { var ( profilePaths = []string{ diff --git a/pkg/phlaredb/labels/labels.go b/pkg/phlaredb/labels/labels.go index fb931ad160..405955abde 100644 --- a/pkg/phlaredb/labels/labels.go +++ b/pkg/phlaredb/labels/labels.go @@ -1,7 +1,7 @@ package labels import ( - "fmt" + "sort" "strings" "github.com/prometheus/common/model" @@ -11,9 +11,7 @@ import ( phlaremodel "github.com/grafana/pyroscope/pkg/model" ) -var labelNameServiceName = fmt.Sprintf("__%s__", phlaremodel.LabelNameServiceName) - -func CreateProfileLabels(p *profilev1.Profile, externalLabels ...*typesv1.LabelPair) ([]phlaremodel.Labels, []model.Fingerprint) { +func CreateProfileLabels(enforceOrder bool, p *profilev1.Profile, externalLabels ...*typesv1.LabelPair) ([]phlaremodel.Labels, []model.Fingerprint) { // build label set per sample type before references are rewritten var ( sb strings.Builder @@ -24,8 +22,8 @@ func CreateProfileLabels(p *profilev1.Profile, externalLabels ...*typesv1.LabelP // Inject into labels the __service_name__ label if it exists // This allows better locality of the data in parquet files (row group are sorted by). - if serviceName := lbls.Labels().Get(phlaremodel.LabelNameServiceName); serviceName != "" { - lbls.Set(labelNameServiceName, serviceName) + if serviceName := phlaremodel.Labels(externalLabels).Get(phlaremodel.LabelNameServiceName); serviceName != "" { + lbls.Set(phlaremodel.LabelNameServiceNamePrivate, serviceName) } // set common labels @@ -56,7 +54,12 @@ func CreateProfileLabels(p *profilev1.Profile, externalLabels ...*typesv1.LabelP _, _ = sb.WriteString(periodUnit) t := sb.String() lbls.Set(phlaremodel.LabelNameProfileType, t) - lbs := lbls.Labels().Clone() + lbs := lbls.LabelsUnsorted().Clone() + if enforceOrder { + sort.Sort(phlaremodel.LabelsEnforcedOrder(lbs)) + } else { + sort.Sort(lbs) + } profilesLabels[pos] = lbs seriesRefs[pos] = model.Fingerprint(lbs.Hash()) diff --git a/pkg/phlaredb/labels/labels_test.go b/pkg/phlaredb/labels/labels_test.go index 2d7b6dc09a..aaf7786362 100644 --- a/pkg/phlaredb/labels/labels_test.go +++ b/pkg/phlaredb/labels/labels_test.go @@ -1,7 +1,6 @@ package labels import ( - "sort" "testing" "github.com/prometheus/common/model" @@ -21,12 +20,12 @@ func TestLabelsForProfiles(t *testing.T) { "default", phlaremodel.Labels{{Name: model.MetricNameLabel, Value: "cpu"}}, phlaremodel.Labels{ - {Name: model.MetricNameLabel, Value: "cpu"}, - {Name: phlaremodel.LabelNameUnit, Value: "unit"}, {Name: phlaremodel.LabelNameProfileType, Value: "cpu:type:unit:type:unit"}, - {Name: phlaremodel.LabelNameType, Value: "type"}, + {Name: model.MetricNameLabel, Value: "cpu"}, {Name: phlaremodel.LabelNamePeriodType, Value: "type"}, {Name: phlaremodel.LabelNamePeriodUnit, Value: "unit"}, + {Name: phlaremodel.LabelNameType, Value: "type"}, + {Name: phlaremodel.LabelNameUnit, Value: "unit"}, }, }, { @@ -36,21 +35,20 @@ func TestLabelsForProfiles(t *testing.T) { {Name: phlaremodel.LabelNameServiceName, Value: "service_name"}, }, phlaremodel.Labels{ - {Name: model.MetricNameLabel, Value: "cpu"}, - {Name: phlaremodel.LabelNameUnit, Value: "unit"}, {Name: phlaremodel.LabelNameProfileType, Value: "cpu:type:unit:type:unit"}, - {Name: phlaremodel.LabelNameType, Value: "type"}, + {Name: phlaremodel.LabelNameServiceNamePrivate, Value: "service_name"}, + {Name: model.MetricNameLabel, Value: "cpu"}, {Name: phlaremodel.LabelNamePeriodType, Value: "type"}, {Name: phlaremodel.LabelNamePeriodUnit, Value: "unit"}, - {Name: labelNameServiceName, Value: "service_name"}, + {Name: phlaremodel.LabelNameType, Value: "type"}, + {Name: phlaremodel.LabelNameUnit, Value: "unit"}, {Name: phlaremodel.LabelNameServiceName, Value: "service_name"}, }, }, } { tt := tt t.Run(tt.name, func(t *testing.T) { - sort.Sort(tt.expected) - result, fps := CreateProfileLabels(newProfileFoo(), tt.in...) + result, fps := CreateProfileLabels(true, newProfileFoo(), tt.in...) require.Equal(t, tt.expected, result[0]) require.Equal(t, model.Fingerprint(tt.expected.Hash()), fps[0]) }) diff --git a/pkg/phlaredb/schemas/v1/testhelper/profile.go b/pkg/phlaredb/schemas/v1/testhelper/profile.go index 0189cdf669..5829b44476 100644 --- a/pkg/phlaredb/schemas/v1/testhelper/profile.go +++ b/pkg/phlaredb/schemas/v1/testhelper/profile.go @@ -14,7 +14,7 @@ import ( func NewProfileSchema(p *profilev1.Profile, name string) ([]schemav1.InMemoryProfile, []phlaremodel.Labels) { var ( - lbls, seriesRefs = labels.CreateProfileLabels(p, &typesv1.LabelPair{Name: model.MetricNameLabel, Value: name}) + lbls, seriesRefs = labels.CreateProfileLabels(true, p, &typesv1.LabelPair{Name: model.MetricNameLabel, Value: name}) ps = make([]schemav1.InMemoryProfile, len(lbls)) ) for idxType := range lbls { diff --git a/pkg/phlaredb/tsdb/index.go b/pkg/phlaredb/tsdb/index.go index ad1ff52e8c..bb8769094d 100644 --- a/pkg/phlaredb/tsdb/index.go +++ b/pkg/phlaredb/tsdb/index.go @@ -298,7 +298,6 @@ func (shard *indexShard) add(metric []*typesv1.LabelPair, fp model.Fingerprint) values.fps[fingerprints.value] = fingerprints internedLabels[i] = &typesv1.LabelPair{Name: values.name, Value: fingerprints.value} } - sort.Sort(internedLabels) return internedLabels } diff --git a/pkg/validation/limits.go b/pkg/validation/limits.go index 64a9fd91b6..4d0131628b 100644 --- a/pkg/validation/limits.go +++ b/pkg/validation/limits.go @@ -33,6 +33,7 @@ type Limits struct { MaxLabelValueLength int `yaml:"max_label_value_length" json:"max_label_value_length"` MaxLabelNamesPerSeries int `yaml:"max_label_names_per_series" json:"max_label_names_per_series"` MaxSessionsPerSeries int `yaml:"max_sessions_per_series" json:"max_sessions_per_series"` + EnforceLabelsOrder bool `yaml:"enforce_labels_order" json:"enforce_labels_order"` MaxProfileSizeBytes int `yaml:"max_profile_size_bytes" json:"max_profile_size_bytes"` MaxProfileStacktraceSamples int `yaml:"max_profile_stacktrace_samples" json:"max_profile_stacktrace_samples"` @@ -109,6 +110,7 @@ func (l *Limits) RegisterFlags(f *flag.FlagSet) { f.IntVar(&l.MaxLabelValueLength, "validation.max-length-label-value", 2048, "Maximum length accepted for label value. This setting also applies to the metric name.") f.IntVar(&l.MaxLabelNamesPerSeries, "validation.max-label-names-per-series", 30, "Maximum number of label names per series.") f.IntVar(&l.MaxSessionsPerSeries, "validation.max-sessions-per-series", 0, "Maximum number of sessions per series. 0 to disable.") + f.BoolVar(&l.EnforceLabelsOrder, "validation.enforce-labels-order", false, "Enforce labels order optimization.") f.IntVar(&l.MaxLocalSeriesPerTenant, "ingester.max-local-series-per-tenant", 0, "Maximum number of active series of profiles per tenant, per ingester. 0 to disable.") f.IntVar(&l.MaxGlobalSeriesPerTenant, "ingester.max-global-series-per-tenant", 5000, "Maximum number of active series of profiles per tenant, across the cluster. 0 to disable. When the global limit is enabled, each ingester is configured with a dynamic local limit based on the replication factor and the current number of healthy ingesters, and is kept updated whenever the number of ingesters change.") @@ -286,6 +288,10 @@ func (o *Overrides) MaxSessionsPerSeries(tenantID string) int { return o.getOverridesForTenant(tenantID).MaxSessionsPerSeries } +func (o *Overrides) EnforceLabelsOrder(tenantID string) bool { + return o.getOverridesForTenant(tenantID).EnforceLabelsOrder +} + func (o *Overrides) DistributorAggregationWindow(tenantID string) model.Duration { return o.getOverridesForTenant(tenantID).DistributorAggregationWindow } From cb303c17bfac19e5fdc4baea467dcce8b5df047b Mon Sep 17 00:00:00 2001 From: Anton Kolesnikov Date: Mon, 10 Jun 2024 22:21:16 +0800 Subject: [PATCH 3/9] fix: goreleaser compatibility issue (#3347) * fix: goreleaser compatibility issue * fix: bump goreleaser dependency --- .github/workflows/weekly-release.yml | 2 +- .goreleaser.yaml | 1 + Makefile | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/weekly-release.yml b/.github/workflows/weekly-release.yml index 16c3e4c9c9..ed9ad108ed 100644 --- a/.github/workflows/weekly-release.yml +++ b/.github/workflows/weekly-release.yml @@ -54,7 +54,7 @@ jobs: # either 'goreleaser' (default) or 'goreleaser-pro': distribution: goreleaser version: latest - args: release --clean --skip-publish --timeout 60m + args: release --clean --skip=publish --timeout 60m env: GITHUB_TOKEN: ${{ steps.app-releaser.outputs.token }} # Your GoReleaser Pro key, if you are using the 'goreleaser-pro' diff --git a/.goreleaser.yaml b/.goreleaser.yaml index d0d52babc8..b114a3d8cc 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -1,4 +1,5 @@ project_name: pyroscope +version: 2 before: hooks: # This hook ensures that goreleaser uses the correct go version for a Pyroscope release diff --git a/Makefile b/Makefile index 90a677a7b5..4d12d055ba 100644 --- a/Makefile +++ b/Makefile @@ -320,7 +320,7 @@ $(BIN)/updater: Makefile $(BIN)/goreleaser: Makefile go.mod @mkdir -p $(@D) - GOBIN=$(abspath $(@D)) $(GO) install github.com/goreleaser/goreleaser@v1.20.0 + GOBIN=$(abspath $(@D)) $(GO) install github.com/goreleaser/goreleaser/v2@v2.0.0 $(BIN)/gotestsum: Makefile go.mod @mkdir -p $(@D) From 25f0771ca960153b447ffce8ae0d61805560ded6 Mon Sep 17 00:00:00 2001 From: Leo <132279615+lpetrazickisupgrade@users.noreply.github.com> Date: Mon, 10 Jun 2024 10:21:40 -0400 Subject: [PATCH 4/9] Omit replicas for components with HPA enabled (#3346) Signed-off-by: Leo Petrazickis --- .../pyroscope/helm/pyroscope/rendered/micro-services-hpa.yaml | 4 ---- .../helm/pyroscope/templates/deployments-statefulsets.yaml | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/operations/pyroscope/helm/pyroscope/rendered/micro-services-hpa.yaml b/operations/pyroscope/helm/pyroscope/rendered/micro-services-hpa.yaml index ad084da3f9..82d9624a6a 100644 --- a/operations/pyroscope/helm/pyroscope/rendered/micro-services-hpa.yaml +++ b/operations/pyroscope/helm/pyroscope/rendered/micro-services-hpa.yaml @@ -2058,7 +2058,6 @@ metadata: app.kubernetes.io/managed-by: Helm app.kubernetes.io/component: "distributor" spec: - replicas: 2 selector: matchLabels: app.kubernetes.io/name: pyroscope @@ -2152,7 +2151,6 @@ metadata: app.kubernetes.io/managed-by: Helm app.kubernetes.io/component: "querier" spec: - replicas: 3 selector: matchLabels: app.kubernetes.io/name: pyroscope @@ -2246,7 +2244,6 @@ metadata: app.kubernetes.io/managed-by: Helm app.kubernetes.io/component: "query-frontend" spec: - replicas: 2 selector: matchLabels: app.kubernetes.io/name: pyroscope @@ -2340,7 +2337,6 @@ metadata: app.kubernetes.io/managed-by: Helm app.kubernetes.io/component: "query-scheduler" spec: - replicas: 2 selector: matchLabels: app.kubernetes.io/name: pyroscope diff --git a/operations/pyroscope/helm/pyroscope/templates/deployments-statefulsets.yaml b/operations/pyroscope/helm/pyroscope/templates/deployments-statefulsets.yaml index f86c3e8bc7..d2bd71a643 100644 --- a/operations/pyroscope/helm/pyroscope/templates/deployments-statefulsets.yaml +++ b/operations/pyroscope/helm/pyroscope/templates/deployments-statefulsets.yaml @@ -17,7 +17,7 @@ spec: serviceName: {{ $values.name }}-headless podManagementPolicy: Parallel {{- end }} -{{- if hasKey $values "replicaCount" }} +{{- if and (hasKey $values "replicaCount") (not ($values.autoscaling).enabled) }} replicas: {{ $values.replicaCount }} {{- end }} selector: From 1bd65d1c01d461c16365117b2ce9a471a6454529 Mon Sep 17 00:00:00 2001 From: Anton Kolesnikov Date: Tue, 11 Jun 2024 12:12:29 +0800 Subject: [PATCH 5/9] perf: optimize tree resolve (#3348) --- pkg/model/stacktraces.go | 60 +++++++++++++++++++++--- pkg/model/tree.go | 10 ++++ pkg/phlaredb/block_querier.go | 12 ++--- pkg/phlaredb/block_querier_test.go | 19 ++++++-- pkg/phlaredb/compact_test.go | 8 ++-- pkg/phlaredb/head_queriers.go | 16 +++---- pkg/phlaredb/sample_merge.go | 4 +- pkg/phlaredb/sample_merge_test.go | 4 +- pkg/phlaredb/symdb/resolver.go | 10 ++-- pkg/phlaredb/symdb/resolver_tree.go | 19 ++++---- pkg/phlaredb/symdb/resolver_tree_test.go | 42 ++++++++++------- pkg/storegateway/bucket_stores_test.go | 2 +- 12 files changed, 147 insertions(+), 59 deletions(-) diff --git a/pkg/model/stacktraces.go b/pkg/model/stacktraces.go index 1aa61b59ef..ca0d1fc203 100644 --- a/pkg/model/stacktraces.go +++ b/pkg/model/stacktraces.go @@ -140,6 +140,18 @@ func NewStacktraceTree(size int) *StacktraceTree { return &t } +func (t *StacktraceTree) Reset() { + if cap(t.Nodes) < 1 { + *t = *(NewStacktraceTree(0)) + return + } + t.Nodes = t.Nodes[:1] + t.Nodes[0] = StacktraceNode{ + FirstChild: sentinel, + NextSibling: sentinel, + } +} + const sentinel = -1 func (t *StacktraceTree) Insert(locations []int32, value int64) int32 { @@ -201,6 +213,7 @@ func (t *StacktraceTree) LookupLocations(dst []uint64, idx int32) []uint64 { // MinValue returns the minimum "total" value a node in a tree has to have. func (t *StacktraceTree) MinValue(maxNodes int64) int64 { + // TODO(kolesnikovae): Consider quickselect. if maxNodes < 1 || maxNodes >= int64(len(t.Nodes)) { return 0 } @@ -225,7 +238,7 @@ func (t *StacktraceTree) MinValue(maxNodes int64) int64 { type StacktraceTreeTraverseFn = func(index int32, children []int32) error func (t *StacktraceTree) Traverse(maxNodes int64, fn StacktraceTreeTraverseFn) error { - min := t.MinValue(maxNodes) + minValue := t.MinValue(maxNodes) children := make([]int32, 0, 128) // Children per node. nodesSize := maxNodes // Depth search buffer. if nodesSize < 1 || nodesSize > 10<<10 { @@ -243,7 +256,7 @@ func (t *StacktraceTree) Traverse(maxNodes int64, fn StacktraceTreeTraverseFn) e for x := n.FirstChild; x > 0; { child := &t.Nodes[x] - if child.Total >= min && child.Location != sentinel { + if child.Total >= minValue && child.Location != sentinel { children = append(children, x) } else { truncated += child.Total @@ -274,8 +287,6 @@ func (t *StacktraceTree) Traverse(maxNodes int64, fn StacktraceTreeTraverseFn) e return nil } -var lostDuringSerializationNameBytes = []byte(truncatedNodeName) - func (t *StacktraceTree) Bytes(dst io.Writer, maxNodes int64, funcs []string) { if len(t.Nodes) == 0 || len(funcs) == 0 { return @@ -290,9 +301,8 @@ func (t *StacktraceTree) Bytes(dst io.Writer, maxNodes int64, funcs []string) { // and the byte slice backing capacity is managed by GC. name = unsafeStringBytes(funcs[n.Location]) case sentinel: - name = lostDuringSerializationNameBytes + name = truncatedNodeNameBytes } - _, _ = vw.Write(dst, uint64(len(name))) _, _ = dst.Write(name) _, _ = vw.Write(dst, uint64(n.Value)) @@ -304,3 +314,41 @@ func (t *StacktraceTree) Bytes(dst io.Writer, maxNodes int64, funcs []string) { func unsafeStringBytes(s string) []byte { return unsafe.Slice(unsafe.StringData(s), len(s)) } + +func (t *StacktraceTree) Tree(maxNodes int64, names []string) *Tree { + if len(t.Nodes) < 2 || len(names) == 0 { + // stack trace tree has root at 0: trees with less + // than 2 nodes are considered empty. + return new(Tree) + } + + nodesSize := maxNodes + if nodesSize < 1 || nodesSize > 10<<10 { + nodesSize = 1 << 10 // Sane default. + } + root := new(node) // Virtual root node. + nodes := make([]*node, 1, nodesSize) + nodes[0] = root + var current *node + + _ = t.Traverse(maxNodes, func(index int32, children []int32) error { + current, nodes = nodes[len(nodes)-1], nodes[:len(nodes)-1] + sn := &t.Nodes[index] + var name string + if sn.Location < 0 { + name = truncatedNodeName + } else { + name = names[sn.Location] + } + n := current.insert(name) + n.self = sn.Value + n.total = sn.Total + n.children = make([]*node, 0, len(children)) + for i := 0; i < len(children); i++ { + nodes = append(nodes, n) + } + return nil + }) + + return &Tree{root: root.children[0].children} +} diff --git a/pkg/model/tree.go b/pkg/model/tree.go index 9077c7e6e7..eb6cbfec73 100644 --- a/pkg/model/tree.go +++ b/pkg/model/tree.go @@ -129,6 +129,14 @@ func (t *Tree) IterateStacks(cb func(name string, self int64, stack []string)) { const defaultDFSSize = 128 func (t *Tree) Merge(src *Tree) { + if t.Total() == 0 && src.Total() > 0 { + *t = *src + return + } + if src.Total() == 0 { + return + } + srcNodes := make([]*node, 0, defaultDFSSize) srcRoot := &node{children: src.root} srcNodes = append(srcNodes, srcRoot) @@ -326,6 +334,8 @@ func (h *minHeap) Pop() interface{} { const truncatedNodeName = "other" +var truncatedNodeNameBytes = []byte(truncatedNodeName) + // MarshalTruncate writes tree byte representation to the writer provider, // the number of nodes is limited to maxNodes. The function modifies // the tree: truncated nodes are removed from the tree. diff --git a/pkg/phlaredb/block_querier.go b/pkg/phlaredb/block_querier.go index 33afa3cf36..e522918ec4 100644 --- a/pkg/phlaredb/block_querier.go +++ b/pkg/phlaredb/block_querier.go @@ -566,14 +566,14 @@ type Querier interface { Open(ctx context.Context) error Sort([]Profile) []Profile - MergeByStacktraces(ctx context.Context, rows iter.Iterator[Profile]) (*phlaremodel.Tree, error) + MergeByStacktraces(ctx context.Context, rows iter.Iterator[Profile], maxNodes int64) (*phlaremodel.Tree, error) MergeBySpans(ctx context.Context, rows iter.Iterator[Profile], spans phlaremodel.SpanSelector) (*phlaremodel.Tree, error) MergeByLabels(ctx context.Context, rows iter.Iterator[Profile], s *typesv1.StackTraceSelector, by ...string) ([]*typesv1.Series, error) MergePprof(ctx context.Context, rows iter.Iterator[Profile], maxNodes int64, s *typesv1.StackTraceSelector) (*profilev1.Profile, error) Series(ctx context.Context, params *ingestv1.SeriesRequest) ([]*typesv1.Labels, error) SelectMatchingProfiles(ctx context.Context, params *ingestv1.SelectProfilesRequest) (iter.Iterator[Profile], error) - SelectMergeByStacktraces(ctx context.Context, params *ingestv1.SelectProfilesRequest) (*phlaremodel.Tree, error) + SelectMergeByStacktraces(ctx context.Context, params *ingestv1.SelectProfilesRequest, maxNodes int64) (*phlaremodel.Tree, error) SelectMergeByLabels(ctx context.Context, params *ingestv1.SelectProfilesRequest, s *typesv1.StackTraceSelector, by ...string) ([]*typesv1.Series, error) SelectMergeBySpans(ctx context.Context, params *ingestv1.SelectSpanProfileRequest) (*phlaremodel.Tree, error) SelectMergePprof(ctx context.Context, params *ingestv1.SelectProfilesRequest, maxNodes int64, s *typesv1.StackTraceSelector) (*profilev1.Profile, error) @@ -835,7 +835,7 @@ func MergeProfilesStacktraces(ctx context.Context, stream *connect.BidiStream[in querier := querier g.Go(util.RecoverPanic(func() error { // TODO(simonswine): Split profiles per row group and run the MergeByStacktraces in parallel. - merge, err := querier.SelectMergeByStacktraces(ctx, request) + merge, err := querier.SelectMergeByStacktraces(ctx, request, r.GetMaxNodes()) if err != nil { return err } @@ -871,7 +871,7 @@ func MergeProfilesStacktraces(ctx context.Context, stream *connect.BidiStream[in // Sort profiles for better read locality. // Merge async the result so we can continue streaming profiles. g.Go(util.RecoverPanic(func() error { - merge, err := querier.MergeByStacktraces(ctx, iter.NewSliceIterator(querier.Sort(selectedProfiles[i]))) + merge, err := querier.MergeByStacktraces(ctx, iter.NewSliceIterator(querier.Sort(selectedProfiles[i])), r.GetMaxNodes()) if err != nil { return err } @@ -1709,7 +1709,7 @@ func (b *singleBlockQuerier) SelectMergeByLabels( return mergeByLabelsWithStackTraceSelector[Profile](ctx, profiles.file, rows, r, by...) } -func (b *singleBlockQuerier) SelectMergeByStacktraces(ctx context.Context, params *ingestv1.SelectProfilesRequest) (tree *phlaremodel.Tree, err error) { +func (b *singleBlockQuerier) SelectMergeByStacktraces(ctx context.Context, params *ingestv1.SelectProfilesRequest, maxNodes int64) (tree *phlaremodel.Tree, err error) { sp, ctx := opentracing.StartSpanFromContext(ctx, "SelectMergeByStacktraces - Block") defer sp.Finish() sp.SetTag("block ULID", b.meta.ULID.String()) @@ -1748,7 +1748,7 @@ func (b *singleBlockQuerier) SelectMergeByStacktraces(ctx context.Context, param } lblsPerRef[int64(chks[0].SeriesIndex)] = struct{}{} } - r := symdb.NewResolver(ctx, b.symbols) + r := symdb.NewResolver(ctx, b.symbols, symdb.WithResolverMaxNodes(maxNodes)) defer r.Release() g, ctx := errgroup.WithContext(ctx) diff --git a/pkg/phlaredb/block_querier_test.go b/pkg/phlaredb/block_querier_test.go index f79d5428e3..4ce7732152 100644 --- a/pkg/phlaredb/block_querier_test.go +++ b/pkg/phlaredb/block_querier_test.go @@ -1163,7 +1163,7 @@ func TestSelectMergeStacktraces(t *testing.T) { }, Start: 0, End: int64(model.TimeFromUnixNano(math.MaxInt64)), - }) + }, 16<<10) require.NoError(t, err) expected := phlaremodel.Tree{} expected.InsertStack(1000, "baz", "bar", "foo") @@ -1293,6 +1293,14 @@ func genPoints(count int) []*typesv1.Point { } func TestSelectMergeByStacktracesRace(t *testing.T) { + testSelectMergeByStacktracesRace(t, 30) +} + +func BenchmarkSelectMergeByStacktracesRace(b *testing.B) { + testSelectMergeByStacktracesRace(b, b.N) +} + +func testSelectMergeByStacktracesRace(t testing.TB, times int) { defer goleak.VerifyNone(t, goleak.IgnoreCurrent()) ctx := context.Background() @@ -1323,7 +1331,12 @@ func TestSelectMergeByStacktracesRace(t *testing.T) { tree := new(phlaremodel.Tree) var m sync.Mutex - for i := 0; i < 30; i++ { + if b, ok := t.(*testing.B); ok { + b.ResetTimer() + b.ReportAllocs() + } + + for i := 0; i < times; i++ { g.Go(func() error { it, err := querier.SelectMatchingProfiles(ctx, &ingesterv1.SelectProfilesRequest{ LabelSelector: `{}`, @@ -1359,7 +1372,7 @@ func TestSelectMergeByStacktracesRace(t *testing.T) { }, Start: 0, End: int64(model.TimeFromUnixNano(math.MaxInt64)), - }) + }, 16<<10) if err != nil { return err } diff --git a/pkg/phlaredb/compact_test.go b/pkg/phlaredb/compact_test.go index e7890ce3bb..e2b5f0ad4e 100644 --- a/pkg/phlaredb/compact_test.go +++ b/pkg/phlaredb/compact_test.go @@ -81,7 +81,7 @@ func TestCompact(t *testing.T) { it, err = querier.SelectMatchingProfiles(ctx, matchAll) require.NoError(t, err) - res, err := querier.MergeByStacktraces(ctx, it) + res, err := querier.MergeByStacktraces(ctx, it, 0) require.NoError(t, err) require.NotNil(t, res) @@ -148,7 +148,7 @@ func TestCompactWithDownsampling(t *testing.T) { it, err = querier.SelectMatchingProfiles(ctx, matchAll) require.NoError(t, err) - res, err := querier.MergeByStacktraces(ctx, it) + res, err := querier.MergeByStacktraces(ctx, it, 0) require.NoError(t, err) require.NotNil(t, res) @@ -156,7 +156,7 @@ func TestCompactWithDownsampling(t *testing.T) { expected.InsertStack(3, "baz", "bar", "foo") require.Equal(t, expected.String(), res.String()) - res, err = querier.SelectMergeByStacktraces(ctx, matchAll) + res, err = querier.SelectMergeByStacktraces(ctx, matchAll, 0) require.NoError(t, err) require.NotNil(t, res) require.Equal(t, expected.String(), res.String()) @@ -281,7 +281,7 @@ func TestCompactWithSplitting(t *testing.T) { // Finally test some stacktraces resolution. it, err = queriers[1].SelectMatchingProfiles(ctx, matchAll) require.NoError(t, err) - res, err := queriers[1].MergeByStacktraces(ctx, it) + res, err := queriers[1].MergeByStacktraces(ctx, it, 0) require.NoError(t, err) expected := new(phlaremodel.Tree) diff --git a/pkg/phlaredb/head_queriers.go b/pkg/phlaredb/head_queriers.go index 3bcf5f43c8..ac5233e45a 100644 --- a/pkg/phlaredb/head_queriers.go +++ b/pkg/phlaredb/head_queriers.go @@ -111,7 +111,7 @@ func (q *headOnDiskQuerier) SelectMatchingProfiles(ctx context.Context, params * return iter.NewSliceIterator(profiles), nil } -func (q *headOnDiskQuerier) SelectMergeByStacktraces(ctx context.Context, params *ingestv1.SelectProfilesRequest) (*phlaremodel.Tree, error) { +func (q *headOnDiskQuerier) SelectMergeByStacktraces(ctx context.Context, params *ingestv1.SelectProfilesRequest, maxNodes int64) (*phlaremodel.Tree, error) { sp, ctx := opentracing.StartSpanFromContext(ctx, "SelectMergeByStacktraces - HeadOnDisk") defer sp.Finish() @@ -137,7 +137,7 @@ func (q *headOnDiskQuerier) SelectMergeByStacktraces(ctx context.Context, params rows := profileRowBatchIterator(it) defer rows.Close() - r := symdb.NewResolver(ctx, q.head.symdb) + r := symdb.NewResolver(ctx, q.head.symdb, symdb.WithResolverMaxNodes(maxNodes)) defer r.Release() if err := mergeByStacktraces[rowProfile](ctx, q.rowGroup(), rows, r); err != nil { @@ -245,10 +245,10 @@ func (q *headOnDiskQuerier) LabelNames(ctx context.Context, req *connect.Request return connect.NewResponse(&typesv1.LabelNamesResponse{}), nil } -func (q *headOnDiskQuerier) MergeByStacktraces(ctx context.Context, rows iter.Iterator[Profile]) (*phlaremodel.Tree, error) { +func (q *headOnDiskQuerier) MergeByStacktraces(ctx context.Context, rows iter.Iterator[Profile], maxNodes int64) (*phlaremodel.Tree, error) { sp, ctx := opentracing.StartSpanFromContext(ctx, "MergeByStacktraces") defer sp.Finish() - r := symdb.NewResolver(ctx, q.head.symdb) + r := symdb.NewResolver(ctx, q.head.symdb, symdb.WithResolverMaxNodes(maxNodes)) defer r.Release() if err := mergeByStacktraces(ctx, q.rowGroup(), rows, r); err != nil { return nil, err @@ -403,10 +403,10 @@ func (q *headInMemoryQuerier) SelectMatchingProfiles(ctx context.Context, params return iter.NewMergeIterator(maxBlockProfile, false, iters...), nil } -func (q *headInMemoryQuerier) SelectMergeByStacktraces(ctx context.Context, params *ingestv1.SelectProfilesRequest) (*phlaremodel.Tree, error) { +func (q *headInMemoryQuerier) SelectMergeByStacktraces(ctx context.Context, params *ingestv1.SelectProfilesRequest, maxNodes int64) (*phlaremodel.Tree, error) { sp, ctx := opentracing.StartSpanFromContext(ctx, "SelectMergeByStacktraces - HeadInMemory") defer sp.Finish() - r := symdb.NewResolver(ctx, q.head.symdb) + r := symdb.NewResolver(ctx, q.head.symdb, symdb.WithResolverMaxNodes(maxNodes)) defer r.Release() index := q.head.profiles.index @@ -551,10 +551,10 @@ func (q *headInMemoryQuerier) LabelNames(ctx context.Context, req *connect.Reque return q.head.LabelNames(ctx, req) } -func (q *headInMemoryQuerier) MergeByStacktraces(ctx context.Context, rows iter.Iterator[Profile]) (*phlaremodel.Tree, error) { +func (q *headInMemoryQuerier) MergeByStacktraces(ctx context.Context, rows iter.Iterator[Profile], maxNodes int64) (*phlaremodel.Tree, error) { sp, _ := opentracing.StartSpanFromContext(ctx, "MergeByStacktraces - HeadInMemory") defer sp.Finish() - r := symdb.NewResolver(ctx, q.head.symdb) + r := symdb.NewResolver(ctx, q.head.symdb, symdb.WithResolverMaxNodes(maxNodes)) defer r.Release() for rows.Next() { p, ok := rows.At().(ProfileWithLabels) diff --git a/pkg/phlaredb/sample_merge.go b/pkg/phlaredb/sample_merge.go index 801c385bdd..411a10bf24 100644 --- a/pkg/phlaredb/sample_merge.go +++ b/pkg/phlaredb/sample_merge.go @@ -20,7 +20,7 @@ import ( "github.com/grafana/pyroscope/pkg/phlaredb/symdb" ) -func (b *singleBlockQuerier) MergeByStacktraces(ctx context.Context, rows iter.Iterator[Profile]) (*phlaremodel.Tree, error) { +func (b *singleBlockQuerier) MergeByStacktraces(ctx context.Context, rows iter.Iterator[Profile], maxNodes int64) (*phlaremodel.Tree, error) { sp, ctx := opentracing.StartSpanFromContext(ctx, "MergeByStacktraces - Block") defer sp.Finish() sp.SetTag("block ULID", b.meta.ULID.String()) @@ -32,7 +32,7 @@ func (b *singleBlockQuerier) MergeByStacktraces(ctx context.Context, rows iter.I defer b.queries.Done() ctx = query.AddMetricsToContext(ctx, b.metrics.query) - r := symdb.NewResolver(ctx, b.symbols) + r := symdb.NewResolver(ctx, b.symbols, symdb.WithResolverMaxNodes(maxNodes)) defer r.Release() if err := mergeByStacktraces(ctx, b.profileSourceTable().file, rows, r); err != nil { return nil, err diff --git a/pkg/phlaredb/sample_merge_test.go b/pkg/phlaredb/sample_merge_test.go index c0ed15086e..3f3568c88c 100644 --- a/pkg/phlaredb/sample_merge_test.go +++ b/pkg/phlaredb/sample_merge_test.go @@ -125,7 +125,7 @@ func TestMergeSampleByStacktraces(t *testing.T) { }) require.NoError(t, err) - r, err := q.queriers[0].MergeByStacktraces(ctx, profiles) + r, err := q.queriers[0].MergeByStacktraces(ctx, profiles, 0) require.NoError(t, err) require.Equal(t, expected.String(), r.String()) }) @@ -220,7 +220,7 @@ func TestHeadMergeSampleByStacktraces(t *testing.T) { End: int64(model.TimeFromUnixNano(int64(1 * time.Minute))), }) require.NoError(t, err) - r, err := db.queriers()[0].MergeByStacktraces(ctx, profiles) + r, err := db.queriers()[0].MergeByStacktraces(ctx, profiles, 0) require.NoError(t, err) require.Equal(t, expected.String(), r.String()) }) diff --git a/pkg/phlaredb/symdb/resolver.go b/pkg/phlaredb/symdb/resolver.go index 8705ae31cd..6049ed0528 100644 --- a/pkg/phlaredb/symdb/resolver.go +++ b/pkg/phlaredb/symdb/resolver.go @@ -223,7 +223,7 @@ func (r *Resolver) Tree() (*model.Tree, error) { var lock sync.Mutex tree := new(model.Tree) err := r.withSymbols(ctx, func(symbols *Symbols, samples schemav1.Samples) error { - resolved, err := symbols.Tree(ctx, samples) + resolved, err := symbols.Tree(ctx, samples, r.maxNodes) if err != nil { return err } @@ -309,14 +309,18 @@ func (r *Symbols) Pprof( return b.buildPprof(), nil } -func (r *Symbols) Tree(ctx context.Context, samples schemav1.Samples) (*model.Tree, error) { +func (r *Symbols) Tree( + ctx context.Context, + samples schemav1.Samples, + maxNodes int64, +) (*model.Tree, error) { t := treeSymbolsFromPool() defer t.reset() t.init(r, samples) if err := r.Stacktraces.ResolveStacktraceLocations(ctx, t, samples.StacktraceIDs); err != nil { return nil, err } - return t.tree, nil + return t.tree.Tree(maxNodes, t.symbols.Strings), nil } // findCallSite returns the stack trace of the call site diff --git a/pkg/phlaredb/symdb/resolver_tree.go b/pkg/phlaredb/symdb/resolver_tree.go index df63fa2880..9deac35547 100644 --- a/pkg/phlaredb/symdb/resolver_tree.go +++ b/pkg/phlaredb/symdb/resolver_tree.go @@ -10,8 +10,8 @@ import ( type treeSymbols struct { symbols *Symbols samples *schemav1.Samples - tree *model.Tree - lines []string + tree *model.StacktraceTree + lines []int32 cur int } @@ -26,7 +26,7 @@ func treeSymbolsFromPool() *treeSymbols { func (r *treeSymbols) reset() { r.symbols = nil r.samples = nil - r.tree = nil + r.tree.Reset() r.lines = r.lines[:0] r.cur = 0 treeSymbolsPool.Put(r) @@ -35,18 +35,21 @@ func (r *treeSymbols) reset() { func (r *treeSymbols) init(symbols *Symbols, samples schemav1.Samples) { r.symbols = symbols r.samples = &samples - r.tree = new(model.Tree) + if r.tree == nil { + // Branching factor. + r.tree = model.NewStacktraceTree(samples.Len() * 2) + } } func (r *treeSymbols) InsertStacktrace(_ uint32, locations []int32) { r.lines = r.lines[:0] - for i := len(locations) - 1; i >= 0; i-- { + for i := 0; i < len(locations); i++ { lines := r.symbols.Locations[locations[i]].Line - for j := len(lines) - 1; j >= 0; j-- { + for j := 0; j < len(lines); j++ { f := r.symbols.Functions[lines[j].FunctionId] - r.lines = append(r.lines, r.symbols.Strings[f.Name]) + r.lines = append(r.lines, int32(f.Name)) } } - r.tree.InsertStack(int64(r.samples.Values[r.cur]), r.lines...) + r.tree.Insert(r.lines, int64(r.samples.Values[r.cur])) r.cur++ } diff --git a/pkg/phlaredb/symdb/resolver_tree_test.go b/pkg/phlaredb/symdb/resolver_tree_test.go index b083929b29..75baf98de8 100644 --- a/pkg/phlaredb/symdb/resolver_tree_test.go +++ b/pkg/phlaredb/symdb/resolver_tree_test.go @@ -5,6 +5,8 @@ import ( "testing" "github.com/stretchr/testify/require" + + v1 "github.com/grafana/pyroscope/pkg/phlaredb/schemas/v1" ) func Test_memory_Resolver_ResolveTree(t *testing.T) { @@ -30,26 +32,34 @@ func Test_block_Resolver_ResolveTree(t *testing.T) { require.Equal(t, expectedFingerprint, treeFingerprint(resolved)) } -func Benchmark_block_Resolver_ResolveTree_Small(t *testing.B) { - s := newMemSuite(t, [][]string{{"testdata/profile.pb.gz"}}) - t.ResetTimer() - t.ReportAllocs() - for i := 0; i < t.N; i++ { - r := NewResolver(context.Background(), s.db) - r.AddSamples(0, s.indexed[0][0].Samples) - _, _ = r.Tree() - } +func Benchmark_Resolver_ResolveTree_Small(b *testing.B) { + s := newMemSuite(b, [][]string{{"testdata/profile.pb.gz"}}) + samples := s.indexed[0][0].Samples + b.Run("0", benchmarkResolverResolveTree(s.db, samples, 0)) + b.Run("1K", benchmarkResolverResolveTree(s.db, samples, 1<<10)) + b.Run("8K", benchmarkResolverResolveTree(s.db, samples, 8<<10)) } -func Benchmark_block_Resolver_ResolveTree_Big(b *testing.B) { +func Benchmark_Resolver_ResolveTree_Big(b *testing.B) { s := memSuite{t: b, files: [][]string{{"testdata/big-profile.pb.gz"}}} s.config = DefaultConfig().WithDirectory(b.TempDir()) s.init() - b.ResetTimer() - b.ReportAllocs() - for i := 0; i < b.N; i++ { - r := NewResolver(context.Background(), s.db) - r.AddSamples(0, s.indexed[0][0].Samples) - _, _ = r.Tree() + samples := s.indexed[0][0].Samples + b.Run("0", benchmarkResolverResolveTree(s.db, samples, 0)) + b.Run("8K", benchmarkResolverResolveTree(s.db, samples, 8<<10)) + b.Run("16K", benchmarkResolverResolveTree(s.db, samples, 16<<10)) + b.Run("32K", benchmarkResolverResolveTree(s.db, samples, 32<<10)) + b.Run("64K", benchmarkResolverResolveTree(s.db, samples, 64<<10)) +} + +func benchmarkResolverResolveTree(sym SymbolsReader, samples v1.Samples, n int64) func(b *testing.B) { + return func(b *testing.B) { + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + r := NewResolver(context.Background(), sym, WithResolverMaxNodes(n)) + r.AddSamples(0, samples) + _, _ = r.Tree() + } } } diff --git a/pkg/storegateway/bucket_stores_test.go b/pkg/storegateway/bucket_stores_test.go index dfbe2cfb05..e490c5cf80 100644 --- a/pkg/storegateway/bucket_stores_test.go +++ b/pkg/storegateway/bucket_stores_test.go @@ -66,7 +66,7 @@ func TestBucketStores_BlockMetricsRegistration(t *testing.T) { }, Start: 0, End: time.Now().UnixMilli(), - }) + }, 0) require.NoError(t, err) require.NotNil(t, r) From e178664f12c5086d50a224697cf09cc37c7528b6 Mon Sep 17 00:00:00 2001 From: Anton Kolesnikov Date: Tue, 11 Jun 2024 18:12:49 +0800 Subject: [PATCH 6/9] perf: eliminate flamegraph merge (#3349) --- api/gen/proto/go/querier/v1/querier.pb.go | 715 ++++++++++-------- .../proto/go/querier/v1/querier_vtproto.pb.go | 168 ++++ api/openapiv2/gen/phlare.swagger.json | 23 + api/querier/v1/querier.proto | 16 + pkg/frontend/frontend_diff.go | 24 +- .../frontend_select_merge_span_profile.go | 21 +- .../frontend_select_merge_stacktraces.go | 37 +- pkg/model/flamegraph.go | 11 + pkg/model/tree.go | 10 + pkg/phlaredb/block_querier.go | 14 +- pkg/querier/querier.go | 24 +- 11 files changed, 710 insertions(+), 353 deletions(-) diff --git a/api/gen/proto/go/querier/v1/querier.pb.go b/api/gen/proto/go/querier/v1/querier.pb.go index 4951cf9b66..3cfa0a2ec6 100644 --- a/api/gen/proto/go/querier/v1/querier.pb.go +++ b/api/gen/proto/go/querier/v1/querier.pb.go @@ -22,6 +22,55 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) +type ProfileFormat int32 + +const ( + ProfileFormat_PROFILE_FORMAT_UNSPECIFIED ProfileFormat = 0 + ProfileFormat_PROFILE_FORMAT_FLAMEGRAPH ProfileFormat = 1 + ProfileFormat_PROFILE_FORMAT_TREE ProfileFormat = 2 +) + +// Enum value maps for ProfileFormat. +var ( + ProfileFormat_name = map[int32]string{ + 0: "PROFILE_FORMAT_UNSPECIFIED", + 1: "PROFILE_FORMAT_FLAMEGRAPH", + 2: "PROFILE_FORMAT_TREE", + } + ProfileFormat_value = map[string]int32{ + "PROFILE_FORMAT_UNSPECIFIED": 0, + "PROFILE_FORMAT_FLAMEGRAPH": 1, + "PROFILE_FORMAT_TREE": 2, + } +) + +func (x ProfileFormat) Enum() *ProfileFormat { + p := new(ProfileFormat) + *p = x + return p +} + +func (x ProfileFormat) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ProfileFormat) Descriptor() protoreflect.EnumDescriptor { + return file_querier_v1_querier_proto_enumTypes[0].Descriptor() +} + +func (ProfileFormat) Type() protoreflect.EnumType { + return &file_querier_v1_querier_proto_enumTypes[0] +} + +func (x ProfileFormat) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ProfileFormat.Descriptor instead. +func (ProfileFormat) EnumDescriptor() ([]byte, []int) { + return file_querier_v1_querier_proto_rawDescGZIP(), []int{0} +} + type ProfileTypesRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -263,6 +312,9 @@ type SelectMergeStacktracesRequest struct { End int64 `protobuf:"varint,4,opt,name=end,proto3" json:"end,omitempty"` // Limit the nodes returned to only show the node with the max_node's biggest total MaxNodes *int64 `protobuf:"varint,5,opt,name=max_nodes,json=maxNodes,proto3,oneof" json:"max_nodes,omitempty"` + // Profile format specifies the format of profile to be returned. + // If not specified, the profile will be returned in flame graph format. + Format ProfileFormat `protobuf:"varint,6,opt,name=format,proto3,enum=querier.v1.ProfileFormat" json:"format,omitempty"` } func (x *SelectMergeStacktracesRequest) Reset() { @@ -332,12 +384,21 @@ func (x *SelectMergeStacktracesRequest) GetMaxNodes() int64 { return 0 } +func (x *SelectMergeStacktracesRequest) GetFormat() ProfileFormat { + if x != nil { + return x.Format + } + return ProfileFormat_PROFILE_FORMAT_UNSPECIFIED +} + type SelectMergeStacktracesResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Flamegraph *FlameGraph `protobuf:"bytes,1,opt,name=flamegraph,proto3" json:"flamegraph,omitempty"` + // Pyroscope tree bytes. + Tree []byte `protobuf:"bytes,2,opt,name=tree,proto3" json:"tree,omitempty"` } func (x *SelectMergeStacktracesResponse) Reset() { @@ -379,6 +440,13 @@ func (x *SelectMergeStacktracesResponse) GetFlamegraph() *FlameGraph { return nil } +func (x *SelectMergeStacktracesResponse) GetTree() []byte { + if x != nil { + return x.Tree + } + return nil +} + type SelectMergeSpanProfileRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -393,6 +461,9 @@ type SelectMergeSpanProfileRequest struct { End int64 `protobuf:"varint,5,opt,name=end,proto3" json:"end,omitempty"` // Limit the nodes returned to only show the node with the max_node's biggest total MaxNodes *int64 `protobuf:"varint,6,opt,name=max_nodes,json=maxNodes,proto3,oneof" json:"max_nodes,omitempty"` + // Profile format specifies the format of profile to be returned. + // If not specified, the profile will be returned in flame graph format. + Format ProfileFormat `protobuf:"varint,7,opt,name=format,proto3,enum=querier.v1.ProfileFormat" json:"format,omitempty"` } func (x *SelectMergeSpanProfileRequest) Reset() { @@ -469,12 +540,21 @@ func (x *SelectMergeSpanProfileRequest) GetMaxNodes() int64 { return 0 } +func (x *SelectMergeSpanProfileRequest) GetFormat() ProfileFormat { + if x != nil { + return x.Format + } + return ProfileFormat_PROFILE_FORMAT_UNSPECIFIED +} + type SelectMergeSpanProfileResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Flamegraph *FlameGraph `protobuf:"bytes,1,opt,name=flamegraph,proto3" json:"flamegraph,omitempty"` + // Pyroscope tree bytes. + Tree []byte `protobuf:"bytes,2,opt,name=tree,proto3" json:"tree,omitempty"` } func (x *SelectMergeSpanProfileResponse) Reset() { @@ -516,6 +596,13 @@ func (x *SelectMergeSpanProfileResponse) GetFlamegraph() *FlameGraph { return nil } +func (x *SelectMergeSpanProfileResponse) GetTree() []byte { + if x != nil { + return x.Tree + } + return nil +} + type DiffRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1389,7 +1476,7 @@ var file_querier_v1_querier_proto_rawDesc = []byte{ 0x12, 0x2f, 0x0a, 0x0a, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x5f, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x52, 0x09, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x53, 0x65, - 0x74, 0x22, 0xc5, 0x01, 0x0a, 0x1d, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x4d, 0x65, 0x72, 0x67, + 0x74, 0x22, 0xf8, 0x01, 0x0a, 0x1d, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x74, 0x72, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x70, 0x72, 0x6f, @@ -1400,236 +1487,251 @@ var file_querier_v1_querier_proto_rawDesc = []byte{ 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x12, 0x20, 0x0a, 0x09, 0x6d, 0x61, 0x78, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x48, 0x00, 0x52, 0x08, - 0x6d, 0x61, 0x78, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x88, 0x01, 0x01, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, - 0x6d, 0x61, 0x78, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x22, 0x58, 0x0a, 0x1e, 0x53, 0x65, 0x6c, - 0x65, 0x63, 0x74, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x74, 0x72, 0x61, - 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x0a, 0x66, - 0x6c, 0x61, 0x6d, 0x65, 0x67, 0x72, 0x61, 0x70, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x16, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x6c, 0x61, - 0x6d, 0x65, 0x47, 0x72, 0x61, 0x70, 0x68, 0x52, 0x0a, 0x66, 0x6c, 0x61, 0x6d, 0x65, 0x67, 0x72, - 0x61, 0x70, 0x68, 0x22, 0xea, 0x01, 0x0a, 0x1d, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x4d, 0x65, - 0x72, 0x67, 0x65, 0x53, 0x70, 0x61, 0x6e, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, - 0x5f, 0x74, 0x79, 0x70, 0x65, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x70, - 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x49, 0x44, 0x12, 0x25, 0x0a, 0x0e, - 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x53, 0x65, 0x6c, 0x65, 0x63, - 0x74, 0x6f, 0x72, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x70, 0x61, 0x6e, 0x5f, 0x73, 0x65, 0x6c, 0x65, - 0x63, 0x74, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x70, 0x61, 0x6e, - 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, - 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x10, - 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x65, 0x6e, 0x64, - 0x12, 0x20, 0x0a, 0x09, 0x6d, 0x61, 0x78, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x03, 0x48, 0x00, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x88, - 0x01, 0x01, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x73, - 0x22, 0x58, 0x0a, 0x1e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x53, - 0x70, 0x61, 0x6e, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x36, 0x0a, 0x0a, 0x66, 0x6c, 0x61, 0x6d, 0x65, 0x67, 0x72, 0x61, 0x70, 0x68, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x6c, 0x61, 0x6d, 0x65, 0x47, 0x72, 0x61, 0x70, 0x68, 0x52, 0x0a, - 0x66, 0x6c, 0x61, 0x6d, 0x65, 0x67, 0x72, 0x61, 0x70, 0x68, 0x22, 0x8d, 0x01, 0x0a, 0x0b, 0x44, - 0x69, 0x66, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3d, 0x0a, 0x04, 0x6c, 0x65, - 0x66, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x69, - 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x4d, 0x65, 0x72, 0x67, - 0x65, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x74, 0x72, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x52, 0x04, 0x6c, 0x65, 0x66, 0x74, 0x12, 0x3f, 0x0a, 0x05, 0x72, 0x69, 0x67, - 0x68, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x69, - 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x4d, 0x65, 0x72, 0x67, - 0x65, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x74, 0x72, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x52, 0x05, 0x72, 0x69, 0x67, 0x68, 0x74, 0x22, 0x4a, 0x0a, 0x0c, 0x44, 0x69, - 0x66, 0x66, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, 0x0a, 0x0a, 0x66, 0x6c, - 0x61, 0x6d, 0x65, 0x67, 0x72, 0x61, 0x70, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, - 0x2e, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x6c, 0x61, 0x6d, - 0x65, 0x47, 0x72, 0x61, 0x70, 0x68, 0x44, 0x69, 0x66, 0x66, 0x52, 0x0a, 0x66, 0x6c, 0x61, 0x6d, - 0x65, 0x67, 0x72, 0x61, 0x70, 0x68, 0x22, 0x7e, 0x0a, 0x0a, 0x46, 0x6c, 0x61, 0x6d, 0x65, 0x47, - 0x72, 0x61, 0x70, 0x68, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x09, 0x52, 0x05, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x29, 0x0a, 0x06, 0x6c, 0x65, - 0x76, 0x65, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x71, 0x75, 0x65, - 0x72, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x06, 0x6c, - 0x65, 0x76, 0x65, 0x6c, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x19, 0x0a, 0x08, 0x6d, - 0x61, 0x78, 0x5f, 0x73, 0x65, 0x6c, 0x66, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x6d, - 0x61, 0x78, 0x53, 0x65, 0x6c, 0x66, 0x22, 0xc0, 0x01, 0x0a, 0x0e, 0x46, 0x6c, 0x61, 0x6d, 0x65, - 0x47, 0x72, 0x61, 0x70, 0x68, 0x44, 0x69, 0x66, 0x66, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x61, 0x6d, - 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x12, - 0x29, 0x0a, 0x06, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x11, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x65, 0x76, - 0x65, 0x6c, 0x52, 0x06, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, - 0x74, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, - 0x12, 0x19, 0x0a, 0x08, 0x6d, 0x61, 0x78, 0x5f, 0x73, 0x65, 0x6c, 0x66, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x07, 0x6d, 0x61, 0x78, 0x53, 0x65, 0x6c, 0x66, 0x12, 0x1c, 0x0a, 0x09, 0x6c, - 0x65, 0x66, 0x74, 0x54, 0x69, 0x63, 0x6b, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, - 0x6c, 0x65, 0x66, 0x74, 0x54, 0x69, 0x63, 0x6b, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x72, 0x69, 0x67, - 0x68, 0x74, 0x54, 0x69, 0x63, 0x6b, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x72, - 0x69, 0x67, 0x68, 0x74, 0x54, 0x69, 0x63, 0x6b, 0x73, 0x22, 0x1f, 0x0a, 0x05, 0x4c, 0x65, 0x76, - 0x65, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x03, 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x22, 0xaf, 0x02, 0x0a, 0x19, 0x53, - 0x65, 0x6c, 0x65, 0x63, 0x74, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, - 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x66, - 0x69, 0x6c, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x49, 0x44, 0x12, - 0x25, 0x0a, 0x0e, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, - 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x53, 0x65, - 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x10, 0x0a, 0x03, - 0x65, 0x6e, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x12, 0x20, - 0x0a, 0x09, 0x6d, 0x61, 0x78, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x03, 0x48, 0x00, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x88, 0x01, 0x01, - 0x12, 0x53, 0x0a, 0x14, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, - 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, - 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x54, - 0x72, 0x61, 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x48, 0x01, 0x52, 0x12, - 0x73, 0x74, 0x61, 0x63, 0x6b, 0x54, 0x72, 0x61, 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, - 0x6f, 0x72, 0x88, 0x01, 0x01, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x6e, 0x6f, - 0x64, 0x65, 0x73, 0x42, 0x17, 0x0a, 0x15, 0x5f, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x5f, 0x74, 0x72, - 0x61, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x22, 0x84, 0x03, 0x0a, - 0x13, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x5f, - 0x74, 0x79, 0x70, 0x65, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x70, 0x72, - 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x49, 0x44, 0x12, 0x25, 0x0a, 0x0e, 0x6c, - 0x61, 0x62, 0x65, 0x6c, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, - 0x6f, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x67, 0x72, - 0x6f, 0x75, 0x70, 0x5f, 0x62, 0x79, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x67, 0x72, - 0x6f, 0x75, 0x70, 0x42, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x74, 0x65, 0x70, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x01, 0x52, 0x04, 0x73, 0x74, 0x65, 0x70, 0x12, 0x4a, 0x0a, 0x0b, 0x61, 0x67, 0x67, - 0x72, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x23, - 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x53, 0x65, - 0x72, 0x69, 0x65, 0x73, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, - 0x79, 0x70, 0x65, 0x48, 0x00, 0x52, 0x0b, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x53, 0x0a, 0x14, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x5f, 0x74, - 0x72, 0x61, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x08, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, - 0x74, 0x61, 0x63, 0x6b, 0x54, 0x72, 0x61, 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, - 0x72, 0x48, 0x01, 0x52, 0x12, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x54, 0x72, 0x61, 0x63, 0x65, 0x53, - 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x88, 0x01, 0x01, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x61, - 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x17, 0x0a, 0x15, 0x5f, 0x73, - 0x74, 0x61, 0x63, 0x6b, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, - 0x74, 0x6f, 0x72, 0x22, 0x40, 0x0a, 0x14, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x53, 0x65, 0x72, - 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, 0x06, 0x73, - 0x65, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x74, 0x79, - 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x52, 0x06, 0x73, - 0x65, 0x72, 0x69, 0x65, 0x73, 0x22, 0x53, 0x0a, 0x13, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x7a, 0x65, - 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, - 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x73, 0x74, 0x61, - 0x72, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x03, 0x65, 0x6e, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0x8d, 0x01, 0x0a, 0x14, 0x41, - 0x6e, 0x61, 0x6c, 0x79, 0x7a, 0x65, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x0c, 0x71, 0x75, 0x65, 0x72, 0x79, 0x5f, 0x73, 0x63, 0x6f, - 0x70, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x71, 0x75, 0x65, 0x72, - 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x63, 0x6f, 0x70, - 0x65, 0x52, 0x0b, 0x71, 0x75, 0x65, 0x72, 0x79, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x12, 0x3a, - 0x0a, 0x0c, 0x71, 0x75, 0x65, 0x72, 0x79, 0x5f, 0x69, 0x6d, 0x70, 0x61, 0x63, 0x74, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x49, 0x6d, 0x70, 0x61, 0x63, 0x74, 0x52, 0x0b, 0x71, - 0x75, 0x65, 0x72, 0x79, 0x49, 0x6d, 0x70, 0x61, 0x63, 0x74, 0x22, 0xd1, 0x02, 0x0a, 0x0a, 0x51, - 0x75, 0x65, 0x72, 0x79, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x63, 0x6f, 0x6d, - 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0d, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, - 0x12, 0x27, 0x0a, 0x0f, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, - 0x6e, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x62, 0x6c, 0x6f, - 0x63, 0x6b, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, - 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, - 0x72, 0x69, 0x65, 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, - 0x52, 0x0b, 0x73, 0x65, 0x72, 0x69, 0x65, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x23, 0x0a, - 0x0d, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x43, 0x6f, 0x75, - 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, - 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5f, 0x62, - 0x79, 0x74, 0x65, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x69, 0x6e, 0x64, 0x65, - 0x78, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, - 0x65, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x70, - 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x73, - 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x0b, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x42, 0x79, 0x74, 0x65, 0x73, 0x22, 0xac, - 0x01, 0x0a, 0x0b, 0x51, 0x75, 0x65, 0x72, 0x79, 0x49, 0x6d, 0x70, 0x61, 0x63, 0x74, 0x12, 0x38, - 0x0a, 0x19, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x69, 0x6e, - 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x15, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x42, 0x79, 0x74, 0x65, 0x73, 0x49, 0x6e, 0x54, - 0x69, 0x6d, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x30, 0x0a, 0x14, 0x74, 0x6f, 0x74, 0x61, - 0x6c, 0x5f, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x64, 0x5f, 0x73, 0x65, 0x72, 0x69, 0x65, 0x73, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x12, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x51, 0x75, 0x65, - 0x72, 0x69, 0x65, 0x64, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x12, 0x31, 0x0a, 0x14, 0x64, 0x65, - 0x64, 0x75, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x65, 0x65, 0x64, - 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x64, 0x65, 0x64, 0x75, 0x70, 0x6c, - 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x65, 0x65, 0x64, 0x65, 0x64, 0x32, 0xbb, 0x07, - 0x0a, 0x0e, 0x51, 0x75, 0x65, 0x72, 0x69, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x12, 0x53, 0x0a, 0x0c, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x73, - 0x12, 0x1f, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, - 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x20, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, - 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4c, 0x0a, 0x0b, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x56, 0x61, - 0x6c, 0x75, 0x65, 0x73, 0x12, 0x1c, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, - 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x61, - 0x62, 0x65, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x00, 0x12, 0x49, 0x0a, 0x0a, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x4e, 0x61, 0x6d, 0x65, - 0x73, 0x12, 0x1b, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x61, 0x62, - 0x65, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, - 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x4e, - 0x61, 0x6d, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x41, - 0x0a, 0x06, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x12, 0x19, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x69, - 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x2e, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x00, 0x12, 0x71, 0x0a, 0x16, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x4d, 0x65, 0x72, 0x67, 0x65, - 0x53, 0x74, 0x61, 0x63, 0x6b, 0x74, 0x72, 0x61, 0x63, 0x65, 0x73, 0x12, 0x29, 0x2e, 0x71, 0x75, - 0x65, 0x72, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x4d, - 0x65, 0x72, 0x67, 0x65, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x74, 0x72, 0x61, 0x63, 0x65, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x72, + 0x6d, 0x61, 0x78, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x88, 0x01, 0x01, 0x12, 0x31, 0x0a, 0x06, 0x66, + 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x71, 0x75, + 0x65, 0x72, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, + 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x52, 0x06, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x42, 0x0c, + 0x0a, 0x0a, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x22, 0x6c, 0x0a, 0x1e, + 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x53, 0x74, 0x61, 0x63, 0x6b, + 0x74, 0x72, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, + 0x0a, 0x0a, 0x66, 0x6c, 0x61, 0x6d, 0x65, 0x67, 0x72, 0x61, 0x70, 0x68, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x46, 0x6c, 0x61, 0x6d, 0x65, 0x47, 0x72, 0x61, 0x70, 0x68, 0x52, 0x0a, 0x66, 0x6c, 0x61, 0x6d, + 0x65, 0x67, 0x72, 0x61, 0x70, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x72, 0x65, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x74, 0x72, 0x65, 0x65, 0x22, 0x9d, 0x02, 0x0a, 0x1d, 0x53, + 0x65, 0x6c, 0x65, 0x63, 0x74, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x53, 0x70, 0x61, 0x6e, 0x50, 0x72, + 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x0e, + 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x49, 0x44, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x54, 0x79, 0x70, + 0x65, 0x49, 0x44, 0x12, 0x25, 0x0a, 0x0e, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x5f, 0x73, 0x65, 0x6c, + 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6c, 0x61, 0x62, + 0x65, 0x6c, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x70, + 0x61, 0x6e, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x0c, 0x73, 0x70, 0x61, 0x6e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, + 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, + 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x12, 0x20, 0x0a, 0x09, 0x6d, 0x61, 0x78, 0x5f, 0x6e, + 0x6f, 0x64, 0x65, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x48, 0x00, 0x52, 0x08, 0x6d, 0x61, + 0x78, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x88, 0x01, 0x01, 0x12, 0x31, 0x0a, 0x06, 0x66, 0x6f, 0x72, + 0x6d, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x71, 0x75, 0x65, 0x72, + 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x46, 0x6f, + 0x72, 0x6d, 0x61, 0x74, 0x52, 0x06, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x42, 0x0c, 0x0a, 0x0a, + 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x22, 0x6c, 0x0a, 0x1e, 0x53, 0x65, + 0x6c, 0x65, 0x63, 0x74, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x53, 0x70, 0x61, 0x6e, 0x50, 0x72, 0x6f, + 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x0a, + 0x66, 0x6c, 0x61, 0x6d, 0x65, 0x67, 0x72, 0x61, 0x70, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x16, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x6c, + 0x61, 0x6d, 0x65, 0x47, 0x72, 0x61, 0x70, 0x68, 0x52, 0x0a, 0x66, 0x6c, 0x61, 0x6d, 0x65, 0x67, + 0x72, 0x61, 0x70, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x72, 0x65, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x04, 0x74, 0x72, 0x65, 0x65, 0x22, 0x8d, 0x01, 0x0a, 0x0b, 0x44, 0x69, 0x66, + 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3d, 0x0a, 0x04, 0x6c, 0x65, 0x66, 0x74, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x53, + 0x74, 0x61, 0x63, 0x6b, 0x74, 0x72, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x52, 0x04, 0x6c, 0x65, 0x66, 0x74, 0x12, 0x3f, 0x0a, 0x05, 0x72, 0x69, 0x67, 0x68, 0x74, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x53, - 0x74, 0x61, 0x63, 0x6b, 0x74, 0x72, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x00, 0x12, 0x71, 0x0a, 0x16, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x4d, 0x65, - 0x72, 0x67, 0x65, 0x53, 0x70, 0x61, 0x6e, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x29, - 0x2e, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6c, 0x65, - 0x63, 0x74, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x53, 0x70, 0x61, 0x6e, 0x50, 0x72, 0x6f, 0x66, 0x69, - 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x71, 0x75, 0x65, 0x72, - 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x4d, 0x65, 0x72, - 0x67, 0x65, 0x53, 0x70, 0x61, 0x6e, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x51, 0x0a, 0x12, 0x53, 0x65, 0x6c, 0x65, 0x63, - 0x74, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x25, 0x2e, + 0x74, 0x61, 0x63, 0x6b, 0x74, 0x72, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x52, 0x05, 0x72, 0x69, 0x67, 0x68, 0x74, 0x22, 0x4a, 0x0a, 0x0c, 0x44, 0x69, 0x66, 0x66, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, 0x0a, 0x0a, 0x66, 0x6c, 0x61, 0x6d, + 0x65, 0x67, 0x72, 0x61, 0x70, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x71, + 0x75, 0x65, 0x72, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x6c, 0x61, 0x6d, 0x65, 0x47, + 0x72, 0x61, 0x70, 0x68, 0x44, 0x69, 0x66, 0x66, 0x52, 0x0a, 0x66, 0x6c, 0x61, 0x6d, 0x65, 0x67, + 0x72, 0x61, 0x70, 0x68, 0x22, 0x7e, 0x0a, 0x0a, 0x46, 0x6c, 0x61, 0x6d, 0x65, 0x47, 0x72, 0x61, + 0x70, 0x68, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x05, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x29, 0x0a, 0x06, 0x6c, 0x65, 0x76, 0x65, + 0x6c, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x69, + 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x06, 0x6c, 0x65, 0x76, + 0x65, 0x6c, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x19, 0x0a, 0x08, 0x6d, 0x61, 0x78, + 0x5f, 0x73, 0x65, 0x6c, 0x66, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x6d, 0x61, 0x78, + 0x53, 0x65, 0x6c, 0x66, 0x22, 0xc0, 0x01, 0x0a, 0x0e, 0x46, 0x6c, 0x61, 0x6d, 0x65, 0x47, 0x72, + 0x61, 0x70, 0x68, 0x44, 0x69, 0x66, 0x66, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x61, 0x6d, 0x65, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x29, 0x0a, + 0x06, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, + 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x65, 0x76, 0x65, 0x6c, + 0x52, 0x06, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, + 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x19, + 0x0a, 0x08, 0x6d, 0x61, 0x78, 0x5f, 0x73, 0x65, 0x6c, 0x66, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x07, 0x6d, 0x61, 0x78, 0x53, 0x65, 0x6c, 0x66, 0x12, 0x1c, 0x0a, 0x09, 0x6c, 0x65, 0x66, + 0x74, 0x54, 0x69, 0x63, 0x6b, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x6c, 0x65, + 0x66, 0x74, 0x54, 0x69, 0x63, 0x6b, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x72, 0x69, 0x67, 0x68, 0x74, + 0x54, 0x69, 0x63, 0x6b, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x72, 0x69, 0x67, + 0x68, 0x74, 0x54, 0x69, 0x63, 0x6b, 0x73, 0x22, 0x1f, 0x0a, 0x05, 0x4c, 0x65, 0x76, 0x65, 0x6c, + 0x12, 0x16, 0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x03, + 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x22, 0xaf, 0x02, 0x0a, 0x19, 0x53, 0x65, 0x6c, + 0x65, 0x63, 0x74, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, + 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, + 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x49, 0x44, 0x12, 0x25, 0x0a, + 0x0e, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x53, 0x65, 0x6c, 0x65, + 0x63, 0x74, 0x6f, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x6e, + 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x12, 0x20, 0x0a, 0x09, + 0x6d, 0x61, 0x78, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x48, + 0x00, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x88, 0x01, 0x01, 0x12, 0x53, + 0x0a, 0x14, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x73, 0x65, + 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x74, + 0x79, 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x54, 0x72, 0x61, + 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x48, 0x01, 0x52, 0x12, 0x73, 0x74, + 0x61, 0x63, 0x6b, 0x54, 0x72, 0x61, 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, + 0x88, 0x01, 0x01, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x6e, 0x6f, 0x64, 0x65, + 0x73, 0x42, 0x17, 0x0a, 0x15, 0x5f, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x5f, 0x74, 0x72, 0x61, 0x63, + 0x65, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x22, 0x84, 0x03, 0x0a, 0x13, 0x53, + 0x65, 0x6c, 0x65, 0x63, 0x74, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x74, 0x79, + 0x70, 0x65, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x66, + 0x69, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x49, 0x44, 0x12, 0x25, 0x0a, 0x0e, 0x6c, 0x61, 0x62, + 0x65, 0x6c, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0d, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, + 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x67, 0x72, 0x6f, 0x75, + 0x70, 0x5f, 0x62, 0x79, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x67, 0x72, 0x6f, 0x75, + 0x70, 0x42, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x74, 0x65, 0x70, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x01, 0x52, 0x04, 0x73, 0x74, 0x65, 0x70, 0x12, 0x4a, 0x0a, 0x0b, 0x61, 0x67, 0x67, 0x72, 0x65, + 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x23, 0x2e, 0x74, + 0x79, 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x69, + 0x65, 0x73, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, + 0x65, 0x48, 0x00, 0x52, 0x0b, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x88, 0x01, 0x01, 0x12, 0x53, 0x0a, 0x14, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x5f, 0x74, 0x72, 0x61, + 0x63, 0x65, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1c, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, + 0x63, 0x6b, 0x54, 0x72, 0x61, 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x48, + 0x01, 0x52, 0x12, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x54, 0x72, 0x61, 0x63, 0x65, 0x53, 0x65, 0x6c, + 0x65, 0x63, 0x74, 0x6f, 0x72, 0x88, 0x01, 0x01, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x61, 0x67, 0x67, + 0x72, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x17, 0x0a, 0x15, 0x5f, 0x73, 0x74, 0x61, + 0x63, 0x6b, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, + 0x72, 0x22, 0x40, 0x0a, 0x14, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x53, 0x65, 0x72, 0x69, 0x65, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, 0x06, 0x73, 0x65, 0x72, + 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x74, 0x79, 0x70, 0x65, + 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x52, 0x06, 0x73, 0x65, 0x72, + 0x69, 0x65, 0x73, 0x22, 0x53, 0x0a, 0x13, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x7a, 0x65, 0x51, 0x75, + 0x65, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, + 0x61, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, + 0x12, 0x10, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x65, + 0x6e, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0x8d, 0x01, 0x0a, 0x14, 0x41, 0x6e, 0x61, + 0x6c, 0x79, 0x7a, 0x65, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x39, 0x0a, 0x0c, 0x71, 0x75, 0x65, 0x72, 0x79, 0x5f, 0x73, 0x63, 0x6f, 0x70, 0x65, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x52, + 0x0b, 0x71, 0x75, 0x65, 0x72, 0x79, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x12, 0x3a, 0x0a, 0x0c, + 0x71, 0x75, 0x65, 0x72, 0x79, 0x5f, 0x69, 0x6d, 0x70, 0x61, 0x63, 0x74, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x51, 0x75, 0x65, 0x72, 0x79, 0x49, 0x6d, 0x70, 0x61, 0x63, 0x74, 0x52, 0x0b, 0x71, 0x75, 0x65, + 0x72, 0x79, 0x49, 0x6d, 0x70, 0x61, 0x63, 0x74, 0x22, 0xd1, 0x02, 0x0a, 0x0a, 0x51, 0x75, 0x65, + 0x72, 0x79, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, + 0x6e, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0d, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x27, + 0x0a, 0x0f, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, + 0x6e, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, + 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x62, 0x6c, + 0x6f, 0x63, 0x6b, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x72, 0x69, + 0x65, 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, + 0x73, 0x65, 0x72, 0x69, 0x65, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x70, + 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, + 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x43, 0x6f, + 0x75, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5f, 0x62, 0x79, 0x74, + 0x65, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x42, + 0x79, 0x74, 0x65, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x5f, + 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x70, 0x72, 0x6f, + 0x66, 0x69, 0x6c, 0x65, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x79, 0x6d, + 0x62, 0x6f, 0x6c, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x0b, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x42, 0x79, 0x74, 0x65, 0x73, 0x22, 0xac, 0x01, 0x0a, + 0x0b, 0x51, 0x75, 0x65, 0x72, 0x79, 0x49, 0x6d, 0x70, 0x61, 0x63, 0x74, 0x12, 0x38, 0x0a, 0x19, + 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x69, 0x6e, 0x5f, 0x74, + 0x69, 0x6d, 0x65, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x15, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x42, 0x79, 0x74, 0x65, 0x73, 0x49, 0x6e, 0x54, 0x69, 0x6d, + 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x30, 0x0a, 0x14, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, + 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x64, 0x5f, 0x73, 0x65, 0x72, 0x69, 0x65, 0x73, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x12, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x51, 0x75, 0x65, 0x72, 0x69, + 0x65, 0x64, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x12, 0x31, 0x0a, 0x14, 0x64, 0x65, 0x64, 0x75, + 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x65, 0x65, 0x64, 0x65, 0x64, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x64, 0x65, 0x64, 0x75, 0x70, 0x6c, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x65, 0x65, 0x64, 0x65, 0x64, 0x2a, 0x67, 0x0a, 0x0d, 0x50, + 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x12, 0x1e, 0x0a, 0x1a, + 0x50, 0x52, 0x4f, 0x46, 0x49, 0x4c, 0x45, 0x5f, 0x46, 0x4f, 0x52, 0x4d, 0x41, 0x54, 0x5f, 0x55, + 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1d, 0x0a, 0x19, + 0x50, 0x52, 0x4f, 0x46, 0x49, 0x4c, 0x45, 0x5f, 0x46, 0x4f, 0x52, 0x4d, 0x41, 0x54, 0x5f, 0x46, + 0x4c, 0x41, 0x4d, 0x45, 0x47, 0x52, 0x41, 0x50, 0x48, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x50, + 0x52, 0x4f, 0x46, 0x49, 0x4c, 0x45, 0x5f, 0x46, 0x4f, 0x52, 0x4d, 0x41, 0x54, 0x5f, 0x54, 0x52, + 0x45, 0x45, 0x10, 0x02, 0x32, 0xbb, 0x07, 0x0a, 0x0e, 0x51, 0x75, 0x65, 0x72, 0x69, 0x65, 0x72, + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x53, 0x0a, 0x0c, 0x50, 0x72, 0x6f, 0x66, 0x69, + 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x73, 0x12, 0x1f, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x69, + 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x54, 0x79, 0x70, + 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4c, 0x0a, 0x0b, + 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x1c, 0x2e, 0x74, 0x79, + 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x56, 0x61, 0x6c, 0x75, + 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x74, 0x79, 0x70, 0x65, + 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x49, 0x0a, 0x0a, 0x4c, 0x61, + 0x62, 0x65, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x1b, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, + 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31, + 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x41, 0x0a, 0x06, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x12, + 0x19, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x72, + 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x71, 0x75, 0x65, + 0x72, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x71, 0x0a, 0x16, 0x53, 0x65, 0x6c, 0x65, + 0x63, 0x74, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x74, 0x72, 0x61, 0x63, + 0x65, 0x73, 0x12, 0x29, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x53, 0x74, 0x61, 0x63, 0x6b, + 0x74, 0x72, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6c, 0x65, 0x63, - 0x74, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x76, 0x31, - 0x2e, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x22, 0x00, 0x12, 0x53, 0x0a, 0x0c, 0x53, 0x65, - 0x6c, 0x65, 0x63, 0x74, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x12, 0x1f, 0x2e, 0x71, 0x75, 0x65, - 0x72, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x53, 0x65, - 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x71, 0x75, - 0x65, 0x72, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x53, - 0x65, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, - 0x3b, 0x0a, 0x04, 0x44, 0x69, 0x66, 0x66, 0x12, 0x17, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x66, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x18, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, - 0x66, 0x66, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x58, 0x0a, 0x0f, - 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, - 0x20, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, - 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x21, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, - 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x53, 0x0a, 0x0c, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x7a, - 0x65, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x1f, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x7a, 0x65, 0x51, 0x75, 0x65, 0x72, 0x79, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x7a, 0x65, 0x51, 0x75, 0x65, 0x72, - 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0xab, 0x01, 0x0a, 0x0e, - 0x63, 0x6f, 0x6d, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x42, 0x0c, - 0x51, 0x75, 0x65, 0x72, 0x69, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x42, - 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x66, 0x61, - 0x6e, 0x61, 0x2f, 0x70, 0x79, 0x72, 0x6f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x2f, 0x61, 0x70, 0x69, - 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x6f, 0x2f, 0x71, 0x75, - 0x65, 0x72, 0x69, 0x65, 0x72, 0x2f, 0x76, 0x31, 0x3b, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x72, - 0x76, 0x31, 0xa2, 0x02, 0x03, 0x51, 0x58, 0x58, 0xaa, 0x02, 0x0a, 0x51, 0x75, 0x65, 0x72, 0x69, - 0x65, 0x72, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x0a, 0x51, 0x75, 0x65, 0x72, 0x69, 0x65, 0x72, 0x5c, - 0x56, 0x31, 0xe2, 0x02, 0x16, 0x51, 0x75, 0x65, 0x72, 0x69, 0x65, 0x72, 0x5c, 0x56, 0x31, 0x5c, - 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x0b, 0x51, 0x75, - 0x65, 0x72, 0x69, 0x65, 0x72, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x33, + 0x74, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x74, 0x72, 0x61, 0x63, 0x65, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x71, 0x0a, 0x16, 0x53, + 0x65, 0x6c, 0x65, 0x63, 0x74, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x53, 0x70, 0x61, 0x6e, 0x50, 0x72, + 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x29, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x72, 0x2e, + 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x53, 0x70, + 0x61, 0x6e, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x2a, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, + 0x6c, 0x65, 0x63, 0x74, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x53, 0x70, 0x61, 0x6e, 0x50, 0x72, 0x6f, + 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x51, + 0x0a, 0x12, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, + 0x66, 0x69, 0x6c, 0x65, 0x12, 0x25, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x72, 0x2e, 0x76, + 0x31, 0x2e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, + 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x22, + 0x00, 0x12, 0x53, 0x0a, 0x0c, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x53, 0x65, 0x72, 0x69, 0x65, + 0x73, 0x12, 0x1f, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, + 0x65, 0x6c, 0x65, 0x63, 0x74, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3b, 0x0a, 0x04, 0x44, 0x69, 0x66, 0x66, 0x12, 0x17, + 0x2e, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x66, 0x66, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x66, 0x66, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x00, 0x12, 0x58, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, + 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x20, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x76, + 0x31, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, + 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x53, 0x74, + 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x53, 0x0a, + 0x0c, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x7a, 0x65, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x1f, 0x2e, + 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x6e, 0x61, 0x6c, 0x79, + 0x7a, 0x65, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, + 0x2e, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x6e, 0x61, 0x6c, + 0x79, 0x7a, 0x65, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x00, 0x42, 0xab, 0x01, 0x0a, 0x0e, 0x63, 0x6f, 0x6d, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x69, + 0x65, 0x72, 0x2e, 0x76, 0x31, 0x42, 0x0c, 0x51, 0x75, 0x65, 0x72, 0x69, 0x65, 0x72, 0x50, 0x72, + 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x42, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x66, 0x61, 0x6e, 0x61, 0x2f, 0x70, 0x79, 0x72, 0x6f, 0x73, 0x63, + 0x6f, 0x70, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2f, 0x67, 0x6f, 0x2f, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x72, 0x2f, 0x76, 0x31, 0x3b, + 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x72, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x51, 0x58, 0x58, 0xaa, + 0x02, 0x0a, 0x51, 0x75, 0x65, 0x72, 0x69, 0x65, 0x72, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x0a, 0x51, + 0x75, 0x65, 0x72, 0x69, 0x65, 0x72, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x16, 0x51, 0x75, 0x65, 0x72, + 0x69, 0x65, 0x72, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x74, 0x61, 0xea, 0x02, 0x0b, 0x51, 0x75, 0x65, 0x72, 0x69, 0x65, 0x72, 0x3a, 0x3a, 0x56, 0x31, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1644,84 +1746,88 @@ func file_querier_v1_querier_proto_rawDescGZIP() []byte { return file_querier_v1_querier_proto_rawDescData } +var file_querier_v1_querier_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_querier_v1_querier_proto_msgTypes = make([]protoimpl.MessageInfo, 20) var file_querier_v1_querier_proto_goTypes = []interface{}{ - (*ProfileTypesRequest)(nil), // 0: querier.v1.ProfileTypesRequest - (*ProfileTypesResponse)(nil), // 1: querier.v1.ProfileTypesResponse - (*SeriesRequest)(nil), // 2: querier.v1.SeriesRequest - (*SeriesResponse)(nil), // 3: querier.v1.SeriesResponse - (*SelectMergeStacktracesRequest)(nil), // 4: querier.v1.SelectMergeStacktracesRequest - (*SelectMergeStacktracesResponse)(nil), // 5: querier.v1.SelectMergeStacktracesResponse - (*SelectMergeSpanProfileRequest)(nil), // 6: querier.v1.SelectMergeSpanProfileRequest - (*SelectMergeSpanProfileResponse)(nil), // 7: querier.v1.SelectMergeSpanProfileResponse - (*DiffRequest)(nil), // 8: querier.v1.DiffRequest - (*DiffResponse)(nil), // 9: querier.v1.DiffResponse - (*FlameGraph)(nil), // 10: querier.v1.FlameGraph - (*FlameGraphDiff)(nil), // 11: querier.v1.FlameGraphDiff - (*Level)(nil), // 12: querier.v1.Level - (*SelectMergeProfileRequest)(nil), // 13: querier.v1.SelectMergeProfileRequest - (*SelectSeriesRequest)(nil), // 14: querier.v1.SelectSeriesRequest - (*SelectSeriesResponse)(nil), // 15: querier.v1.SelectSeriesResponse - (*AnalyzeQueryRequest)(nil), // 16: querier.v1.AnalyzeQueryRequest - (*AnalyzeQueryResponse)(nil), // 17: querier.v1.AnalyzeQueryResponse - (*QueryScope)(nil), // 18: querier.v1.QueryScope - (*QueryImpact)(nil), // 19: querier.v1.QueryImpact - (*v1.ProfileType)(nil), // 20: types.v1.ProfileType - (*v1.Labels)(nil), // 21: types.v1.Labels - (*v1.StackTraceSelector)(nil), // 22: types.v1.StackTraceSelector - (v1.TimeSeriesAggregationType)(0), // 23: types.v1.TimeSeriesAggregationType - (*v1.Series)(nil), // 24: types.v1.Series - (*v1.LabelValuesRequest)(nil), // 25: types.v1.LabelValuesRequest - (*v1.LabelNamesRequest)(nil), // 26: types.v1.LabelNamesRequest - (*v1.GetProfileStatsRequest)(nil), // 27: types.v1.GetProfileStatsRequest - (*v1.LabelValuesResponse)(nil), // 28: types.v1.LabelValuesResponse - (*v1.LabelNamesResponse)(nil), // 29: types.v1.LabelNamesResponse - (*v11.Profile)(nil), // 30: google.v1.Profile - (*v1.GetProfileStatsResponse)(nil), // 31: types.v1.GetProfileStatsResponse + (ProfileFormat)(0), // 0: querier.v1.ProfileFormat + (*ProfileTypesRequest)(nil), // 1: querier.v1.ProfileTypesRequest + (*ProfileTypesResponse)(nil), // 2: querier.v1.ProfileTypesResponse + (*SeriesRequest)(nil), // 3: querier.v1.SeriesRequest + (*SeriesResponse)(nil), // 4: querier.v1.SeriesResponse + (*SelectMergeStacktracesRequest)(nil), // 5: querier.v1.SelectMergeStacktracesRequest + (*SelectMergeStacktracesResponse)(nil), // 6: querier.v1.SelectMergeStacktracesResponse + (*SelectMergeSpanProfileRequest)(nil), // 7: querier.v1.SelectMergeSpanProfileRequest + (*SelectMergeSpanProfileResponse)(nil), // 8: querier.v1.SelectMergeSpanProfileResponse + (*DiffRequest)(nil), // 9: querier.v1.DiffRequest + (*DiffResponse)(nil), // 10: querier.v1.DiffResponse + (*FlameGraph)(nil), // 11: querier.v1.FlameGraph + (*FlameGraphDiff)(nil), // 12: querier.v1.FlameGraphDiff + (*Level)(nil), // 13: querier.v1.Level + (*SelectMergeProfileRequest)(nil), // 14: querier.v1.SelectMergeProfileRequest + (*SelectSeriesRequest)(nil), // 15: querier.v1.SelectSeriesRequest + (*SelectSeriesResponse)(nil), // 16: querier.v1.SelectSeriesResponse + (*AnalyzeQueryRequest)(nil), // 17: querier.v1.AnalyzeQueryRequest + (*AnalyzeQueryResponse)(nil), // 18: querier.v1.AnalyzeQueryResponse + (*QueryScope)(nil), // 19: querier.v1.QueryScope + (*QueryImpact)(nil), // 20: querier.v1.QueryImpact + (*v1.ProfileType)(nil), // 21: types.v1.ProfileType + (*v1.Labels)(nil), // 22: types.v1.Labels + (*v1.StackTraceSelector)(nil), // 23: types.v1.StackTraceSelector + (v1.TimeSeriesAggregationType)(0), // 24: types.v1.TimeSeriesAggregationType + (*v1.Series)(nil), // 25: types.v1.Series + (*v1.LabelValuesRequest)(nil), // 26: types.v1.LabelValuesRequest + (*v1.LabelNamesRequest)(nil), // 27: types.v1.LabelNamesRequest + (*v1.GetProfileStatsRequest)(nil), // 28: types.v1.GetProfileStatsRequest + (*v1.LabelValuesResponse)(nil), // 29: types.v1.LabelValuesResponse + (*v1.LabelNamesResponse)(nil), // 30: types.v1.LabelNamesResponse + (*v11.Profile)(nil), // 31: google.v1.Profile + (*v1.GetProfileStatsResponse)(nil), // 32: types.v1.GetProfileStatsResponse } var file_querier_v1_querier_proto_depIdxs = []int32{ - 20, // 0: querier.v1.ProfileTypesResponse.profile_types:type_name -> types.v1.ProfileType - 21, // 1: querier.v1.SeriesResponse.labels_set:type_name -> types.v1.Labels - 10, // 2: querier.v1.SelectMergeStacktracesResponse.flamegraph:type_name -> querier.v1.FlameGraph - 10, // 3: querier.v1.SelectMergeSpanProfileResponse.flamegraph:type_name -> querier.v1.FlameGraph - 4, // 4: querier.v1.DiffRequest.left:type_name -> querier.v1.SelectMergeStacktracesRequest - 4, // 5: querier.v1.DiffRequest.right:type_name -> querier.v1.SelectMergeStacktracesRequest - 11, // 6: querier.v1.DiffResponse.flamegraph:type_name -> querier.v1.FlameGraphDiff - 12, // 7: querier.v1.FlameGraph.levels:type_name -> querier.v1.Level - 12, // 8: querier.v1.FlameGraphDiff.levels:type_name -> querier.v1.Level - 22, // 9: querier.v1.SelectMergeProfileRequest.stack_trace_selector:type_name -> types.v1.StackTraceSelector - 23, // 10: querier.v1.SelectSeriesRequest.aggregation:type_name -> types.v1.TimeSeriesAggregationType - 22, // 11: querier.v1.SelectSeriesRequest.stack_trace_selector:type_name -> types.v1.StackTraceSelector - 24, // 12: querier.v1.SelectSeriesResponse.series:type_name -> types.v1.Series - 18, // 13: querier.v1.AnalyzeQueryResponse.query_scopes:type_name -> querier.v1.QueryScope - 19, // 14: querier.v1.AnalyzeQueryResponse.query_impact:type_name -> querier.v1.QueryImpact - 0, // 15: querier.v1.QuerierService.ProfileTypes:input_type -> querier.v1.ProfileTypesRequest - 25, // 16: querier.v1.QuerierService.LabelValues:input_type -> types.v1.LabelValuesRequest - 26, // 17: querier.v1.QuerierService.LabelNames:input_type -> types.v1.LabelNamesRequest - 2, // 18: querier.v1.QuerierService.Series:input_type -> querier.v1.SeriesRequest - 4, // 19: querier.v1.QuerierService.SelectMergeStacktraces:input_type -> querier.v1.SelectMergeStacktracesRequest - 6, // 20: querier.v1.QuerierService.SelectMergeSpanProfile:input_type -> querier.v1.SelectMergeSpanProfileRequest - 13, // 21: querier.v1.QuerierService.SelectMergeProfile:input_type -> querier.v1.SelectMergeProfileRequest - 14, // 22: querier.v1.QuerierService.SelectSeries:input_type -> querier.v1.SelectSeriesRequest - 8, // 23: querier.v1.QuerierService.Diff:input_type -> querier.v1.DiffRequest - 27, // 24: querier.v1.QuerierService.GetProfileStats:input_type -> types.v1.GetProfileStatsRequest - 16, // 25: querier.v1.QuerierService.AnalyzeQuery:input_type -> querier.v1.AnalyzeQueryRequest - 1, // 26: querier.v1.QuerierService.ProfileTypes:output_type -> querier.v1.ProfileTypesResponse - 28, // 27: querier.v1.QuerierService.LabelValues:output_type -> types.v1.LabelValuesResponse - 29, // 28: querier.v1.QuerierService.LabelNames:output_type -> types.v1.LabelNamesResponse - 3, // 29: querier.v1.QuerierService.Series:output_type -> querier.v1.SeriesResponse - 5, // 30: querier.v1.QuerierService.SelectMergeStacktraces:output_type -> querier.v1.SelectMergeStacktracesResponse - 7, // 31: querier.v1.QuerierService.SelectMergeSpanProfile:output_type -> querier.v1.SelectMergeSpanProfileResponse - 30, // 32: querier.v1.QuerierService.SelectMergeProfile:output_type -> google.v1.Profile - 15, // 33: querier.v1.QuerierService.SelectSeries:output_type -> querier.v1.SelectSeriesResponse - 9, // 34: querier.v1.QuerierService.Diff:output_type -> querier.v1.DiffResponse - 31, // 35: querier.v1.QuerierService.GetProfileStats:output_type -> types.v1.GetProfileStatsResponse - 17, // 36: querier.v1.QuerierService.AnalyzeQuery:output_type -> querier.v1.AnalyzeQueryResponse - 26, // [26:37] is the sub-list for method output_type - 15, // [15:26] is the sub-list for method input_type - 15, // [15:15] is the sub-list for extension type_name - 15, // [15:15] is the sub-list for extension extendee - 0, // [0:15] is the sub-list for field type_name + 21, // 0: querier.v1.ProfileTypesResponse.profile_types:type_name -> types.v1.ProfileType + 22, // 1: querier.v1.SeriesResponse.labels_set:type_name -> types.v1.Labels + 0, // 2: querier.v1.SelectMergeStacktracesRequest.format:type_name -> querier.v1.ProfileFormat + 11, // 3: querier.v1.SelectMergeStacktracesResponse.flamegraph:type_name -> querier.v1.FlameGraph + 0, // 4: querier.v1.SelectMergeSpanProfileRequest.format:type_name -> querier.v1.ProfileFormat + 11, // 5: querier.v1.SelectMergeSpanProfileResponse.flamegraph:type_name -> querier.v1.FlameGraph + 5, // 6: querier.v1.DiffRequest.left:type_name -> querier.v1.SelectMergeStacktracesRequest + 5, // 7: querier.v1.DiffRequest.right:type_name -> querier.v1.SelectMergeStacktracesRequest + 12, // 8: querier.v1.DiffResponse.flamegraph:type_name -> querier.v1.FlameGraphDiff + 13, // 9: querier.v1.FlameGraph.levels:type_name -> querier.v1.Level + 13, // 10: querier.v1.FlameGraphDiff.levels:type_name -> querier.v1.Level + 23, // 11: querier.v1.SelectMergeProfileRequest.stack_trace_selector:type_name -> types.v1.StackTraceSelector + 24, // 12: querier.v1.SelectSeriesRequest.aggregation:type_name -> types.v1.TimeSeriesAggregationType + 23, // 13: querier.v1.SelectSeriesRequest.stack_trace_selector:type_name -> types.v1.StackTraceSelector + 25, // 14: querier.v1.SelectSeriesResponse.series:type_name -> types.v1.Series + 19, // 15: querier.v1.AnalyzeQueryResponse.query_scopes:type_name -> querier.v1.QueryScope + 20, // 16: querier.v1.AnalyzeQueryResponse.query_impact:type_name -> querier.v1.QueryImpact + 1, // 17: querier.v1.QuerierService.ProfileTypes:input_type -> querier.v1.ProfileTypesRequest + 26, // 18: querier.v1.QuerierService.LabelValues:input_type -> types.v1.LabelValuesRequest + 27, // 19: querier.v1.QuerierService.LabelNames:input_type -> types.v1.LabelNamesRequest + 3, // 20: querier.v1.QuerierService.Series:input_type -> querier.v1.SeriesRequest + 5, // 21: querier.v1.QuerierService.SelectMergeStacktraces:input_type -> querier.v1.SelectMergeStacktracesRequest + 7, // 22: querier.v1.QuerierService.SelectMergeSpanProfile:input_type -> querier.v1.SelectMergeSpanProfileRequest + 14, // 23: querier.v1.QuerierService.SelectMergeProfile:input_type -> querier.v1.SelectMergeProfileRequest + 15, // 24: querier.v1.QuerierService.SelectSeries:input_type -> querier.v1.SelectSeriesRequest + 9, // 25: querier.v1.QuerierService.Diff:input_type -> querier.v1.DiffRequest + 28, // 26: querier.v1.QuerierService.GetProfileStats:input_type -> types.v1.GetProfileStatsRequest + 17, // 27: querier.v1.QuerierService.AnalyzeQuery:input_type -> querier.v1.AnalyzeQueryRequest + 2, // 28: querier.v1.QuerierService.ProfileTypes:output_type -> querier.v1.ProfileTypesResponse + 29, // 29: querier.v1.QuerierService.LabelValues:output_type -> types.v1.LabelValuesResponse + 30, // 30: querier.v1.QuerierService.LabelNames:output_type -> types.v1.LabelNamesResponse + 4, // 31: querier.v1.QuerierService.Series:output_type -> querier.v1.SeriesResponse + 6, // 32: querier.v1.QuerierService.SelectMergeStacktraces:output_type -> querier.v1.SelectMergeStacktracesResponse + 8, // 33: querier.v1.QuerierService.SelectMergeSpanProfile:output_type -> querier.v1.SelectMergeSpanProfileResponse + 31, // 34: querier.v1.QuerierService.SelectMergeProfile:output_type -> google.v1.Profile + 16, // 35: querier.v1.QuerierService.SelectSeries:output_type -> querier.v1.SelectSeriesResponse + 10, // 36: querier.v1.QuerierService.Diff:output_type -> querier.v1.DiffResponse + 32, // 37: querier.v1.QuerierService.GetProfileStats:output_type -> types.v1.GetProfileStatsResponse + 18, // 38: querier.v1.QuerierService.AnalyzeQuery:output_type -> querier.v1.AnalyzeQueryResponse + 28, // [28:39] is the sub-list for method output_type + 17, // [17:28] is the sub-list for method input_type + 17, // [17:17] is the sub-list for extension type_name + 17, // [17:17] is the sub-list for extension extendee + 0, // [0:17] is the sub-list for field type_name } func init() { file_querier_v1_querier_proto_init() } @@ -1980,13 +2086,14 @@ func file_querier_v1_querier_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_querier_v1_querier_proto_rawDesc, - NumEnums: 0, + NumEnums: 1, NumMessages: 20, NumExtensions: 0, NumServices: 1, }, GoTypes: file_querier_v1_querier_proto_goTypes, DependencyIndexes: file_querier_v1_querier_proto_depIdxs, + EnumInfos: file_querier_v1_querier_proto_enumTypes, MessageInfos: file_querier_v1_querier_proto_msgTypes, }.Build() File_querier_v1_querier_proto = out.File diff --git a/api/gen/proto/go/querier/v1/querier_vtproto.pb.go b/api/gen/proto/go/querier/v1/querier_vtproto.pb.go index af659588c7..fd94196998 100644 --- a/api/gen/proto/go/querier/v1/querier_vtproto.pb.go +++ b/api/gen/proto/go/querier/v1/querier_vtproto.pb.go @@ -136,6 +136,7 @@ func (m *SelectMergeStacktracesRequest) CloneVT() *SelectMergeStacktracesRequest r.LabelSelector = m.LabelSelector r.Start = m.Start r.End = m.End + r.Format = m.Format if rhs := m.MaxNodes; rhs != nil { tmpVal := *rhs r.MaxNodes = &tmpVal @@ -157,6 +158,11 @@ func (m *SelectMergeStacktracesResponse) CloneVT() *SelectMergeStacktracesRespon } r := new(SelectMergeStacktracesResponse) r.Flamegraph = m.Flamegraph.CloneVT() + if rhs := m.Tree; rhs != nil { + tmpBytes := make([]byte, len(rhs)) + copy(tmpBytes, rhs) + r.Tree = tmpBytes + } if len(m.unknownFields) > 0 { r.unknownFields = make([]byte, len(m.unknownFields)) copy(r.unknownFields, m.unknownFields) @@ -177,6 +183,7 @@ func (m *SelectMergeSpanProfileRequest) CloneVT() *SelectMergeSpanProfileRequest r.LabelSelector = m.LabelSelector r.Start = m.Start r.End = m.End + r.Format = m.Format if rhs := m.SpanSelector; rhs != nil { tmpContainer := make([]string, len(rhs)) copy(tmpContainer, rhs) @@ -203,6 +210,11 @@ func (m *SelectMergeSpanProfileResponse) CloneVT() *SelectMergeSpanProfileRespon } r := new(SelectMergeSpanProfileResponse) r.Flamegraph = m.Flamegraph.CloneVT() + if rhs := m.Tree; rhs != nil { + tmpBytes := make([]byte, len(rhs)) + copy(tmpBytes, rhs) + r.Tree = tmpBytes + } if len(m.unknownFields) > 0 { r.unknownFields = make([]byte, len(m.unknownFields)) copy(r.unknownFields, m.unknownFields) @@ -671,6 +683,9 @@ func (this *SelectMergeStacktracesRequest) EqualVT(that *SelectMergeStacktracesR if p, q := this.MaxNodes, that.MaxNodes; (p == nil && q != nil) || (p != nil && (q == nil || *p != *q)) { return false } + if this.Format != that.Format { + return false + } return string(this.unknownFields) == string(that.unknownFields) } @@ -690,6 +705,9 @@ func (this *SelectMergeStacktracesResponse) EqualVT(that *SelectMergeStacktraces if !this.Flamegraph.EqualVT(that.Flamegraph) { return false } + if string(this.Tree) != string(that.Tree) { + return false + } return string(this.unknownFields) == string(that.unknownFields) } @@ -730,6 +748,9 @@ func (this *SelectMergeSpanProfileRequest) EqualVT(that *SelectMergeSpanProfileR if p, q := this.MaxNodes, that.MaxNodes; (p == nil && q != nil) || (p != nil && (q == nil || *p != *q)) { return false } + if this.Format != that.Format { + return false + } return string(this.unknownFields) == string(that.unknownFields) } @@ -749,6 +770,9 @@ func (this *SelectMergeSpanProfileResponse) EqualVT(that *SelectMergeSpanProfile if !this.Flamegraph.EqualVT(that.Flamegraph) { return false } + if string(this.Tree) != string(that.Tree) { + return false + } return string(this.unknownFields) == string(that.unknownFields) } @@ -1905,6 +1929,11 @@ func (m *SelectMergeStacktracesRequest) MarshalToSizedBufferVT(dAtA []byte) (int i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } + if m.Format != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Format)) + i-- + dAtA[i] = 0x30 + } if m.MaxNodes != nil { i = protohelpers.EncodeVarint(dAtA, i, uint64(*m.MaxNodes)) i-- @@ -1967,6 +1996,13 @@ func (m *SelectMergeStacktracesResponse) MarshalToSizedBufferVT(dAtA []byte) (in i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } + if len(m.Tree) > 0 { + i -= len(m.Tree) + copy(dAtA[i:], m.Tree) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Tree))) + i-- + dAtA[i] = 0x12 + } if m.Flamegraph != nil { size, err := m.Flamegraph.MarshalToSizedBufferVT(dAtA[:i]) if err != nil { @@ -2010,6 +2046,11 @@ func (m *SelectMergeSpanProfileRequest) MarshalToSizedBufferVT(dAtA []byte) (int i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } + if m.Format != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Format)) + i-- + dAtA[i] = 0x38 + } if m.MaxNodes != nil { i = protohelpers.EncodeVarint(dAtA, i, uint64(*m.MaxNodes)) i-- @@ -2081,6 +2122,13 @@ func (m *SelectMergeSpanProfileResponse) MarshalToSizedBufferVT(dAtA []byte) (in i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } + if len(m.Tree) > 0 { + i -= len(m.Tree) + copy(dAtA[i:], m.Tree) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Tree))) + i-- + dAtA[i] = 0x12 + } if m.Flamegraph != nil { size, err := m.Flamegraph.MarshalToSizedBufferVT(dAtA[:i]) if err != nil { @@ -2971,6 +3019,9 @@ func (m *SelectMergeStacktracesRequest) SizeVT() (n int) { if m.MaxNodes != nil { n += 1 + protohelpers.SizeOfVarint(uint64(*m.MaxNodes)) } + if m.Format != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.Format)) + } n += len(m.unknownFields) return n } @@ -2985,6 +3036,10 @@ func (m *SelectMergeStacktracesResponse) SizeVT() (n int) { l = m.Flamegraph.SizeVT() n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } + l = len(m.Tree) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } n += len(m.unknownFields) return n } @@ -3018,6 +3073,9 @@ func (m *SelectMergeSpanProfileRequest) SizeVT() (n int) { if m.MaxNodes != nil { n += 1 + protohelpers.SizeOfVarint(uint64(*m.MaxNodes)) } + if m.Format != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.Format)) + } n += len(m.unknownFields) return n } @@ -3032,6 +3090,10 @@ func (m *SelectMergeSpanProfileResponse) SizeVT() (n int) { l = m.Flamegraph.SizeVT() n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } + l = len(m.Tree) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } n += len(m.unknownFields) return n } @@ -3928,6 +3990,25 @@ func (m *SelectMergeStacktracesRequest) UnmarshalVT(dAtA []byte) error { } } m.MaxNodes = &v + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Format", wireType) + } + m.Format = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Format |= ProfileFormat(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) @@ -4015,6 +4096,40 @@ func (m *SelectMergeStacktracesResponse) UnmarshalVT(dAtA []byte) error { return err } iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Tree", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Tree = append(m.Tree[:0], dAtA[iNdEx:postIndex]...) + if m.Tree == nil { + m.Tree = []byte{} + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) @@ -4220,6 +4335,25 @@ func (m *SelectMergeSpanProfileRequest) UnmarshalVT(dAtA []byte) error { } } m.MaxNodes = &v + case 7: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Format", wireType) + } + m.Format = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Format |= ProfileFormat(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) @@ -4307,6 +4441,40 @@ func (m *SelectMergeSpanProfileResponse) UnmarshalVT(dAtA []byte) error { return err } iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Tree", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Tree = append(m.Tree[:0], dAtA[iNdEx:postIndex]...) + if m.Tree == nil { + m.Tree = []byte{} + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) diff --git a/api/openapiv2/gen/phlare.swagger.json b/api/openapiv2/gen/phlare.swagger.json index 145f6a2369..5fe7102344 100644 --- a/api/openapiv2/gen/phlare.swagger.json +++ b/api/openapiv2/gen/phlare.swagger.json @@ -1063,6 +1063,15 @@ } } }, + "v1ProfileFormat": { + "type": "string", + "enum": [ + "PROFILE_FORMAT_UNSPECIFIED", + "PROFILE_FORMAT_FLAMEGRAPH", + "PROFILE_FORMAT_TREE" + ], + "default": "PROFILE_FORMAT_UNSPECIFIED" + }, "v1ProfileSets": { "type": "object", "properties": { @@ -1238,6 +1247,11 @@ "properties": { "flamegraph": { "$ref": "#/definitions/v1FlameGraph" + }, + "tree": { + "type": "string", + "format": "byte", + "description": "Pyroscope tree bytes." } } }, @@ -1264,6 +1278,10 @@ "type": "string", "format": "int64", "title": "Limit the nodes returned to only show the node with the max_node's biggest total" + }, + "format": { + "$ref": "#/definitions/v1ProfileFormat", + "description": "Profile format specifies the format of profile to be returned.\nIf not specified, the profile will be returned in flame graph format." } } }, @@ -1272,6 +1290,11 @@ "properties": { "flamegraph": { "$ref": "#/definitions/v1FlameGraph" + }, + "tree": { + "type": "string", + "format": "byte", + "description": "Pyroscope tree bytes." } } }, diff --git a/api/querier/v1/querier.proto b/api/querier/v1/querier.proto index 3192ecc663..78ae0da066 100644 --- a/api/querier/v1/querier.proto +++ b/api/querier/v1/querier.proto @@ -68,10 +68,21 @@ message SelectMergeStacktracesRequest { int64 end = 4; // Limit the nodes returned to only show the node with the max_node's biggest total optional int64 max_nodes = 5; + // Profile format specifies the format of profile to be returned. + // If not specified, the profile will be returned in flame graph format. + ProfileFormat format = 6; +} + +enum ProfileFormat { + PROFILE_FORMAT_UNSPECIFIED = 0; + PROFILE_FORMAT_FLAMEGRAPH = 1; + PROFILE_FORMAT_TREE = 2; } message SelectMergeStacktracesResponse { FlameGraph flamegraph = 1; + // Pyroscope tree bytes. + bytes tree = 2; } message SelectMergeSpanProfileRequest { @@ -84,10 +95,15 @@ message SelectMergeSpanProfileRequest { int64 end = 5; // Limit the nodes returned to only show the node with the max_node's biggest total optional int64 max_nodes = 6; + // Profile format specifies the format of profile to be returned. + // If not specified, the profile will be returned in flame graph format. + ProfileFormat format = 7; } message SelectMergeSpanProfileResponse { FlameGraph flamegraph = 1; + // Pyroscope tree bytes. + bytes tree = 2; } message DiffRequest { diff --git a/pkg/frontend/frontend_diff.go b/pkg/frontend/frontend_diff.go index 46745599d2..1390770776 100644 --- a/pkg/frontend/frontend_diff.go +++ b/pkg/frontend/frontend_diff.go @@ -38,26 +38,16 @@ func (f *Frontend) Diff(ctx context.Context, var left, right *phlaremodel.Tree g.Go(func() error { - resp, err := f.SelectMergeStacktraces(ctx, connect.NewRequest(c.Msg.Left)) - if err != nil { - return err - } - m := phlaremodel.NewFlameGraphMerger() - m.MergeFlameGraph(resp.Msg.Flamegraph) - left = m.Tree() - return err + var leftErr error + left, leftErr = f.selectMergeStacktracesTree(ctx, connect.NewRequest(c.Msg.Left)) + return leftErr }) g.Go(func() error { - resp, err := f.SelectMergeStacktraces(ctx, connect.NewRequest(c.Msg.Right)) - if err != nil { - return err - } - m := phlaremodel.NewFlameGraphMerger() - m.MergeFlameGraph(resp.Msg.Flamegraph) - right = m.Tree() - return err + var rightErr error + right, rightErr = f.selectMergeStacktracesTree(ctx, connect.NewRequest(c.Msg.Right)) + return rightErr }) - if err := g.Wait(); err != nil { + if err = g.Wait(); err != nil { return nil, err } diff --git a/pkg/frontend/frontend_select_merge_span_profile.go b/pkg/frontend/frontend_select_merge_span_profile.go index 9648907df1..85ac11ffc6 100644 --- a/pkg/frontend/frontend_select_merge_span_profile.go +++ b/pkg/frontend/frontend_select_merge_span_profile.go @@ -66,6 +66,7 @@ func (f *Frontend) SelectMergeSpanProfile(ctx context.Context, End: r.End.UnixMilli(), MaxNodes: &maxNodes, SpanSelector: c.Msg.SpanSelector, + Format: querierv1.ProfileFormat_PROFILE_FORMAT_TREE, }) resp, err := connectgrpc.RoundTripUnary[ querierv1.SelectMergeSpanProfileRequest, @@ -73,8 +74,13 @@ func (f *Frontend) SelectMergeSpanProfile(ctx context.Context, if err != nil { return err } - m.MergeFlameGraph(resp.Msg.Flamegraph) - return nil + if len(resp.Msg.Tree) > 0 { + err = m.MergeTreeBytes(resp.Msg.Tree) + } else if resp.Msg.Flamegraph != nil { + // For backward compatibility. + m.MergeFlameGraph(resp.Msg.Flamegraph) + } + return err }) } @@ -83,7 +89,12 @@ func (f *Frontend) SelectMergeSpanProfile(ctx context.Context, } t := m.Tree() - return connect.NewResponse(&querierv1.SelectMergeSpanProfileResponse{ - Flamegraph: phlaremodel.NewFlameGraph(t, c.Msg.GetMaxNodes()), - }), nil + var resp querierv1.SelectMergeSpanProfileResponse + switch c.Msg.Format { + default: + resp.Flamegraph = phlaremodel.NewFlameGraph(t, c.Msg.GetMaxNodes()) + case querierv1.ProfileFormat_PROFILE_FORMAT_TREE: + resp.Tree = t.Bytes(c.Msg.GetMaxNodes()) + } + return connect.NewResponse(&resp), nil } diff --git a/pkg/frontend/frontend_select_merge_stacktraces.go b/pkg/frontend/frontend_select_merge_stacktraces.go index 046a688a6e..1811318e04 100644 --- a/pkg/frontend/frontend_select_merge_stacktraces.go +++ b/pkg/frontend/frontend_select_merge_stacktraces.go @@ -21,6 +21,24 @@ import ( func (f *Frontend) SelectMergeStacktraces(ctx context.Context, c *connect.Request[querierv1.SelectMergeStacktracesRequest]) ( *connect.Response[querierv1.SelectMergeStacktracesResponse], error, +) { + t, err := f.selectMergeStacktracesTree(ctx, c) + if err != nil { + return nil, err + } + var resp querierv1.SelectMergeStacktracesResponse + switch c.Msg.Format { + default: + resp.Flamegraph = phlaremodel.NewFlameGraph(t, c.Msg.GetMaxNodes()) + case querierv1.ProfileFormat_PROFILE_FORMAT_TREE: + resp.Tree = t.Bytes(c.Msg.GetMaxNodes()) + } + return connect.NewResponse(&resp), nil +} + +func (f *Frontend) selectMergeStacktracesTree(ctx context.Context, + c *connect.Request[querierv1.SelectMergeStacktracesRequest]) ( + *phlaremodel.Tree, error, ) { opentracing.SpanFromContext(ctx). SetTag("start", model.Time(c.Msg.Start).Time().String()). @@ -40,9 +58,7 @@ func (f *Frontend) SelectMergeStacktraces(ctx context.Context, return nil, connect.NewError(connect.CodeInvalidArgument, err) } if validated.IsEmpty { - return connect.NewResponse(&querierv1.SelectMergeStacktracesResponse{ - Flamegraph: &querierv1.FlameGraph{}, - }), nil + return new(phlaremodel.Tree), nil } maxNodes, err := validation.ValidateMaxNodes(f.limits, tenantIDs, c.Msg.GetMaxNodes()) if err != nil { @@ -67,6 +83,7 @@ func (f *Frontend) SelectMergeStacktraces(ctx context.Context, Start: r.Start.UnixMilli(), End: r.End.UnixMilli(), MaxNodes: &maxNodes, + Format: querierv1.ProfileFormat_PROFILE_FORMAT_TREE, }) resp, err := connectgrpc.RoundTripUnary[ querierv1.SelectMergeStacktracesRequest, @@ -74,8 +91,13 @@ func (f *Frontend) SelectMergeStacktraces(ctx context.Context, if err != nil { return err } - m.MergeFlameGraph(resp.Msg.Flamegraph) - return nil + if len(resp.Msg.Tree) > 0 { + err = m.MergeTreeBytes(resp.Msg.Tree) + } else if resp.Msg.Flamegraph != nil { + // For backward compatibility. + m.MergeFlameGraph(resp.Msg.Flamegraph) + } + return err }) } @@ -83,8 +105,5 @@ func (f *Frontend) SelectMergeStacktraces(ctx context.Context, return nil, err } - t := m.Tree() - return connect.NewResponse(&querierv1.SelectMergeStacktracesResponse{ - Flamegraph: phlaremodel.NewFlameGraph(t, c.Msg.GetMaxNodes()), - }), nil + return m.Tree(), nil } diff --git a/pkg/model/flamegraph.go b/pkg/model/flamegraph.go index 25628b1b2b..9838c3a5ca 100644 --- a/pkg/model/flamegraph.go +++ b/pkg/model/flamegraph.go @@ -207,6 +207,17 @@ func (m *FlameGraphMerger) MergeFlameGraph(src *querierv1.FlameGraph) { } } +func (m *FlameGraphMerger) MergeTreeBytes(src []byte) error { + t, err := UnmarshalTree(src) + if err != nil { + return err + } + m.mu.Lock() + defer m.mu.Unlock() + m.t.Merge(t) + return nil +} + func (m *FlameGraphMerger) Tree() *Tree { return m.t } func (m *FlameGraphMerger) FlameGraph(maxNodes int64) *querierv1.FlameGraph { diff --git a/pkg/model/tree.go b/pkg/model/tree.go index eb6cbfec73..4ad11b04f8 100644 --- a/pkg/model/tree.go +++ b/pkg/model/tree.go @@ -1,6 +1,7 @@ package model import ( + "bytes" "container/heap" "fmt" "io" @@ -336,6 +337,15 @@ const truncatedNodeName = "other" var truncatedNodeNameBytes = []byte(truncatedNodeName) +// Bytes returns marshaled tree byte representation; the number of nodes +// is limited to maxNodes. The function modifies the tree: truncated nodes +// are removed from the tree in place. +func (t *Tree) Bytes(maxNodes int64) []byte { + var buf bytes.Buffer + _ = t.MarshalTruncate(&buf, maxNodes) + return buf.Bytes() +} + // MarshalTruncate writes tree byte representation to the writer provider, // the number of nodes is limited to maxNodes. The function modifies // the tree: truncated nodes are removed from the tree. diff --git a/pkg/phlaredb/block_querier.go b/pkg/phlaredb/block_querier.go index e522918ec4..6608b18c40 100644 --- a/pkg/phlaredb/block_querier.go +++ b/pkg/phlaredb/block_querier.go @@ -894,13 +894,8 @@ func MergeProfilesStacktraces(ctx context.Context, stream *connect.BidiStream[in return err } - var buf bytes.Buffer - if err = t.MarshalTruncate(&buf, r.GetMaxNodes()); err != nil { - return err - } - // sends the final result to the client. - treeBytes := buf.Bytes() + treeBytes := t.Bytes(r.GetMaxNodes()) sp.LogFields( otlog.String("msg", "sending the final result to the client"), otlog.Int("tree_bytes", len(treeBytes)), @@ -1042,13 +1037,8 @@ func MergeSpanProfile(ctx context.Context, stream *connect.BidiStream[ingestv1.M return err } - var buf bytes.Buffer - if err = t.MarshalTruncate(&buf, r.GetMaxNodes()); err != nil { - return err - } - // sends the final result to the client. - treeBytes := buf.Bytes() + treeBytes := t.Bytes(r.GetMaxNodes()) sp.LogFields( otlog.String("msg", "sending the final result to the client"), otlog.Int("tree_bytes", len(treeBytes)), diff --git a/pkg/querier/querier.go b/pkg/querier/querier.go index 0dee90a9fd..78977b9b2f 100644 --- a/pkg/querier/querier.go +++ b/pkg/querier/querier.go @@ -604,9 +604,14 @@ func (q *Querier) SelectMergeStacktraces(ctx context.Context, req *connect.Reque return nil, err } - return connect.NewResponse(&querierv1.SelectMergeStacktracesResponse{ - Flamegraph: phlaremodel.NewFlameGraph(t, req.Msg.GetMaxNodes()), - }), nil + var resp querierv1.SelectMergeStacktracesResponse + switch req.Msg.Format { + default: + resp.Flamegraph = phlaremodel.NewFlameGraph(t, req.Msg.GetMaxNodes()) + case querierv1.ProfileFormat_PROFILE_FORMAT_TREE: + resp.Tree = t.Bytes(req.Msg.GetMaxNodes()) + } + return connect.NewResponse(&resp), nil } func (q *Querier) SelectMergeSpanProfile(ctx context.Context, req *connect.Request[querierv1.SelectMergeSpanProfileRequest]) (*connect.Response[querierv1.SelectMergeSpanProfileResponse], error) { @@ -631,9 +636,14 @@ func (q *Querier) SelectMergeSpanProfile(ctx context.Context, req *connect.Reque return nil, err } - return connect.NewResponse(&querierv1.SelectMergeSpanProfileResponse{ - Flamegraph: phlaremodel.NewFlameGraph(t, req.Msg.GetMaxNodes()), - }), nil + var resp querierv1.SelectMergeSpanProfileResponse + switch req.Msg.Format { + default: + resp.Flamegraph = phlaremodel.NewFlameGraph(t, req.Msg.GetMaxNodes()) + case querierv1.ProfileFormat_PROFILE_FORMAT_TREE: + resp.Tree = t.Bytes(req.Msg.GetMaxNodes()) + } + return connect.NewResponse(&resp), nil } func isEndpointNotExistingErr(err error) bool { @@ -718,6 +728,7 @@ func (sq storeQuery) MergeStacktracesRequest(req *querierv1.SelectMergeStacktrac LabelSelector: req.LabelSelector, ProfileTypeID: req.ProfileTypeID, MaxNodes: req.MaxNodes, + Format: req.Format, } } @@ -743,6 +754,7 @@ func (sq storeQuery) MergeSpanProfileRequest(req *querierv1.SelectMergeSpanProfi LabelSelector: req.LabelSelector, SpanSelector: req.SpanSelector, MaxNodes: req.MaxNodes, + Format: req.Format, } } From efc7f2a3ba35a5721e296fa0f86f3869d87910e4 Mon Sep 17 00:00:00 2001 From: Anton Kolesnikov Date: Tue, 11 Jun 2024 18:18:13 +0800 Subject: [PATCH 7/9] perf: optimize deduplication (#3351) Co-authored-by: Aleksandar Petrov <8142643+aleks-p@users.noreply.github.com> --- api/gen/proto/go/ingester/v1/ingester.pb.go | 435 +++++++++--------- .../go/ingester/v1/ingester_vtproto.pb.go | 117 +++++ api/ingester/v1/ingester.proto | 2 + api/openapiv2/gen/phlare.swagger.json | 10 +- pkg/phlaredb/block_querier.go | 2 +- pkg/phlaredb/filter_profiles_bidi.go | 33 +- pkg/phlaredb/filter_profiles_bidi_test.go | 24 +- pkg/phlaredb/phlaredb_test.go | 4 +- pkg/querier/select_merge.go | 77 +++- 9 files changed, 441 insertions(+), 263 deletions(-) diff --git a/api/gen/proto/go/ingester/v1/ingester.pb.go b/api/gen/proto/go/ingester/v1/ingester.pb.go index 09928c4b9f..3b679ca791 100644 --- a/api/gen/proto/go/ingester/v1/ingester.pb.go +++ b/api/gen/proto/go/ingester/v1/ingester.pb.go @@ -929,8 +929,10 @@ type ProfileSets struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - LabelsSets []*v1.Labels `protobuf:"bytes,1,rep,name=labelsSets,proto3" json:"labelsSets,omitempty"` - Profiles []*SeriesProfile `protobuf:"bytes,2,rep,name=profiles,proto3" json:"profiles,omitempty"` + // DEPRECATED: Use fingerprints instead. + LabelsSets []*v1.Labels `protobuf:"bytes,1,rep,name=labelsSets,proto3" json:"labelsSets,omitempty"` + Profiles []*SeriesProfile `protobuf:"bytes,2,rep,name=profiles,proto3" json:"profiles,omitempty"` + Fingerprints []uint64 `protobuf:"varint,3,rep,packed,name=fingerprints,proto3" json:"fingerprints,omitempty"` } func (x *ProfileSets) Reset() { @@ -979,6 +981,13 @@ func (x *ProfileSets) GetProfiles() []*SeriesProfile { return nil } +func (x *ProfileSets) GetFingerprints() []uint64 { + if x != nil { + return x.Fingerprints + } + return nil +} + type SeriesProfile struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1961,218 +1970,220 @@ var file_ingester_v1_ingester_proto_rawDesc = []byte{ 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x37, 0x0a, 0x16, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x53, 0x70, 0x61, 0x6e, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x72, 0x65, 0x65, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x09, 0x74, 0x72, 0x65, 0x65, 0x42, 0x79, 0x74, 0x65, 0x73, 0x22, 0x77, - 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x53, 0x65, 0x74, 0x73, 0x12, 0x30, 0x0a, - 0x0a, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x53, 0x65, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x10, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x61, 0x62, - 0x65, 0x6c, 0x73, 0x52, 0x0a, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x53, 0x65, 0x74, 0x73, 0x12, - 0x36, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x1a, 0x2e, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x08, 0x70, - 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x22, 0x4d, 0x0a, 0x0d, 0x53, 0x65, 0x72, 0x69, 0x65, - 0x73, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x6c, 0x61, 0x62, 0x65, - 0x6c, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x6c, 0x61, - 0x62, 0x65, 0x6c, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, - 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x69, 0x6d, - 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0xd0, 0x01, 0x0a, 0x07, 0x50, 0x72, 0x6f, 0x66, 0x69, - 0x6c, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, - 0x49, 0x44, 0x12, 0x29, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x15, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x66, - 0x69, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x2b, 0x0a, - 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, - 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x50, 0x61, - 0x69, 0x72, 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, - 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, - 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x3f, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x63, - 0x6b, 0x74, 0x72, 0x61, 0x63, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, - 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x63, - 0x6b, 0x74, 0x72, 0x61, 0x63, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x52, 0x0b, 0x73, 0x74, - 0x61, 0x63, 0x6b, 0x74, 0x72, 0x61, 0x63, 0x65, 0x73, 0x22, 0x4b, 0x0a, 0x10, 0x53, 0x74, 0x61, - 0x63, 0x6b, 0x74, 0x72, 0x61, 0x63, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x12, 0x21, 0x0a, - 0x0c, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x05, 0x52, 0x0b, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x73, - 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0xf4, 0x01, 0x0a, 0x1a, 0x4d, 0x65, 0x72, 0x67, 0x65, - 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3c, 0x0a, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, - 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x07, 0x72, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x62, 0x79, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, - 0x02, 0x62, 0x79, 0x12, 0x53, 0x0a, 0x14, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x5f, 0x74, 0x72, 0x61, - 0x63, 0x65, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1c, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, - 0x63, 0x6b, 0x54, 0x72, 0x61, 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x48, - 0x00, 0x52, 0x12, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x54, 0x72, 0x61, 0x63, 0x65, 0x53, 0x65, 0x6c, - 0x65, 0x63, 0x74, 0x6f, 0x72, 0x88, 0x01, 0x01, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x66, - 0x69, 0x6c, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x08, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x66, - 0x69, 0x6c, 0x65, 0x73, 0x42, 0x17, 0x0a, 0x15, 0x5f, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x5f, 0x74, - 0x72, 0x61, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x22, 0x8d, 0x01, - 0x0a, 0x1b, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x4c, - 0x61, 0x62, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, - 0x10, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, - 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, - 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x53, 0x65, 0x74, - 0x73, 0x52, 0x10, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x50, 0x72, 0x6f, 0x66, 0x69, - 0x6c, 0x65, 0x73, 0x12, 0x28, 0x0a, 0x06, 0x73, 0x65, 0x72, 0x69, 0x65, 0x73, 0x18, 0x02, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, - 0x65, 0x72, 0x69, 0x65, 0x73, 0x52, 0x06, 0x73, 0x65, 0x72, 0x69, 0x65, 0x73, 0x22, 0x93, 0x02, - 0x0a, 0x19, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x50, - 0x70, 0x72, 0x6f, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3c, 0x0a, 0x07, 0x72, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x69, - 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6c, 0x65, 0x63, - 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x52, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x09, 0x6d, 0x61, 0x78, - 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x48, 0x00, 0x52, 0x08, - 0x6d, 0x61, 0x78, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x88, 0x01, 0x01, 0x12, 0x53, 0x0a, 0x14, 0x73, - 0x74, 0x61, 0x63, 0x6b, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, - 0x74, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x74, 0x79, 0x70, 0x65, - 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x54, 0x72, 0x61, 0x63, 0x65, 0x53, - 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x48, 0x01, 0x52, 0x12, 0x73, 0x74, 0x61, 0x63, 0x6b, - 0x54, 0x72, 0x61, 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x88, 0x01, 0x01, - 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, - 0x28, 0x08, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x42, 0x0c, 0x0a, 0x0a, - 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x42, 0x17, 0x0a, 0x15, 0x5f, 0x73, - 0x74, 0x61, 0x63, 0x6b, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, - 0x74, 0x6f, 0x72, 0x22, 0x7a, 0x0a, 0x1a, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x66, - 0x69, 0x6c, 0x65, 0x73, 0x50, 0x70, 0x72, 0x6f, 0x66, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x44, 0x0a, 0x10, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x50, 0x72, 0x6f, - 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x69, 0x6e, - 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, - 0x65, 0x53, 0x65, 0x74, 0x73, 0x52, 0x10, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x50, - 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, - 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, - 0x3e, 0x0a, 0x14, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x10, 0x0a, - 0x03, 0x65, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x22, - 0x44, 0x0a, 0x15, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x06, 0x62, 0x6c, 0x6f, 0x63, - 0x6b, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, - 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x06, 0x62, - 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x22, 0x36, 0x0a, 0x05, 0x48, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x2d, - 0x0a, 0x05, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, - 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6c, 0x6f, 0x63, - 0x6b, 0x48, 0x69, 0x6e, 0x74, 0x73, 0x52, 0x05, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x22, 0x48, 0x0a, - 0x0a, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x75, - 0x6c, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x75, 0x6c, 0x69, 0x64, - 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x64, 0x65, 0x64, 0x75, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x64, 0x65, 0x64, 0x75, 0x70, 0x6c, - 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x2c, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x42, 0x6c, - 0x6f, 0x63, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x14, 0x0a, 0x05, 0x75, 0x6c, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, - 0x75, 0x6c, 0x69, 0x64, 0x73, 0x22, 0x51, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, - 0x6b, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x38, - 0x0a, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x0a, 0x62, 0x6c, - 0x6f, 0x63, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x73, 0x22, 0xe0, 0x01, 0x0a, 0x0a, 0x42, 0x6c, 0x6f, - 0x63, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x72, 0x69, 0x65, - 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, - 0x65, 0x72, 0x69, 0x65, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x72, - 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, - 0x21, 0x0a, 0x0c, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x43, 0x6f, 0x75, - 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5f, 0x62, 0x79, 0x74, 0x65, - 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x42, 0x79, - 0x74, 0x65, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x62, - 0x79, 0x74, 0x65, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x66, - 0x69, 0x6c, 0x65, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x79, 0x6d, 0x62, - 0x6f, 0x6c, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, - 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x42, 0x79, 0x74, 0x65, 0x73, 0x2a, 0x6b, 0x0a, 0x16, 0x53, - 0x74, 0x61, 0x63, 0x6b, 0x74, 0x72, 0x61, 0x63, 0x65, 0x73, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x46, - 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x12, 0x1c, 0x0a, 0x18, 0x4d, 0x45, 0x52, 0x47, 0x45, 0x5f, 0x46, - 0x4f, 0x52, 0x4d, 0x41, 0x54, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, - 0x44, 0x10, 0x00, 0x12, 0x1c, 0x0a, 0x18, 0x4d, 0x45, 0x52, 0x47, 0x45, 0x5f, 0x46, 0x4f, 0x52, - 0x4d, 0x41, 0x54, 0x5f, 0x53, 0x54, 0x41, 0x43, 0x4b, 0x54, 0x52, 0x41, 0x43, 0x45, 0x53, 0x10, - 0x01, 0x12, 0x15, 0x0a, 0x11, 0x4d, 0x45, 0x52, 0x47, 0x45, 0x5f, 0x46, 0x4f, 0x52, 0x4d, 0x41, - 0x54, 0x5f, 0x54, 0x52, 0x45, 0x45, 0x10, 0x02, 0x32, 0x90, 0x09, 0x0a, 0x0f, 0x49, 0x6e, 0x67, - 0x65, 0x73, 0x74, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x35, 0x0a, 0x04, - 0x50, 0x75, 0x73, 0x68, 0x12, 0x14, 0x2e, 0x70, 0x75, 0x73, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x50, - 0x75, 0x73, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x70, 0x75, 0x73, - 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x00, 0x12, 0x4c, 0x0a, 0x0b, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x56, 0x61, 0x6c, 0x75, - 0x65, 0x73, 0x12, 0x1c, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x61, - 0x62, 0x65, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x61, 0x62, 0x65, - 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x00, 0x12, 0x49, 0x0a, 0x0a, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, - 0x1b, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, - 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x74, - 0x79, 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x4e, 0x61, 0x6d, - 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x55, 0x0a, 0x0c, - 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x73, 0x12, 0x20, 0x2e, 0x69, + 0x01, 0x28, 0x0c, 0x52, 0x09, 0x74, 0x72, 0x65, 0x65, 0x42, 0x79, 0x74, 0x65, 0x73, 0x22, 0x9b, + 0x01, 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x53, 0x65, 0x74, 0x73, 0x12, 0x30, + 0x0a, 0x0a, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x53, 0x65, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x61, + 0x62, 0x65, 0x6c, 0x73, 0x52, 0x0a, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x53, 0x65, 0x74, 0x73, + 0x12, 0x36, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x08, + 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x22, 0x0a, 0x0c, 0x66, 0x69, 0x6e, 0x67, + 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x04, 0x52, 0x0c, + 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x73, 0x22, 0x4d, 0x0a, 0x0d, + 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x1e, 0x0a, + 0x0a, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x0a, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x1c, 0x0a, + 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0xd0, 0x01, 0x0a, 0x07, + 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x44, 0x12, 0x29, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31, + 0x2e, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, + 0x70, 0x65, 0x12, 0x2b, 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x61, + 0x62, 0x65, 0x6c, 0x50, 0x61, 0x69, 0x72, 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12, + 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x3f, 0x0a, + 0x0b, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x74, 0x72, 0x61, 0x63, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x74, 0x72, 0x61, 0x63, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, + 0x65, 0x52, 0x0b, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x74, 0x72, 0x61, 0x63, 0x65, 0x73, 0x22, 0x4b, + 0x0a, 0x10, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x74, 0x72, 0x61, 0x63, 0x65, 0x53, 0x61, 0x6d, 0x70, + 0x6c, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, + 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x05, 0x52, 0x0b, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x49, 0x64, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0xf4, 0x01, 0x0a, 0x1a, + 0x4d, 0x65, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x4c, 0x61, 0x62, + 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3c, 0x0a, 0x07, 0x72, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x69, 0x6e, + 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, + 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, + 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x62, 0x79, 0x18, 0x02, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x02, 0x62, 0x79, 0x12, 0x53, 0x0a, 0x14, 0x73, 0x74, 0x61, 0x63, + 0x6b, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x76, + 0x31, 0x2e, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x54, 0x72, 0x61, 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, + 0x63, 0x74, 0x6f, 0x72, 0x48, 0x00, 0x52, 0x12, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x54, 0x72, 0x61, + 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x88, 0x01, 0x01, 0x12, 0x1a, 0x0a, + 0x08, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x08, 0x52, + 0x08, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x42, 0x17, 0x0a, 0x15, 0x5f, 0x73, 0x74, + 0x61, 0x63, 0x6b, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, + 0x6f, 0x72, 0x22, 0x8d, 0x01, 0x0a, 0x1b, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x66, + 0x69, 0x6c, 0x65, 0x73, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x44, 0x0a, 0x10, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x50, 0x72, + 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x66, 0x69, - 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, - 0x2e, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, - 0x66, 0x69, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x00, 0x12, 0x43, 0x0a, 0x06, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x12, 0x1a, 0x2e, - 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x72, 0x69, - 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x69, 0x6e, 0x67, 0x65, - 0x73, 0x74, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x40, 0x0a, 0x05, 0x46, 0x6c, 0x75, 0x73, - 0x68, 0x12, 0x19, 0x2e, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x46, 0x6c, 0x75, 0x73, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x69, - 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x6c, 0x75, 0x73, 0x68, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x7d, 0x0a, 0x18, 0x4d, 0x65, - 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x53, 0x74, 0x61, 0x63, 0x6b, - 0x74, 0x72, 0x61, 0x63, 0x65, 0x73, 0x12, 0x2c, 0x2e, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, - 0x65, 0x73, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x74, 0x72, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x2e, - 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, - 0x53, 0x74, 0x61, 0x63, 0x6b, 0x74, 0x72, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x12, 0x6e, 0x0a, 0x13, 0x4d, 0x65, 0x72, - 0x67, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, - 0x12, 0x27, 0x2e, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, - 0x65, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x4c, 0x61, 0x62, 0x65, - 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x69, 0x6e, 0x67, 0x65, + 0x6c, 0x65, 0x53, 0x65, 0x74, 0x73, 0x52, 0x10, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, + 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x28, 0x0a, 0x06, 0x73, 0x65, 0x72, 0x69, + 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, + 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x52, 0x06, 0x73, 0x65, 0x72, 0x69, + 0x65, 0x73, 0x22, 0x93, 0x02, 0x0a, 0x19, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x66, + 0x69, 0x6c, 0x65, 0x73, 0x50, 0x70, 0x72, 0x6f, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x3c, 0x0a, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x22, 0x2e, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, + 0x0a, 0x09, 0x6d, 0x61, 0x78, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x03, 0x48, 0x00, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x88, 0x01, 0x01, + 0x12, 0x53, 0x0a, 0x14, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, + 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, + 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x54, + 0x72, 0x61, 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x48, 0x01, 0x52, 0x12, + 0x73, 0x74, 0x61, 0x63, 0x6b, 0x54, 0x72, 0x61, 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, + 0x6f, 0x72, 0x88, 0x01, 0x01, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, + 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x08, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, + 0x73, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x42, + 0x17, 0x0a, 0x15, 0x5f, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, + 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x22, 0x7a, 0x0a, 0x1a, 0x4d, 0x65, 0x72, 0x67, + 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x50, 0x70, 0x72, 0x6f, 0x66, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x10, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, + 0x65, 0x64, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x18, 0x2e, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, + 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x53, 0x65, 0x74, 0x73, 0x52, 0x10, 0x73, 0x65, 0x6c, 0x65, + 0x63, 0x74, 0x65, 0x64, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, + 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x72, 0x65, + 0x73, 0x75, 0x6c, 0x74, 0x22, 0x3e, 0x0a, 0x14, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x4d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, + 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x73, 0x74, 0x61, + 0x72, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x03, 0x65, 0x6e, 0x64, 0x22, 0x44, 0x0a, 0x15, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x4d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, + 0x06, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, + 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x49, 0x6e, + 0x66, 0x6f, 0x52, 0x06, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x22, 0x36, 0x0a, 0x05, 0x48, 0x69, + 0x6e, 0x74, 0x73, 0x12, 0x2d, 0x0a, 0x05, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x69, 0x6e, 0x74, 0x73, 0x52, 0x05, 0x62, 0x6c, 0x6f, + 0x63, 0x6b, 0x22, 0x48, 0x0a, 0x0a, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x69, 0x6e, 0x74, 0x73, + 0x12, 0x14, 0x0a, 0x05, 0x75, 0x6c, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x05, 0x75, 0x6c, 0x69, 0x64, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x64, 0x65, 0x64, 0x75, 0x70, 0x6c, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x64, + 0x65, 0x64, 0x75, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x2c, 0x0a, 0x14, + 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x75, 0x6c, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x09, 0x52, 0x05, 0x75, 0x6c, 0x69, 0x64, 0x73, 0x22, 0x51, 0x0a, 0x15, 0x47, 0x65, + 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x38, 0x0a, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x73, 0x74, 0x61, + 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x69, 0x6e, 0x67, 0x65, 0x73, + 0x74, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x53, 0x74, 0x61, 0x74, + 0x73, 0x52, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x73, 0x22, 0xe0, 0x01, + 0x0a, 0x0a, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x21, 0x0a, 0x0c, + 0x73, 0x65, 0x72, 0x69, 0x65, 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x0b, 0x73, 0x65, 0x72, 0x69, 0x65, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, + 0x23, 0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x43, + 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x5f, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x61, 0x6d, 0x70, + 0x6c, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x64, 0x65, 0x78, + 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x69, 0x6e, + 0x64, 0x65, 0x78, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x66, + 0x69, 0x6c, 0x65, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x0c, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x21, 0x0a, + 0x0c, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x42, 0x79, 0x74, 0x65, 0x73, + 0x2a, 0x6b, 0x0a, 0x16, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x74, 0x72, 0x61, 0x63, 0x65, 0x73, 0x4d, + 0x65, 0x72, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x12, 0x1c, 0x0a, 0x18, 0x4d, 0x45, + 0x52, 0x47, 0x45, 0x5f, 0x46, 0x4f, 0x52, 0x4d, 0x41, 0x54, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, + 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1c, 0x0a, 0x18, 0x4d, 0x45, 0x52, 0x47, + 0x45, 0x5f, 0x46, 0x4f, 0x52, 0x4d, 0x41, 0x54, 0x5f, 0x53, 0x54, 0x41, 0x43, 0x4b, 0x54, 0x52, + 0x41, 0x43, 0x45, 0x53, 0x10, 0x01, 0x12, 0x15, 0x0a, 0x11, 0x4d, 0x45, 0x52, 0x47, 0x45, 0x5f, + 0x46, 0x4f, 0x52, 0x4d, 0x41, 0x54, 0x5f, 0x54, 0x52, 0x45, 0x45, 0x10, 0x02, 0x32, 0x90, 0x09, + 0x0a, 0x0f, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x12, 0x35, 0x0a, 0x04, 0x50, 0x75, 0x73, 0x68, 0x12, 0x14, 0x2e, 0x70, 0x75, 0x73, 0x68, + 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x15, 0x2e, 0x70, 0x75, 0x73, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4c, 0x0a, 0x0b, 0x4c, 0x61, 0x62, 0x65, + 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x1c, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, + 0x76, 0x31, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31, + 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x49, 0x0a, 0x0a, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x4e, + 0x61, 0x6d, 0x65, 0x73, 0x12, 0x1b, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, + 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1c, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x61, 0x62, + 0x65, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x00, 0x12, 0x55, 0x0a, 0x0c, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, + 0x73, 0x12, 0x20, 0x2e, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x76, + 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x43, 0x0a, 0x06, 0x53, 0x65, 0x72, 0x69, + 0x65, 0x73, 0x12, 0x1a, 0x2e, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, + 0x2e, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x72, + 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x40, 0x0a, + 0x05, 0x46, 0x6c, 0x75, 0x73, 0x68, 0x12, 0x19, 0x2e, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x6c, 0x75, 0x73, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1a, 0x2e, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x46, 0x6c, 0x75, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, + 0x7d, 0x0a, 0x18, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, + 0x53, 0x74, 0x61, 0x63, 0x6b, 0x74, 0x72, 0x61, 0x63, 0x65, 0x73, 0x12, 0x2c, 0x2e, 0x69, 0x6e, + 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x50, + 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x74, 0x72, 0x61, 0x63, + 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, - 0x66, 0x69, 0x6c, 0x65, 0x73, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x12, 0x6b, 0x0a, 0x12, 0x4d, 0x65, 0x72, - 0x67, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x50, 0x70, 0x72, 0x6f, 0x66, 0x12, - 0x26, 0x2e, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, - 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x50, 0x70, 0x72, 0x6f, 0x66, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, - 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, - 0x6c, 0x65, 0x73, 0x50, 0x70, 0x72, 0x6f, 0x66, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x12, 0x65, 0x0a, 0x10, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x53, - 0x70, 0x61, 0x6e, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x24, 0x2e, 0x69, 0x6e, 0x67, - 0x65, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x53, 0x70, - 0x61, 0x6e, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x25, 0x2e, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, - 0x65, 0x72, 0x67, 0x65, 0x53, 0x70, 0x61, 0x6e, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x12, 0x58, 0x0a, - 0x0d, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x21, - 0x2e, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6c, 0x6f, - 0x63, 0x6b, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x22, 0x2e, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x58, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x50, 0x72, - 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x20, 0x2e, 0x74, 0x79, 0x70, - 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, - 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x74, - 0x79, 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, - 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x00, 0x12, 0x58, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x53, 0x74, 0x61, - 0x74, 0x73, 0x12, 0x21, 0x2e, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x53, 0x74, 0x61, 0x74, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0xb3, 0x01, 0x0a, 0x0f, - 0x63, 0x6f, 0x6d, 0x2e, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x42, - 0x0d, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, - 0x5a, 0x44, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, - 0x66, 0x61, 0x6e, 0x61, 0x2f, 0x70, 0x79, 0x72, 0x6f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x2f, 0x61, - 0x70, 0x69, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x6f, 0x2f, - 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x2f, 0x76, 0x31, 0x3b, 0x69, 0x6e, 0x67, 0x65, - 0x73, 0x74, 0x65, 0x72, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x49, 0x58, 0x58, 0xaa, 0x02, 0x0b, 0x49, - 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x0b, 0x49, 0x6e, 0x67, - 0x65, 0x73, 0x74, 0x65, 0x72, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x17, 0x49, 0x6e, 0x67, 0x65, 0x73, - 0x74, 0x65, 0x72, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, - 0x74, 0x61, 0xea, 0x02, 0x0c, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x3a, 0x3a, 0x56, - 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x66, 0x69, 0x6c, 0x65, 0x73, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x74, 0x72, 0x61, 0x63, 0x65, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x12, 0x6e, + 0x0a, 0x13, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x4c, + 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12, 0x27, 0x2e, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, + 0x73, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, + 0x2e, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x72, + 0x67, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x12, 0x6b, + 0x0a, 0x12, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x50, + 0x70, 0x72, 0x6f, 0x66, 0x12, 0x26, 0x2e, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x2e, + 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, + 0x50, 0x70, 0x72, 0x6f, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x69, + 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x72, 0x67, 0x65, + 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x50, 0x70, 0x72, 0x6f, 0x66, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x12, 0x65, 0x0a, 0x10, 0x4d, + 0x65, 0x72, 0x67, 0x65, 0x53, 0x70, 0x61, 0x6e, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, + 0x24, 0x2e, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, + 0x72, 0x67, 0x65, 0x53, 0x70, 0x61, 0x6e, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x53, 0x70, 0x61, 0x6e, 0x50, 0x72, 0x6f, + 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, + 0x30, 0x01, 0x12, 0x58, 0x0a, 0x0d, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x4d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x12, 0x21, 0x2e, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x76, + 0x31, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x58, 0x0a, 0x0f, + 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, + 0x20, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, + 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x21, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, + 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x58, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, + 0x63, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x21, 0x2e, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, + 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x53, 0x74, + 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x69, 0x6e, 0x67, + 0x65, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, + 0x6b, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, + 0x42, 0xb3, 0x01, 0x0a, 0x0f, 0x63, 0x6f, 0x6d, 0x2e, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x42, 0x0d, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x50, 0x72, + 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x44, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x66, 0x61, 0x6e, 0x61, 0x2f, 0x70, 0x79, 0x72, 0x6f, 0x73, 0x63, + 0x6f, 0x70, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2f, 0x67, 0x6f, 0x2f, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x2f, 0x76, 0x31, + 0x3b, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x49, 0x58, + 0x58, 0xaa, 0x02, 0x0b, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x56, 0x31, 0xca, + 0x02, 0x0b, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x17, + 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, + 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x0c, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, + 0x65, 0x72, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/api/gen/proto/go/ingester/v1/ingester_vtproto.pb.go b/api/gen/proto/go/ingester/v1/ingester_vtproto.pb.go index 30f26068b1..1a00cf8185 100644 --- a/api/gen/proto/go/ingester/v1/ingester_vtproto.pb.go +++ b/api/gen/proto/go/ingester/v1/ingester_vtproto.pb.go @@ -386,6 +386,11 @@ func (m *ProfileSets) CloneVT() *ProfileSets { } r.Profiles = tmpContainer } + if rhs := m.Fingerprints; rhs != nil { + tmpContainer := make([]uint64, len(rhs)) + copy(tmpContainer, rhs) + r.Fingerprints = tmpContainer + } if len(m.unknownFields) > 0 { r.unknownFields = make([]byte, len(m.unknownFields)) copy(r.unknownFields, m.unknownFields) @@ -1214,6 +1219,15 @@ func (this *ProfileSets) EqualVT(that *ProfileSets) bool { } } } + if len(this.Fingerprints) != len(that.Fingerprints) { + return false + } + for i, vx := range this.Fingerprints { + vy := that.Fingerprints[i] + if vx != vy { + return false + } + } return string(this.unknownFields) == string(that.unknownFields) } @@ -3172,6 +3186,26 @@ func (m *ProfileSets) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } + if len(m.Fingerprints) > 0 { + var pksize2 int + for _, num := range m.Fingerprints { + pksize2 += protohelpers.SizeOfVarint(uint64(num)) + } + i -= pksize2 + j1 := i + for _, num := range m.Fingerprints { + for num >= 1<<7 { + dAtA[j1] = uint8(uint64(num)&0x7f | 0x80) + num >>= 7 + j1++ + } + dAtA[j1] = uint8(num) + j1++ + } + i = protohelpers.EncodeVarint(dAtA, i, uint64(pksize2)) + i-- + dAtA[i] = 0x1a + } if len(m.Profiles) > 0 { for iNdEx := len(m.Profiles) - 1; iNdEx >= 0; iNdEx-- { size, err := m.Profiles[iNdEx].MarshalToSizedBufferVT(dAtA[:i]) @@ -4376,6 +4410,13 @@ func (m *ProfileSets) SizeVT() (n int) { n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } } + if len(m.Fingerprints) > 0 { + l = 0 + for _, e := range m.Fingerprints { + l += protohelpers.SizeOfVarint(uint64(e)) + } + n += 1 + protohelpers.SizeOfVarint(uint64(l)) + l + } n += len(m.unknownFields) return n } @@ -6643,6 +6684,82 @@ func (m *ProfileSets) UnmarshalVT(dAtA []byte) error { return err } iNdEx = postIndex + case 3: + if wireType == 0 { + var v uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Fingerprints = append(m.Fingerprints, v) + } else if wireType == 2 { + var packedLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + packedLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if packedLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + packedLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var elementCount int + var count int + for _, integer := range dAtA[iNdEx:postIndex] { + if integer < 128 { + count++ + } + } + elementCount = count + if elementCount != 0 && len(m.Fingerprints) == 0 { + m.Fingerprints = make([]uint64, 0, elementCount) + } + for iNdEx < postIndex { + var v uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Fingerprints = append(m.Fingerprints, v) + } + } else { + return fmt.Errorf("proto: wrong wireType = %d for field Fingerprints", wireType) + } default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) diff --git a/api/ingester/v1/ingester.proto b/api/ingester/v1/ingester.proto index 52bf13600e..1c294519ad 100644 --- a/api/ingester/v1/ingester.proto +++ b/api/ingester/v1/ingester.proto @@ -136,8 +136,10 @@ message MergeSpanProfileResult { } message ProfileSets { + // DEPRECATED: Use fingerprints instead. repeated types.v1.Labels labelsSets = 1; repeated SeriesProfile profiles = 2; + repeated uint64 fingerprints = 3; } message SeriesProfile { diff --git a/api/openapiv2/gen/phlare.swagger.json b/api/openapiv2/gen/phlare.swagger.json index 5fe7102344..1162fbcc02 100644 --- a/api/openapiv2/gen/phlare.swagger.json +++ b/api/openapiv2/gen/phlare.swagger.json @@ -1080,7 +1080,8 @@ "items": { "type": "object", "$ref": "#/definitions/v1Labels" - } + }, + "description": "DEPRECATED: Use fingerprints instead." }, "profiles": { "type": "array", @@ -1088,6 +1089,13 @@ "type": "object", "$ref": "#/definitions/v1SeriesProfile" } + }, + "fingerprints": { + "type": "array", + "items": { + "type": "string", + "format": "uint64" + } } } }, diff --git a/pkg/phlaredb/block_querier.go b/pkg/phlaredb/block_querier.go index 6608b18c40..e5d45b5e35 100644 --- a/pkg/phlaredb/block_querier.go +++ b/pkg/phlaredb/block_querier.go @@ -49,7 +49,7 @@ import ( ) const ( - defaultBatchSize = 4096 + defaultBatchSize = 64 << 10 // This controls the buffer size for reads to a parquet io.Reader. This value should be small for memory or // disk backed readers, but when the reader is backed by network storage a larger size will be advantageous. diff --git a/pkg/phlaredb/filter_profiles_bidi.go b/pkg/phlaredb/filter_profiles_bidi.go index 7e9e77d3f7..7cdf6dc131 100644 --- a/pkg/phlaredb/filter_profiles_bidi.go +++ b/pkg/phlaredb/filter_profiles_bidi.go @@ -11,9 +11,7 @@ import ( "github.com/prometheus/common/model" ingestv1 "github.com/grafana/pyroscope/api/gen/proto/go/ingester/v1" - typesv1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1" "github.com/grafana/pyroscope/pkg/iter" - phlaremodel "github.com/grafana/pyroscope/pkg/model" ) type BidiServerMerge[Res any, Req any] interface { @@ -21,11 +19,6 @@ type BidiServerMerge[Res any, Req any] interface { Receive() (Req, error) } -type labelWithIndex struct { - phlaremodel.Labels - index int -} - type ProfileWithIndex struct { Profile Index int @@ -72,8 +65,8 @@ func filterProfiles[B BidiServerMerge[Res, Req], Res filterResponse, Req filterR defer sp.Finish() selection := make([][]Profile, len(profiles)) selectProfileResult := &ingestv1.ProfileSets{ - Profiles: make([]*ingestv1.SeriesProfile, 0, batchProfileSize), - LabelsSets: make([]*typesv1.Labels, 0, batchProfileSize), + Profiles: make([]*ingestv1.SeriesProfile, 0, batchProfileSize), + Fingerprints: make([]uint64, 0, batchProfileSize), } its := make([]iter.Iterator[ProfileWithIndex], len(profiles)) for i, iter := range profiles { @@ -92,28 +85,26 @@ func filterProfiles[B BidiServerMerge[Res, Req], Res filterResponse, Req filterR otlog.Int("batch_requested_size", batchProfileSize), ) - seriesByFP := map[model.Fingerprint]labelWithIndex{} - selectProfileResult.LabelsSets = selectProfileResult.LabelsSets[:0] + seriesByFP := map[model.Fingerprint]int{} selectProfileResult.Profiles = selectProfileResult.Profiles[:0] + selectProfileResult.Fingerprints = selectProfileResult.Fingerprints[:0] for _, profile := range batch { var ok bool - var lblsIdx labelWithIndex - lblsIdx, ok = seriesByFP[profile.Fingerprint()] + var idx int + fp := profile.Fingerprint() + idx, ok = seriesByFP[fp] if !ok { - lblsIdx = labelWithIndex{ - Labels: profile.Labels(), - index: len(selectProfileResult.LabelsSets), - } - seriesByFP[profile.Fingerprint()] = lblsIdx - selectProfileResult.LabelsSets = append(selectProfileResult.LabelsSets, &typesv1.Labels{Labels: profile.Labels()}) + idx = len(selectProfileResult.Fingerprints) + seriesByFP[fp] = idx + selectProfileResult.Fingerprints = append(selectProfileResult.Fingerprints, uint64(fp)) } selectProfileResult.Profiles = append(selectProfileResult.Profiles, &ingestv1.SeriesProfile{ - LabelIndex: int32(lblsIdx.index), + LabelIndex: int32(idx), Timestamp: int64(profile.Timestamp()), }) - } + sp.LogFields(otlog.String("msg", "sending batch to client")) var err error switch s := BidiServerMerge[Res, Req](stream).(type) { diff --git a/pkg/phlaredb/filter_profiles_bidi_test.go b/pkg/phlaredb/filter_profiles_bidi_test.go index 0f93b587f7..1639ae2068 100644 --- a/pkg/phlaredb/filter_profiles_bidi_test.go +++ b/pkg/phlaredb/filter_profiles_bidi_test.go @@ -11,7 +11,6 @@ import ( "github.com/stretchr/testify/require" ingestv1 "github.com/grafana/pyroscope/api/gen/proto/go/ingester/v1" - typesv1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1" "github.com/grafana/pyroscope/pkg/iter" phlaremodel "github.com/grafana/pyroscope/pkg/model" schemav1 "github.com/grafana/pyroscope/pkg/phlaredb/schemas/v1" @@ -39,34 +38,36 @@ func TestFilterProfiles(t *testing.T) { require.NoError(t, err) require.Equal(t, 2, len(filtered[0])) require.Equal(t, 3, len(bidi.profilesSent)) - testhelper.EqualProto(t, []*ingestv1.ProfileSets{ + + expectedSent := []*ingestv1.ProfileSets{ { - LabelsSets: lo.Times(5, func(i int) *typesv1.Labels { - return &typesv1.Labels{Labels: phlaremodel.LabelsFromStrings("foo", "bar", "i", fmt.Sprintf("%d", i))} + Fingerprints: lo.Times(5, func(i int) uint64 { + return phlaremodel.LabelsFromStrings("foo", "bar", "i", fmt.Sprintf("%d", i)).Hash() }), Profiles: lo.Times(5, func(i int) *ingestv1.SeriesProfile { return &ingestv1.SeriesProfile{Timestamp: int64(model.TimeFromUnixNano(int64(i * int(time.Minute)))), LabelIndex: int32(i)} }), }, { - LabelsSets: lo.Times(5, func(i int) *typesv1.Labels { - return &typesv1.Labels{Labels: phlaremodel.LabelsFromStrings("foo", "bar", "i", fmt.Sprintf("%d", i+5))} + Fingerprints: lo.Times(5, func(i int) uint64 { + return phlaremodel.LabelsFromStrings("foo", "bar", "i", fmt.Sprintf("%d", i+5)).Hash() }), Profiles: lo.Times(5, func(i int) *ingestv1.SeriesProfile { return &ingestv1.SeriesProfile{Timestamp: int64(model.TimeFromUnixNano(int64((i + 5) * int(time.Minute)))), LabelIndex: int32(i)} }), }, { - LabelsSets: lo.Times(1, func(i int) *typesv1.Labels { - return &typesv1.Labels{Labels: phlaremodel.LabelsFromStrings("foo", "bar", "i", fmt.Sprintf("%d", i+10))} + Fingerprints: lo.Times(1, func(i int) uint64 { + return phlaremodel.LabelsFromStrings("foo", "bar", "i", fmt.Sprintf("%d", i+10)).Hash() }), Profiles: lo.Times(1, func(i int) *ingestv1.SeriesProfile { return &ingestv1.SeriesProfile{Timestamp: int64(model.TimeFromUnixNano(int64((i + 10) * int(time.Minute)))), LabelIndex: int32(i)} }), }, - }, bidi.profilesSent) + } + testhelper.EqualProto(t, expectedSent, bidi.profilesSent) - require.Equal(t, []Profile{ + expectedFiltered := []Profile{ ProfileWithLabels{ profile: &schemav1.InMemoryProfile{TimeNanos: int64(5 * int(time.Minute))}, lbs: phlaremodel.LabelsFromStrings("foo", "bar", "i", fmt.Sprintf("%d", 5)), @@ -77,5 +78,6 @@ func TestFilterProfiles(t *testing.T) { lbs: phlaremodel.LabelsFromStrings("foo", "bar", "i", fmt.Sprintf("%d", 10)), fp: model.Fingerprint(phlaremodel.LabelsFromStrings("foo", "bar", "i", fmt.Sprintf("%d", 10)).Hash()), }, - }, filtered[0]) + } + require.Equal(t, expectedFiltered, filtered[0]) } diff --git a/pkg/phlaredb/phlaredb_test.go b/pkg/phlaredb/phlaredb_test.go index 1af6e2d172..603cbe112a 100644 --- a/pkg/phlaredb/phlaredb_test.go +++ b/pkg/phlaredb/phlaredb_test.go @@ -195,7 +195,7 @@ func TestMergeProfilesStacktraces(t *testing.T) { resp, err := bidi.Receive() require.NoError(t, err) require.Nil(t, resp.Result) - require.Len(t, resp.SelectedProfiles.LabelsSets, 1) + require.Len(t, resp.SelectedProfiles.Fingerprints, 1) require.Len(t, resp.SelectedProfiles.Profiles, 5) require.NoError(t, bidi.Send(&ingestv1.MergeProfilesStacktracesRequest{ @@ -325,7 +325,7 @@ func TestMergeProfilesPprof(t *testing.T) { resp, err := bidi.Receive() require.NoError(t, err) require.Nil(t, resp.Result) - require.Len(t, resp.SelectedProfiles.LabelsSets, 1) + require.Len(t, resp.SelectedProfiles.Fingerprints, 1) require.Len(t, resp.SelectedProfiles.Profiles, 5) require.NoError(t, bidi.Send(&ingestv1.MergeProfilesPprofRequest{ diff --git a/pkg/querier/select_merge.go b/pkg/querier/select_merge.go index 961f4b2ce5..e4bb2a48c6 100644 --- a/pkg/querier/select_merge.go +++ b/pkg/querier/select_merge.go @@ -26,9 +26,10 @@ import ( ) type ProfileWithLabels struct { - Timestamp int64 - phlaremodel.Labels + Timestamp int64 + Fingerprint uint64 IngesterAddr string + phlaremodel.Labels } type BidiClientMerge[Req any, Res any] interface { @@ -139,16 +140,25 @@ func (s *mergeIterator[R, Req, Res]) Next() bool { return false } s.currIdx = 0 - s.currentProfile.Timestamp = s.curr.Profiles[s.currIdx].Timestamp - s.currentProfile.Labels = s.curr.LabelsSets[s.curr.Profiles[s.currIdx].LabelIndex].Labels + s.setCurrentProfile() return true } s.currIdx++ - s.currentProfile.Timestamp = s.curr.Profiles[s.currIdx].Timestamp - s.currentProfile.Labels = s.curr.LabelsSets[s.curr.Profiles[s.currIdx].LabelIndex].Labels + s.setCurrentProfile() return true } +func (s *mergeIterator[R, Req, Res]) setCurrentProfile() { + p := s.curr.Profiles[s.currIdx] + s.currentProfile.Timestamp = p.Timestamp + if len(s.curr.LabelsSets) > 0 { + s.currentProfile.Labels = s.curr.LabelsSets[p.LabelIndex].Labels + } + if len(s.curr.Fingerprints) > 0 { + s.currentProfile.Fingerprint = s.curr.Fingerprints[p.LabelIndex] + } +} + func (s *mergeIterator[R, Req, Res]) fetchBatch() { var selectedProfiles *ingestv1.ProfileSets switch bidi := (s.bidi).(type) { @@ -250,10 +260,7 @@ func skipDuplicates(ctx context.Context, its []MergeIterator) error { return s.At() }, func(p1, p2 *ProfileWithLabels) bool { - if p1.Timestamp == p2.Timestamp { - return phlaremodel.CompareLabelPairs(p1.Labels, p2.Labels) < 0 - } - return p1.Timestamp < p2.Timestamp + return p1.Timestamp <= p2.Timestamp }, func(s MergeIterator) { if err := s.Close(); err != nil { @@ -262,17 +269,21 @@ func skipDuplicates(ctx context.Context, its []MergeIterator) error { }) defer tree.Close() + // We rely on the fact that profiles are ordered by timestamp. + // In order to deduplicate profiles, we only keep the first profile + // with a given fingerprint for a given timestamp. + fingerprints := newTimestampedFingerprints() duplicates := 0 total := 0 - previousTs := int64(-1) - previousLabels := phlaremodel.Labels{} for tree.Next() { next := tree.Winner() profile := next.At() total++ - if previousTs != profile.Timestamp || phlaremodel.CompareLabelPairs(previousLabels, profile.Labels) != 0 { - previousTs = profile.Timestamp - previousLabels = profile.Labels + fingerprint := profile.Fingerprint + if fingerprint == 0 && len(profile.Labels) > 0 { + fingerprint = profile.Labels.Hash() + } + if fingerprints.keep(profile.Timestamp, fingerprint) { next.Keep() continue } @@ -287,6 +298,42 @@ func skipDuplicates(ctx context.Context, its []MergeIterator) error { return errors.Err() } +func newTimestampedFingerprints() *timestampedFingerprints { + return ×tampedFingerprints{ + timestamp: math.MaxInt64, + fingerprints: make(map[uint64]struct{}), + } +} + +type timestampedFingerprints struct { + timestamp int64 + fingerprints map[uint64]struct{} +} + +// keep reports whether the profile has unique fingerprint for the timestamp. +func (p *timestampedFingerprints) keep(ts int64, fingerprint uint64) bool { + if p.timestamp != ts { + p.reset(ts, fingerprint) + return true + } + return !p.fingerprintSeen(fingerprint) +} + +func (p *timestampedFingerprints) reset(ts int64, fingerprint uint64) { + p.timestamp = ts + clear(p.fingerprints) + p.fingerprints[fingerprint] = struct{}{} +} + +func (p *timestampedFingerprints) fingerprintSeen(fingerprint uint64) (seen bool) { + _, seen = p.fingerprints[fingerprint] + if seen { + return true + } + p.fingerprints[fingerprint] = struct{}{} + return false +} + // selectMergeTree selects the profile from each ingester by deduping them and // returns merge of stacktrace samples represented as a tree. func selectMergeTree(ctx context.Context, responses []ResponseFromReplica[clientpool.BidiClientMergeProfilesStacktraces]) (*phlaremodel.Tree, error) { From 5eda406d7de9b8083ae2e83b0246e5e34dc41954 Mon Sep 17 00:00:00 2001 From: Anton Kolesnikov Date: Tue, 11 Jun 2024 21:46:17 +0800 Subject: [PATCH 8/9] fix: group by with enforced labels order (#3352) --- pkg/distributor/distributor.go | 4 +- pkg/model/labels.go | 154 +++++++++------------------------ pkg/model/labels_test.go | 24 ++++- 3 files changed, 65 insertions(+), 117 deletions(-) diff --git a/pkg/distributor/distributor.go b/pkg/distributor/distributor.go index a1065a6328..64d5592803 100644 --- a/pkg/distributor/distributor.go +++ b/pkg/distributor/distributor.go @@ -419,9 +419,7 @@ func (d *Distributor) sendRequests(ctx context.Context, req *distributormodel.Pu enforceLabelsOrder := d.limits.EnforceLabelsOrder(tenantID) for i, series := range profileSeries { if enforceLabelsOrder { - labels := phlaremodel.Labels(series.Labels) - labels.Insert(phlaremodel.LabelNameOrder, phlaremodel.LabelOrderEnforced) - series.Labels = labels + series.Labels = phlaremodel.Labels(series.Labels).InsertSorted(phlaremodel.LabelNameOrder, phlaremodel.LabelOrderEnforced) } if err = validation.ValidateLabels(d.limits, tenantID, series.Labels); err != nil { validation.DiscardedProfiles.WithLabelValues(string(validation.ReasonOf(err)), tenantID).Add(float64(req.TotalProfiles)) diff --git a/pkg/model/labels.go b/pkg/model/labels.go index f66de3a9ab..b4dd84f5c1 100644 --- a/pkg/model/labels.go +++ b/pkg/model/labels.go @@ -5,6 +5,7 @@ import ( "encoding/binary" "encoding/hex" "fmt" + "slices" "sort" "strconv" "strings" @@ -12,10 +13,8 @@ import ( "github.com/cespare/xxhash/v2" pmodel "github.com/prometheus/common/model" "github.com/prometheus/prometheus/model/labels" - "github.com/prometheus/prometheus/promql/parser" typesv1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1" - "github.com/grafana/pyroscope/pkg/slices" "github.com/grafana/pyroscope/pkg/util" ) @@ -102,52 +101,25 @@ func (ls Labels) Hash() uint64 { return xxhash.Sum64(b) } -// HashForLabels returns a hash value for the labels matching the provided names. -// 'names' have to be sorted in ascending order. -func (ls Labels) HashForLabels(b []byte, names ...string) (uint64, []byte) { - b = b[:0] - i, j := 0, 0 - for i < len(ls) && j < len(names) { - if names[j] < ls[i].Name { - j++ - } else if ls[i].Name < names[j] { - i++ - } else { - b = append(b, ls[i].Name...) - b = append(b, seps[0]) - b = append(b, ls[i].Value...) - b = append(b, seps[0]) - i++ - j++ - } - } - return xxhash.Sum64(b), b -} - // BytesWithLabels is just as Bytes(), but only for labels matching names. -// 'names' have to be sorted in ascending order. // It uses an byte invalid character as a separator and so should not be used for printing. func (ls Labels) BytesWithLabels(buf []byte, names ...string) []byte { - b := bytes.NewBuffer(buf[:0]) - b.WriteByte(labelSep) - i, j := 0, 0 - for i < len(ls) && j < len(names) { - if names[j] < ls[i].Name { - j++ - } else if ls[i].Name < names[j] { - i++ - } else { - if b.Len() > 1 { - b.WriteByte(seps[0]) + buf = buf[:0] + buf = append(buf, labelSep) + for _, name := range names { + for _, l := range ls { + if l.Name == name { + if len(buf) > 1 { + buf = append(buf, seps[0]) + } + buf = append(buf, l.Name...) + buf = append(buf, seps[0]) + buf = append(buf, l.Value...) + break } - b.WriteString(ls[i].Name) - b.WriteByte(seps[0]) - b.WriteString(ls[i].Value) - i++ - j++ } } - return b.Bytes() + return buf } func (ls Labels) ToPrometheusLabels() labels.Labels { @@ -180,22 +152,18 @@ func IsLabelAllowedForIngestion(name string) bool { return allowed } -// WithLabels returns a subset of Labels that matches match with the provided label names. +// WithLabels returns a subset of Labels that match with the provided label names. func (ls Labels) WithLabels(names ...string) Labels { - matchedLabels := Labels{} - - nameSet := make(map[string]struct{}, len(names)) - for _, n := range names { - nameSet[n] = struct{}{} - } - - for _, v := range ls { - if _, ok := nameSet[v.Name]; ok { - matchedLabels = append(matchedLabels, v) + matched := make(Labels, 0, len(names)) + for _, name := range names { + for _, l := range ls { + if l.Name == name { + matched = append(matched, l) + break + } } } - - return matchedLabels + return matched } // Get returns the value for the label with the given name. @@ -221,37 +189,42 @@ func (ls Labels) GetLabel(name string) (*typesv1.LabelPair, bool) { // Delete removes the first label encountered with the name given in place. func (ls Labels) Delete(name string) Labels { - return slices.RemoveInPlace(ls, func(pair *typesv1.LabelPair, i int) bool { - return pair.Name == name - }) + for i, l := range ls { + if l.Name == name { + return slices.Delete(ls, i, i+1) + } + } + return ls } -// Insert adds the given label to the set of labels. -// It assumes the labels are ordered -func (ls *Labels) Insert(name, value string) { - // Find the index where the new label should be inserted +// InsertSorted adds the given label to the set of labels. +// It assumes the labels are sorted lexicographically. +func (ls Labels) InsertSorted(name, value string) Labels { + // Find the index where the new label should be inserted. + // TODO: Use binary search on large label sets. index := -1 - for i, label := range *ls { + for i, label := range ls { if label.Name > name { index = i break } if label.Name == name { label.Value = value - return + return ls } } - // Insert the new label at the found index + // Insert the new label at the found index. l := &typesv1.LabelPair{ Name: name, Value: value, } - *ls = append(*ls, l) + c := append(ls, l) if index == -1 { - return + return c } - copy((*ls)[index+1:], (*ls)[index:]) - (*ls)[index] = l + copy((c)[index+1:], (c)[index:]) + (c)[index] = l + return c } func (ls Labels) Clone() Labels { @@ -301,22 +274,6 @@ func LabelPairsString(lbs []*typesv1.LabelPair) string { return b.String() } -// StringToLabelsPairs converts a string representation of label pairs to a slice of label pairs. -func StringToLabelsPairs(s string) ([]*typesv1.LabelPair, error) { - matchers, err := parser.ParseMetricSelector(s) - if err != nil { - return nil, err - } - result := make([]*typesv1.LabelPair, len(matchers)) - for i := range matchers { - result[i] = &typesv1.LabelPair{ - Name: matchers[i].Name, - Value: matchers[i].Value, - } - } - return result, nil -} - // LabelsFromStrings creates new labels from pairs of strings. func LabelsFromStrings(ss ...string) Labels { if len(ss)%2 != 0 { @@ -454,33 +411,6 @@ Outer: return append(res, b.add...) } -// StableHash is a labels hashing implementation which is guaranteed to not change over time. -// This function should be used whenever labels hashing backward compatibility must be guaranteed. -func StableHash(ls labels.Labels) uint64 { - // Use xxhash.Sum64(b) for fast path as it's faster. - b := make([]byte, 0, 1024) - for i, v := range ls { - if len(b)+len(v.Name)+len(v.Value)+2 >= cap(b) { - // If labels entry is 1KB+ do not allocate whole entry. - h := xxhash.New() - _, _ = h.Write(b) - for _, v := range ls[i:] { - _, _ = h.WriteString(v.Name) - _, _ = h.Write(seps) - _, _ = h.WriteString(v.Value) - _, _ = h.Write(seps) - } - return h.Sum64() - } - - b = append(b, v.Name...) - b = append(b, seps[0]) - b = append(b, v.Value...) - b = append(b, seps[0]) - } - return xxhash.Sum64(b) -} - type SessionID uint64 func (s SessionID) String() string { diff --git a/pkg/model/labels_test.go b/pkg/model/labels_test.go index 89380d1d79..5e7fdcd26c 100644 --- a/pkg/model/labels_test.go +++ b/pkg/model/labels_test.go @@ -158,6 +158,27 @@ func TestLabels_LabelsEnforcedOrder(t *testing.T) { }) } +func TestLabels_LabelsEnforcedOrder_BytesWithLabels(t *testing.T) { + labels := Labels{ + {Name: LabelNameProfileType, Value: "cpu"}, + {Name: LabelNameServiceNamePrivate, Value: "service"}, + {Name: "__request_id__", Value: "mess"}, + {Name: "A_label", Value: "bad"}, + {Name: "foo", Value: "bar"}, + } + sort.Sort(LabelsEnforcedOrder(labels)) + + assert.NotEqual(t, + labels.BytesWithLabels(nil, "A_label"), + labels.BytesWithLabels(nil, "not_a_label"), + ) + + assert.Equal(t, + labels.BytesWithLabels(nil, "A_label"), + Labels{{Name: "A_label", Value: "bad"}}.BytesWithLabels(nil, "A_label"), + ) +} + func permute[T any](s []T, f func([]T)) { n := len(s) stack := make([]int, n) @@ -256,8 +277,7 @@ func TestInsert(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - test.labels.Insert(test.insertName, test.insertValue) - assert.Equal(t, test.expected, test.labels) + assert.Equal(t, test.expected, test.labels.InsertSorted(test.insertName, test.insertValue)) }) } } From 30af2124e6229dd97e1568a3297b07e126442043 Mon Sep 17 00:00:00 2001 From: Christian Simon Date: Tue, 11 Jun 2024 14:46:39 +0100 Subject: [PATCH 9/9] Fix truncated Go CPU profiles (#3344) * Fix truncated CPU profiles * Write a test for minGroupSize. Not quite sure where the requirement for minGroupSize 2 comes from * Update maxRecursionDepth to 56 * Rename method, to something better maybe? --- pkg/pprof/fix_go_heap_truncated_test.go | 42 --------- pkg/pprof/fix_go_profile.go | 4 +- pkg/pprof/fix_go_profile_test.go | 2 +- ..._heap_truncated.go => fix_go_truncated.go} | 57 ++++++++--- pkg/pprof/fix_go_truncated_test.go | 89 ++++++++++++++++++ .../gotruncatefix/cpu_go_truncated_1.pb.gz | Bin 0 -> 72560 bytes .../cpu_go_truncated_1.pb.gz.fixed | Bin 0 -> 64342 bytes .../heap_go_truncated_1.pb.gz | Bin .../heap_go_truncated_1.pb.gz.fixed | Bin .../heap_go_truncated_2.pb.gz | Bin .../heap_go_truncated_2.pb.gz.fixed | Bin .../heap_go_truncated_3.pb.gz | Bin .../heap_go_truncated_3.pb.gz.fixed | Bin .../heap_go_truncated_4.pb.gz | Bin .../heap_go_truncated_4.pb.gz.fixed | Bin 15 files changed, 135 insertions(+), 59 deletions(-) delete mode 100644 pkg/pprof/fix_go_heap_truncated_test.go rename pkg/pprof/{fix_go_heap_truncated.go => fix_go_truncated.go} (83%) create mode 100644 pkg/pprof/fix_go_truncated_test.go create mode 100644 pkg/pprof/testdata/gotruncatefix/cpu_go_truncated_1.pb.gz create mode 100644 pkg/pprof/testdata/gotruncatefix/cpu_go_truncated_1.pb.gz.fixed rename pkg/pprof/testdata/{goheapfix => gotruncatefix}/heap_go_truncated_1.pb.gz (100%) rename pkg/pprof/testdata/{goheapfix => gotruncatefix}/heap_go_truncated_1.pb.gz.fixed (100%) rename pkg/pprof/testdata/{goheapfix => gotruncatefix}/heap_go_truncated_2.pb.gz (100%) rename pkg/pprof/testdata/{goheapfix => gotruncatefix}/heap_go_truncated_2.pb.gz.fixed (100%) rename pkg/pprof/testdata/{goheapfix => gotruncatefix}/heap_go_truncated_3.pb.gz (100%) rename pkg/pprof/testdata/{goheapfix => gotruncatefix}/heap_go_truncated_3.pb.gz.fixed (100%) rename pkg/pprof/testdata/{goheapfix => gotruncatefix}/heap_go_truncated_4.pb.gz (100%) rename pkg/pprof/testdata/{goheapfix => gotruncatefix}/heap_go_truncated_4.pb.gz.fixed (100%) diff --git a/pkg/pprof/fix_go_heap_truncated_test.go b/pkg/pprof/fix_go_heap_truncated_test.go deleted file mode 100644 index 5c648507e7..0000000000 --- a/pkg/pprof/fix_go_heap_truncated_test.go +++ /dev/null @@ -1,42 +0,0 @@ -package pprof - -import ( - "os" - "testing" - - "github.com/stretchr/testify/require" -) - -func Benchmark_RepairGoTruncatedStacktraces(b *testing.B) { - p, err := OpenFile("testdata/goheapfix/heap_go_truncated_3.pb.gz") - require.NoError(b, err) - b.ResetTimer() - b.ReportAllocs() - for i := 0; i < b.N; i++ { - RepairGoHeapTruncatedStacktraces(FixGoProfile(p.CloneVT())) - } -} - -func Test_UpdateFixtures_RepairGoTruncatedStacktraces(t *testing.T) { - t.Skip() - t.Helper() - paths := []string{ - "testdata/goheapfix/heap_go_truncated_1.pb.gz", // Cortex. - "testdata/goheapfix/heap_go_truncated_2.pb.gz", // Cortex. - "testdata/goheapfix/heap_go_truncated_3.pb.gz", // Loki. Pathological. - "testdata/goheapfix/heap_go_truncated_4.pb.gz", // Pyroscope. - } - for _, path := range paths { - func() { - p, err := OpenFile(path) - require.NoError(t, err, path) - f, err := os.Create(path + ".fixed") - require.NoError(t, err, path) - defer f.Close() - p.Profile = FixGoProfile(p.Profile) - RepairGoHeapTruncatedStacktraces(p.Profile) - _, err = p.WriteTo(f) - require.NoError(t, err, path) - }() - } -} diff --git a/pkg/pprof/fix_go_profile.go b/pkg/pprof/fix_go_profile.go index e073f9ae21..adf6715de0 100644 --- a/pkg/pprof/fix_go_profile.go +++ b/pkg/pprof/fix_go_profile.go @@ -19,8 +19,8 @@ func FixGoProfile(p *profilev1.Profile) *profilev1.Profile { // Now that the profile is normalized, we can try to repair // truncated stack traces, if any. Note that repaired stacks // are not deduplicated, so the caller need to normalize the - if MayHaveGoHeapTruncatedStacktraces(p) { - RepairGoHeapTruncatedStacktraces(p) + if PotentialTruncatedGoStacktraces(p) { + RepairGoTruncatedStacktraces(p) } return p } diff --git a/pkg/pprof/fix_go_profile_test.go b/pkg/pprof/fix_go_profile_test.go index 30be1864ad..6fa8d48e8f 100644 --- a/pkg/pprof/fix_go_profile_test.go +++ b/pkg/pprof/fix_go_profile_test.go @@ -10,7 +10,7 @@ import ( ) func Test_FixGoProfile(t *testing.T) { - p, err := OpenFile("testdata/goheapfix/heap_go_truncated_4.pb.gz") + p, err := OpenFile("testdata/gotruncatefix/heap_go_truncated_4.pb.gz") require.NoError(t, err) f := FixGoProfile(p.Profile) diff --git a/pkg/pprof/fix_go_heap_truncated.go b/pkg/pprof/fix_go_truncated.go similarity index 83% rename from pkg/pprof/fix_go_heap_truncated.go rename to pkg/pprof/fix_go_truncated.go index e5ef857f2f..1e169165a0 100644 --- a/pkg/pprof/fix_go_heap_truncated.go +++ b/pkg/pprof/fix_go_truncated.go @@ -10,30 +10,49 @@ import ( ) const ( - minGroupSize = 2 + minGroupSize = 2 + maxRecursiveDepth = 56 // Profiles with deeply recursive stack traces are ignored. tokens = 8 tokenLen = 16 - suffixLen = tokens + tokenLen + suffixLen = tokens + tokenLen // stacktraces shorter than suffixLen are not considered as truncated or missing stacks copied from tokenBytesLen = tokenLen * 8 suffixBytesLen = suffixLen * 8 ) -// MayHaveGoHeapTruncatedStacktraces reports whether there are +// PotentialTruncatedGoStacktraces reports whether there are // any chances that the profile may have truncated stack traces. -func MayHaveGoHeapTruncatedStacktraces(p *profilev1.Profile) bool { - if !hasGoHeapSampleTypes(p) { +func PotentialTruncatedGoStacktraces(p *profilev1.Profile) bool { + var minDepth int + var maxDepth int + + if hasGoHeapSampleTypes(p) { + minDepth = 32 + + // Go heap profiles in Go 1.23+ have a depth limit of 128 frames. Let's not try to fix truncation if we see any longer stacks. + maxDepth = 33 + } else if hasGoCPUSampleTypes(p) { + minDepth = 64 + } else { return false } - // Some truncated stacks have depth less than the depth limit (32). - const minDepth = 28 + + // Some truncated heap stacks have depth less than the depth limit. + // https://github.com/golang/go/blob/f7c330eac7777612574d8a1652fd415391f6095e/src/runtime/mprof.go#L446 + minDepth -= 4 + + deepEnough := false for _, s := range p.Sample { if len(s.LocationId) >= minDepth { - return true + deepEnough = true + } + // when it's too deep we no longer perform reassemlbing of truncated stacktraces + if maxDepth != 0 && len(s.LocationId) >= maxDepth { + return false } } - return false + return deepEnough } func hasGoHeapSampleTypes(p *profilev1.Profile) bool { @@ -50,7 +69,18 @@ func hasGoHeapSampleTypes(p *profilev1.Profile) bool { return false } -// RepairGoHeapTruncatedStacktraces repairs truncated stack traces +func hasGoCPUSampleTypes(p *profilev1.Profile) bool { + for _, st := range p.SampleType { + switch p.StringTable[st.Type] { + case + "cpu": + return true + } + } + return false +} + +// RepairGoTruncatedStacktraces repairs truncated stack traces // in Go heap profiles. // // Go heap profile has a depth limit of 32 frames, which often @@ -66,7 +96,7 @@ func hasGoHeapSampleTypes(p *profilev1.Profile) bool { // one by at least 16 frames in a row from the top, and has frames // above its root, stack S considered truncated, and the missing part // is copied from R. -func RepairGoHeapTruncatedStacktraces(p *profilev1.Profile) { +func RepairGoTruncatedStacktraces(p *profilev1.Profile) { // Group stack traces by bottom (closest to the root) locations. // Typically, there are very few groups (a hundred or two). samples, groups := split(p) @@ -82,7 +112,7 @@ func RepairGoHeapTruncatedStacktraces(p *profilev1.Profile) { if i+1 < len(groups) { n = groups[i+1] } - if s := n - g; s < minGroupSize { + if s := n - g; s < (minGroupSize - 1) { continue } // We take suffix of the first sample in the group. @@ -168,8 +198,7 @@ func RepairGoHeapTruncatedStacktraces(p *profilev1.Profile) { } c = n j++ - if j == tokenLen { - // Profiles with deeply recursive stack traces are ignored. + if j == maxRecursiveDepth { return } } diff --git a/pkg/pprof/fix_go_truncated_test.go b/pkg/pprof/fix_go_truncated_test.go new file mode 100644 index 0000000000..cd6c15af64 --- /dev/null +++ b/pkg/pprof/fix_go_truncated_test.go @@ -0,0 +1,89 @@ +package pprof + +import ( + "fmt" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/grafana/pyroscope/pkg/pprof/testhelper" +) + +func Benchmark_RepairGoTruncatedStacktraces(b *testing.B) { + p, err := OpenFile("testdata/gotruncatefix/heap_go_truncated_3.pb.gz") + require.NoError(b, err) + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + RepairGoTruncatedStacktraces(FixGoProfile(p.CloneVT())) + } +} + +func generateStackTrace(n int) []string { + res := make([]string, n) + runes := []rune("abcdefghijklmnopqrstuvwxyz") + + for idx := range res { + dest := n - (idx + 1) + if idx == 0 { + res[dest] = "start" + continue + } + res[dest] = fmt.Sprintf("%c%d", runes[(idx-1)/10], (idx-1)%10) + } + return res +} + +func Test_RepairGoTruncatedStacktraces(t *testing.T) { + n := 128 + fullStack := generateStackTrace(n) + b := testhelper.NewProfileBuilder(0).CPUProfile() + b.ForStacktraceString(fullStack[n-24:]...).AddSamples(1) + b.ForStacktraceString(fullStack[n-58 : n-9]...).AddSamples(2) + b.ForStacktraceString(fullStack[n-57 : n-8]...).AddSamples(4) + b.ForStacktraceString(fullStack[n-56 : n-7]...).AddSamples(8) + b.ForStacktraceString(append([]string{"yy1"}, fullStack[n-22:]...)...).AddSamples(16) + + RepairGoTruncatedStacktraces(b.Profile) + + // ensure all stacktraces start with the same 8 location ids + stacks := make([]uint64, 8) + for idx, sample := range b.Profile.Sample { + first8Stacks := sample.LocationId[len(sample.LocationId)-8:] + if idx == 0 { + copy(stacks, first8Stacks) + continue + } + t.Log(stacks) + assert.Equal(t, stacks, first8Stacks) + } +} + +func Test_UpdateFixtures_RepairGoTruncatedStacktraces(t *testing.T) { + if os.Getenv("UPDATE_FIXTURES") != "true" { + t.Skip() + } + t.Helper() + paths := []string{ + "testdata/gotruncatefix/heap_go_truncated_1.pb.gz", // Cortex. + "testdata/gotruncatefix/heap_go_truncated_2.pb.gz", // Cortex. + "testdata/gotruncatefix/heap_go_truncated_3.pb.gz", // Loki. Pathological. + "testdata/gotruncatefix/heap_go_truncated_4.pb.gz", // Pyroscope. + "testdata/gotruncatefix/cpu_go_truncated_1.pb.gz", // Cloudwatch Exporter + } + for _, path := range paths { + func() { + p, err := OpenFile(path) + require.NoError(t, err, path) + f, err := os.Create(path + ".fixed") + require.NoError(t, err, path) + defer f.Close() + p.Profile = FixGoProfile(p.Profile) + RepairGoTruncatedStacktraces(p.Profile) + _, err = p.WriteTo(f) + require.NoError(t, err, path) + }() + } +} diff --git a/pkg/pprof/testdata/gotruncatefix/cpu_go_truncated_1.pb.gz b/pkg/pprof/testdata/gotruncatefix/cpu_go_truncated_1.pb.gz new file mode 100644 index 0000000000000000000000000000000000000000..8234eba5ddad3e1b2e709579beec15c2c05bd515 GIT binary patch literal 72560 zcmWh!cRX9)8!k19pn}-5M(x$wyVRye?HRjfC_)jN*rWEIrKmkxDCu~WN9{m3xz_#}VS+gz$NZo%9I0p2{$Gh63ZcEAu zGivTNhz0O)%KuZ}GFR@@2a%XxkK~xj+1y4!QWvkILmG}ckOyHXCbvs$dT=;w}jqd4| zLp1qv>U~J9dPc5ZY(@)=HHso<9>bId?QbN;B^{c1PGM#VQ;e9XOdk?$eJum0lZd zPPJ7^dB`I1-%qip332j7O$f6~`rX-dUPu4jyXOdWE$@RSl-V_XprMANbWm|vF@Ck- z4j+a;l=jYYcniUCuHK3?*$laOYfT9#O(o??31sh6>KIlk=Ur9_scH^Z*Bbo!~RICEET;PCuoWPVfAhXa9tdF4&ui;9dMF_G>6o7i~sY*QrqC;H&_XS}N$ zit0k4y~&Qkisj}Y7s-Snzdt|*>pyT|rqFF|q9Uq z?ykSTqHSxkBlkgeU5zH3pw4<1ZA zB9umzE|ebkOFcMq&9n3NxREetZY)uXrradH+Z$REOvryc&o)`!6ocVg$$YFB!}dps zS&^-GvX|;$!gJUsX5i6#t=J`#XxFtzY=dc+Y!vGy1x`lj%1cA+XJ3#Wlpkchq^1X> zo=hn7i5fr*V3a^LMD}Yj632^OK5@=}CC7Rh?t$1jtofpvQn-Y-t&80N;dn*IIp|V4 zd=;iBpGVWX+Dn*Jv-edwziX(!3Ks{99{n@DBnTgZDXtd3FK}8Tg2lpi3?8{E(874c z@YW7&v2jXd>v`8TQUlx%&P-RW)AbH^kG?TCe(6H;o@U*D7!tR9?}IESU;3LLr0o2b zwC|x}O6fUoOttre-J?ZukzCsWYKp1{EJi0WGaIhABo1<@QteQ~-hW;`l;B+!>32Uv z7&Om>q17y_L4&TRZTEXar+9fmV4pQ7+NWpUi%cmaJ?OK`U@RHhn=vKG!J{DTj@2#E zc7XVv5xb04HP<*h_dfJF)Fr7376~0Qy|cY!x@E#9p+De@o(z5&;wXr_P;prpz8K8K z=-lTb6-@rUJ4GOM{$AfTbA7zN!_eL)Vt0J0`oh`P>|9}Yc#HL(bV?6kNidxO9>%-y z-XCx5uri)$nwOdZD?U1DBfnt}B_G&@{rZGUfJ&X7x|taj z*S|Y^8%&qv*g`b_?*-yh)6%=6_>Gw(A0+}$Y4{P*-3 zhVqi~J_t$rdwTTr{9O3=H1Ebb?)%^=Axp2;0WccWQ)8-?A(a#|ZqU2{!;?9-Syp2q~PG#DTUHV3W;+6AtSyPF&y2JB0`qI-SE za7lYVjt$7udJSVdabEmc8cXzXS)w!+hJ>BLSnhQmOOy`I)%PuYm>K-(HB}l78--oK zSi%J#oLL+m7nkD(w$xmzG#-n&} z*K|CyhuG)3DYn?>w^H|A*cA+Zak?EMe&Q(MmfX55kUM`MavgngYZpgc_h(?_ulo5v zy7C-bC9nQR?tu9`>_l_&$hJ>&#k&6Y+>Km>QK`6H)Z4iINv!(iuLqNKr-NX*3cJ+Z zCrhCW;`A<}N?Eh-QIoltbG)A0etaLooa^>0QYW!V@Vy$SU-rS7bAuh&<8sAKKXYnb zRTAlGg$#MKW#;?)VNYH^DQ{iM*lv9~QZ)YRv*4;u%B_`r*XXC=ra`$wseYnhz*%1D z%+M(=iD;RAE=z{87#rG=O) z61zOiPwM;KE0OU13A?L5>)}`RcK6Fa2X0gLaLLT`L-A0Pr0NPJ?{V`-=&7^$_^n7> z_+trJyfGa`*0jijl(nwr>iN`@u5tm!KA#s1Nb%j=w&v)^>m$h{`wMp`2;oiotf?S- zAX00xbaLopmDm(^cfBuf1a`23mv^$U7AY(X?kB58)$9bC)wmtadi9QsZ*df?9ppLeV$TuES4qWio<)gSpW5{!YaOf z-PrC)d4a?8GY@kpHNGe;ZC|dwE|E>$)w=bHYSKwM>#9T0vnJz?%KKovOg0?w%kAJ2 zmmks-KU$(5?W94xqwmHIHY87j$98!|3(=lQ()rA3yg8{i#V6q>H%gc2b?kqmczLDv zmdgyXJ229lO1w+H1+fOla0N29$1>M^IXmVkr3l)OnNvy#<~V=BicGG47((xWs5Z4! zMI>Nz@7s&5bkA@iasZQ{Z=bfFq*DB6z@9jam5i*`SP40nfA<#&iy580y?rw2DqVU- zW)6+t-P2sa;sfwlDK{c4 zNz=&zJ4y+8_2_h_IRVYNE7j&p#}OVm@xuxwQqH4#3$&!~G)K{>-b$2sbS1zdZQ7ek zC9(L804t%*^@pMZsDD4r<~8nVP1TFBX6BQ=VrMJni_^HJk4}DNO-pyWY2#O5_AP0S zs5KbxgcNZrP_iP^n;a-%e+q$LMU~$EWeYz)gWZ0xyFC-Z?${l*4^K8VU#iHqR1U=b zTpAB2*I^KLz844eb0*~5Z4KX?-wg~h9p1pAcL2E(FhVjrHq_(*`ud62-y2x`K+6f1 z#Z=sS>^2Y-F8|={#N2S{rNkMVuQ=lcHsUqFObqs%jIwUy`>s;6yS^O9J#|j2ay~XH z>)FKsJOS2vW$>T<;^3`5NVuFEYu6lyX50;G!ALj8t}9C+y>Egz$Y)?+>~B@@KkUk% z7Y2pcxDLQiaTrV6=s%dlOq~&UB%I7^_5l_YLVqp_yTgF4$UA4&H51lVqC1PPS^kP2 zI^Un6Jz1*qup=uHlBddr&xqdDrqX9iimoDVQv_G1qiD{o8oZaE;(N_8G}KsT*0saY zJRfUPK3=g1FSkUpGXOrRPe?`&y2KTd%<)E_@}!`rD?1BxiL9K<^gz*_387Bni$lI- z4qaatoI={N(Wck$GDtRgiqR#G=UvncZ(k1Nf3;^qdd2Ls>k=X3e|ElNYGrsfo0($h zE{8mtTpt`FCgQKKSgI7xZfa z_U{08bAi2_!Cp_nu;)SUg2m3wx;evZw>j+IJmT^&cbPExTor*7!({Cq`eX3DH;7`4 z-Mf{6b*8mUp2(weaJtfARAF*`Vn`B%(xX7e0m;T)U#Z^kzpM@eTv)skrrhMoMaRb0 z@B0%9ZUVq2%QL=8XO!Q5lWy{)IXK3$q^lBr(EP1iGp7N7?FzgY!X_v1vl{ab6PlAWLFok(`#v7BhsglU&x+vjp z{-x&;VpGHbVC2Uk03L3L$!--l)n6|{VSEFa0$fR>Sqt*%v#%M>t-y0=f|-Ibz#c_A zwHtr5&691lLQ*2i+G4Q~NF16$=auBQ$0nlE6PSi-_80Q>t~H{9#j7e@?u^l0M9aFa z-&CGt=u#5VQ;5RSV|}zzW()lRGh$eIGPAkP(s5t)7i_a9+AdeWo%pTop^;Z~7MEQr ziFNkS{t?xWd9*#5b-r*k_b#PaPjQWy=SfF81K@uAxSIhWEYUl8DXxX+KQpBxy`tc4e9$K`?=%7Z^T*vEF$XMd9DoxWwtVM;CJkINMha^s1v zGt`q{SapKVN7cL##qeA>QI8tFbqCPstAlLncDoIufX zfD=)yuvi~oO&q%A4kTWvV$ad$&atJiyY7mfh^CRqa=2Qsc#FRvb{P}9AO=NX@Xpv; zF<0!v3*rp}vMKEUCP}x(OrR(X)frnGrcos^e;6~TPL_|{nDG?yr>}zOZV?2BrGs50 ztZm*XEg&|6$=vPJr^Slr}aie}>zR*vV?4SccV^ZSVm{-m$P^Z zMjkYlJ!Ue#nKyg~m%n{|dk5#f>$JPOUgv2U^CT@C=U;p#S^T<{Uo!fxx!|rj?#`E| z-nnhj^BgIAjtn_RvfW816v_Y$B#YR!C16ex)2``LVud#{@~hg$^9WyO6}H(mLV%*9 z)#nHm;Zf`3y;ID*%Ic(ZUcd#{_qO!G@`Ondr}P=I(i=i~K_Kpm<6_B@M%hacJV1<} z)=#j#OrC+jMG>j@;Z5y;9zfjfN{Ew@-fe!ejC0vR50EGgT4Jl7SfE!)lmYn<#KiCX zEz4Ko8#Ox4J@w_MN+JZL9kqY&6m1SQNY>tAo)B`Q3V6$bKwUYx*fij2&WA1za&Y4~>zEiYeqEw6K4PUhSTPT;YyX@%xYFfO%Q##m^L2@tqF`ng!pjEbRtafVl zYSSj#ju_IG=V0(bK{!XEHc|4(uVPp_`sRC)q}OV@7&G(JN}opi<}K$=CIR{d-v)HJ zAM~PHWmV9@+;<9WD*j*S=ivq?(X1`qp~=wT$5J{B=T#QEa?w+2Az5`M6D>KS0{^*L zjx;Mi$?ES&RCzAc;>16-DV-!Lp(eT}=n9p=bs~|cBG4E;*!!rbV9+zQjl_x@x7O68 zIZb{kW1`Jm6~zip6+kNR14&vLh{ zlY;Hi!OIMv#`!+H`9gm?Vmd!;&pH~sS~`RDA>wnPMZTk)cr;hcuJQ{2^0l_-IA!-X zm5>*$kLhm@gszO%wx@5Y9!vdcF8X0XjEpKgH$I>bT?&8063vsUw`zw%&JeoEGW@V> zOJ^l2rFd^qT6UoTD3Fn~+WugawXaS#3c9(_i0-yPVpJ>@JIZoZBJpL%LKxcpJa_KF ztZUr6bxG*XgJ6PXgnc^5Dp}i$c}38TDe?=0{_nQ36addlT*&dY2CL)~SWfs}hm$R% zHt_T;lC^bYggeT})l`0ndH%}OL)lA2%Np(S7`ro!Iv1IsydO&`0Ej``k6p;31q_x9 zmxk!;Y@@X4B5RjuviDKC2Ow zUuRUK(~&2w)s(C`uNCr(xs(+S6tZ@f@KoobHyKP>`cf>848&YR=zsk+Z)cc^iR_VNa~WQOj-Y zoLs`Z0YyYfsJ{{Kll^+io>7=Trm|Y(0` zSeQ%tKQh3z^tpxP4pL>++J12*TOApgF&O1WYwhHlxWzY7vyhGm$v_Vo=k#@>%1-u` z3pt2J?cNjyzz5w^Kfr~gPv7E3V}Eba0OrGcz5_%S?C<=IoB){i4&CpHM@ulzJa%Cr zf=Hk++C9V84x&#gl+EaicAz<;X?C~?<{W1gBDBjN&Dtu*ZR{Sg^}u1bpC_SE0MJGI z{ae>3u|?gs_qx?8snM$jq)h6e%<_cp5-y>LXi#8@jXm8Gw^6eeEtCzP{2}xf8 z>`mKf^d7Pht#=4@BR3dFJwRti?B2(sMJAUKvTt+M-yGm>@b8_zW0F5CSNF?BR}^=k zmew7=P2qbTr!mW1(64Uea9WR>^sz+u@M%u=oVa`4nj`c!YG-dx{BnU>d)DeuvIlwG z`i`VUV0hKlJQwri4QErPA!YeZ@~UNhxx=0%S!1wFRQg>?*B82l|1W$KKt0e*GI+G-F{By z*t7Zf&-KMS29zGr=nL|k4Mmq%DpkVy+LY3Y>`_)P-wS7L{m@}@8yBYs@agQgN>5vO zq$NJ1@PBgsegw@i8h;*hTy~3tLMP{-vT4s^8RWCFIz^KVw$B?_ zU0Y>7ty?OS@f9Oi9(svO7u7Uz%|~l6vq7+8l*k48J}v4zp4+!nC0rl#kdW`w_>4`u z6cd%%1!Ej9?BfW9xH*d^PW77zogsM!&qr(D^NG$!y>So!BQ1H@a)z{jB9xNL6`GmZ z8cZ>>6D(hF6q?$_Y6TX{cp;!?D4yL);6kFrDqNKzya5diBzZ1Sh+IuLo8A>Q+)z`A zvVB+=K_>I5S01h`!#t;6rY9W#%co0TlOo%KY3sKu2; z-FP*iV4M0~UCcnl?_oW@(Y^rF9us7Q;Od~X@d!j8;xGS=rxd9=H%IGRFP1nPjUP!E z)ox6t`O6Mb{>r`_lcYvZ1Tmg&5$UrgUZ(NuR@q{#G-^SB(YcQ{TQ^;Rqtg=31pJ#M z*``$SMv>JZ6IC4uZjR73q;%w2F?os8juYgN3|PnbkZ!t=j{X(sEazsf`8Jwa_e^Vp z3vB>G(?xaXkDqGW)&Sc1c3x?E^NR87QAw+(+X&xepDv&nag1wigmy54$X?c^lVU6u zbMj#hSijMIQU0$&a0?^h0eiY7`g+5}QX0mFntGNxDsl9MkImgwCZ05scBqtR=p-XN z-O;p8L0eejX*gl?r4Dr?^kbC4*8J7iL6b4a%PEyP3;j*ZdXAdGrl`%vTeMjsMoMMX z1k&3YEE3=+-lfC!{AD$Nv`L4YHDIa+z=vI?BKpICGnLD}hy*F-VyW>^~ z>j5gAI)EyC7mMFYE`Q5d!N>}47&9pl@$JXZC18YbWx)oti-+T?AFnpdUq*kTnfFAO z^)t_KZTqVYRTW+z>d0NlAFUrT|g*FhJ8dP60G7@Annrl^;x8N0P`C4Q{x%*XXQc}=1*tJg+vs0lhYn7 z@t{bdsJzRr44(WK^$63O3u0r(ze&URbYug1F?7&3$>CqrHh?!9^eHo(sZwdI`Xuf7 z6KL=DxII5}e&0GwE*$f$5$%$iJx@X9w)Pb*oi@#BEqqJMDOmMbhmqT_UF>y7KkUX4f^b5!cH<+_9KskGLc=V)3_GC(6;a5VkMX({yV-I z*}#jKDW_|XEoj4JXKe=3YnfP#KY;YG-L7qO9he#>16n|pi{U?qm_PANA$&dmK(Jc! z-|QX;(yad#_d$|FV0hOf%2`sI5lD!c)Tz%DbKuM8&;88>d>h%j(!A6%bNvr{U`0e_ zv6LQ@meLK>-P4_gG^GO4`g~mvzm1m*e*BRAA`QVR_ds3nz6<{A&*!U9JiD(4K52IfP&z2EwFtaPr#`-?=j1l;0G{U(=PveT1fEdCi~ z)iv90gs7Z5Z@R3=xD*RjE3A(==q(^5Df;l=5$5Na8ETk8iUyvm*oQw7yw|1=GzoXP zOfVl?x%!AAG7zhmnf|Elx#qJnwKCW;@LMp%e6t9DJsE?Nk6y27n9Pe)NbRrhFq>-V*^M}~A*Mi~xYBk)#*?aB{nAR^ zTx))P7_yR3-o$f{(rPOR!sz$M1)chVH+y8xDHi*BqFxY*oel%E-69qB3_3|xd2YL6 z2FN#Eghqo$UB6NFXYwu>2*`NSb=zFZ{b$1H^8+GSq_|DA-0q*Jb!0nSo~U0yudu}D z`vDUKZ;o5;OGJ7{_8di%X6lAWyLf%sCK_+p{qdSTpY$EUaZ8?57H8(xj^Irs1;zfV zcNGQ2JHq3RJdWQcBBOe|g$afG+s!$$9dR$To`n=MpOdWee)Xs?q?mfnd``CN!{#)v zy@+Vp({+@8Rg;9Y64d&=>8refhi0Qew>hp#eF=VIH}Uh7wZG)z4|zMQl;5F}r$L`L z1?nA_F*+UbVn|7FC>K|uQjcz><$SZ~%(2?rbK+;Ff~s9=s9(COBrZ)tE?$sXjP5&W z>eUR1MUKY6tj)L9K&dkMI(0d$6enF%hiG5b4H9%I4aaGLm5lA0{d1NBnZpDk4~2Ne z$grsQqJ_{17p75obfMeF5SgOQ$Y(xLc8iee0m72jZ;Fb$$yeW-uwGBrQBr3@p#tk8 zV|CKQo_l!#O2w%+U=_}kQZ#uB#PwFQo3h;&T| zmc97R?tSjpD?JL9^zUZcSCsUZ7QhHPP~bTA!Z7A{twK29>%6d)%VYUxfwe#Pgy~q6 zPew*^%Cn+wX~==tYn(oth=Ae|>{W~a{Qo6|kJ*#RPCvJ#yzEBiX%czf+cVBpwfJ8u zsm|`5HD1g$8m>2z6m$f(&Fm8om^T;a`;y-*PorC#J?!P;lki1Iv6+tys9?}M#~)MG z1j{ND!%>00^gwB@+X>FY^2(h@qu2jz2$shjB&Rf!wao}@sqSA>t#O%dJ{=V}0<|Y0 zk8AaMq^+r;Q&B%2|3^Q{w~yPH2D#>H2ZSIwy$(uB2+BV%Zbg-b(*8Kg&oyMlg^|5e z?Fld1daqbdJnj67mM`1D*IL0};?%WA!wu<8@srS&?w*`#cb1@N`_jJWm-2@^ZpT6C zd5zY5?&yrUOnE;#v7S5w$Yi&L%3D{;c$?YKBaZ77sn~$5XE?@ds-*`C%*gGdvbudn z>rK+jeYZ+&-$gTr})(&@L4gj%qkt!?vheX zI;F-x-4Xl7YM#TJdt`w=o0T-w&BM~b>5O@l%*Nv@C{$WSlF}c_g~`CMC5b*yC_H0M zBVSHs;rQZd6&CZN0QGDi%Oe{v;#)M|Qu0xTQLAjsL^C%}_zvLiMNKCoJg}mk?zkE< z$79T446dmP50laq%avXeZcg7~<`i0iP*?!ls+0`l*2#<}ACJbri%)2ip;9TSQSC%| zzL}tlrt!~rT&g>Wksd=Qb$0N|Y->(tgnt4kX^?8Bnw@!nGx-OR29evb#mmEkL2c1- zC;X#rd%N@WrJL!j0XH=9q429Yk#w|F6peq%R^_PaUxlg0tT1s}Qf`p;fM#41snKLW zs@dL2mG_1JWtEq{Qal(wJSUQYwj<=*B^teZP3G=Zj7BPQxJtj*7R(OM2Pjch)ent^BJjM=*D89K;exwNQbty(|&MZm%QYqzf zi?&{Iw5VRsJJAzB{Bs=-QcWMV%P9GQO7=u;Hs2=g@8UWdg=`G+7o3tY->*?j6H;UY z1*+}1kpt{{T%%eOg>DHVeoP_hy0HvE*F`udZ#FTroYXS>3o3rfBUjo)ry(ai1z?A( zr{bCAQWxQjo0yQBat;a!?&BGvu8z?oGXg#yy^Rk4qlH_6&_(@#SfnSsjXRh;?5U>M zKK{?r&U8y6Hhd7wcoRA3SiV)~qmit#^O;it6y!jrDkZbH0j}2y@xj17Eil-1jv-eF zGZ?{rDkalUMb1yopa%@RE$*%zp4M(&)TDNtc~1X$g{krsbJ=|By;? z;?g~W0*Tcz;bR!Y%R6YOQL~4eXBjWc2>ZF2X z;EhDNvN;hQydA+hPH0&*`YWdt+2)#lXJD&d0E z-55VGrxq#&rX=QVC-OM9LX##5XvjZ)U$vznYZys9@qsGnGy%!&Wp$B&G=l3?59r3N z1cuK zcc2;d?t`!f)eVGG(iqT}eaMOAzw||wybw#4d4f)E&u2na32deG2k-sFv~)qnC=)}D z8WQ2B0BBy}2ZTci=7x54=27d8E?u6aqfoI0HDTzma6BAemK)~`NC6B0%j#$rGy786 zm+%!IKh1g)XXe%!Aq=+xUESmK(^;Vwt7{MEJer6)pmb1W%FWfIV5t$T?CfundG+dA z;7r)1mclUGMoD_IYMI))LSQhdfIa@Tb6t>NsMB~=C1=+7f5@p90?bIs_F4qv zTl%j{82GL1FoK3;Q4rGh*BI%UnXvn4_3bb?k#Iphsoxv{f?GXu{Fg_Lzo*cg2gD8_@8zV8?*17PMWm!v>{aeB3C!aeFyV(BK>? zzJnj`+6BvxI0AO*{NX;~QLM8@P{8F5quB`gY7944lWv^KCb{sGnxmz(5c+UAq3s*o z7S82+t#+U&XMte+c&}iHa7bo7%ud~~oYSCXN3p8hfcT?$Jz;9oQ3d?+p z3V5d1h`(ZH^(r>ub_>Vtia-Vkv*+FW%s2XEsP)PwpE*34&Z_fO-o)Eydyh64i!|OS zN#aI@=+b=yk`T@lGG{_Huh%IB`ZXzv@N@U*sA`Oq4Y$8q^KCSvkTFY_IPoQiRQ{E# z*lT=oMTZX++6XGR#v}jKgc0$`s{m({833M_U0WUleyX;-b{?M{j=TSi(Ai_9qN)S= zZo4PpqVA^HkRUW9`8XiM{A;0lk{fN3dZ9UIPNFmT^X0m6SHd3Urb{fs|Fx@|z|Tk0 zOevu$5nI4Coq>|rc(-jc(p4i@6$wJXTxyEpehi8l^wubYvKDbaEcvZLQf2oIVbOc2 z(e;`6WoU#;HX`vzGVx7LL=)lot*8U4n7hmXT&f<9Lm}Vs_kL+UM#*v}RKg#bCS+IG zGys*XSqxD$^Bm2agWljbE=dlfn@jlmTJqd?^Ww>bka<4e@L#Opf4>P1!GTTRN&d@S z^btGw@6zgt?^Q=&#lpX#xJYhebTUd}PD!9q)rf`iA!;FSM> zPXKq)^FqEG=Yn&!Q&gCbxEdQzV*g<5uf6hS#9w0POKtJUN7B5fMSo`|G?ki7H?qmj zyZi}n>uo^(gx2ZML9imQ%z{_6epPjsB!PH*lI}5K>?F0X;ime&T_#5r&wLUG{KedW zAQCD%{rAzK$W#qWm$EY1Y(gylnp3=UWP|{)NfY}KV3j~1MB}Ts`F!6l7wr+%Z6Sg| zWXGgBvG8s3aQ*jH@tV*I~yp?24lPI)g) ze0mTTS<9MGTby0QI*CyJ{pC0C2u?eFVLt)~U7Ht1XwAO#p~h%#J`srC`}T63ulb`N zVPwvuugS_f1oAnT=GX50@|u8s;YdolMUW7!VMB1T_CD!6ZO4D?kis6q8k~8=8Q&9y zYh@sn5UfpBen(KU-jh(f<%cK9DfhBkH#R@<{f(adeKu+p78t$wR1!V~PNGlITjMX` z3R^+N9C12CEOYQ2(ohG&NqUCOA1<(-{u9ai;?jgzDmGo}P*tY6>`_(D84^?<|78Ba zr4;df)U)?0e#3%(!t9v#2IUXawZ9;{8b5UzKp^L9z8TqQ#XnxMA+SybEo0{FYu7tv9{F9OrP1+`& z5Kpms!fpOZ?nyPR;}-m&(onUW1PVMp&>RQ5fkVlkv)7Ms1&5$$m=Q14s6O77P?fQA zP7MOqJa-Ncg2vMYCw>|}L8I^%QYE&kyh?mtXFVPstb%Dd4)!_XyS`?r zNbDi#{b9PwU8d#$=hSIZav?vYBSxt}G?_sfsM(g@@fJG47$p=Ccf%I`iG0E2L+3BU z&2x2D%5_6Vazisj5P#K;kA89rNBENdJrDy@K^Dkr-l#)^1{wlUE#kVdD@9y${~&X0 zjF2r@55SLmLiCgLsj+#@tsm|5Zk<@jb1gL2j)AEI75O+{V;&rdBTAf>Fok@AK>Xi=(ZwtHkHQ{+t>#A8(1UE`!wDy@E`k8v* z9U+RVlxk7QNo5$o`VxQGB682b;v$_K|I^EQNOZ(L=?4KUJ(30-vda*l z@y?6pRb8{umA^A?g&hWvdQP{e>w(SjdbE_Yhxo~^JReK#5&Q`H`$|c2zTJwn#{vO} zm3VL0I7Fs+wmcJW1v^V z$Vemk6!wW!DW9?O<1Sy(hubm1UY)l)iHK|ePLg*7Z;jYZlC{rhr@P8%MaVXHKO(M@ zgxs1USPYHNNzKcABXQCLY|8`x2ItNxjZmk;g;-KL%Wt9hHgtCFW6TA68!4Ed$E*&l27vg_tXh{N7 z$9pt&K11gq5~I(T@bGS);u(l%7tl!ZDaovEr9o(W_&Ax+LJnbRK4hLu`It7Rct+Rb zN1mP~st0#v5uD55_!vDcU?p)9jj&O88>#={lvfL%| z0CVN~A=4cZQ#3QD=mo*Kb=QwXFa>o9Myp@wIH6~=gg&q&sqhBBr?v4kMOCCWl5aN> z(F$gI)skk|U(W8$I=5kE_x&N@w|iRZpOK4Um2>4oFy(N`Q+=*sd0?KTedCk{(E5f7 z&x>^B{eW6*JBpP|{v4yYpO=>SwNdb8bv0=&zh8B$cKtKBYwlD9hiuL(dKT(tU9|X) zh*`PDwJ)6Rr{!0Om}iiTuzl>N{k%=b&-tZ=MNXTbn>SSKn4$Ja>= z@q}sDsCEr`Tagj*@z`xDpM(Nsrg^#}+wgzzTi4$P@N+dnBT;p{j3jfz=m3L;X1KW3 z?pN!4-ZN*sj0;sb??J03GsEL_refoV_#rgsPmLaIf0KF51$iA!NKVQ$pUqe~7ZQ6Q z}&#YV4$wtldZYPN+6k)k>R7__(9&4FAoOZ^3v>G~Uf+jQH8wtLu&` zbOban%gFF0`B&Xk2nqPJq_I)2wRO1_a=8h2fI%PVsH#8G_?~VdMjl~eTQxxwKnwl} zE|SxoKf?(al~vtL=}N^k{h1_uKX(h9p(txe@BQ2Mc+o6w;Q}Wyiz=Uxp%>B28@yv< zeg*96UaPtp(BRe`{tz`(`S6qp+`{-eA-cguG$mZ=x4okJOAQ~YLVg{)|ALsLXo2%C zyT)h2Bu^80&NCyXNTmdn>|)fPxz2O3<~ysrz?amRTTYJ_ z1@W|~n85GkU;P*=Wvwz%YXqK|BH%UNv3Ty5jcT|$m+ttK5?O=%OHbAEGmimy^TFT) zcZfP-1>gA->A~V9i+PEYlCL^KG8UJ$HnBbx|Iy;f(Hu9!vWlB6H$O!dw&#C>JMflz z;0N-r*4^5Si>E&a5-0g&ctULu#R-gdbj?<1@1zz7K?!c#75A=raxp=~ugA?AXx1G% zwazU|xRhn%_dhOCW_^72O+$T&lM{Hsi`s&2t)>%JQLU=gIHsg@C=AzJiX)ml;e|ey z^*df-hcWN05aF>mv=%}z`IqmT(&x9|TJY6x1FoX7{#;lU#X&n7G9;WEi?|!pAjPOG z1)FEiK?IX#=j>meY(xw_99OCxx~lr!n7e5BQ?IOtb9@L`ImZzjFZiEgW4a7*Vy1!P zn*v^LrAjaFM)MF7^cL<0iAKRk8>NHo5Wf>#`$RSphMt%fS*yz*Si`S~k3L_Jo2xb4 zxB>w~c0a~KWHcnde*SY{cRxbL-O&bf`>>}K>id1Ppfyerz789TRFQ z6t3{Xl+RkQY_Y|}F%m^worCNfc3|u{d?#AOf9`R%9lFF0`nsxA9hqJukWd&4oS`wO zDi3?wAex2Ca!BlGHcT}Z=XYY{Uh^nc<2srZ4Jy>F<D7$9AEO7?*h9`x*^qlXffi zyO%$KPi|skTMZ@0NCrgam&gbmZ%S3K#|25SU=qEY7`)hr zptE2jWu3$PoDK9B_;x>!Sb>NYSD|x~XLgcvp*80y#$AW|2PMx)RVu+}nl|0d-eKc9 zE9xCgTf(_*=Kdms2FtUQKtnUY4ky9QJ6r>P#pQl*;H?fFeLk?XcGmPV_)91|Ct z-WrjdZS})QXgyw?rsC{3D!KPpk~V@1;s*V_84m^NF5AI8#*l`)U4I>K9Na zZP6}QOPWroTbv|_X04?x9KSIex)DW~twW#qQ8?Gv3~fX9yZo+4AMCxXyQyo%aJvvf z_E)pF$4mO3jfl{YVO)61VdgKtMr&Rsi?kx`&;R|k)H5j`$_%HZ*|}*w|_&fN-~n+uy*jtmCwI%XSEhd%}H6WF^m(5vD+}0@qXkG z@i?q@s1&E~RHDKHesnk{5{!Q@(2dm~W#hxPkgjWt5pn4Y%IY!B)&WxupNsKVyX($} zf1#kvK=oUIe`FgId`u^4!jh6OFiHsiC#ZJ@g3Gubrk3!arS;AjDRyRK^s>_H#Efs$ z1?43rYu%z(pGu8Q`6acNj9GD0*tT*SlgGmzwsL!CLH)rkkKX4CM|LM`lhC=xW$mlv zj0heXEI9bRNnUgEnEVl*NB*pXlSD&J4_E6z=qT$I(%hQK<@ zAwqB?&J$YJqI{!;Hr;Fp(y^AA_~Z2)Sm0iSL&CK!ud-Bk{;zaBX>o86pz6x^Tcq}` ziQ!C9Ul#cx*)(elx6*Im(QJ%iTS~DJC|7fYpX&r%B5^8^)}g1gj-!+gkE*k)*3)_O znma$1<1&XukrvlZ!l@kid-pbO-8t#8s2pksCHB+BkcrCgS_hHEy0K%7yeoBgs_9?~ z?NvFat2D5_hd01xojYhk!LMnR_hRDVpEBl*0ZJ`vGxzAM1MWqq6P!0U>g-qLeyCfv^W#GqsCp_dLbRUK36#ptmpUKo|2~s$Pn+c+iYPJFcV47 z`C2_vYf{(wSGmsj_aNfmbHxbToWr-`9 zUT!Y+oYRy}p@9&|tw{^`QoUfT#Zw2?`~${nJx8iaVu|O|?J+JCEw?GaH_v}86Z^1Z z?cv;JM?L~vtK9sGABW_h%#2481Tefew@UvS{Lfv^evj*T0G8;Sy^mSaLa9W1OVFNqw96UoFpFAEgP6t&v&)Pp1X8LRS=fW%}jv%&L- z4F$xek0#)(o@mEfGOajk@91<6CHytX&y%!C2iViJ$iYbNz?2DXUf=C<&+#2?EA`BI z^`S-ohI;5%yN0k%ks_FM>4ocMZT&ayrLXbZ&R6dm$Z&s0gJlfhq$4vf$-m3p6(E_z z!v)TATgSedyuTt@wkF{DmpZ>Jxpgawal4`n?HQ`H;qYDWZ1Fogm3P~p|XOnp2a6|S82QpZ;2sFAx z`6Yk3?L^&2NoLCCXf9F}^XqU;&Py8@gXWLqmHjoq?$s$mHvD)GopZDj#7F)|)ma9$ z)qU~$tpLSaC>EqG?(SNOySqCCw?NTCkV0^W;O_3);BLX)U5Zom=6~mYxnK57GMULt za?Y8(*8V-uVoGE++V(kRnKVgf|0$zg?3{DkrhQMO8X&9hGZ1pflFVx7ro)`0VwQJ% z?mYZ8%k)g9ysCD#O8RdH10|=8UG0sJkTlyJ)ftc_AMRd20=hJyGl}h&Nc#boPV)YG zv6qdhV4M7XSS36@ZnC^a=kZvlIpD*lSQy_G_Y_Ha94pl{Q+eEEZjHp_M5u?gvv8c} zL1n{kMN&1Seq%@-IJ+Q=?kPkE`yxluY{N#1GbfpXg$m(frXB@!=hF!;1KBcg_{m2s zi-Nc#?Spg_`rF-t#Tvp$V(AcxdGdeV&Hv*M97Y5#E8A(#M^G+vd?>$+a9U`|gmj-= z29etcR|rd==+KBM;OYMk!b|59e}aonUi2wU!uqL}{1rUq z`@VfU=TD`lQWS}TG6p|`lOm4+WUJ=%Ota1Ek0bRrrzyow`@8SCq>FxOaxS7BS1ouG zwo>^pD~Gj_WEOTM+v+RsPZVvl$Z(3#$tkEu3hJp)s;}(O+S$~E9_pfASz{o&?K#fD zLh|rmE?M-9E5;K;$f#~Ze+X9{qlPtJw>O5>w7r4cOb_ljr8aOxgk5!8_O$7kw!^%$ zJ9MOCsYa?>S=_~&vV6K(HlF@E27wX>UBd078=TonoQl1?tIK5__TGKe!*`ZI*qG_w#?rAHnp_eLLQ4ya%D?9T6Eo|5|) zl!#w^KRw88kj=UhS1+kE1BAKg*4J%vCv`Z?!1?@Q4-+;otw*)N1hK zGj6@XkE(vdQ^(28E(Cy()_vPC_qUcp6w^a`q1ZX^mgg|0mPTI7lXc=!Js5+Vk2Ps1 zJ)gRa4p>^M44-l1ieupEQ>V9MQPNKDA}mqF<2E!%JcuYuAu=Y>&U zt-n1|p3F3>spY?TAfj-2PIpmoPUS}``6%fbKh!qPv)YumfMVoe6uA(kXfq{C!b*+0 z89R-lqgj&K?+yX3cymb@G+|%z9STx@q6t(k`kbQVkM+gM2d@G=Y!P^876npC_bqW* z*ccyeUc?p~{pnP*)T2G4zWkwj2n`Ll)|lGWY}b?Rk8}1lMrG}vLzy2!DHHeusD&^5 z#O1JM7RsEfsP95?Xr$TD@v!v2D$eKr#o}Q!<4p zkDO<9BL_x-%{Ho@H6RME=ifm>oQsIBoo_aJL~(0QwDgYcK814Q9ux;P{w5?zbW+|H zJ^eCm!V#uDhE}JgGs%Y9_vs)ds0Vx&a*D_HrAKu3SCSmWI~ z)}(LHp@!yRrN=q_*hU>eCCkNPRP(&PHPuZjvT!3n@ESB+nKOPg=v6ou`jvmr3fN8O z9;n3IRl0(NBYl@d3VM>2JJo`T&vd1Ez*fKd)>E`4UaI^$#F;ckW?UM6R9KRh$9H@k zvsfut=PepxE`y8~hLV}VjY^nnab$Kne#8|K`-6uU%ZAN^En{LQMS;WrMTkgEPE@_4zw_8S>A^D3z{;G7VtEk@9);8RMZ3 zjPD`TQ(dCyP}OvGo>u>Fb&B*DAL<0saC&l24rree14uISf`;$M`oHgmdn!J{#qZC) zI<$U1%QD5@M8~$jy6-ldftgS)kXWQKH(%c2pzy;?cexe{bPPg z1KMMe&e=;`eXa{T9y74ry5;c>Q3Pak6D2cxbcMvaN_^xocNP{2Lk3xk6L3;)#`ViB zeVI;Bp>UXESc#AQm?ZnVxKE*~JCmFNSwCq~h67R2TSO)|_)COBWQLcrSsf=|s>8+5 zPqWy*o!F`;A8Fdo^5a?Po~j&cP=ansF^Bi^i5$7yQXLKndgWXzqk>)!ow!WUvdYt*}&J-FV?OLm2a-lw=q#xgtc5aJPY1>};Z0NwX zuJ`%2Xt_RFis;l-Hoo-l2GNKqiLvsJPO>~3Ri07>;G3e)-=MSj?m?s$db7VTEur1s z8cB7X%->$=+Sn@PnsY8fcFP^7LF<)H8lgyIvDJKi?4|;rvFzlXmgnGG8etuearVHr zr3;aNrx|F;1rwB|&SH|9J?8YQGoEUX7k}SFyn8fKGV0Ahbb{675d71z-nbUJ&7^+4 zv}utp7SG7V3fAMA8^U&~3TZFB?zVvk?6o*2y>0u`^{LGfI!)zngGdj1q;o_TI=VZz zoo=QOWRl_A&x>-R+BQpekLA1 z6YMX(mfof~6`9*)(&ai{LF=xiD+x~p|$1$C1CWk7-3$N1Mk;RVDl;*rCo643E$8pygXx`DtpE9a$o}+2PK$L478Zp-7a+gm z%xMQftjod&<`)S;#tsiN6qZz1pIv25q{vIQOl3=X(=@IU$VXGMV&hSTa?`8Os;cO7 zx#CJTH0veV@pgDqw(naMJ>!vUj5en+@e>Mg{Ni-bE=fk~vcu{fCTQ&r8?wXDq+Qby z>G)3YKYX_g!k9X=bGO~;BtLxGXUV5jaiey4sZoZCmBx47^f$ri!^6coi)fX&MJe(H zZ&A=m5bbvfuW`}fk$Ihhc@1QU;Z@M``7eO_T>N%=_tSevpCHfePdH|3C>dJK`P`pY zoo2O%L86;$t&+N@d}t4ObVDl22gW(uSp=H^-FyPPvkXVk7PN2*OI9t>Sg-_sf`pih zjLCDFg*qIBWffH6D{bP~;G0xLKB62U+lPfO;1`B_mS@sh?lkh&`@;@{XVt3XKQviV zM)xOnntY+35}UK#y+};<`2xOUtQXgYAj2^nf*A>S8O)NAqi{gyFN^k;(!a`W_U-|1 zk2lrm5nx_96522+V^hV%hFS4H4K5{R)fk#1fK?@(-Gs38v~^CP&vZZ*ev$zKJCMOr zbfMkXARD`=w%#yMYeA){vl?>`reRT}eCH4wcpj&mZ7gv5dzQRal_k|s9^_QJt>oP8 z2)AHEE9B>$ed(}aY;rSrCKI}NO%wM=xHs0su0UcYx44F?3A>^u;~C7V$)wxni3nVc zDH!_-x6=C+gYm^KH_uZ{Vw`_$tI+EO$Nwb>a9cs_=)lmftgHmd6^B_IIhF)oO^q|C zC~q1EZ!5WWe_Q%qfL18LyDH2prFRk4%_o5Mv4vSJ2KTvCUbFz57-h5@gp2B?wYfQ@ z$xN$NO`)5J`acX?&*H%tBzygPhf_6|h%b z#8B*7J$`XaN{o_JP;*s(<6ff9Dv&lQi{wg@z8&N0f;3eT1~`^VkLCF|A|T5>?bbRz zc`RNyK8<@rD3xRvsmH8aaSk3diB+!!M0+fP%VpmjW~!N~1D&d27yJC1oNsr5Cq!Y~ zs{o?=OU}L1;e4~U9}5dUvJ=@g8-Aw1+v%Bzy{3^lK7OtJ!yk!NMo*NnwVGzTH+gQ2 zqsrW!DU1hbdwjeT7yAO6e*6UJT$)vN*+8VGTv)kg^&c1#zFP>8epRd zHOx~zn|lR-FBprPP(a^a+?wY094--OegoPW!-)6{y|qjA&;5TI2KDq-VWHYcp{4an|oV218BtTu#No)Z77}}a$vR_>wGfErd zUgFNTQZ_5`V<|%tvl+vyYj!>(mPvGXyov$INP#=5`UTg)2*I?kEh-aa8W_<$3IM$k zYPeeyZFRawS>W+`ZGFCkXcKrfs%s8aZb+w21ICXszlbEEdQBe5z+wM#)`vEHfHP)J zf5W>VmO_5%Z9Uh_*JHZ$`Ljd{H_|bwzEX;hDo%rS`ZC`Mya`%QnD;PB-60LJT+JQe zzit4gYh)O&yV7~Wa)%j6U{b(N6zgIylO^Ntl>|m&Ik-J3C|Ywhj&SbD>tmYRX?-jh*Do}#14EfYkU=f|dt8l~yTkkm$_tf& zOjr&zDRnz%jX$gHDMg99PG?v}Vry*Eu|^s**Mm@LW3x`h#gxv-a|44l+R2=?R`n|EwBBz|O|}**lHrljGBGgy zL!B%P3&rE(-Uo>o5Kw>49CZhXg!##`Aut2$jB+&n>wk5UX3w$Q!z=&JDW`jl5Dht_ zDbp%b-3p1&vl-j#uGazoC7T-(go6zL=p#<+B=%cx_AamFvpHM${NyL0(u)hrjQ#-{JNi7X zo3hCFfhJA;-GhQpcodJ z%TtfyeYEI`-Q0q*X;-@;Ec~~IPSwV5u+&NPQAw2^XQN_6b^{tnC4L~a0&s5Vy_MGi zT#%eE+b9N0o$~mRMQ|*0TG}^CN$2_!&p3tjVxmRyd5->|%Xt7rM&H`!SK6tw3aQ%K zsXMK}wxeEsc!OsE0-X5e4W%ob4vY6}s04F|WUF82jtx6R9cQ4|mK_p~4X^8kO~<5O zb!r{NHipZn=zNSJOPfkK>e5`nV2!O!0)L{XfG6# zZcJ--x}`(cPx)XP(&;r}Xqx;lVU$NZ%vZ(AT!1T&UGBw7=?37m$(%mJlg(Um^(r)!nQyzp)i(lgerK!Uats{Ke0AX-6& z!p{UHmZojPgO+!Dg9v?Ta}rM*a6VIr+w~XMIzlMtoj^YJF$Mo`Xdd9oYT@@;2AY?( zxtTiu@rF()c`H0Z&!Dcob!MC zM3(8=0mC#u6)elK(Qtw87(>6OuA=2a&_E1?ZU`kUT>U;65r^VDE2~3gy``m%0_~9@ z?UM-ZE8`6Nls+c(AK{}fl^G*onzD^{@II3@7vz<)#W0l%U+tk2_W^rU zu>X;>=5ivd!7#hXZ3oFKHXaMn{wd;G8_kk_wCJETv5Y|8tbbAaUDWdh zd#liBJLiyleN`AK{u^SAA9)-w;TZl7^TkE^6GY{gtVTjxn8VA}5n+WCIF&u>P zEz_{`F5^QKOmU>zGdY<c1_|P~nI?~9XRVhveB(Key z`6fy^7IYM`A?mf6FXDWhv0!f))8LI=hI%aOrFRNRMoZJ|6S2^`b9m)h7~@k%Rm`* zlpe?k*13ft9l5uH|Do86A%C{Z4T&hlBRiJN+{+hWu@(;jTiw#|ChLgXsLB_DpYKkF z9xTg@&m(tI#`(k@e-$`oo|Gt*>Fio0Qz!iH&;O3_UHD%Fk~8l z$Z}AE8P77zwQu$PqT@UJtvruBOTI{--1yTc!?&2(-~XVXNSGz54geu)Jv z1b91uQ7%81=8Q@#Mi*A^V7zi>y5>(N%yfeY{Z@)*_h$iK)U)jx?pS%ld?xrK4eg)utFlAd%XEObhR$wfoiQI79)yyrBr z7^J({N~7uBhts8B{JWhJfy1OePuUR<0{ONFQ-_Zo*?xHm`cRM25HYUqu+BXPMsQK^;8 z7}J?t{vMQfuQo~RGe^}PwQ~vBoDh1BQ2-V4s%6-05kZj}yJ0J8KYM?5Q99>k{pSq^ zS2~4Y_QFX7Sxq0AzR5!4{!6tPPYr^BTETKsA6!sihROFQ421$_sF&zqDSFa&VMokE zSzrq0J!*d{J<7-xbV%J*OjIb$hbNJXuTr@l7xvsA4P`z?5LGELKkcRQ{ zp7wToX4AfUSo<^?HA9Qi^E? zAoae^@!n@o1X|MaN=wd9RYw4e`hNyg`W1q;q<(~k2##dV^7A@|D+GJ!!P0-5u4}R` zZkTT?vBiq>kHA)aliJr+{z@fGiS)oz zCoHetfV)J@ITorNkGfb2xUa00XzXtcc^&`uRubi^C?-rSBuckL_zTMmsWi@w-OCE} zgm4t4>iwg@`#>fd!7*xPLbK`;*CSGwcUjx*&31rTjY#Gj7bl}0e%(S3LZ5-wEyTUB z#>GIqDz}nQvcZq~i_JAl+&0}oI;M34VPlzqJ+Epmd9j*~{LIdtHZDL~%~l-u_^V&p z@pPLL*z$?tN?9}!M!(Hsyvew=gwMMWo2)4IEDakgI%yYFW$oF((1VkihU4?|iu44M zF)ZK&kTDccEM|h49@@wU$ z0?Xl`mfS2U1Wh*s;Vv+9in@4>$LT6c>BAyhY7A**Q^9^jR-BnMu7=&aD%@v&Wku}Q zTU_JcN7`Zj6wi0%6xV!18WuftZJ7!C+5ak$BA4(@s>cU>75NHvB3kuGFEa^%A%&<0 zJgLQ)Ch;z`*wRud@4XK_7j`ym;D=;zTZLT(VgTew+qt?$9g8@u({=WYU$;AYA9ihM zX>{Ea7JIMRys9TReu%YJu$#6g38Z*09f7b|p`?}(n`R|-qp5kx0u+@$8AwhlZF0k# zoV4Y24^?(=(P%a|*-DewdbyWhGY7FUOf~tNcWM*2E^nMvJA5!Bk%=SM&C4ZnR$;yr zoqA1!ZS;NRF4g`Px`N7kd6P;fW5qQ-1@N<(G75?{)w1+`;mZ}NtVhVc@a>9`@ZH7! z>sQB;*!+K@E9IbSdiXFs62B@s!uu5qljwi_9DLcY5DJFRFFJMqz+6=M?UEB>2_Eau z`Y23o(+r2bz!&QUpj7&InS&o&mJXfxGe%g1(^(zncvC#covBz4)d$|sRyZHzt}_0N zvTPfHxk{Z3Y3AEI86#?2CS<*n*2=GEP0KJL=qVzB^PR{jzGMF9RFsv&%U@_?%|bzIo?lj%_L0!@_aP^ zDyjQ#{prrppQry19uJg+(M~Siw22PQ%1y$t7DaP2i`>D62vIhUVKMVN_N9yA6k;yR zL`l)?svfZz}l}~+Dmx32+%?lIUd#i9)TqeRE1ug+vm3?Ne{20?pv0jU<3C?^mlJv zNJbt`+^T``z97XvTg7^9B>HpKJ+vF?L+~L6H#Z%)O z3jxGdhQr~OS6c)w3@vApQAFrXTVT8W>kLD&EPW^L)qY8{@k&$XkunHF02-JfqH8+n z%ZL6{Uo6E`J+W(-(hwfxv}yX=_8GDvHLiMMyQWE2;j|6wrn-T};}cgy{o9^rEV6co zQ=M6yVRGuLUtoc~Mc+=QR{cPBQeIE*pvm?zegaY#R>wm3&9X^1jT~}<&evLfh2Rhk zWG$lC%n$H^!svx_5+^MF(Daoc3ZrnolZ~7gjID`6oBN2+jI z!&9mSxTvT_wg;>WCQpjE+>-^r69IFC`etA1r>-?^4%ingzKrJP@SPbS9x4*ihHt&B z;U)`^xkd>v>nyU-G#C`fYn2>$M<>Z_ybV{PZ3G`7mK*EIb~Tg zKApE9NqNnNkOt)WI1*jc{-=kT>U#p7BGMC*Pkhl*3`OfO`(-<*rL{GAp&2Lq(Wc1X z4noTl2xhWCqU4;lE{5zR05Fd+Dmnb3QLAtbM2;;E1R-~$@$-5 z2@lt1>tdo)h~{gGRly9=={L>l)2-6$#(&{PaDcHK^wKQeT3Fh6I%dm!CyGG!P71+9 z=Fz4L@i83dTe#xgGzA6fk9MXLPEKIxUWyFx?LWzojE)OaVoyK}=>D@g&R+3-6RTeh?76f9Slp=WIF zJ1W&dtuZDsrbB&)h#1N}6^fQpV|ME&?sml4H+wN_n<6C$;s*H9S{g1sVADaxgqqWgn5LjVdGe+nVQ-|JwHSJ;+Ct^XWVwB|0zxKM+tt@dQ5X(wj?GRcwb0T&#c+Acg&VouDONXifE0cSq}{LNIu zSDYuD2)VGX&HM_enz1fgQQjXRPqQOs8SF>3plS$gXqCg} z7LecZ=!J1A&2dQG^AydAoQgbA(Wo&ITg@LnKFq#QPp6{5a^X}ZU4H9XIP{Q}D9`C> zOj}d6B)uy501l&u?pEt34M=Xm2U6|8hlmr991)U0Zzb61U=Pp0#-;AaKGU=lYwi~a z=Lt{qdW@epfsT_4rlmqncFEhhX%s27XbY$p&A?8Yrt?!DYH||M1P8CcfUYW(%Z_4i z^Hb=6N?jTk39ILydlZFco&NPi!u%OQu{+!6fHnMo$X`J61ynlSCu5&ro@&NU>$$U> zu}px*PbCb-sMrbkn4uVC>@RQdD@n`V4e}saj?xY&yn;1yhAbrt7d}&}iX;=3lggA`F3~(i z%Z;MDL?r;qjpDWKy~7fT4QLB22VNr{7&x>!a)ktCNcs+7dX_KZaGwy|5+ZY~`te>f{?A=v@6pqVx)|Er4-FH;pkjhUEqhR>4f5T-?wM_2NSpX*W{9+n zLm?5z_r1zPa}ZcYn{UQY9#YxUJ+DQ&B8JTsAguFR>s24;9CJ3pm5RQ zQhEB!F|zR@>Wz2KE|iY#lNu6_tOO8A4qh*a_=D`2(Gar>=(N!R?*bk$ZgmFN-#e>W zIbAm5f2h~V*;J_?)w1Tb_?z%;KGmDk=LUHM0jkJiCX*wUBFxOjY{>gGGuE=aL?Cc} zQi;ZAGzsIDxOLB+ij4si`HO%nN_+6lISv^_t}8oyVtxN+%?erUGp#ao@P*CPQYVA}f<_!(~ccO&ZG z`<8FCVrPWo)|uZTbt$qO@l%2er#Mq33K#2Bmc$zmG{NXB%NhEkUzwIxA()wmdXmQ$ z6(M78Jh~CG$ry(0FG57jUTGw`m}i8w+3o=PyepfGkZ*_{TY}6!90#nm#8fOLHmBAY zDgQuyS|&dc&Uf4~PX1ti@P-Qi34QiORET)0bw-zGbYKnX0z}|CS=% z#O`0XfNzN8KFr`)XhcOD-Yo$E|4iYQNht-EsX7js-ak@1PO&?B4*qY>pF-F?VWrzx zA(kt)BKfbR9GezEP%x$Wxbg+UIu~FUtYc$%g6TrhB#Gh-cW}~m<^Hs2-%b552ZI3l zi(q4!4>;rwDSXZ|!P*@hVvAfw^xBHL&e0#f=AilfvRkvXaq2_NQmrC}+J}l@2tr`Q z`7VRG)xWU7$LQtF9;u>J_cT6Cnoul`-jK-k3}l|Hnnl2pXCS(f*n>TqSvTc!JSjx%tQ{4(DkVRF~;wC?@x??)P2)sH;3n8KB{?`@z#&nmrt_`xIi zy32j|m>RKYW{{9&WJhBEA1R8V2GOt=wZ+~)+;Y5k`MzcqI#ynzS${fXP3MROfATv| z4+J1pv2`Giz*~P`+s`8C^;CO6CxEujpof4lak+Fe3MMdz{};h^dil?97>IrPVxfn+LOW*uT{&$VC(tV`f_@rF9am^Km#*1d8z_&0@UEq`06 z__o^THb3E1@ekAnD-*CMxU;@b3_h8PIW#RiwrbgAk(P0AvYsNUjXIgF=H*Yl1*RNq ziO)}f!i*Hy1TfbG90Y2V)XM*Na_OQ!>Rf_5Ghu+N7p*(!hbhvSwN&+OzE2-LXD6`@ zGxMyz$NhUx+JQJ<_P2>X!Q_y#)Tj(0WXH@seKZbqTaT$c zk;&5D4t3dDyl%~$8_Hx^mRcR8bPwI{x-Vey-yQj^^c=Zw^pWcBNvCrRf~iE`*j z;@Hqz&YnkLRIzU4gxg#GNh}Pz!+R9fKg93Ff7yUzcko}s{KhnH(nMEbb~I?@008bN zd_~ouPV+@x29AVv%~n4e1h8Y3`}g7M9{5Ve&v7~266?DTsLqK2*nhM46{~NU2FMK= zj>nh%c>}h8#&^>ev*O$c<@(&JZ7o_+AAtpEpR%$DPX7Kbwos*F-p}<931`nG>>F6i zk*4?07$xKDS2n?QUh~@jXF2-6lVG}dO8nir4h9dMZn53BqoF;>BV`q9tdTGnjeBb7 zn4^C(jDwHvzV^PC_V&NgPhhP^_mb~vN-@3+FQ?*aT`s*(ERj9_{|RNte7-4$9dcCs z_80H*%gsb+<6E?M;_9v)$g>78Kzn+V6j%k6gB@1dgBNQ)GbDAn`V(L94V_muE~PYW z-PFO6-TrB^ea!f6ek&o=>8#E9!uS$#(5=bh>Yp#;9gjS3)yuhPUn zh_5ndrcbr|gi1aO_CnimY>8E{lg7q81>|*5Oo=a?plrwAE(z0`kZf~pz$c|}mhJkP zBXOMoyOaQhIpD^gS*G*ZSHHw4y!Z$5zE^0X($AiS%3}}fGrCgXo49dguoo#TFF+Ba z1iE1EP4=eO@qkXpM?Y1u7RYX;$PlIxUId2~Mv~G=&huQ+=6nEm5of-{mm@gqV&70D zf)O_=poT-~m=$aMUp+qZSYcLFi zCB+of{kuZY2pAY|SrCn0%QOgF|Ft%}vX_k0)6F^@tDdhcC&=$}-n1J*kzPy(&fj!c zxy|jncYs{JdR_#O8dZ7Xy0Y@i?wqWxi0?OY9JXWJR9LC6Fe6DMZv1YlaEQE|A zUTK6)130qMg^^GQGQp@4?FU8iy%{;=IUhS1DVzriFRKeXa#hcbtsVeJ(-D)kP%xo=7^8)DR#+nE_%HnQzhq zXGe*1O~}hj^nasV;KazDWHKLs_7enk>#BsIFxH4`eiYI*`<2kKlDHg#(`h(U9iS_s zq0OZSjK~~yb1sxC9ltT(Z!>WkY3LQV&x=1e$EkRjc8AlW%|?nW4o)8e1Z^Dv`id&k7vpFH7DaF&;U$*-4HB2SNqaSDWv4n+FSa}O|s=}Cz)CuuRSUa0=G z^08^f@jPPGQBnWFEF*!{B8t`F_cr_LhY$spe`?x4((S`r%&PsNh*uLaqTz!oW(!QN z%l~i4O}m*SG{*D{Fv>5OQBIM})xN44Jp!<7<6XWvUiqt#z_4We!i?UM`wcC=GdE|g z&ucylx5oYs^NjA43h6`xQN;?tG%zulxtm|~dD7)j2Ht@mWqCdx@Pb7x`9?yKAB*e@ ztQ625iu>{a$6jpf7qh# z=?k~u=HKU6Xin^{vQ0VP@=4BhwTxqg9&Y`a_3b@hoBZg$9TI}X%v_zKE{l0;Dw9{S zUmX}arL5@YEY{k9MXHwc5H>5W1`A7P@U&Yj7f9Ie(&nIoHM~SrE+3?Dj0l zldgz4!|}pVmPM%(6=|Ywe>g)p;XHM)~m6ovqGQ^sNAPY%ZVGv(r#O zQd{#1mBJbdVjRk1AJRSn&Q5HIqxK*LCWF5IW*k{`4XhB(U#lDGa*s-O2&>N zsHk~P)VCCZIe!|UGrAG}7?fyPs@EH?gUVoGQJ&829t>I5qH?SJtiTgesNU>P-t&gY zKy{RpplWWEO7hG(aNfJzqyWNT(6x%$C2FIh-!5RwoIz!SHJaOj2yoLAXz9pvakWhM zz1ZtePg1W2#P4hpsR%`F{5>O4Lefjyk!<%Tsw{{U#^B7G_ z9psgrgwXDkg%l(EeSv#7;kdQN{=PoBa!ERPKv=AJ+Tr&iNCq>Jx@L?xKfLc)uHwNO z5^^(@S>ev+>rXCZ3WxvM@{c!G5Z2iqcYDRAFVMYfp-IU9|Aw%w(|{3&8Y6w(G>#u+ z*y;@0b!-QCTuRaVQ~3$x@U`F|B2ey#%f=kXt@N;qaU{f6-=7`5N*KW-bwHj4O2fL9 z+_E#zbfZ;DM*5i1)BfQMXPuf_v^F4};{Q!4G@lt399i8S1s$(=d5l9c0BEXCHI4i= zn4K3vU(9;Y@hv<3*ra~`vaEzeMu*#sU|L;`fi`-Qn#~0!PfeSSm2fv*5mqu+XOm!e ztWoFk-^mDYMwei|HKD@;M}ud*@8yu2%dzs^d7(N50;Mh+aW~F@9P9rt$WPWg_Lks_ zLE|?XjTL&Xtt#YC5*QuVTCS@<&wO~CRr=mGMa{>voctiL&CT0F8tNJ{+}f(C2!p5l zyhy^~jBdm~&9(%iq0|Ic?UU7YgMFD?sN_w-7;~)qr)H!G>mSxQ)jy5voM&7Pt0iaL78i7&Sd5JJz}u6)nN&tbIOj@ z-b4h)S{LL}f`fF9OkdwZ=-kLbe5ta;`YYd4&@OXy_qOr{Za*BUR)@)_ZI8JST%p;~ zSh*5jzO@f08H`*QBuC+HZJvtDUKZchYWA$5%W*D_5MNGYW+s%5_)z0(w$jO=F>Km2 zX0R1raK_%G#p0pW%rFo!l_Ktsy_h$cCcIYP=4k_cmaTaQk%{p6lbx8BAg{#T(j4ZQ zq06l@09SL|XviDi#bqC|asD%(STgF}_W&ipRnJwHM6 ztRe;S)(0X7rndR*E#*x2gSI{s{?q&~G*Oj~Rjyg;`&^e~JX+~5@D)^0Z1Iv8L9#_T z0x3`{_N|(8%gUB{O-|U<$QWJ<(Xfat%F&y)A&l?7kiEl z+Tj3(w6&e)o%fif4Xbo5-#ApY!Jc1IAdEb%o_0H?yZB4Lo$H4Bgs{u+6PR)WKFrWs zEFN%QD&%cvnR2&zzD`xAcV_HdH!p6PYF0cybJA=U3XBE3VVlGZugD(EXz_9 zp3#dJ3@7D1*b+rArJCd>IR4^3xj+(AovzEC#$!_VsD^Xj)U3B^e-{6ybJ;*&7$++W zp9>fY`59WlojWbVSR;#q)#5ow#nkH`8_?S~m(Jk^czHJ(?(@~7r5TjZg2b7YcR1P9 zCa{umCh~N$-)6WF;u)j;JxqEku{|R`n6+myheqOtw50SV_B8 zZ}*Q00e0KVC!UDtMM&FfIW5x)8NvJ>{xpB)=6BqZleiMMnZ7*n*oWWQ=k6DxX?^Mf z)%@fSvsfgq7lbC%pNQKh`bx_{+w;7WVkIVsPp|ZC2OUyY!2Jl$4H{EYvD_pPOS;p= zdlOBZyaXx@ z|Gjduf!0^I&)n}?OuM}Q2pvc^O{fovIW%S`F!~RYJ^3$2^rsY2aPbi3B-L8TGg>`f z`#s}_%Q#&CO}g*N-lAu+N$Kk^EIGU#7c+yyDO?eVhK1zEl+_MGdh!%tNE0e712%QfJ-h9RRAO8H@7 zVqrA%8moYJ_9ypEAA=9yryWx|pky5X$%cR{{l_ukzcqw*x619|m=^hNAvd;HB)sQa zKOsa)Id7OVhlg^!_5_p_NyH*#3G~}H9tc)h^^l$6=ccpI%x{^TawEnN+6tG8xMS>= z90ek`{=U%XMhWzc!MKkIbc%pwxr#KuGsB(j`8N_5zp@+!;$1v~D(x7|OcE#O(|W@P z{rGo3>wh9TLPuK~^Idx(-t7p9R_lTRT*6mx2g8a?cju2gLDe#Ktkn%4L9)YGp;sCy zZMg8<$sbGt$AwNryYmqllv}T?^H7ZRWX$trSmgw9pWJ?C|lTd z-t0M0kg~{9h!6m_F|WA~)!m2$I`4%;<{KRf0CSjD;N|Rtq$4(Qlfl(&mbL`q)9^=e z&H6HC?nqt}J&C7c?t@aOgNxBY2miRft8-6qP3|+<8|a|@UsqJGyQdgA6aD#>G8Wn9 zFB+IpA@le{raI)f&c6P3oW6fSuB(0Nkh$^pnSf-ZB}P}{Z$HJJzqkj=kv8vd36;I8 z=i%pfTJL*wb7B0&OUZw(@1eG(|BRTbFn%DdeKbOQ`@%e8MBC*StqfGC0-wm?QT{Ipqp;?bTc!C#7 z)WD|$i2GyqZwvzX`NE&%V2m^PhiFTjp9kY+ulpOtQp{^*b#LdL)TdHR{CB7$cFYr< z*}OAn$+6H5qTXL|V$)!2TXd_xS^Y$udbWA1$31mpheRyXOUSh%1cJ9~#-2T+;!4NQ z8-|1$Sd|;Aj~b(MG4+{yX*)MUdMnO<5b^m(u-_6HQcPTTK6aeH{z*IEq8{m4Vzd=B z-2HQAO;zFYb*zVTLg}u1b>=mUY)zpldp1g;0Acr04GAU6%sLGAmLW(!HF<7*qYEX) zN~a3A*k?@-tm+A1#I9q!vmQb>A&17@zBr(S87{Q zv3e9y_I&2fHWg8*y?Cc6@Esql5W%wR>WgBe3g->k_qyBP8$%cI-F|(SAWKT5R2==m z&(@WC04KO=_lZ7mNeC$U!*CtmPB8TKJze2KkE_(Obkw2C22?Zqz~$>e4@oq$)gynZ zkYIb`&|dN>$oT&N7eVO0XT$<^JCN&Pu1EQ0cO(VlOsR*<2UpR=t#x2KnNim{kYa~o ztpj-;=6QZ63%62AjCBs=dzkO}1zz}6kYdU_JTlQH@$GTQ_b)Y>3WXjPdVY}?5{p^x z;l^V&iSK$RVHjTTK(U9#o?qgT%#dOxd3fb+x&1b?r}Jn?)~|kD?3H+>UYS?!P4YU~ z|G3s|vWJ_SD(Qspbznxs8udC*>S3wpmw90eNiI`7%xRlWpbbuBev475+{1FupX8B; zblgn!@JfL-0dI65C*oo^IxyM8$sU=2GwIx_@UU#o2C8Ydga?sMYMO`p57-T|$+0ef zsWB=|@ordFxA7Up1+@m2SpG!l^z~0nn<$n76&#) z`hhJDRCrk7`A2!!ql37q@^E>91#NYpD*|nGV48>1EU2A8Gd!$G%cGjMIZ*a8yFRu# zFx|uHoFcu(fio-Zh8$rBNIDQ2dJt49>+-+Y2o zo#{Pk;rY|-ytt|Lu=v0M8rn(9I=NA(hKFklY=WM5!!a7jQBRUu@4lMlz3bhho+O$D= z;rWJiqPahThcRGd6h^bX+bl}j^Rqgyo|Fo6JiPm4CaM(E;NgQto85AU1JBy^w8Mca z534-?ps;&=u7~$3Y?jQ!BGTlH9zAos25+u6#&2n!hpU%cCEZR3W(iAhb~-S_!x^4m z?S<8}ALe`5zSn|wIWRL?>bo4M@vz2%vT3O=@bF%jJ;*n4D0qz>lZ77EKek!hE;Bet z+SJje%=6}Z3%rG1q)!dvrqRP4BUcVkp3pJiM(+l@$;MnT(BxsymV{FTop3?iEb{O` z+GVot?{=V*+rioGz)TNkdj4-HEmn$Y_Hba8O&_$!v8magrb4ZUwFG%X5H~Fz?zlBX zwd{4^J{`u%RGQ`CEP|Y+FSUAjF2}k^>~k!yNO@iznC;#xa}(&R1jn!OgU)m!Yf(VAG|Vaax@CV#+e z65=tL-IjQd81eP-7!2EYC|HdP8K7C};oy3EMZp0FUL7cN(1E|PBXq!ldJpS8e~xFp zzz;gm;9-O3uW`c91}SElha21MFPoKZ;pja*OTA^@!syb19!EdSBwt z50QMZ(!;C~pGr!o7XMy?{C#bum(YU!slvZx9!^Y8tUZ=Ol z>++_t>8zBO@p3+ikIBT*?ct*JIn<*q404izjbTnl)rZ!Hz1v&Q#wvm5x??SxW3Jv$ zF>5_s-)_A`BKMFn_KVegt(P_S0;O?gorgC%%Zar+;lQT?O?2&I6gF_efo2bzJ<2fo z$uS0#b>3b+Mr7&naQXa1PKnEO9*ruid%OmlQ{tRFFW1RN*2EUEX7Y}SXPC2mH!^MP|L9}qAhczQH9L2*i93CEjVDBM6?Z8a3 zUB;-?=3$%X&k2; zZQgdGygNKB89B>#(pff22zQL0Vmm!d%d}_ZIR`SMv+^9-1aP%SUhSE5R_^j}&)!Pf z_~#vap;u~*N*x|{c>YQmX3~b;?P2qxDgs?_?7fSrF)DR>*l9uANbug{;jTsty6C{R zi0{7Wz#0$NSkOiS?e*~7<|3-;k^>8)L;jKjT^@FMey$VFr$c_9hbtafA$G`t@<>b> za-iG8ZjXY13n?yZzlWQyTTn5B>yh|&z{AFK$Efzp4qT3!aM^*i9v^^a`&{1y?JzkigVaNpYL?ecbeVvo1i+vn}~ z_BaQ;gWe(Uuy@2eI_BSF9_1Z9?w#39JobRsTh^Ec(}!aE>o;|zlR;wYbkDPqM&nXj8jsdcgj2M^?Q|~ zO8l>sRF>a*1<(_SqZ>mngl#sEYWEch)O&U*Iqp zHv=ACx|ayf8PK+xK@S(LttN$DgN9oI#C|4x89#nsWE!B&BJZ>!ENgNc@K{~*enBW?mCbgk=nZsZ1-@x zN7vw|6Ft7*;ouf~dHp>H_C%Zeo&!5P++jgmXmelmu(#ZTPDt2DF7Kn<`Z@2scfq^p zEmrnc`nb8|;lN;G0O=a+_|Fqt;0KqyC^b)?EEGkeSd@rTQT8XZBwpZyWX?}9Lmsx* z&ZdnzO+n1=-OC;>8@ZqHcTYI;4SDJMvUk~ACwjymdqYaxT=8&K+6p>tav9`Cs=lip zcDL9|%dI-Yc*MkRk?)N!d0!v{$@rcGbJ)6|iB0^K}oe z=UNTO0|y>P8~1?&yFA?Gkp|>8ZQL6ku3KR>AP*fVBQ;4fm3Di$+w-4~Zl%8I;kK*1cq_KUMP zoQS@7&%>-H>yX?lVd;|&$#=blypiAYUQ9;8Zzt}?p*%<)4(_ll&|?RtM;CQHc3`iE zdx-_gd`l2F4?LWI%ZjyW1}47F9tUX#_IbFEFn8(VnlsxR7Ycs zHjh2Z7M_+gjXcT6n0zu-!sL^lghdZ+V>1lwAU#Dgl@54#!1I&BEtCx)BMEzMuOUiQ zs)1zSATXIpSf6)`5@)B0CB1Ji1SNWG1&d$uWCm zVp4X}JU-W%H=>fsNy2>@D+!%tU~|N5W*Io-;UUkTtw_f(&g3THs>#+?J<))@=YFBE zuAVV^aM;7c7CVU;)4U`+Fn2mR&wdBiB>{_nEI~y-kdu^~l$VrPu4ICK{w)7P!U}@8 z$xp(X5nak7>S>B8NWv{!XA>`-Z76ec>eEy>;^7g`e_!~w0hq!h+|p<{i`T=iV66Tn zKdB(8Fe$-YjL{Az?wLN!kMN`X*syFrRy#Nb+^P@ogZzbxfU)2pZiQoCl2ch> zeAHXKC@Iq6mq`l1DpJMjMMNizskhFF>WypzFS1^Wsz}1k%dAy0$H0R~2*@#T)Wf45 zStTEkKUql<_LS$)@zo1;#w5MO=%Lgw^^#XLBgIVscQDf zUA?7AFJd^Og-YQYNjURlf-`tT;Tx|=tm$}JQZ{=*RYki5C2901OfgfFu#Z@5WnK$2X!pF_R3u^L!aCYLuY+c~WbpMiICRwad1_L{ z$OfSZizK{0C6_w&PRJpabmT6aX~b(yPr~X=iHk}ob>`^%=}AF~sZ7F!nOU?r@(kr6 zNlm80aSxAsl;D<5ovI{!G|S3j`34q8Y(l<)6CR$hn1#e9%t*pXd#%0W6obl0Q(c{e zTefEr&&S22Of-()0Vg@Xf(EGb;&qY^x?R zT4>S_&^nrU$1yJmpc`$J@q463k4Zloho}vy$e#*hqpg$;b-bz2@XdmIBhf zjx)29aMqALuZs-1ohtJTJbKD1DvAu8_VBdlr;BhJITa7<$(-3qDRFWtPBC>!Se-~W zkVYrOsJn1o(lDkz2``O!KR;`QnK?;#e!C?>#Rlr5)lqDq-@|_Uwb`^f8j^6*E(^-! z&`A={=$@!gnv>L!l*{w1r$vgHn}qvL+3@fZ1MjyyPKkjt9-i_1CCcVJpO=JpZq1@a zJ4e8)0&N)stDB?5yNi;r@^m6* zWH!;?QB!|oQd82RBpWCaH_b`d^(aB?FOi?aXp)QOr2jvYT(l(N<9aIq4V!?+h*T{} zk-fBD%~1_1h?~|V>=?==lDA4g`AeV3;nbRRQ2ZWen$ADUJQ$>y#Yvc7Yp?9Ak(A;q z7;oB=uy~vGQzpgk^yHj}=RAr$KSbx}k|eyh%!U?klFK66Oxt6Qr0$SHX@m%aXAF+zWi?dY<&1Tbi`T z+3PGzy82>#=YqIdo`kgz6Km(flWS-B|F3IjMG{`jwnJPg;4$Tu9t}dGcl%p2t?sI?zDi$xx!8#@0FjHe=*3#TOSJ;v zc(6jC+ZQombRZ&qLBAlu)8$(9axLE$6!<^>I%uUpCvRtB)F4FKF))J=xl)T>VMW^d zy=av{=Plh1WN$Dc!#KY^7?G>B=+#=_gW4Gwz|{gB_BCSwhal1wXrlyA*J{ygz$9)1 zP3Ibcjz7W5bas)=;0W1r$NHVL1&2e=2i2e}6`zOoMbT%cwh3D*1u{L2whQ#&d{$#< zAzA41TS-^!iramE%S@~+D-a`|Ac)a?GSbfy9CgA%z3P{i0@m! z7ws14ijSE+=SC433*-H-=8E{!Zo#tDJQ@cCkwEkF7UV6=TadSiCwSc>(7prAU?j+6 z=dXkI3bbce<|8l)85f9-Lgc$z^t)QVYpwtUZ0-~2v@uKzInu>_eCne80^PcMBWNZ^ z!@=n0yCRyvPcTM`Ro?G#-)m z0&Ooqp&M}29TDio^^;+gCLnS+pPfMy5a}S$4uY?v5LgGz9Tn)gwXD!YL{{@c6A}4P zpdYeAtDw*q0`1;|6`F*|Rj%-ygh(fWc4CDtz6j^L35K4>A?$4Mx?VqI}5%p zLf{6_(c=OgF^(1b6tPG_h)zP}BY}Rz3VjcdP6%{b-;S_vryz3YDTeqIM7j#JtKb{y z3S4~3!3G@?4hu(wqrw-$G2ytNo)91zk{6v6=!J0~0*tAM?0c5Mn2Ja@fp%js_C5=- zQUcv#-5fX-qB*_jlt3q}xer{#-2xUS;IE5L3-s*X(=aE~5a|l*Z8{={c^9W4(p{k4 z1z!&#&pcUJUIOjK3VjAcd{&^lF0n#05$VO} zVJ0HI1=^bxx(<^1Ie{L!%&6WhM84wf&qAb+K>G;3@vgvTXzsi~yASvj=3zD>{eYXQ z4NrXq+E?&>ECg6c+E)VIFpd?PgUDoFXbvL%1lo@k8VwueYk{tvIu&-oTx8_Wn2Sh% zf%X@Cp9sv&@Pa@)ZDuQD0wSmKGgQ}(n}kmau`h*F@XwT@sYR!SGs0QnobcDy=Y_9? zuZ0U$r|+Ubk6LK~k8(5pC4tTz!JI1QAu=1WDV>MN0D%q=d<*ge(~+N|%L4s)++a8k z=OfY(G9nn!2F~y*$(GD>!i3T<|x-b>T+UUV70@fsWh325|{u@dOaqg2-@z z4i|hU3j%lD{<`RvK>OTbg_a^RANJ@nL^|>jUy8^GfsSB>=jB6m10o{@I+9JnJ-9jP zTY(O1&z8Y*MD{=ex6crn$!jl1WRyThvBEowN@~NW(E=UKUQC1u{7#^~XPcdZ3AF7~ zH-%fmx59T-yHyw67U=f1Y`j(=(gg^>N<_NwHdi1rMxbNZxOIm9u0&+4K*zEd>tVF- z2=tCM+B1Okr)E@zoyF0~c9H+YiV!Oq>eO_LwU?y{H4G zmnX9BuSDb!5EgsPI^Y~*))CX0dl(U&j(A#;VzZyVj`)E@=R-`#f7>C?UkE8LJMpe{ z!t~yD*4-tDi~!%Dx~MazQwFjXXs+MYh+N|1yc&`50v*p5+*vrPyI^{DFe|(Uk^8*R z8bl@tbOI}M9SVJf>A9nblG)odZO%&)vRtVh9yJ9-%1S8q&5ZTX3_Buo+33L)$ zYCGY4?S^TmK5W<)BeDWkjXeS%;!e0T?t(wUU2!*@W&74a-7)R^DGSJ7kHD>{M1qIO z0-Y@QJ{1DM(f7b~$tCj)It6M=n*?^pJ#bE zm@b;YR>pe7+~FZ42h(mN*}F~1ysCK+GufOS0VPU5 zOlQw9Nz$tsXG(9}1NXsw@yECy&T>lj$Mm~9%ynXP9+?BWGdozPKc12YjuM}u9Jd5~ zf@znLrnB~V*tB-I$0s;5+~f0Xj@kn--FV;d0P>Ahpou1h|Ch`ECHf{An!!bQ`X(YJ!Y=a9w{RL>do2Ad&;U@L{1!$+6IX>@p6L#y0eGBF=JS}fH zzUsc_{>FXX%>_$`Lg-u`qzeBk@0{VR1&(f)@-F9H$-A0&E$^GW>v=bTUmbzz=nbYJ zawia!+UCp_j=;?P=tUzj{qCTt6KM~HZ0G!u_!Bo|e}0mx3+A5huY*Qm`ppog#^36K zg!z7oMq}FN#7dw9+YssO^5>)GB+Qu6ZHUYk=xhOQzV6`i{>kh{qfvM?wiB!W3zLow zQ@J%>-QC&8YefD?XKwT{m|h(`7S`DKe9~WFR=%;A_8iCFZAWCJ#BkY;$Q*&r5qtv+ z0~^7cXB?(O=8uAkI}rJhvo1RjnJdt_f^SG+;1X~u<1rmLfEC(_$Oulab|NxQpz~Ov z0l*hdz;wi7W(41bNSy72U5LyV=zPI9yf6^;d(lKp*Y4O1jkV7s-+|@GzU6le9*f7} z@puCMb)a}6?#z?3c+n(GyY(Cgly)~F5cyM58=e*jbb;VoCYq4Us-Pcgl)gcaJ0$Xedjy@)Ip=weoA1t^E6U^;#wE3^-hMZC~HM3xA2 z2`jV^3Qfgy{%E!>uN9JAnacD@cryMJPr+016H%sn(KJktuVYs4{fJBf{j~&7O9i@A z@CkuQKnAB{dgz+De;0Ds>lv7SagC+nI)DgVga^6*5m_eCWrFW=L0~bQL^CnnZzaa= z4ZKYqGz-&({aKRhvxVSS@2`VqV|rs8dv(xFp3gXors3)MjQgznoO=eIiD%*2_&c{1 zLskdP!Ss-2@M=c@*j!9EoElo_FF*$o7^F)icvvpb<$~`sA>i}ZLGv)3-EkNcIfTed zY%Lu^WQ9Oiup)4I&wNb#bznsfBl1I5Ou@g$o z!E^CEJRdK>U25!n%6F(!f5omZ0CILJ-$~AH!XjCS>GiSZK{_3@c6Ox8LR|T__fM-v zPWsRyO!rUd2~z9l1;pm;3VngdJznSwMAiy)Eh{t>w)0XF?eNU0q zncf+zu@~f~r_mZr`+^5YAv%r7I#8-SMeg$)QKu2vEYQs`w`&R7j4W)`499h@9cH{tO~p1iD4=Z50Bi z^1(2J>DpcAq0m`G*6>1S5!oitZGvyR5SR>wHetGW8!L1UaX0DO@U%mqJ6NH42eU>i7%Lc|`s<+Xv?n*(uPSg0Ej;;6MFdv>DTDW6wZiWAn*s7zkS?-+(vbP55)X z8Sl?OkUt<#EgJ|7#1>53cQ-AhAF$(kE2iTYGqdkkh+mIX8=iIvbe8}rUuPBiDcXkV zOzXaxwNxZ^2;3+={p1sc1Xifen1UXeXv$S>Cbjp^G+CzY~ut{I1}3LFE|gN8|7PbyF1#DBEm&8u(zVL9 z+O@u5LxJIsvIo;HohJh6nOZ>h!5a3Wy_g<6xB}i?L}ajwF-aE@*(=b!g6}{On2To=oqGJwlZg_$1YO1fj~W5BEiE6fu0b2Cxrm=*Fk46?JO76l;Ip`LgvdEe*WO~ei*6Y*f&@>e1bPZq*>KSC zp2u{~O;%=^iwuW_@+~5(xxwOFL{1CzwBQ?`7nqpm_oA;bU3`rdUR*@>@J;tMrd!(g zgo-2b$e04+WI60*I^T5;yU*jV@Eyskk>T*mbF8ivXp5EvOHx`W6$fu3WiOcngU zMprPsdTTkb6n7D60Ey}m@%anUJw)z+UHU#EKV=ho7m@PZSW;5%3(_Yt`y&`Yeqv;I2hDyCB}v6k9<2#*A5?;)23dRg#Y z5dttS*D&ohng#N9@Q`tw0qfu)R|R@ifN0;3V9vk6bjY2qFl8M*gq{xZITui|U?8+;w#z(&mO)95CqH}35L#Ja(ioc;oI#0Ba{$ATGEbiME< zo=~{ZzV`bTrk#!+g0p^79=Qz)Xgh^o z6ZAc%omQ@c`^)~7_%m)}Nw;acF>h1e=Xsm+w&dN#-(y3@dlW5Gp}HN!GeN? z1&a!nWh2vGbPvZ~g}Qo3Z+@(G^^n^F zz0C^UhGVV0NGEk?g}Qmj5e})Fhk$j76*>roI*4@ttp1?PyXPjWKoqu@?LB-Sw--Bz z)AJiOY4j)OImZRwM;Q#bhDM~TJ2IByvWtB9bcSlSqsW)6pP&y#+WW*Nw?7~K9g`vL zRDR&y^P%{6EMB>jNKcP2h3TQ7mA3i4PNFGHUn;mnw6lsi#pFkuKiS0~Sz+JQg0%F^_x+szlou)5{YT5WT?|ZO88W zNVE^g6Rwl4FI}fxr(I`UXI*lw=z4b#xhv4Sg0Ep2dogF6o*vRGkG0s-L+%Omp5S{f@F9pUJw>{w zFAGh2hHM1f?Tc!9h&{!h{M6_EnIB?MdWm$vLYDWqmxp{_hXL#5A@>D(A7t9k>Oe4* zNY~D0WqNzab;yA$!Bcxo+hgBP17AQAhdv@bGPnnv93u}&N>dgY+dxRNDtj)GkqnW90zK3 zHJ|JOmlA^diS+Erl|Yu7lUBrem(a)JrUEe2%yjn?pOAvMzesy6XNSn90+w*d??a!6 zwDXpMu<|q2OMN^n+Mz^(rw*8Qz`l||A2^Bzi1gH6#`X90a6cUho;qUM5z6dNTlZ=<8%Rpv;B#QyMskK@dld_BaGDhvbhI>z{>R(KM@Cr1I3B% zK_U-#>*X3Ot}9w!G`FaV9dC$8hxF+TOJr#wnG4cL9W+#=Ll&(D%S?zc<)0nCGDI9I zhOF%C6b%#Uob#Jtqw%u@XwiRW$Uqqf#V~^?4HxP53rsV6*Ufy;96q$e#g7WR7IrIq zA~*B+y=a6;_l`0N;2|J@wvJ(h$jx#Y>Q_1g?h>>C#WT0zNW10#R@FHXbRC5=V<; z#IejIA<~aJPJ^>*Wg%gC?`?i(oH)}NBtBlGm#rv)3$WI_Xo5&TTC)-+b4MN-2vVqR zi5@R@aZM1j$D)FLLZs_&u}!kFfWRdNelMCN(uKpBl5L=ysbw6KJW*U+{-tHsg*y ze=7DUnC+fW^aP@=ncOr*q?0pZy!~D@RixJr_kb0!F`sPxrwqmB6p=fTPbiuyK3WGG z+8hm-S46ta@*)2SmW%BOKTR})JsxjJS&M!znl939%UGz-AUBx=XXkt7{JuS1d_u81 z4bMPRGekP;5?ji>UF0UW2+b7f!S9(HbC-M`IBPGj8RE3OnWA+=M;$avq#u20D6jVb z({7tzXNgRC?M1Ui+I@m~GIxNJ*)BAjoyvYMnj_LJhgs^)`}w3BblM)^IpPr}D@`bR zY%BJc(CfJ({n)ydssr!!JUIHpus&nKsXX z?}K zzfgowt-VE$W2b)_EfVRlPJ@Ao80aC8f&6?C=?``m30`!=v>Wzy$IL5qu}FIy9}1^* z2QGv_u#-qPe$Czu@|c#}K_1cr(;nEjI6v?;Fv3ekdeF*twFBG}yl9z7C!98QvR6Po zR0k~=>5(xk76Iyr^&a9OpM$=AtDE!(nWi?p>4j-8#^Y>4 zjy%_+okz6m*%O~01UhgK;A$G71+91*)-PsNp z>S1D_1P{G2?TsNE{vZ5av{9s8wwc%LjeCl?9C7dK#nbNHMH|G8;$z3X`=3UeM0#XV z7g&}CY1r&$-Xnf5`dp+V7qZFO;wHV_Od{AU(%G}wyI~%(ici!q59xzxAM9I_7nlyx z=N6G3>1uZHA2X2OBz`V#7PmYhG5r`rChj@R{3g=5lbB}X zCNsY|G#fj`U81S_&!X9=gLaE_^esl5MtTU>Sd8?Lewg;dzWz7>{Jt?6_akscf?D`nN^Ls^lzXQ{SbwXqlq?eAcccVT03c=d&Gyu~9tl|w|oeqn1_Zn7cL;<+~vbPr<5$UWu z?A`DJ@(nPCwm|JR{Zyiuumaa#U1vN_3p~*>rJjoDr_%0D$;#d zSAd*1#zPJO$59)e24Xr8`8I0**3?{EjVA?q)(wRr*z@i-QVH(Pk+VC_4(;*m4Uef{6X^~F6 zY?{2lJ>4;d={EBUz7$W1r^Q?*FGnBk3vx`^?lHsa&xmyG80LdK!9(ukF_pyx4;hN- zQ0#jza12h8vm(9P;}V!aUS_u*IF7+lMQ6mb;!7_Zj>?}^N~iEJb{xMKofB#Mq3oO< z;AZEVpP=(1J->zN{$6K>Z`%@mPCPF*tWgtxY54Dh3+-4+=8Lu!ad*+m{%G6KcC-WS zMC=x-uS7cch$)fv16pP;x39z_1-MZ%-7-q_d<83C9Wn3F-{s=hGjIkAkt$!9k=;xJEN!&mN6Z{=!etQfx+T@&fd+h%fwQLr@ak-a89?$jAe(LBn;??tyn+GUps1U#%9 zX1bf=l)R~V!}4!c2K22+KkmXVf|%?f6FJj3*+WKRIuiS&z#s${aEbJ*QGH=bKlPBl zK+tQ$(6)cw2=02tRN`?n^hnGy}xzAMssAQcp#r^wvQ^Zbr@m+8eE^1v*S zd1_Mhy-4RS>jaXJ!_vZpo_iwgJ(H=>rh3R|%ucGQ9x@iwv0wn}gZ*{UeUYvo$;wOv zamZf?hOnE!y-V$#ty7^lT}5`CBBd7r)NF403Lmw5dJ zXtWDl;JppZsrdV@5+s4)(qK0!^S*?ku3@h3k~+erj&!M`T%%oMTw`78IF~x!1@{Nk zMLi@s@eB*pyI4q8!j8IB$oLehf zj6M?W`4y8U@47+Qhh&u!o!k2ipgqe&HiK2GHatzjbQ1PW#(_>y=wpd?9m)#L_Av1q zV%!4S1S6NH=o5*>b&jn-Tbv&fXx zhNr2RPQ?&fKMscN6NzrReF~P?U&%Lawyg(9wEtw*1UEjfeS!>>mK2RII9$M0=U)^oEy~=FHDPWnpANql z4U*`Lb?lmec^)zye9vpc(=<${VNk(Ns^#~h!4loyg{4D2SwuEMl%^LAk?1D_SLgc+ z(R>dX$SLN051EeXbPP9R_s;XzMMEVzYZ&unUEm>~fVg(ZO*Zj~Ti_uxFrC2)_j5af zfd)x~C3erk5NW7nojG;UFo~X?#(YZV=9AB4e<51vAxoZND|MlV%*1pi92twA@q5v5 ziT3Nw%3gMn>vc0O$`~dMw>SC-i7q|P2+Jq=WHc9GM@qE!Q05xG$U_G4#j(gkW??!D z`(|S>8q`6fB-&}+IdBbM>>)2QBo=$f98Bk6-&`Dc0n$@Sbn+@zWQm77&5A7Xka?KS zV?`iJWsF3-jbx6*OFg7NADE>cG9T0V*w?Qxz!EczmFU`WtkBqevH|Fez3oOwBc)N& zXlaZzR@$F$Ihof*<0N{UPwa9dBz~TUEa8!b<0abl4wFomdB~?A1xoO=0Mi8^y-oxn zaDqe^o@Qlc=94?XiQ48Wh#I~nj5FtHyfi_&oPW@L$bHy-#QnmHmhALTHCUT)b(KU% z?imf?56eBIJ{zay98}2+&RcGWJ;JQO_B^E=S7nxdU2d- zEnf|^+unYYC2lQ0WMn1&+_kBya^X{nu3o^L82Y%#I$$B|qA3zRFl!mC;8}%a7f`Vt zF0u&b;zuSGlRlNENZnlBT|LZv?NcS%XN$>q?gmWkjohtZsx-{tJ9|E|y_Ojlne}U$ zMB9JQIG8bcWFNPNPnYOLD}r<~M3CC;Pm>@Lc)HZP;IUKtp2~B;y&`rm03&hFg1%2A zY48k*_F3Eu%)y)T$t6g7Xs^Q=(xce8jWGj*oFSut*P*{Inkms6Ygh)I&phNH91bfy z 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 YO5Vzt=lOU4F8~1l|G6-KRK=MB0Qa7Gpa1{> literal 0 HcmV?d00001 diff --git a/pkg/pprof/testdata/gotruncatefix/cpu_go_truncated_1.pb.gz.fixed b/pkg/pprof/testdata/gotruncatefix/cpu_go_truncated_1.pb.gz.fixed new file mode 100644 index 0000000000000000000000000000000000000000..d57624acec161daf8d37aa992d1531b49a1413b5 GIT binary patch literal 64342 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 LgJ2w@t(^h@K>*6< literal 0 HcmV?d00001 diff --git a/pkg/pprof/testdata/goheapfix/heap_go_truncated_1.pb.gz b/pkg/pprof/testdata/gotruncatefix/heap_go_truncated_1.pb.gz similarity index 100% rename from pkg/pprof/testdata/goheapfix/heap_go_truncated_1.pb.gz rename to pkg/pprof/testdata/gotruncatefix/heap_go_truncated_1.pb.gz diff --git a/pkg/pprof/testdata/goheapfix/heap_go_truncated_1.pb.gz.fixed b/pkg/pprof/testdata/gotruncatefix/heap_go_truncated_1.pb.gz.fixed similarity index 100% rename from pkg/pprof/testdata/goheapfix/heap_go_truncated_1.pb.gz.fixed rename to pkg/pprof/testdata/gotruncatefix/heap_go_truncated_1.pb.gz.fixed diff --git a/pkg/pprof/testdata/goheapfix/heap_go_truncated_2.pb.gz b/pkg/pprof/testdata/gotruncatefix/heap_go_truncated_2.pb.gz similarity index 100% rename from pkg/pprof/testdata/goheapfix/heap_go_truncated_2.pb.gz rename to pkg/pprof/testdata/gotruncatefix/heap_go_truncated_2.pb.gz diff --git a/pkg/pprof/testdata/goheapfix/heap_go_truncated_2.pb.gz.fixed b/pkg/pprof/testdata/gotruncatefix/heap_go_truncated_2.pb.gz.fixed similarity index 100% rename from pkg/pprof/testdata/goheapfix/heap_go_truncated_2.pb.gz.fixed rename to pkg/pprof/testdata/gotruncatefix/heap_go_truncated_2.pb.gz.fixed diff --git a/pkg/pprof/testdata/goheapfix/heap_go_truncated_3.pb.gz b/pkg/pprof/testdata/gotruncatefix/heap_go_truncated_3.pb.gz similarity index 100% rename from pkg/pprof/testdata/goheapfix/heap_go_truncated_3.pb.gz rename to pkg/pprof/testdata/gotruncatefix/heap_go_truncated_3.pb.gz diff --git a/pkg/pprof/testdata/goheapfix/heap_go_truncated_3.pb.gz.fixed b/pkg/pprof/testdata/gotruncatefix/heap_go_truncated_3.pb.gz.fixed similarity index 100% rename from pkg/pprof/testdata/goheapfix/heap_go_truncated_3.pb.gz.fixed rename to pkg/pprof/testdata/gotruncatefix/heap_go_truncated_3.pb.gz.fixed diff --git a/pkg/pprof/testdata/goheapfix/heap_go_truncated_4.pb.gz b/pkg/pprof/testdata/gotruncatefix/heap_go_truncated_4.pb.gz similarity index 100% rename from pkg/pprof/testdata/goheapfix/heap_go_truncated_4.pb.gz rename to pkg/pprof/testdata/gotruncatefix/heap_go_truncated_4.pb.gz diff --git a/pkg/pprof/testdata/goheapfix/heap_go_truncated_4.pb.gz.fixed b/pkg/pprof/testdata/gotruncatefix/heap_go_truncated_4.pb.gz.fixed similarity index 100% rename from pkg/pprof/testdata/goheapfix/heap_go_truncated_4.pb.gz.fixed rename to pkg/pprof/testdata/gotruncatefix/heap_go_truncated_4.pb.gz.fixed