Skip to content

Commit 4e61673

Browse files
committed
feat(metering): add meter for traces
1 parent 9756822 commit 4e61673

File tree

7 files changed

+395
-3
lines changed

7 files changed

+395
-3
lines changed

exporter/clickhousetracesexporter/schema-signoz.go

+6-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"go.uber.org/zap/zapcore"
2121
)
2222

23+
// TODO: Read from github.com/SigNoz/signoz-otel-collector/pkg/schema/traces
2324
type Event struct {
2425
Name string `json:"name,omitempty"`
2526
TimeUnixNano uint64 `json:"timeUnixNano,omitempty"`
@@ -131,6 +132,7 @@ type Span struct {
131132
SpanAttributes []SpanAttribute `json:"spanAttributes,omitempty"`
132133
}
133134

135+
// TODO: Read from github.com/SigNoz/signoz-otel-collector/pkg/schema/traces
134136
type ErrorEvent struct {
135137
Event Event `json:"errorEvent,omitempty"`
136138
ErrorID string `json:"errorID,omitempty"`
@@ -167,7 +169,9 @@ type SpanV3 struct {
167169
ResourcesString map[string]string `json:"resources_string,omitempty"`
168170

169171
// for events
170-
Events []string `json:"event,omitempty"`
172+
// TODO: Read from github.com/SigNoz/signoz-otel-collector/pkg/schema/traces
173+
Events []string `json:"event,omitempty"`
174+
// TODO: Read from github.com/SigNoz/signoz-otel-collector/pkg/schema/traces
171175
ErrorEvents []ErrorEvent `json:"-"`
172176

173177
ServiceName string `json:"serviceName,omitempty"` // for error table
@@ -239,6 +243,7 @@ func (s *Span) MarshalLogObject(enc zapcore.ObjectEncoder) error {
239243
return nil
240244
}
241245

246+
// TODO: Read from github.com/SigNoz/signoz-otel-collector/pkg/schema/traces
242247
type OtelSpanRef struct {
243248
TraceId string `json:"traceId,omitempty"`
244249
SpanId string `json:"spanId,omitempty"`

pkg/metering/json.go

+138-2
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,21 @@ package metering
22

33
import (
44
"encoding/json"
5+
"fmt"
6+
"strconv"
57

8+
"github.com/SigNoz/signoz-otel-collector/pkg/schema/traces"
9+
"github.com/SigNoz/signoz-otel-collector/utils"
10+
"github.com/SigNoz/signoz-otel-collector/utils/flatten"
11+
"go.opentelemetry.io/collector/pdata/pcommon"
612
"go.uber.org/zap"
713
)
814

915
type jsonSizer struct {
1016
Logger *zap.Logger
1117
}
1218

13-
func NewJSONSizer(logger *zap.Logger) *jsonSizer {
19+
func NewJSONSizer(logger *zap.Logger) Sizer {
1420
return &jsonSizer{
1521
Logger: logger,
1622
}
@@ -19,7 +25,137 @@ func NewJSONSizer(logger *zap.Logger) *jsonSizer {
1925
func (sizer *jsonSizer) SizeOfMapStringAny(input map[string]any) int {
2026
bytes, err := json.Marshal(input)
2127
if err != nil {
22-
sizer.Logger.Error("cannot marshal object, setting size to 0", zap.Any("obj", input))
28+
sizer.Logger.Error("cannot marshal object, setting size to 0", zap.Error(err), zap.Any("obj", input))
29+
return 0
30+
}
31+
32+
return len(bytes)
33+
}
34+
35+
func (sizer *jsonSizer) SizeOfFlatPcommonMapInMapStringString(input pcommon.Map) int {
36+
output := map[string]string{}
37+
38+
input.Range(func(k string, v pcommon.Value) bool {
39+
switch v.Type() {
40+
case pcommon.ValueTypeMap:
41+
flattened := flatten.FlattenJSON(v.Map().AsRaw(), k)
42+
for kf, vf := range flattened {
43+
output[kf] = fmt.Sprintf("%v", vf)
44+
}
45+
default:
46+
output[k] = v.AsString()
47+
}
48+
return true
49+
})
50+
51+
bytes, err := json.Marshal(output)
52+
if err != nil {
53+
sizer.Logger.Error("cannot marshal object, setting size to 0", zap.Error(err), zap.Any("obj", input))
54+
return 0
55+
}
56+
57+
return len(bytes)
58+
}
59+
60+
func (sizer *jsonSizer) SizeOfFlatPcommonMapInNumberStringBool(input pcommon.Map) (int, int, int) {
61+
n := map[string]float64{}
62+
s := map[string]string{}
63+
b := map[string]bool{}
64+
65+
input.Range(func(k string, v pcommon.Value) bool {
66+
switch v.Type() {
67+
case pcommon.ValueTypeDouble:
68+
if utils.IsValidFloat(v.Double()) {
69+
n[k] = v.Double()
70+
}
71+
case pcommon.ValueTypeInt:
72+
n[k] = float64(v.Int())
73+
case pcommon.ValueTypeBool:
74+
b[k] = v.Bool()
75+
case pcommon.ValueTypeMap:
76+
flattened := flatten.FlattenJSON(v.Map().AsRaw(), k)
77+
for kf, vf := range flattened {
78+
switch tvf := vf.(type) {
79+
case string:
80+
s[kf] = tvf
81+
case float64:
82+
n[kf] = tvf
83+
case bool:
84+
b[kf] = tvf
85+
}
86+
}
87+
default:
88+
s[k] = v.AsString()
89+
}
90+
91+
return true
92+
})
93+
94+
nbytes, err := json.Marshal(n)
95+
if err != nil {
96+
sizer.Logger.Error("cannot marshal object, setting size to 0", zap.Error(err), zap.Any("obj", input))
97+
nbytes = []byte(nil)
98+
}
99+
100+
sbytes, err := json.Marshal(s)
101+
if err != nil {
102+
sizer.Logger.Error("cannot marshal object, setting size to 0", zap.Error(err), zap.Any("obj", input))
103+
sbytes = []byte(nil)
104+
}
105+
106+
bbytes, err := json.Marshal(b)
107+
if err != nil {
108+
sizer.Logger.Error("cannot marshal object, setting size to 0", zap.Error(err), zap.Any("obj", input))
109+
bbytes = []byte(nil)
110+
}
111+
112+
return len(nbytes), len(sbytes), len(bbytes)
113+
}
114+
115+
func (sizer *jsonSizer) SizeOfInt(input int) int {
116+
return len(strconv.Itoa(input))
117+
}
118+
119+
func (sizer *jsonSizer) SizeOfFloat64(input float64) int {
120+
return len(strconv.FormatFloat(input, 'f', -1, 64))
121+
}
122+
123+
func (sizer *jsonSizer) SizeOfTraceID(input pcommon.TraceID) int {
124+
if input.IsEmpty() {
125+
return 0
126+
}
127+
128+
// Since we encode to hex, the original 16 bytes are stored in 32 bytes
129+
return 32
130+
}
131+
132+
func (sizer *jsonSizer) SizeOfSpanID(input pcommon.SpanID) int {
133+
if input.IsEmpty() {
134+
return 0
135+
}
136+
137+
// Since we encode to hex, the original 8 bytes are stored in 16 bytes
138+
return 16
139+
}
140+
141+
func (sizer *jsonSizer) SizeOfStringSlice(input []string) int {
142+
bytes, err := json.Marshal(input)
143+
if err != nil {
144+
sizer.Logger.Error("cannot marshal object, setting size to 0", zap.Error(err), zap.Any("obj", input))
145+
return 0
146+
}
147+
148+
return len(bytes)
149+
}
150+
151+
func (sizer *jsonSizer) SizeOfOtelSpanRefs(input []traces.OtelSpanRef) int {
152+
if input == nil {
153+
return 0
154+
}
155+
156+
bytes, err := json.Marshal(input)
157+
if err != nil {
158+
sizer.Logger.Error("cannot marshal object, setting size to 0", zap.Error(err), zap.Any("obj", input))
23159
return 0
24160
}
25161

pkg/metering/meter.go

+10
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package metering
22

33
import (
4+
"github.com/SigNoz/signoz-otel-collector/pkg/schema/traces"
5+
"go.opentelemetry.io/collector/pdata/pcommon"
46
"go.opentelemetry.io/collector/pdata/plog"
57
"go.opentelemetry.io/collector/pdata/pmetric"
68
"go.opentelemetry.io/collector/pdata/ptrace"
@@ -19,6 +21,14 @@ type Meter[T ptrace.Traces | pmetric.Metrics | plog.Logs] interface {
1921
// data structures
2022
type Sizer interface {
2123
SizeOfMapStringAny(map[string]any) int
24+
SizeOfFlatPcommonMapInMapStringString(pcommon.Map) int
25+
SizeOfInt(int) int
26+
SizeOfFloat64(float64) int
27+
SizeOfTraceID(pcommon.TraceID) int
28+
SizeOfSpanID(pcommon.SpanID) int
29+
SizeOfFlatPcommonMapInNumberStringBool(pcommon.Map) (int, int, int)
30+
SizeOfStringSlice(input []string) int
31+
SizeOfOtelSpanRefs(input []traces.OtelSpanRef) int
2232
}
2333

2434
// Calculates billable metrics for logs.

pkg/metering/v1/traces.go

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package v1
2+
3+
import (
4+
"github.com/SigNoz/signoz-otel-collector/pkg/metering"
5+
"github.com/SigNoz/signoz-otel-collector/pkg/schema/common"
6+
schema "github.com/SigNoz/signoz-otel-collector/pkg/schema/traces"
7+
"go.opentelemetry.io/collector/pdata/ptrace"
8+
"go.uber.org/zap"
9+
)
10+
11+
type traces struct {
12+
Logger *zap.Logger
13+
Sizer metering.Sizer
14+
15+
KeySizes map[string]int
16+
}
17+
18+
func NewTraces(logger *zap.Logger) metering.Traces {
19+
return &traces{
20+
Logger: logger,
21+
Sizer: metering.NewJSONSizer(logger),
22+
KeySizes: map[string]int{
23+
"resources_string": len("resources_string"),
24+
"startTimeUnixNano": len("startTimeUnixNano"),
25+
"traceId": len("traceId"),
26+
"spanId": len("spanId"),
27+
"traceState": len("traceState"),
28+
"flags": len("flags"),
29+
"name": len("name"),
30+
"kind": len("kind"),
31+
"spanKind": len("spanKind"),
32+
},
33+
}
34+
}
35+
36+
func (meter *traces) Size(td ptrace.Traces) int {
37+
total := 0
38+
39+
for i := 0; i < td.ResourceSpans().Len(); i++ {
40+
resourceSpan := td.ResourceSpans().At(i)
41+
resourceAttributesSize := meter.Sizer.SizeOfFlatPcommonMapInMapStringString(resourceSpan.Resource().Attributes())
42+
43+
for j := 0; j < resourceSpan.ScopeSpans().Len(); j++ {
44+
scopeSpans := resourceSpan.ScopeSpans().At(j)
45+
46+
for k := 0; k < scopeSpans.Spans().Len(); k++ {
47+
span := scopeSpans.Spans().At(k)
48+
49+
sizeOfOtelSpanRefs := 0
50+
otelSpanRefs, err := schema.NewOtelSpanRefs(span.Links(), span.ParentSpanID(), span.TraceID())
51+
if err != nil {
52+
meter.Logger.Error("cannot create span refs", zap.Error(err))
53+
} else {
54+
sizeOfOtelSpanRefs = meter.Sizer.SizeOfOtelSpanRefs(otelSpanRefs)
55+
}
56+
57+
serviceName := common.ServiceName(resourceSpan.Resource())
58+
sizeOfServiceName := len(serviceName)
59+
60+
events, _ := schema.NewEventsAndErrorEvents(span.Events(), serviceName, false)
61+
sizeOfEvents := meter.Sizer.SizeOfStringSlice(events)
62+
63+
// Let's start making the json object
64+
// 2({}) + 5("":"")
65+
total += 2 +
66+
(meter.KeySizes["resources_string"] + resourceAttributesSize + 5) +
67+
(meter.KeySizes["startTimeUnixNano"] + meter.Sizer.SizeOfInt(int(span.StartTimestamp())) + 5) +
68+
(meter.KeySizes["spanId"] + meter.Sizer.SizeOfSpanID(span.SpanID()) + 5) +
69+
(meter.KeySizes["traceId"] + meter.Sizer.SizeOfTraceID(span.TraceID()) + 5) +
70+
(meter.KeySizes["traceState"] + len(span.TraceState().AsRaw()) + 5) +
71+
(meter.KeySizes["parentSpanId"] + meter.Sizer.SizeOfSpanID(span.ParentSpanID()) + 5) +
72+
(meter.KeySizes["flags"] + meter.Sizer.SizeOfInt(int(span.Flags())) + 5) +
73+
(meter.KeySizes["name"] + len(span.Name()) + 5) +
74+
(meter.KeySizes["kind"] + meter.Sizer.SizeOfInt(int(span.Kind())) + 5) +
75+
(meter.KeySizes["spanKind"] + len(span.Kind().String()) + 5) +
76+
(meter.KeySizes["attributes_string"] + meter.KeySizes["attributes_bool"] + meter.KeySizes["attributes_number"] + meter.Sizer.SizeOfFlatPcommonMapInMapStringString(span.Attributes()) + 15) +
77+
(meter.KeySizes["serviceName"] + sizeOfServiceName + 5) +
78+
(meter.KeySizes["events"] + sizeOfEvents + 5) +
79+
(meter.KeySizes["references"] + sizeOfOtelSpanRefs + 5)
80+
}
81+
82+
}
83+
}
84+
85+
return total
86+
}
87+
func (*traces) Count(td ptrace.Traces) int {
88+
return td.SpanCount()
89+
}

pkg/schema/common/resource.go

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package common
2+
3+
import (
4+
"go.opentelemetry.io/collector/pdata/pcommon"
5+
semconv "go.opentelemetry.io/collector/semconv/v1.5.0"
6+
)
7+
8+
func ServiceName(input pcommon.Resource) string {
9+
service, found := input.Attributes().Get(semconv.AttributeServiceName)
10+
if !found {
11+
return "<nil-service-name>"
12+
}
13+
14+
return service.Str()
15+
}

0 commit comments

Comments
 (0)