mirror of
https://github.com/prometheus/prometheus.git
synced 2025-02-21 03:16:00 -08:00
Merge pull request #13997 from bboreham/api-marshalling
bugfix: API: encode empty Vector/Matrix as [] not null
This commit is contained in:
commit
3d42466894
|
@ -15,7 +15,6 @@ package v1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
@ -35,6 +34,7 @@ import (
|
||||||
"github.com/prometheus/prometheus/util/testutil"
|
"github.com/prometheus/prometheus/util/testutil"
|
||||||
|
|
||||||
"github.com/go-kit/log"
|
"github.com/go-kit/log"
|
||||||
|
jsoniter "github.com/json-iterator/go"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
config_util "github.com/prometheus/common/config"
|
config_util "github.com/prometheus/common/config"
|
||||||
"github.com/prometheus/common/model"
|
"github.com/prometheus/common/model"
|
||||||
|
@ -910,6 +910,7 @@ func TestStats(t *testing.T) {
|
||||||
require.IsType(t, &QueryData{}, i)
|
require.IsType(t, &QueryData{}, i)
|
||||||
qd := i.(*QueryData)
|
qd := i.(*QueryData)
|
||||||
require.NotNil(t, qd.Stats)
|
require.NotNil(t, qd.Stats)
|
||||||
|
json := jsoniter.ConfigCompatibleWithStandardLibrary
|
||||||
j, err := json.Marshal(qd.Stats)
|
j, err := json.Marshal(qd.Stats)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.JSONEq(t, `{"custom":"Custom Value"}`, string(j))
|
require.JSONEq(t, `{"custom":"Custom Value"}`, string(j))
|
||||||
|
@ -1171,6 +1172,25 @@ func testEndpoints(t *testing.T, api *API, tr *testTargetRetriever, es storage.E
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// Test empty vector result
|
||||||
|
{
|
||||||
|
endpoint: api.query,
|
||||||
|
query: url.Values{
|
||||||
|
"query": []string{"bottomk(2, notExists)"},
|
||||||
|
},
|
||||||
|
responseAsJSON: `{"resultType":"vector","result":[]}`,
|
||||||
|
},
|
||||||
|
// Test empty matrix result
|
||||||
|
{
|
||||||
|
endpoint: api.queryRange,
|
||||||
|
query: url.Values{
|
||||||
|
"query": []string{"bottomk(2, notExists)"},
|
||||||
|
"start": []string{"0"},
|
||||||
|
"end": []string{"2"},
|
||||||
|
"step": []string{"1"},
|
||||||
|
},
|
||||||
|
responseAsJSON: `{"resultType":"matrix","result":[]}`,
|
||||||
|
},
|
||||||
// Missing query params in range queries.
|
// Missing query params in range queries.
|
||||||
{
|
{
|
||||||
endpoint: api.queryRange,
|
endpoint: api.queryRange,
|
||||||
|
@ -2891,10 +2911,13 @@ func testEndpoints(t *testing.T, api *API, tr *testTargetRetriever, es storage.E
|
||||||
if test.zeroFunc != nil {
|
if test.zeroFunc != nil {
|
||||||
test.zeroFunc(res.data)
|
test.zeroFunc(res.data)
|
||||||
}
|
}
|
||||||
assertAPIResponse(t, res.data, test.response)
|
if test.response != nil {
|
||||||
|
assertAPIResponse(t, res.data, test.response)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if test.responseAsJSON != "" {
|
if test.responseAsJSON != "" {
|
||||||
|
json := jsoniter.ConfigCompatibleWithStandardLibrary
|
||||||
s, err := json.Marshal(res.data)
|
s, err := json.Marshal(res.data)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.JSONEq(t, test.responseAsJSON, string(s))
|
require.JSONEq(t, test.responseAsJSON, string(s))
|
||||||
|
@ -3292,18 +3315,7 @@ func TestRespondError(t *testing.T) {
|
||||||
require.Equal(t, want, have, "Return code %d expected in error response but got %d", want, have)
|
require.Equal(t, want, have, "Return code %d expected in error response but got %d", want, have)
|
||||||
h := resp.Header.Get("Content-Type")
|
h := resp.Header.Get("Content-Type")
|
||||||
require.Equal(t, "application/json", h, "Expected Content-Type %q but got %q", "application/json", h)
|
require.Equal(t, "application/json", h, "Expected Content-Type %q but got %q", "application/json", h)
|
||||||
|
require.JSONEq(t, `{"status": "error", "data": "test", "errorType": "timeout", "error": "message"}`, string(body))
|
||||||
var res Response
|
|
||||||
err = json.Unmarshal(body, &res)
|
|
||||||
require.NoError(t, err, "Error unmarshaling JSON body")
|
|
||||||
|
|
||||||
exp := &Response{
|
|
||||||
Status: statusError,
|
|
||||||
Data: "test",
|
|
||||||
ErrorType: errorTimeout,
|
|
||||||
Error: "message",
|
|
||||||
}
|
|
||||||
require.Equal(t, exp, &res)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseTimeParam(t *testing.T) {
|
func TestParseTimeParam(t *testing.T) {
|
||||||
|
|
|
@ -25,11 +25,13 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
jsoniter.RegisterTypeEncoderFunc("promql.Series", marshalSeriesJSON, marshalSeriesJSONIsEmpty)
|
jsoniter.RegisterTypeEncoderFunc("promql.Vector", unsafeMarshalVectorJSON, neverEmpty)
|
||||||
jsoniter.RegisterTypeEncoderFunc("promql.Sample", marshalSampleJSON, marshalSampleJSONIsEmpty)
|
jsoniter.RegisterTypeEncoderFunc("promql.Matrix", unsafeMarshalMatrixJSON, neverEmpty)
|
||||||
jsoniter.RegisterTypeEncoderFunc("promql.FPoint", marshalFPointJSON, marshalPointJSONIsEmpty)
|
jsoniter.RegisterTypeEncoderFunc("promql.Series", unsafeMarshalSeriesJSON, neverEmpty)
|
||||||
jsoniter.RegisterTypeEncoderFunc("promql.HPoint", marshalHPointJSON, marshalPointJSONIsEmpty)
|
jsoniter.RegisterTypeEncoderFunc("promql.Sample", unsafeMarshalSampleJSON, neverEmpty)
|
||||||
jsoniter.RegisterTypeEncoderFunc("exemplar.Exemplar", marshalExemplarJSON, marshalExemplarJSONEmpty)
|
jsoniter.RegisterTypeEncoderFunc("promql.FPoint", unsafeMarshalFPointJSON, neverEmpty)
|
||||||
|
jsoniter.RegisterTypeEncoderFunc("promql.HPoint", unsafeMarshalHPointJSON, neverEmpty)
|
||||||
|
jsoniter.RegisterTypeEncoderFunc("exemplar.Exemplar", marshalExemplarJSON, neverEmpty)
|
||||||
jsoniter.RegisterTypeEncoderFunc("labels.Labels", unsafeMarshalLabelsJSON, labelsIsEmpty)
|
jsoniter.RegisterTypeEncoderFunc("labels.Labels", unsafeMarshalLabelsJSON, labelsIsEmpty)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,8 +68,12 @@ func (j JSONCodec) Encode(resp *Response) ([]byte, error) {
|
||||||
// < more histograms >
|
// < more histograms >
|
||||||
// ],
|
// ],
|
||||||
// },
|
// },
|
||||||
func marshalSeriesJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) {
|
func unsafeMarshalSeriesJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) {
|
||||||
s := *((*promql.Series)(ptr))
|
s := *((*promql.Series)(ptr))
|
||||||
|
marshalSeriesJSON(s, stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
func marshalSeriesJSON(s promql.Series, stream *jsoniter.Stream) {
|
||||||
stream.WriteObjectStart()
|
stream.WriteObjectStart()
|
||||||
stream.WriteObjectField(`metric`)
|
stream.WriteObjectField(`metric`)
|
||||||
marshalLabelsJSON(s.Metric, stream)
|
marshalLabelsJSON(s.Metric, stream)
|
||||||
|
@ -78,7 +84,7 @@ func marshalSeriesJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) {
|
||||||
stream.WriteObjectField(`values`)
|
stream.WriteObjectField(`values`)
|
||||||
stream.WriteArrayStart()
|
stream.WriteArrayStart()
|
||||||
}
|
}
|
||||||
marshalFPointJSON(unsafe.Pointer(&p), stream)
|
marshalFPointJSON(p, stream)
|
||||||
}
|
}
|
||||||
if len(s.Floats) > 0 {
|
if len(s.Floats) > 0 {
|
||||||
stream.WriteArrayEnd()
|
stream.WriteArrayEnd()
|
||||||
|
@ -89,7 +95,7 @@ func marshalSeriesJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) {
|
||||||
stream.WriteObjectField(`histograms`)
|
stream.WriteObjectField(`histograms`)
|
||||||
stream.WriteArrayStart()
|
stream.WriteArrayStart()
|
||||||
}
|
}
|
||||||
marshalHPointJSON(unsafe.Pointer(&p), stream)
|
marshalHPointJSON(p, stream)
|
||||||
}
|
}
|
||||||
if len(s.Histograms) > 0 {
|
if len(s.Histograms) > 0 {
|
||||||
stream.WriteArrayEnd()
|
stream.WriteArrayEnd()
|
||||||
|
@ -97,7 +103,8 @@ func marshalSeriesJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) {
|
||||||
stream.WriteObjectEnd()
|
stream.WriteObjectEnd()
|
||||||
}
|
}
|
||||||
|
|
||||||
func marshalSeriesJSONIsEmpty(unsafe.Pointer) bool {
|
// In the Prometheus API we render an empty object as `[]` or similar.
|
||||||
|
func neverEmpty(unsafe.Pointer) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,8 +129,12 @@ func marshalSeriesJSONIsEmpty(unsafe.Pointer) bool {
|
||||||
// },
|
// },
|
||||||
// "histogram": [ 1435781451.781, { < histogram, see jsonutil.MarshalHistogram > } ]
|
// "histogram": [ 1435781451.781, { < histogram, see jsonutil.MarshalHistogram > } ]
|
||||||
// },
|
// },
|
||||||
func marshalSampleJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) {
|
func unsafeMarshalSampleJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) {
|
||||||
s := *((*promql.Sample)(ptr))
|
s := *((*promql.Sample)(ptr))
|
||||||
|
marshalSampleJSON(s, stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
func marshalSampleJSON(s promql.Sample, stream *jsoniter.Stream) {
|
||||||
stream.WriteObjectStart()
|
stream.WriteObjectStart()
|
||||||
stream.WriteObjectField(`metric`)
|
stream.WriteObjectField(`metric`)
|
||||||
marshalLabelsJSON(s.Metric, stream)
|
marshalLabelsJSON(s.Metric, stream)
|
||||||
|
@ -145,13 +156,13 @@ func marshalSampleJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) {
|
||||||
stream.WriteObjectEnd()
|
stream.WriteObjectEnd()
|
||||||
}
|
}
|
||||||
|
|
||||||
func marshalSampleJSONIsEmpty(unsafe.Pointer) bool {
|
// marshalFPointJSON writes `[ts, "1.234"]`.
|
||||||
return false
|
func unsafeMarshalFPointJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) {
|
||||||
|
p := *((*promql.FPoint)(ptr))
|
||||||
|
marshalFPointJSON(p, stream)
|
||||||
}
|
}
|
||||||
|
|
||||||
// marshalFPointJSON writes `[ts, "1.234"]`.
|
func marshalFPointJSON(p promql.FPoint, stream *jsoniter.Stream) {
|
||||||
func marshalFPointJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) {
|
|
||||||
p := *((*promql.FPoint)(ptr))
|
|
||||||
stream.WriteArrayStart()
|
stream.WriteArrayStart()
|
||||||
jsonutil.MarshalTimestamp(p.T, stream)
|
jsonutil.MarshalTimestamp(p.T, stream)
|
||||||
stream.WriteMore()
|
stream.WriteMore()
|
||||||
|
@ -160,8 +171,12 @@ func marshalFPointJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// marshalHPointJSON writes `[ts, { < histogram, see jsonutil.MarshalHistogram > } ]`.
|
// marshalHPointJSON writes `[ts, { < histogram, see jsonutil.MarshalHistogram > } ]`.
|
||||||
func marshalHPointJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) {
|
func unsafeMarshalHPointJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) {
|
||||||
p := *((*promql.HPoint)(ptr))
|
p := *((*promql.HPoint)(ptr))
|
||||||
|
marshalHPointJSON(p, stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
func marshalHPointJSON(p promql.HPoint, stream *jsoniter.Stream) {
|
||||||
stream.WriteArrayStart()
|
stream.WriteArrayStart()
|
||||||
jsonutil.MarshalTimestamp(p.T, stream)
|
jsonutil.MarshalTimestamp(p.T, stream)
|
||||||
stream.WriteMore()
|
stream.WriteMore()
|
||||||
|
@ -169,10 +184,6 @@ func marshalHPointJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) {
|
||||||
stream.WriteArrayEnd()
|
stream.WriteArrayEnd()
|
||||||
}
|
}
|
||||||
|
|
||||||
func marshalPointJSONIsEmpty(unsafe.Pointer) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// marshalExemplarJSON writes.
|
// marshalExemplarJSON writes.
|
||||||
//
|
//
|
||||||
// {
|
// {
|
||||||
|
@ -201,10 +212,6 @@ func marshalExemplarJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) {
|
||||||
stream.WriteObjectEnd()
|
stream.WriteObjectEnd()
|
||||||
}
|
}
|
||||||
|
|
||||||
func marshalExemplarJSONEmpty(unsafe.Pointer) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func unsafeMarshalLabelsJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) {
|
func unsafeMarshalLabelsJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) {
|
||||||
labelsPtr := (*labels.Labels)(ptr)
|
labelsPtr := (*labels.Labels)(ptr)
|
||||||
marshalLabelsJSON(*labelsPtr, stream)
|
marshalLabelsJSON(*labelsPtr, stream)
|
||||||
|
@ -229,3 +236,23 @@ func labelsIsEmpty(ptr unsafe.Pointer) bool {
|
||||||
labelsPtr := (*labels.Labels)(ptr)
|
labelsPtr := (*labels.Labels)(ptr)
|
||||||
return labelsPtr.IsEmpty()
|
return labelsPtr.IsEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Marshal a Vector as `[sample,sample,...]` - empty Vector is `[]`.
|
||||||
|
func unsafeMarshalVectorJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) {
|
||||||
|
v := *((*promql.Vector)(ptr))
|
||||||
|
stream.WriteArrayStart()
|
||||||
|
for _, s := range v {
|
||||||
|
marshalSampleJSON(s, stream)
|
||||||
|
}
|
||||||
|
stream.WriteArrayEnd()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal a Matrix as `[series,series,...]` - empty Matrix is `[]`.
|
||||||
|
func unsafeMarshalMatrixJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) {
|
||||||
|
m := *((*promql.Matrix)(ptr))
|
||||||
|
stream.WriteArrayStart()
|
||||||
|
for _, s := range m {
|
||||||
|
marshalSeriesJSON(s, stream)
|
||||||
|
}
|
||||||
|
stream.WriteArrayEnd()
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue