diff --git a/util/jsonutil/marshal.go b/util/jsonutil/marshal.go new file mode 100644 index 000000000..a82ae100d --- /dev/null +++ b/util/jsonutil/marshal.go @@ -0,0 +1,62 @@ +// Copyright 2022 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package jsonutil + +import ( + "math" + "strconv" + + jsoniter "github.com/json-iterator/go" +) + +// MarshalTimestamp marshals a point timestamp using the passed jsoniter stream. +func MarshalTimestamp(t int64, stream *jsoniter.Stream) { + // Write out the timestamp as a float divided by 1000. + // This is ~3x faster than converting to a float. + if t < 0 { + stream.WriteRaw(`-`) + t = -t + } + stream.WriteInt64(t / 1000) + fraction := t % 1000 + if fraction != 0 { + stream.WriteRaw(`.`) + if fraction < 100 { + stream.WriteRaw(`0`) + } + if fraction < 10 { + stream.WriteRaw(`0`) + } + stream.WriteInt64(fraction) + } +} + +// MarshalValue marshals a point value using the passed jsoniter stream. +func MarshalValue(v float64, stream *jsoniter.Stream) { + stream.WriteRaw(`"`) + // Taken from https://github.com/json-iterator/go/blob/master/stream_float.go#L71 as a workaround + // to https://github.com/json-iterator/go/issues/365 (jsoniter, to follow json standard, doesn't allow inf/nan). + buf := stream.Buffer() + abs := math.Abs(v) + fmt := byte('f') + // Note: Must use float32 comparisons for underlying float32 value to get precise cutoffs right. + if abs != 0 { + if abs < 1e-6 || abs >= 1e21 { + fmt = 'e' + } + } + buf = strconv.AppendFloat(buf, v, fmt, -1, 64) + stream.SetBuffer(buf) + stream.WriteRaw(`"`) +} diff --git a/web/api/v1/api.go b/web/api/v1/api.go index 9ceaa8c3f..bb97e7eeb 100644 --- a/web/api/v1/api.go +++ b/web/api/v1/api.go @@ -52,6 +52,7 @@ import ( "github.com/prometheus/prometheus/tsdb" "github.com/prometheus/prometheus/tsdb/index" "github.com/prometheus/prometheus/util/httputil" + "github.com/prometheus/prometheus/util/jsonutil" "github.com/prometheus/prometheus/util/stats" ) @@ -1659,9 +1660,9 @@ OUTER: func marshalPointJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) { p := *((*promql.Point)(ptr)) stream.WriteArrayStart() - marshalTimestamp(p.T, stream) + jsonutil.MarshalTimestamp(p.T, stream) stream.WriteMore() - marshalValue(p.V, stream) + jsonutil.MarshalValue(p.V, stream) stream.WriteArrayEnd() } @@ -1692,13 +1693,12 @@ func marshalExemplarJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) { // "value" key. stream.WriteMore() stream.WriteObjectField(`value`) - marshalValue(p.Value, stream) + jsonutil.MarshalValue(p.Value, stream) // "timestamp" key. stream.WriteMore() stream.WriteObjectField(`timestamp`) - marshalTimestamp(p.Ts, stream) - // marshalTimestamp(p.Ts, stream) + jsonutil.MarshalTimestamp(p.Ts, stream) stream.WriteObjectEnd() } @@ -1706,42 +1706,3 @@ func marshalExemplarJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) { func marshalExemplarJSONEmpty(ptr unsafe.Pointer) bool { return false } - -func marshalTimestamp(t int64, stream *jsoniter.Stream) { - // Write out the timestamp as a float divided by 1000. - // This is ~3x faster than converting to a float. - if t < 0 { - stream.WriteRaw(`-`) - t = -t - } - stream.WriteInt64(t / 1000) - fraction := t % 1000 - if fraction != 0 { - stream.WriteRaw(`.`) - if fraction < 100 { - stream.WriteRaw(`0`) - } - if fraction < 10 { - stream.WriteRaw(`0`) - } - stream.WriteInt64(fraction) - } -} - -func marshalValue(v float64, stream *jsoniter.Stream) { - stream.WriteRaw(`"`) - // Taken from https://github.com/json-iterator/go/blob/master/stream_float.go#L71 as a workaround - // to https://github.com/json-iterator/go/issues/365 (jsoniter, to follow json standard, doesn't allow inf/nan). - buf := stream.Buffer() - abs := math.Abs(v) - fmt := byte('f') - // Note: Must use float32 comparisons for underlying float32 value to get precise cutoffs right. - if abs != 0 { - if abs < 1e-6 || abs >= 1e21 { - fmt = 'e' - } - } - buf = strconv.AppendFloat(buf, v, fmt, -1, 64) - stream.SetBuffer(buf) - stream.WriteRaw(`"`) -}