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

feat: introduce code for JSON encoding protobuf types #42

Merged
merged 30 commits into from
Jun 8, 2023
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
c9c46fd
feat: introduce code for JSON encoding protobuf types
kruskall May 26, 2023
6dd06c6
fix: add missing apmevent JSON encoding method
kruskall May 26, 2023
8765d93
fix: protect again nil pointer dereference
kruskall May 26, 2023
678e61f
build: fix missing license
kruskall May 29, 2023
bcefefd
fix: make ppid not optional in protobuf models
kruskall May 29, 2023
c8ca993
fix: avoid mapping empty or missing fields
kruskall May 31, 2023
0828469
refactor: fix remaining issues and move json methods to separate files
kruskall Jun 1, 2023
27a81fd
test: add modelpb json test to validate mapping to modeljson
kruskall Jun 1, 2023
9cd0607
lint: run lint tasks
kruskall Jun 1, 2023
9957d6a
test: add more test util methods and support testing.TB
kruskall Jun 1, 2023
06c74f4
test: add missing url model tests
kruskall Jun 1, 2023
34a02e0
test: add basic benchmarks for modeljson mapping
kruskall Jun 1, 2023
151cdea
refactor: pre populate pointers to avoid allocating them on the heap
kruskall Jun 2, 2023
474a182
Merge branch 'main' into feat/protobuf-json
kruskall Jun 4, 2023
cdbbb64
refactor: remove unused noinline
kruskall Jun 4, 2023
61f0c10
test: add temporary benchmark to model apmevent for comparison
kruskall Jun 4, 2023
953cfd5
test: copy util_test over to model folder
kruskall Jun 4, 2023
637f7dd
test: fix failing tests
kruskall Jun 4, 2023
303b15e
refactor: do not preassign pointers and reuse the approach of the sta…
kruskall Jun 5, 2023
e54f2ed
refactor: sanitize maps in place in protobuf models
kruskall Jun 6, 2023
e6d2710
refactor: do not call AsMap twice
kruskall Jun 6, 2023
7ca96a7
fix: update compression strategy output
kruskall Jun 6, 2023
10bed20
fix: update compression strategy metric type output
kruskall Jun 6, 2023
17e2794
refactor: remove unused methods
kruskall Jun 6, 2023
ea6299c
lint: fix linter issues
kruskall Jun 6, 2023
7528a9a
refactor: run the marshaling logic and remove tmp error
kruskall Jun 6, 2023
6618662
Merge branch 'main' into feat/protobuf-json
kruskall Jun 6, 2023
f4abe77
fix: nil pointer dereference when accessing the event processor in ap…
kruskall Jun 6, 2023
7182d38
fix: event labels variable shadowing preventing proper assignment
kruskall Jun 6, 2023
17e6405
fix: update http request body protobuf type
kruskall Jun 6, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions model/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,20 @@ func (e *Error) toModelJSON(out *modeljson.Error) {

func (e *Exception) toModelJSON(out *modeljson.Exception) {
*out = modeljson.Exception{
Message: e.Message,
Module: e.Module,
Code: e.Code,
Attributes: e.Attributes,
Type: e.Type,
Handled: e.Handled,
Message: e.Message,
Module: e.Module,
Code: e.Code,
Type: e.Type,
Handled: e.Handled,
}
if e.Attributes != nil {
if attr, ok := e.Attributes.(map[string]any); ok {
if n := len(attr); n > 0 {
out.Attributes = e.Attributes
}
} else {
out.Attributes = e.Attributes
}
}
if n := len(e.Cause); n > 0 {
out.Cause = make([]modeljson.Exception, n)
Expand Down
27 changes: 27 additions & 0 deletions model/modelpb/agent.pb.json.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you 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 modelpb

import "github.com/elastic/apm-data/model/internal/modeljson"

func (a *Agent) toModelJSON(out *modeljson.Agent) {
out.Name = a.Name
out.Version = a.Version
out.EphemeralID = a.EphemeralId
out.ActivationMethod = a.ActivationMethod
}
60 changes: 60 additions & 0 deletions model/modelpb/agent.pb.json_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you 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 modelpb

import (
"testing"

"github.com/elastic/apm-data/model/internal/modeljson"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
)

func TestAgentToModelJSON(t *testing.T) {
testCases := map[string]struct {
proto *Agent
expected *modeljson.Agent
}{
"empty": {
proto: &Agent{},
expected: &modeljson.Agent{},
},
"full": {
proto: &Agent{
Name: "name",
Version: "version",
EphemeralId: "ephemeralid",
ActivationMethod: "activationmethod",
},
expected: &modeljson.Agent{
Name: "name",
Version: "version",
EphemeralID: "ephemeralid",
ActivationMethod: "activationmethod",
},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
var out modeljson.Agent
tc.proto.toModelJSON(&out)
diff := cmp.Diff(*tc.expected, out)
require.Empty(t, diff)
})
}
}
279 changes: 279 additions & 0 deletions model/modelpb/apmevent.pb.json.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,279 @@
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you 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 modelpb

import (
"github.com/elastic/apm-data/model/internal/modeljson"
"go.elastic.co/fastjson"
)

func (e *APMEvent) MarshalJSON() ([]byte, error) {
var w fastjson.Writer
if err := e.MarshalFastJSON(&w); err != nil {
return nil, err
}
return w.Bytes(), nil
}

func (e *APMEvent) MarshalFastJSON(w *fastjson.Writer) error {
out := modeljson.Document{
Span: &modeljson.Span{
Message: &modeljson.Message{},
Composite: &modeljson.SpanComposite{},
Destination: &modeljson.SpanDestination{},
DB: &modeljson.DB{},
},
Transaction: &modeljson.Transaction{
UserExperience: &modeljson.UserExperience{},
Message: &modeljson.Message{},
},
Metricset: &modeljson.Metricset{},
Error: &modeljson.Error{
Exception: &modeljson.Exception{},
Log: &modeljson.ErrorLog{},
},
TimestampStruct: &modeljson.Timestamp{},
Cloud: &modeljson.Cloud{},
Service: &modeljson.Service{
Node: &modeljson.ServiceNode{},
Language: &modeljson.Language{},
Runtime: &modeljson.Runtime{},
Framework: &modeljson.Framework{},
Origin: &modeljson.ServiceOrigin{},
Target: &modeljson.ServiceTarget{},
},
FAAS: &modeljson.FAAS{},
Network: &modeljson.Network{},
Container: &modeljson.Container{},
User: &modeljson.User{},
Device: &modeljson.Device{},
Kubernetes: &modeljson.Kubernetes{},
Observer: &modeljson.Observer{},
Agent: &modeljson.Agent{},
HTTP: &modeljson.HTTP{
Request: &modeljson.HTTPRequest{
Body: &modeljson.HTTPRequestBody{},
},
Response: &modeljson.HTTPResponse{},
},
UserAgent: &modeljson.UserAgent{},
Parent: &modeljson.Parent{},
Trace: &modeljson.Trace{},
Host: &modeljson.Host{
OS: &modeljson.OS{},
},
URL: &modeljson.URL{},
Log: &modeljson.Log{},
Source: &modeljson.Source{},
Client: &modeljson.Client{},
Child: &modeljson.Child{},
Destination: &modeljson.Destination{},
Session: &modeljson.Session{},
Process: &modeljson.Process{},
Event: &modeljson.Event{},
}
e.updateModelJSON(&out)
return ErrInvalidLength
//return out.MarshalFastJSON(w)
}

//go:noinline
func (e *APMEvent) updateModelJSON(doc *modeljson.Document) {
doc.Timestamp = modeljson.Time(e.Timestamp.AsTime())
doc.Message = e.Message

if n := len(e.Labels); n > 0 {
labels := make(map[string]modeljson.Label)
for k, label := range e.Labels {
if label != nil {
labels[sanitizeLabelKey(k)] = modeljson.Label{
Value: label.Value,
Values: label.Values,
}
}
}
doc.Labels = labels
}

if n := len(e.NumericLabels); n > 0 {
numericLabels := make(map[string]modeljson.NumericLabel)
for k, label := range e.NumericLabels {
if label != nil {
numericLabels[sanitizeLabelKey(k)] = modeljson.NumericLabel{
Value: label.Value,
Values: label.Values,
}
}
}
doc.NumericLabels = numericLabels
}

if e.DataStream != nil {
doc.DataStreamType = e.DataStream.Type
doc.DataStreamDataset = e.DataStream.Dataset
doc.DataStreamNamespace = e.DataStream.Namespace
}

if e.Processor != nil {
doc.Processor = modeljson.Processor{
Name: e.Processor.Name,
Event: e.Processor.Event,
}
}

if e.Transaction != nil {
e.Transaction.toModelJSON(doc.Transaction, e.Processor.Name == "metric" && e.Processor.Event == "metric")
}

if e.Span != nil {
e.Span.toModelJSON(doc.Span)
}

if e.Metricset != nil {
e.Metricset.toModelJSON(doc.Metricset)
doc.DocCount = e.Metricset.DocCount
}

if e.Error != nil {
e.Error.toModelJSON(doc.Error)
}

if e.Event != nil {
e.Event.toModelJSON(doc.Event)
}

// Set high resolution timestamp.
//
// TODO(axw) change @timestamp to use date_nanos, and remove this field.
var timestampStruct modeljson.Timestamp
if !e.Timestamp.AsTime().IsZero() {
if e.Processor != nil {
processorName := e.Processor.Name
processorEvent := e.Processor.Event
if (processorName == "error" && processorEvent == "error") || (processorName == "transaction" && (processorEvent == "transaction" || processorEvent == "span")) {
timestampStruct.US = int(e.Timestamp.AsTime().UnixNano() / 1000)
doc.TimestampStruct = &timestampStruct
}
}
}

if e.Cloud != nil {
e.Cloud.toModelJSON(doc.Cloud)
}

if e.Faas != nil {
e.Faas.toModelJSON(doc.FAAS)
}

if e.Device != nil {
e.Device.toModelJSON(doc.Device)
}

if e.Network != nil {
e.Network.toModelJSON(doc.Network)
}

if e.Observer != nil {
e.Observer.toModelJSON(doc.Observer)
}

if e.Container != nil {
e.Container.toModelJSON(doc.Container)
}

if e.Kubernetes != nil {
e.Kubernetes.toModelJSON(doc.Kubernetes)
}

if e.Agent != nil {
e.Agent.toModelJSON(doc.Agent)
}

if e.Trace != nil {
doc.Trace.ID = e.Trace.Id
}

if e.User != nil {
e.User.toModelJSON(doc.User)
}

if e.Source != nil {
e.Source.toModelJSON(doc.Source)
}

if e.Parent != nil {
doc.Parent.ID = e.Parent.Id
}

if e.Child != nil {
doc.Child.ID = e.Child.Id
}

if e.Client != nil {
e.Client.toModelJSON(doc.Client)
}

if e.UserAgent != nil {
doc.UserAgent.Original = e.UserAgent.Original
doc.UserAgent.Name = e.UserAgent.Name
}

if e.Service != nil {
e.Service.toModelJSON(doc.Service)
}

if e.Http != nil {
e.Http.toModelJSON(doc.HTTP)
}

if e.Host != nil {
e.Host.toModelJSON(doc.Host)
}

if e.Url != nil {
e.Url.toModelJSON(doc.URL)
}

if e.Log != nil {
e.Log.toModelJSON(doc.Log)
}

if e.Process != nil {
e.Process.toModelJSON(doc.Process)
}

if e.Destination != nil {
e.Destination.toModelJSON(doc.Destination)
}

if e.Session != nil {
doc.Session.ID = e.Session.Id
doc.Session.Sequence = int(e.Session.Sequence)
}
}

func setNonZero[T comparable](to **T, from *T) {
if !isZero(*from) {
*to = from
}
}

func isZero[T comparable](t T) bool {
var zero T
return t == zero
}
Loading