diff --git a/web/api/v1/api_test.go b/web/api/v1/api_test.go index 321a13d5e..99e3b292e 100644 --- a/web/api/v1/api_test.go +++ b/web/api/v1/api_test.go @@ -3417,27 +3417,57 @@ func TestReturnAPIError(t *testing.T) { var testResponseWriter = httptest.ResponseRecorder{} func BenchmarkRespond(b *testing.B) { - b.ReportAllocs() - request, err := http.NewRequest(http.MethodGet, "/does-not-matter", nil) - require.NoError(b, err) points := []promql.FPoint{} for i := 0; i < 10000; i++ { points = append(points, promql.FPoint{F: float64(i * 1000000), T: int64(i)}) } - response := &QueryData{ - ResultType: parser.ValueTypeMatrix, - Result: promql.Matrix{ - promql.Series{ - Floats: points, - Metric: labels.EmptyLabels(), - }, - }, + matrix := promql.Matrix{} + for i := 0; i < 1000; i++ { + matrix = append(matrix, promql.Series{ + Metric: labels.FromStrings("__name__", fmt.Sprintf("series%v", i), + "label", fmt.Sprintf("series%v", i), + "label2", fmt.Sprintf("series%v", i)), + Floats: points[:10], + }) } - b.ResetTimer() - api := API{} - api.InstallCodec(JSONCodec{}) - for n := 0; n < b.N; n++ { - api.respond(&testResponseWriter, request, response, nil) + series := []labels.Labels{} + for i := 0; i < 1000; i++ { + series = append(series, labels.FromStrings("__name__", fmt.Sprintf("series%v", i), + "label", fmt.Sprintf("series%v", i), + "label2", fmt.Sprintf("series%v", i))) + } + + cases := []struct { + name string + response interface{} + }{ + {name: "10000 points no labels", response: &QueryData{ + ResultType: parser.ValueTypeMatrix, + Result: promql.Matrix{ + promql.Series{ + Floats: points, + Metric: labels.EmptyLabels(), + }, + }, + }}, + {name: "1000 labels", response: series}, + {name: "1000 series 10 points", response: &QueryData{ + ResultType: parser.ValueTypeMatrix, + Result: matrix, + }}, + } + for _, c := range cases { + b.Run(c.name, func(b *testing.B) { + b.ReportAllocs() + request, err := http.NewRequest(http.MethodGet, "/does-not-matter", nil) + require.NoError(b, err) + b.ResetTimer() + api := API{} + api.InstallCodec(JSONCodec{}) + for n := 0; n < b.N; n++ { + api.respond(&testResponseWriter, request, c.response, nil) + } + }) } } diff --git a/web/api/v1/json_codec.go b/web/api/v1/json_codec.go index 62e7563b1..f1a8104cc 100644 --- a/web/api/v1/json_codec.go +++ b/web/api/v1/json_codec.go @@ -19,6 +19,7 @@ import ( jsoniter "github.com/json-iterator/go" "github.com/prometheus/prometheus/model/exemplar" + "github.com/prometheus/prometheus/model/labels" "github.com/prometheus/prometheus/promql" "github.com/prometheus/prometheus/util/jsonutil" ) @@ -29,6 +30,7 @@ func init() { jsoniter.RegisterTypeEncoderFunc("promql.FPoint", marshalFPointJSON, marshalPointJSONIsEmpty) jsoniter.RegisterTypeEncoderFunc("promql.HPoint", marshalHPointJSON, marshalPointJSONIsEmpty) jsoniter.RegisterTypeEncoderFunc("exemplar.Exemplar", marshalExemplarJSON, marshalExemplarJSONEmpty) + jsoniter.RegisterTypeEncoderFunc("labels.Labels", unsafeMarshalLabelsJSON, labelsIsEmpty) } // JSONCodec is a Codec that encodes API responses as JSON. @@ -68,12 +70,7 @@ func marshalSeriesJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) { s := *((*promql.Series)(ptr)) stream.WriteObjectStart() stream.WriteObjectField(`metric`) - m, err := s.Metric.MarshalJSON() - if err != nil { - stream.Error = err - return - } - stream.SetBuffer(append(stream.Buffer(), m...)) + marshalLabelsJSON(s.Metric, stream) for i, p := range s.Floats { stream.WriteMore() @@ -129,12 +126,7 @@ func marshalSampleJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) { s := *((*promql.Sample)(ptr)) stream.WriteObjectStart() stream.WriteObjectField(`metric`) - m, err := s.Metric.MarshalJSON() - if err != nil { - stream.Error = err - return - } - stream.SetBuffer(append(stream.Buffer(), m...)) + marshalLabelsJSON(s.Metric, stream) stream.WriteMore() if s.H == nil { stream.WriteObjectField(`value`) @@ -194,12 +186,7 @@ func marshalExemplarJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) { // "labels" key. stream.WriteObjectField(`labels`) - lbls, err := p.Labels.MarshalJSON() - if err != nil { - stream.Error = err - return - } - stream.SetBuffer(append(stream.Buffer(), lbls...)) + marshalLabelsJSON(p.Labels, stream) // "value" key. stream.WriteMore() @@ -217,3 +204,28 @@ func marshalExemplarJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) { func marshalExemplarJSONEmpty(unsafe.Pointer) bool { return false } + +func unsafeMarshalLabelsJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) { + labelsPtr := (*labels.Labels)(ptr) + marshalLabelsJSON(*labelsPtr, stream) +} + +func marshalLabelsJSON(lbls labels.Labels, stream *jsoniter.Stream) { + stream.WriteObjectStart() + i := 0 + lbls.Range(func(v labels.Label) { + if i != 0 { + stream.WriteMore() + } + i++ + stream.WriteString(v.Name) + stream.WriteRaw(`:`) + stream.WriteString(v.Value) + }) + stream.WriteObjectEnd() +} + +func labelsIsEmpty(ptr unsafe.Pointer) bool { + labelsPtr := (*labels.Labels)(ptr) + return labelsPtr.IsEmpty() +}