Skip to content

Commit 320e475

Browse files
authored
Query Frontend: add new field for dense native histogram format (#6199)
1 parent 264e640 commit 320e475

File tree

6 files changed

+271
-146
lines changed

6 files changed

+271
-146
lines changed

Diff for: pkg/api/handlers.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,9 @@ func NewQuerierHandler(
233233
)
234234

235235
// JSON codec is already installed. Install Protobuf codec to give the option for using either.
236-
api.InstallCodec(codec.ProtobufCodec{})
236+
api.InstallCodec(codec.ProtobufCodec{CortexInternal: false})
237+
// Protobuf codec for Cortex internal requests. This should be used by Cortex Ruler only for remote evaluation.
238+
api.InstallCodec(codec.ProtobufCodec{CortexInternal: true})
237239

238240
router := mux.NewRouter()
239241

Diff for: pkg/querier/codec/protobuf_codec.go

+46-23
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,20 @@ import (
1313
"github.com/cortexproject/cortex/pkg/querier/tripperware"
1414
)
1515

16-
type ProtobufCodec struct{}
16+
type ProtobufCodec struct {
17+
// cortexInternal enables encoding the whole native histogram data fields in response instead of keeping
18+
// only few sparse information like the default JSON/Protobuf codec does.
19+
// This will be used by Cortex Ruler to get native histograms data from Cortex Query Frontend because
20+
// rule evaluation requires the full native histogram data.
21+
CortexInternal bool
22+
}
1723

1824
func (p ProtobufCodec) ContentType() v1.MIMEType {
19-
return v1.MIMEType{Type: "application", SubType: "x-protobuf"}
25+
if !p.CortexInternal {
26+
return v1.MIMEType{Type: "application", SubType: "x-protobuf"}
27+
}
28+
// TODO: switch to use constants.
29+
return v1.MIMEType{Type: "application", SubType: "x-cortex-query+proto"}
2030
}
2131

2232
func (p ProtobufCodec) CanEncode(resp *v1.Response) bool {
@@ -29,15 +39,15 @@ func (p ProtobufCodec) CanEncode(resp *v1.Response) bool {
2939

3040
// ProtobufCodec implementation is derived from https://github.com/prometheus/prometheus/blob/main/web/api/v1/json_codec.go
3141
func (p ProtobufCodec) Encode(resp *v1.Response) ([]byte, error) {
32-
prometheusQueryResponse, err := createPrometheusQueryResponse(resp)
42+
prometheusQueryResponse, err := createPrometheusQueryResponse(resp, p.CortexInternal)
3343
if err != nil {
3444
return []byte{}, err
3545
}
3646
b, err := proto.Marshal(prometheusQueryResponse)
3747
return b, err
3848
}
3949

40-
func createPrometheusQueryResponse(resp *v1.Response) (*tripperware.PrometheusResponse, error) {
50+
func createPrometheusQueryResponse(resp *v1.Response, cortexInternal bool) (*tripperware.PrometheusResponse, error) {
4151
var data = resp.Data.(*v1.QueryData)
4252

4353
var queryResult tripperware.PrometheusQueryResult
@@ -51,7 +61,10 @@ func createPrometheusQueryResponse(resp *v1.Response) (*tripperware.PrometheusRe
5161
case model.ValVector.String():
5262
queryResult.Result = &tripperware.PrometheusQueryResult_Vector{
5363
Vector: &tripperware.Vector{
54-
Samples: *getVectorSamples(data),
64+
// cortexInternal tries to encode native histogram as dense format instead of sparse format.
65+
// This is only used for vector response type since internal response is only available for Ruler
66+
// client and Ruler only expects vector or scalar response type.
67+
Samples: *getVectorSamples(data, cortexInternal),
5568
},
5669
}
5770
default:
@@ -139,7 +152,7 @@ func getMatrixSampleStreams(data *v1.QueryData) *[]tripperware.SampleStream {
139152
return &sampleStreams
140153
}
141154

142-
func getVectorSamples(data *v1.QueryData) *[]tripperware.Sample {
155+
func getVectorSamples(data *v1.QueryData, cortexInternal bool) *[]tripperware.Sample {
143156
vectorSamplesLen := len(data.Result.(promql.Vector))
144157
vectorSamples := make([]tripperware.Sample, vectorSamplesLen)
145158

@@ -158,27 +171,37 @@ func getVectorSamples(data *v1.QueryData) *[]tripperware.Sample {
158171
}
159172
vectorSamples[i].Labels = labels
160173

161-
if sample.H != nil {
162-
bucketsLen := len(sample.H.NegativeBuckets) + len(sample.H.PositiveBuckets)
163-
if sample.H.ZeroCount > 0 {
164-
bucketsLen = len(sample.H.NegativeBuckets) + len(sample.H.PositiveBuckets) + 1
165-
}
166-
buckets := make([]*tripperware.HistogramBucket, bucketsLen)
167-
it := sample.H.AllBucketIterator()
168-
getBuckets(buckets, it)
169-
vectorSamples[i].Histogram = &tripperware.SampleHistogramPair{
170-
TimestampMs: sample.T,
171-
Histogram: tripperware.SampleHistogram{
172-
Count: sample.H.Count,
173-
Sum: sample.H.Sum,
174-
Buckets: buckets,
175-
},
176-
}
177-
} else {
174+
// Float samples only.
175+
if sample.H == nil {
178176
vectorSamples[i].Sample = &cortexpb.Sample{
179177
TimestampMs: sample.T,
180178
Value: sample.F,
181179
}
180+
continue
181+
}
182+
183+
// Cortex Internal request. Encode dense float native histograms.
184+
if cortexInternal {
185+
hp := cortexpb.FloatHistogramToHistogramProto(sample.T, sample.H)
186+
vectorSamples[i].RawHistogram = &hp
187+
continue
188+
}
189+
190+
// Encode sparse native histograms.
191+
bucketsLen := len(sample.H.NegativeBuckets) + len(sample.H.PositiveBuckets)
192+
if sample.H.ZeroCount > 0 {
193+
bucketsLen = len(sample.H.NegativeBuckets) + len(sample.H.PositiveBuckets) + 1
194+
}
195+
buckets := make([]*tripperware.HistogramBucket, bucketsLen)
196+
it := sample.H.AllBucketIterator()
197+
getBuckets(buckets, it)
198+
vectorSamples[i].Histogram = &tripperware.SampleHistogramPair{
199+
TimestampMs: sample.T,
200+
Histogram: tripperware.SampleHistogram{
201+
Count: sample.H.Count,
202+
Sum: sample.H.Sum,
203+
Buckets: buckets,
204+
},
182205
}
183206
}
184207
return &vectorSamples

Diff for: pkg/querier/codec/protobuf_codec_test.go

+69-43
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,29 @@ import (
1717
)
1818

1919
func TestProtobufCodec_Encode(t *testing.T) {
20+
testFloatHistogram := &histogram.FloatHistogram{
21+
Schema: 2,
22+
ZeroThreshold: 0.001,
23+
ZeroCount: 12,
24+
Count: 10,
25+
Sum: 20,
26+
PositiveSpans: []histogram.Span{
27+
{Offset: 3, Length: 2},
28+
{Offset: 1, Length: 3},
29+
},
30+
NegativeSpans: []histogram.Span{
31+
{Offset: 2, Length: 2},
32+
},
33+
PositiveBuckets: []float64{1, 2, 2, 1, 1},
34+
NegativeBuckets: []float64{2, 1},
35+
}
36+
testProtoHistogram := cortexpb.FloatHistogramToHistogramProto(1000, testFloatHistogram)
37+
2038
tests := []struct {
21-
data interface{}
22-
expected *tripperware.PrometheusResponse
39+
name string
40+
data *v1.QueryData
41+
cortexInternal bool
42+
expected *tripperware.PrometheusResponse
2343
}{
2444
{
2545
data: &v1.QueryData{
@@ -207,23 +227,8 @@ func TestProtobufCodec_Encode(t *testing.T) {
207227
ResultType: parser.ValueTypeMatrix,
208228
Result: promql.Matrix{
209229
promql.Series{
210-
Histograms: []promql.HPoint{{H: &histogram.FloatHistogram{
211-
Schema: 2,
212-
ZeroThreshold: 0.001,
213-
ZeroCount: 12,
214-
Count: 10,
215-
Sum: 20,
216-
PositiveSpans: []histogram.Span{
217-
{Offset: 3, Length: 2},
218-
{Offset: 1, Length: 3},
219-
},
220-
NegativeSpans: []histogram.Span{
221-
{Offset: 2, Length: 2},
222-
},
223-
PositiveBuckets: []float64{1, 2, 2, 1, 1},
224-
NegativeBuckets: []float64{2, 1},
225-
}, T: 1000}},
226-
Metric: labels.FromStrings("__name__", "foo"),
230+
Histograms: []promql.HPoint{{H: testFloatHistogram, T: 1000}},
231+
Metric: labels.FromStrings("__name__", "foo"),
227232
},
228233
},
229234
},
@@ -313,22 +318,7 @@ func TestProtobufCodec_Encode(t *testing.T) {
313318
promql.Sample{
314319
Metric: labels.FromStrings("__name__", "foo"),
315320
T: 1000,
316-
H: &histogram.FloatHistogram{
317-
Schema: 2,
318-
ZeroThreshold: 0.001,
319-
ZeroCount: 12,
320-
Count: 10,
321-
Sum: 20,
322-
PositiveSpans: []histogram.Span{
323-
{Offset: 3, Length: 2},
324-
{Offset: 1, Length: 3},
325-
},
326-
NegativeSpans: []histogram.Span{
327-
{Offset: 2, Length: 2},
328-
},
329-
PositiveBuckets: []float64{1, 2, 2, 1, 1},
330-
NegativeBuckets: []float64{2, 1},
331-
},
321+
H: testFloatHistogram,
332322
},
333323
},
334324
},
@@ -409,17 +399,53 @@ func TestProtobufCodec_Encode(t *testing.T) {
409399
},
410400
},
411401
},
402+
{
403+
name: "cortex internal with native histogram",
404+
cortexInternal: true,
405+
data: &v1.QueryData{
406+
ResultType: parser.ValueTypeVector,
407+
Result: promql.Vector{
408+
promql.Sample{
409+
Metric: labels.FromStrings("__name__", "foo"),
410+
T: 1000,
411+
H: testFloatHistogram,
412+
},
413+
},
414+
},
415+
expected: &tripperware.PrometheusResponse{
416+
Status: tripperware.StatusSuccess,
417+
Data: tripperware.PrometheusData{
418+
ResultType: model.ValVector.String(),
419+
Result: tripperware.PrometheusQueryResult{
420+
Result: &tripperware.PrometheusQueryResult_Vector{
421+
Vector: &tripperware.Vector{
422+
Samples: []tripperware.Sample{
423+
{
424+
Labels: []cortexpb.LabelAdapter{
425+
{Name: "__name__", Value: "foo"},
426+
},
427+
RawHistogram: &testProtoHistogram,
428+
},
429+
},
430+
},
431+
},
432+
},
433+
},
434+
},
435+
},
412436
}
413437

414-
codec := ProtobufCodec{}
415438
for _, test := range tests {
416-
body, err := codec.Encode(&v1.Response{
417-
Status: tripperware.StatusSuccess,
418-
Data: test.data,
439+
t.Run(test.name, func(t *testing.T) {
440+
codec := ProtobufCodec{CortexInternal: test.cortexInternal}
441+
body, err := codec.Encode(&v1.Response{
442+
Status: tripperware.StatusSuccess,
443+
Data: test.data,
444+
})
445+
require.NoError(t, err)
446+
b, err := proto.Marshal(test.expected)
447+
require.NoError(t, err)
448+
require.Equal(t, string(b), string(body))
419449
})
420-
require.NoError(t, err)
421-
b, err := proto.Marshal(test.expected)
422-
require.NoError(t, err)
423-
require.Equal(t, string(b), string(body))
424450
}
425451
}

Diff for: pkg/querier/tripperware/merge.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,7 @@ func sliceSamples(samples []cortexpb.Sample, minTs int64) []cortexpb.Sample {
400400
return samples[searchResult:]
401401
}
402402

403-
// sliceHistogram assumes given histogram are sorted by timestamp in ascending order and
403+
// sliceHistograms assumes given histogram are sorted by timestamp in ascending order and
404404
// return a sub slice whose first element's is the smallest timestamp that is strictly
405405
// bigger than the given minTs. Empty slice is returned if minTs is bigger than all the
406406
// timestamps in histogram.

0 commit comments

Comments
 (0)