better JSON encoding (less allocs)

This commit is contained in:
Lou Knauer 2022-01-24 09:55:33 +01:00
parent 4a78a24034
commit af38004a76
3 changed files with 73 additions and 45 deletions

11
api.go
View File

@ -8,6 +8,7 @@ import (
"encoding/json"
"errors"
"fmt"
"io"
"log"
"math"
"net/http"
@ -24,7 +25,7 @@ type ApiMetricData struct {
Error *string `json:"error,omitempty"`
From int64 `json:"from"`
To int64 `json:"to"`
Data []Float `json:"data,omitempty"`
Data FloatArray `json:"data,omitempty"`
Avg Float `json:"avg"`
Min Float `json:"min"`
Max Float `json:"max"`
@ -109,7 +110,13 @@ func handleWrite(rw http.ResponseWriter, r *http.Request) {
return
}
dec := lineprotocol.NewDecoder(bufio.NewReader(r.Body))
bytes, err := io.ReadAll(r.Body)
if err != nil {
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}
dec := lineprotocol.NewDecoderWithBytes(bytes)
if err := decodeLine(dec); err != nil {
http.Error(rw, err.Error(), http.StatusBadRequest)
return

58
float.go Normal file
View File

@ -0,0 +1,58 @@
package main
import (
"math"
"strconv"
)
// Go's JSON encoder for floats does not support NaN (https://github.com/golang/go/issues/3480).
// This program uses NaN as a signal for missing data.
// For the HTTP JSON API to be able to handle NaN values,
// we have to use our own type which implements encoding/json.Marshaler itself.
type Float float32
var NaN Float = Float(math.NaN())
var nullAsBytes []byte = []byte("null")
func (f Float) IsNaN() bool {
return math.IsNaN(float64(f))
}
func (f Float) MarshalJSON() ([]byte, error) {
if math.IsNaN(float64(f)) {
return nullAsBytes, nil
}
return strconv.AppendFloat(make([]byte, 0, 10), float64(f), 'f', 1, 32), nil
}
func (f *Float) UnmarshalJSON(input []byte) error {
if string(input) == "null" {
*f = NaN
return nil
}
val, err := strconv.ParseFloat(string(input), 64)
if err != nil {
return err
}
*f = Float(val)
return nil
}
// Same as `[]Float`, but can be marshaled to JSON with less allocations.
type FloatArray []Float
func (fa FloatArray) MarshalJSON() ([]byte, error) {
buf := make([]byte, 0, 2+len(fa)*8)
buf = append(buf, '[')
if len(fa) > 0 {
buf = strconv.AppendFloat(buf, float64(fa[0]), 'f', 1, 32)
for i := 1; i < len(fa); i++ {
buf = append(buf, ',')
buf = strconv.AppendFloat(buf, float64(fa[i]), 'f', 1, 32)
}
}
buf = append(buf, ']')
return buf, nil
}

View File

@ -4,8 +4,6 @@ import (
"context"
"fmt"
"log"
"math"
"strconv"
"sync"
"time"
@ -13,41 +11,6 @@ import (
"github.com/nats-io/nats.go"
)
// Go's JSON encoder for floats does not support NaN (https://github.com/golang/go/issues/3480).
// This program uses NaN as a signal for missing data.
// For the HTTP JSON API to be able to handle NaN values,
// we have to use our own type which implements encoding/json.Marshaler itself.
type Float float64
var NaN Float = Float(math.NaN())
func (f Float) IsNaN() bool {
return math.IsNaN(float64(f))
}
func (f Float) MarshalJSON() ([]byte, error) {
if math.IsNaN(float64(f)) {
return []byte("null"), nil
}
return []byte(strconv.FormatFloat(float64(f), 'f', 2, 64)), nil
}
func (f *Float) UnmarshalJSON(input []byte) error {
s := string(input)
if s == "null" {
*f = NaN
return nil
}
val, err := strconv.ParseFloat(s, 64)
if err != nil {
return err
}
*f = Float(val)
return nil
}
type Metric struct {
Name string
minfo metricInfo