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

23
api.go
View File

@ -8,6 +8,7 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"io"
"log" "log"
"math" "math"
"net/http" "net/http"
@ -21,13 +22,13 @@ import (
) )
type ApiMetricData struct { type ApiMetricData struct {
Error *string `json:"error,omitempty"` Error *string `json:"error,omitempty"`
From int64 `json:"from"` From int64 `json:"from"`
To int64 `json:"to"` To int64 `json:"to"`
Data []Float `json:"data,omitempty"` Data FloatArray `json:"data,omitempty"`
Avg Float `json:"avg"` Avg Float `json:"avg"`
Min Float `json:"min"` Min Float `json:"min"`
Max Float `json:"max"` Max Float `json:"max"`
} }
// TODO: Optimize this, just like the stats endpoint! // TODO: Optimize this, just like the stats endpoint!
@ -109,7 +110,13 @@ func handleWrite(rw http.ResponseWriter, r *http.Request) {
return 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 { if err := decodeLine(dec); err != nil {
http.Error(rw, err.Error(), http.StatusBadRequest) http.Error(rw, err.Error(), http.StatusBadRequest)
return 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" "context"
"fmt" "fmt"
"log" "log"
"math"
"strconv"
"sync" "sync"
"time" "time"
@ -13,41 +11,6 @@ import (
"github.com/nats-io/nats.go" "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 { type Metric struct {
Name string Name string
minfo metricInfo minfo metricInfo