Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Json speed issue 505 #526

Merged
merged 4 commits into from Jun 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 15 additions & 0 deletions v3/internal/jsonx/encode.go
Expand Up @@ -118,6 +118,21 @@ func AppendFloat(buf *bytes.Buffer, x float64) error {
return nil
}

func AppendFloat32(buf *bytes.Buffer, x float32) error {
var scratch [64]byte
x64 := float64(x)

if math.IsInf(x64, 0) || math.IsNaN(x64) {
return &json.UnsupportedValueError{
Value: reflect.ValueOf(x64),
Str: strconv.FormatFloat(x64, 'g', -1, 32),
}
}

buf.Write(strconv.AppendFloat(scratch[:0], x64, 'g', -1, 32))
return nil
}

// AppendFloatArray appends an array of numeric literals to buf.
func AppendFloatArray(buf *bytes.Buffer, a ...float64) error {
buf.WriteByte('[')
Expand Down
96 changes: 74 additions & 22 deletions v3/newrelic/distributed_tracing.go
Expand Up @@ -4,6 +4,7 @@
package newrelic

import (
"bytes"
"encoding/base64"
"encoding/json"
"errors"
Expand All @@ -15,13 +16,21 @@ import (
"time"

"github.com/newrelic/go-agent/v3/internal"
"github.com/newrelic/go-agent/v3/internal/jsonx"
)

type distTraceVersion [2]int

func (v distTraceVersion) major() int { return v[0] }
func (v distTraceVersion) minor() int { return v[1] }

// WriteJSON implements the functionality to support writerField
// in our internal json builder. It appends the JSON representation
// of a distTraceVersion to the destination bytes.Buffer.
func (v distTraceVersion) WriteJSON(buf *bytes.Buffer) {
jsonx.AppendIntArray(buf, int64(v[0]), int64(v[1]))
}

const (
// callerTypeApp is the Type field's value for outbound payloads.
callerTypeApp = "App"
Expand Down Expand Up @@ -57,6 +66,13 @@ func (tm timestampMillis) MarshalJSON() ([]byte, error) {
return json.Marshal(timeToUnixMilliseconds(tm.Time()))
}

// WriteJSON implements the functionality to support writerField
// in our internal json builder. It appends the JSON representation
// of a timestampMillis value to the destination bytes.Buffer.
func (tm timestampMillis) WriteJSON(buf *bytes.Buffer) {
jsonx.AppendUint(buf, timeToUnixMilliseconds(tm.Time()))
}

func (tm timestampMillis) Time() time.Time { return time.Time(tm) }
func (tm *timestampMillis) Set(t time.Time) { *tm = timestampMillis(t) }

Expand Down Expand Up @@ -86,6 +102,37 @@ type payload struct {
OriginalTraceState string `json:"-"`
}

// WriteJSON implements the functionality to support writerField
// in our internal json builder. It appends the JSON representation
// of a payload struct to the destination bytes.Buffer.
func (p payload) WriteJSON(buf *bytes.Buffer) {
buf.WriteByte('{')
w := jsonFieldsWriter{buf: buf}
w.stringField("ty", p.Type)
w.stringField("ap", p.App)
w.stringField("ac", p.Account)
if p.TransactionID != "" {
w.stringField("tx", p.TransactionID)
}
if p.ID != "" {
w.stringField("id", p.ID)
}
w.stringField("tr", p.TracedID)
w.float32Field("pr", float32(p.Priority))

if p.Sampled == nil {
w.addKey("sa")
w.buf.WriteString("null")
} else {
w.boolField("sa", *p.Sampled)
}
w.writerField("ti", p.Timestamp)
if p.TrustedAccountKey != "" {
w.stringField("tk", p.TrustedAccountKey)
}
buf.WriteByte('}')
}

type payloadCaller struct {
TransportType string
Type string
Expand All @@ -109,47 +156,52 @@ func (p payload) validateNewRelicData() error {

// If a payload is missing both `guid` and `transactionId` is received,
// a ParseException supportability metric should be generated.
if "" == p.TransactionID && "" == p.ID {
if p.TransactionID == "" && p.ID == "" {
return errPayloadMissingGUIDTxnID
}

if "" == p.Type {
if p.Type == "" {
return errPayloadMissingType
}

if "" == p.Account {
if p.Account == "" {
return errPayloadMissingAccount
}

if "" == p.App {
if p.App == "" {
return errPayloadMissingApp
}

if "" == p.TracedID {
if p.TracedID == "" {
return errPayloadMissingTraceID
}

if p.Timestamp.Time().IsZero() || 0 == p.Timestamp.Time().Unix() {
if p.Timestamp.Time().IsZero() || p.Timestamp.Time().Unix() == 0 {
return errPayloadMissingTimestamp
}

return nil
}

const payloadJSONStartingSizeEstimate = 256

func (p payload) text(v distTraceVersion) []byte {
// TrustedAccountKey should only be attached to the outbound payload if its value differs
// from the Account field.
if p.TrustedAccountKey == p.Account {
p.TrustedAccountKey = ""
}
js, _ := json.Marshal(struct {
Version distTraceVersion `json:"v"`
Data payload `json:"d"`
}{
Version: v,
Data: p,
})
return js

js := bytes.NewBuffer(make([]byte, 0, payloadJSONStartingSizeEstimate))
w := jsonFieldsWriter{
buf: js,
}
js.WriteByte('{')
w.writerField("v", v)
w.writerField("d", p)
js.WriteByte('}')

return js.Bytes()
}

// NRText implements newrelic.DistributedTracePayload.
Expand Down Expand Up @@ -259,12 +311,12 @@ func processNRDTString(str string, support *distributedTracingSupport) (*payload
return nil, nil
}
var decoded []byte
if '{' == str[0] {
if str[0] == '{' {
decoded = []byte(str)
} else {
var err error
decoded, err = base64.StdEncoding.DecodeString(str)
if nil != err {
if err != nil {
support.AcceptPayloadParseException = true
return nil, fmt.Errorf("unable to decode payload: %v", err)
}
Expand All @@ -273,12 +325,12 @@ func processNRDTString(str string, support *distributedTracingSupport) (*payload
Version distTraceVersion `json:"v"`
Data json.RawMessage `json:"d"`
}{}
if err := json.Unmarshal(decoded, &envelope); nil != err {
if err := json.Unmarshal(decoded, &envelope); err != nil {
support.AcceptPayloadParseException = true
return nil, fmt.Errorf("unable to unmarshal payload: %v", err)
}

if 0 == envelope.Version.major() && 0 == envelope.Version.minor() {
if envelope.Version.major() == 0 && envelope.Version.minor() == 0 {
support.AcceptPayloadParseException = true
return nil, errPayloadMissingVersion
}
Expand All @@ -289,7 +341,7 @@ func processNRDTString(str string, support *distributedTracingSupport) (*payload
envelope.Version.major())
}
payload := new(payload)
if err := json.Unmarshal(envelope.Data, payload); nil != err {
if err := json.Unmarshal(envelope.Data, payload); err != nil {
support.AcceptPayloadParseException = true
return nil, fmt.Errorf("unable to unmarshal payload data: %v", err)
}
Expand All @@ -305,12 +357,12 @@ func processNRDTString(str string, support *distributedTracingSupport) (*payload

func processW3CHeaders(hdrs http.Header, trustedAccountKey string, support *distributedTracingSupport) (*payload, error) {
p, err := processTraceParent(hdrs)
if nil != err {
if err != nil {
support.TraceContextParentParseException = true
return nil, err
}
err = processTraceState(hdrs, trustedAccountKey, p)
if nil != err {
if err != nil {
if err == errInvalidNRTraceState {
support.TraceContextStateInvalidNrEntry = true
} else {
Expand Down Expand Up @@ -394,7 +446,7 @@ func processTraceState(hdrs http.Header, trustedAccountKey string, p *payload) e
app := matches[3]
timestamp, err := strconv.ParseUint(matches[8], 10, 64)

if nil != err || "" == version || "" == parentType || "" == account || "" == app {
if err != nil || version == "" || parentType == "" || account == "" || app == "" {
return errInvalidNRTraceState
}

Expand Down
5 changes: 5 additions & 0 deletions v3/newrelic/json_object_writer.go
Expand Up @@ -44,6 +44,11 @@ func (w *jsonFieldsWriter) floatField(key string, val float64) {
jsonx.AppendFloat(w.buf, val)
}

func (w *jsonFieldsWriter) float32Field(key string, val float32) {
w.addKey(key)
jsonx.AppendFloat32(w.buf, val)
}

func (w *jsonFieldsWriter) boolField(key string, val bool) {
w.addKey(key)
if val {
Expand Down