Skip to content

Commit 7cd1a7a

Browse files
authored
Resource attributes can be set in templated generator (#36)
* Add possiblity to define resource attributes * Resource attributes can be defined in defaults * New test to check the generation of resource attributes * Use more iterators in tests * Document resource attributes in README * gofmt
1 parent 42703da commit 7cd1a7a

File tree

4 files changed

+211
-46
lines changed

4 files changed

+211
-46
lines changed

README.md

+25
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,18 @@ The template has the following schema:
102102
// The number of distinct values to generate for each attribute (optional, default: 50)
103103
cardinality: int
104104
}
105+
// Default resource attributes for all resources in the trace (optional)
106+
resource: {
107+
// Fixed attributs that are added to each resource (optional)
108+
attributes: { string : any },
109+
// Parameters to configure the creation of random resource attributes (optional)
110+
randomAttributes: {
111+
// The number of random attributes to generate
112+
count: int,
113+
// The number of distinct values to generate for each attribute (optional, default: 50)
114+
cardinality: int
115+
}
116+
}
105117
},
106118
// Templates for the individual spans
107119
spans: [
@@ -129,6 +141,19 @@ The template has the following schema:
129141
count: int,
130142
// The number of distinct values to generate for each attribute (optional, default: 50)
131143
cardinality: int
144+
},
145+
// Additional attributes for the resource associated with this span. Resource attribute definitions
146+
// of different spans with the same service name will me merged into a singe resource (optional)
147+
resource: {
148+
// Fixed attributs that are added to the resource (optional)
149+
attributes: { string : any },
150+
// Parameters to configure the creation of random resource attributes (optional)
151+
randomAttributes: {
152+
// The number of random attributes to generate
153+
count: int,
154+
// The number of distinct values to generate for each attribute (optional, default: 50)
155+
cardinality: int
156+
}
132157
}
133158
},
134159
...

examples/template/template.js

+7-5
Original file line numberDiff line numberDiff line change
@@ -25,23 +25,25 @@ const traceDefaults = {
2525
attributes: {"one": "three"},
2626
randomAttributes: {count: 2, cardinality: 5},
2727
randomEvents: {count: 0.1, exceptionCount: 0.2, randomAttributes: {count: 6, cardinality: 20}},
28+
resource: { randomAttributes: {count: 3} },
2829
}
2930

3031
const traceTemplates = [
3132
{
3233
defaults: traceDefaults,
3334
spans: [
34-
{service: "shop-backend", name: "list-articles", duration: {min: 200, max: 900}},
35-
{service: "shop-backend", name: "authenticate", duration: {min: 50, max: 100}},
36-
{service: "auth-service", name: "authenticate"},
35+
{service: "shop-backend", name: "list-articles", duration: {min: 200, max: 900}, resource: { attributes: {"namespace": "shop"} }},
36+
{service: "shop-backend", name: "authenticate", duration: {min: 50, max: 100}, resource: { randomAttributes: {count: 4} }},
37+
{service: "auth-service", name: "authenticate", resource: { randomAttributes: {count: 2}, attributes: {"namespace": "auth"} }},
3738
{service: "shop-backend", name: "fetch-articles", parentIdx: 0},
3839
{
3940
service: "article-service",
4041
name: "list-articles",
41-
links: [{attributes: {"link-type": "parent-child"}, randomAttributes: {count: 2, cardinality: 5}}]
42+
links: [{attributes: {"link-type": "parent-child"}, randomAttributes: {count: 2, cardinality: 5}}],
43+
resource: { attributes: {"namespace": "shop" }}
4244
},
4345
{service: "article-service", name: "select-articles", attributeSemantics: tracing.SEMANTICS_DB},
44-
{service: "postgres", name: "query-articles", attributeSemantics: tracing.SEMANTICS_DB, randomAttributes: {count: 5}},
46+
{service: "postgres", name: "query-articles", attributeSemantics: tracing.SEMANTICS_DB, randomAttributes: {count: 5}, resource: { attributes: {"namespace": "db"} }},
4547
]
4648
},
4749
{

pkg/tracegen/templated.go

+59-9
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ type SpanDefaults struct {
5555
RandomEvents *EventParams `js:"randomEvents"`
5656
// Random links generated for each span
5757
RandomLinks *LinkParams `js:"randomLinks"`
58+
// Resource controls the default attributes for all resources.
59+
Resource *ResourceTemplate `js:"resource"`
5860
}
5961

6062
// SpanTemplate parameters that define how a span is created.
@@ -85,6 +87,17 @@ type SpanTemplate struct {
8587
RandomEvents *EventParams `js:"randomEvents"`
8688
// Generate random links for the span
8789
RandomLinks *LinkParams `js:"randomLinks"`
90+
// Resource controls the attributes generated for the resource. Spans with the same Service will have the same
91+
// resource. Multiple resource definitions will be merged.
92+
Resource *ResourceTemplate `js:"resource"`
93+
}
94+
95+
type ResourceTemplate struct {
96+
// Attributes that are added to this resource.
97+
Attributes map[string]interface{} `js:"attributes"`
98+
// RandomAttributes parameters to configure the creation of random attributes. If missing, no random attributes
99+
// are added to the resource.
100+
RandomAttributes *AttributeParams `js:"randomAttributes"`
88101
}
89102

90103
// TraceTemplate describes how all a trace and it's spans are generated.
@@ -163,11 +176,13 @@ type internalSpanTemplate struct {
163176
}
164177

165178
type internalResourceTemplate struct {
166-
service string
167-
hostName string
168-
hostIP string
169-
transport string
170-
hostPort int
179+
service string
180+
hostName string
181+
hostIP string
182+
transport string
183+
hostPort int
184+
attributes map[string]interface{}
185+
randomAttributes map[string][]interface{}
171186
}
172187

173188
type internalLinkTemplate struct {
@@ -233,6 +248,13 @@ func (g *TemplatedGenerator) generateResourceSpans(resSpanSlice ptrace.ResourceS
233248
resSpans.Resource().Attributes().PutStr("k6", "true")
234249
resSpans.Resource().Attributes().PutStr("service.name", tmpl.service)
235250

251+
for k, v := range tmpl.attributes {
252+
_ = resSpans.Resource().Attributes().PutEmpty(k).FromRaw(v)
253+
}
254+
for k, v := range tmpl.randomAttributes {
255+
_ = resSpans.Resource().Attributes().PutEmpty(k).FromRaw(random.SelectElement(v))
256+
}
257+
236258
scopeSpans := resSpans.ScopeSpans().AppendEmpty()
237259
scopeSpans.Scope().SetName("k6-scope-name/" + random.String(15))
238260
scopeSpans.Scope().SetVersion("k6-scope-version:v" + strconv.Itoa(random.IntBetween(0, 99)) + "." + strconv.Itoa(random.IntBetween(0, 99)))
@@ -465,10 +487,12 @@ func (g *TemplatedGenerator) initialize(template *TraceTemplate) error {
465487
}
466488

467489
// get or generate the corresponding ResourceSpans
468-
_, found := g.resources[tmpl.Service]
490+
res, found := g.resources[tmpl.Service]
469491
if !found {
470-
res := g.initializeResource(&tmpl)
492+
res = g.initializeResource(&tmpl, &template.Defaults)
471493
g.resources[tmpl.Service] = res
494+
} else {
495+
g.amendInitializedResource(res, &tmpl)
472496
}
473497

474498
// span template parent index must reference a previous span
@@ -505,14 +529,40 @@ func (g *TemplatedGenerator) initialize(template *TraceTemplate) error {
505529
return nil
506530
}
507531

508-
func (g *TemplatedGenerator) initializeResource(tmpl *SpanTemplate) *internalResourceTemplate {
509-
return &internalResourceTemplate{
532+
func (g *TemplatedGenerator) initializeResource(tmpl *SpanTemplate, defaults *SpanDefaults) *internalResourceTemplate {
533+
res := internalResourceTemplate{
510534
service: tmpl.Service,
511535
hostName: fmt.Sprintf("%s.local", tmpl.Service),
512536
hostIP: random.IPAddr(),
513537
hostPort: random.Port(),
514538
transport: "ip_tcp",
515539
}
540+
541+
// use defaults if no resource attributes are set
542+
if tmpl.Resource == nil {
543+
tmpl.Resource = defaults.Resource
544+
}
545+
546+
if tmpl.Resource != nil {
547+
res.randomAttributes = initializeRandomAttributes(tmpl.Resource.RandomAttributes)
548+
res.attributes = tmpl.Resource.Attributes
549+
}
550+
551+
return &res
552+
}
553+
554+
func (g *TemplatedGenerator) amendInitializedResource(res *internalResourceTemplate, tmpl *SpanTemplate) {
555+
if tmpl.Resource == nil {
556+
return
557+
}
558+
559+
if tmpl.Resource.RandomAttributes != nil {
560+
randAttr := initializeRandomAttributes(tmpl.Resource.RandomAttributes)
561+
res.randomAttributes = util.MergeMaps(res.randomAttributes, randAttr)
562+
}
563+
if tmpl.Resource.Attributes != nil {
564+
res.attributes = util.MergeMaps(res.attributes, tmpl.Resource.Attributes)
565+
}
516566
}
517567

518568
func (g *TemplatedGenerator) initializeSpan(idx int, parent *internalSpanTemplate, defaults *SpanDefaults, tmpl, child *SpanTemplate) (*internalSpanTemplate, error) {

pkg/tracegen/templated_test.go

+120-32
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"testing"
66

77
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
89
"go.opentelemetry.io/collector/pdata/pcommon"
910
"go.opentelemetry.io/collector/pdata/ptrace"
1011
)
@@ -31,23 +32,66 @@ func TestTemplatedGenerator_Traces(t *testing.T) {
3132
gen, err := NewTemplatedGenerator(&template)
3233
assert.NoError(t, err)
3334

34-
for i := 0; i < testRounds; i++ {
35-
traces := gen.Traces()
36-
spans := collectSpansFromTrace(traces)
37-
38-
assert.Len(t, spans, len(template.Spans))
39-
for i, span := range spans {
40-
assert.GreaterOrEqual(t, attributesWithPrefix(span, "k6."), template.Defaults.RandomAttributes.Count)
35+
for range testRounds {
36+
count := 0
37+
for i, span := range iterSpans(gen.Traces()) {
38+
count++
39+
requireAttributeCountGreaterOrEqual(t, span.Attributes(), 3, "k6.")
4140
if template.Spans[i].Name != nil {
4241
assert.Equal(t, *template.Spans[i].Name, span.Name())
4342
}
4443
if span.Kind() != ptrace.SpanKindInternal {
45-
assert.GreaterOrEqual(t, attributesWithPrefix(span, "net."), 3)
44+
requireAttributeCountGreaterOrEqual(t, span.Attributes(), 3, "net.")
4645
if *template.Defaults.AttributeSemantics == SemanticsHTTP {
47-
assert.GreaterOrEqual(t, attributesWithPrefix(span, "http."), 5)
46+
requireAttributeCountGreaterOrEqual(t, span.Attributes(), 5, "http.")
4847
}
4948
}
5049
}
50+
assert.Equal(t, len(template.Spans), count, "unexpected number of spans")
51+
}
52+
}
53+
}
54+
55+
func TestTemplatedGenerator_Resource(t *testing.T) {
56+
template := TraceTemplate{
57+
Defaults: SpanDefaults{
58+
Attributes: map[string]interface{}{"span-attr": "val-01"},
59+
Resource: &ResourceTemplate{RandomAttributes: &AttributeParams{Count: 2}},
60+
},
61+
Spans: []SpanTemplate{
62+
{Service: "test-service-a", Name: ptr("action-a-a"), Resource: &ResourceTemplate{
63+
Attributes: map[string]interface{}{"res-attr-01": "res-val-01"},
64+
RandomAttributes: &AttributeParams{Count: 5},
65+
}},
66+
{Service: "test-service-a", Name: ptr("action-a-b"), Resource: &ResourceTemplate{
67+
Attributes: map[string]interface{}{"res-attr-02": "res-val-02"},
68+
}},
69+
{Service: "test-service-b", Name: ptr("action-b-a"), Resource: &ResourceTemplate{
70+
Attributes: map[string]interface{}{"res-attr-03": "res-val-03"},
71+
}},
72+
{Service: "test-service-b", Name: ptr("action-b-b")},
73+
},
74+
}
75+
76+
gen, err := NewTemplatedGenerator(&template)
77+
require.NoError(t, err)
78+
79+
for range testRounds {
80+
for _, res := range iterResources(gen.Traces()) {
81+
srv, found := res.Attributes().Get("service.name")
82+
require.True(t, found, "service.name not found")
83+
84+
switch srv.Str() {
85+
case "test-service-a":
86+
requireAttributeCountEqual(t, res.Attributes(), 5, "k6.")
87+
requireAttributeEqual(t, res.Attributes(), "res-attr-01", "res-val-01")
88+
requireAttributeEqual(t, res.Attributes(), "res-attr-02", "res-val-02")
89+
case "test-service-b":
90+
requireAttributeCountEqual(t, res.Attributes(), 3, "k6.")
91+
requireAttributeEqual(t, res.Attributes(), "res-attr-03", "res-val-03")
92+
default:
93+
require.Fail(t, "unexpected service name %s", srv.Str())
94+
}
5195
}
5296
}
5397
}
@@ -76,12 +120,10 @@ func TestTemplatedGenerator_EventsLinks(t *testing.T) {
76120
gen, err := NewTemplatedGenerator(&template)
77121
assert.NoError(t, err)
78122

79-
for i := 0; i < testRounds; i++ {
80-
traces := gen.Traces()
81-
spans := collectSpansFromTrace(traces)
82-
83-
assert.Len(t, spans, len(template.Spans))
84-
for _, span := range spans {
123+
for range testRounds {
124+
count := 0
125+
for _, span := range iterSpans(gen.Traces()) {
126+
count++
85127
events := span.Events()
86128
links := span.Links()
87129
checkEventsLinksLength := func(expectedTemplate, expectedRandom int, spanName string) {
@@ -144,33 +186,79 @@ func TestTemplatedGenerator_EventsLinks(t *testing.T) {
144186
assert.True(t, found, "exception event not found")
145187
}
146188
}
189+
assert.Equal(t, len(template.Spans), count, "unexpected number of spans")
147190
}
148191
}
149192
}
150193

151-
func attributesWithPrefix(span ptrace.Span, prefix string) int {
152-
var count int
153-
span.Attributes().Range(func(k string, _ pcommon.Value) bool {
154-
if strings.HasPrefix(k, prefix) {
155-
count++
194+
func iterSpans(traces ptrace.Traces) func(func(i int, e ptrace.Span) bool) {
195+
count := 0
196+
return func(f func(i int, e ptrace.Span) bool) {
197+
var elem ptrace.Span
198+
for i := 0; i < traces.ResourceSpans().Len(); i++ {
199+
rs := traces.ResourceSpans().At(i)
200+
for j := 0; j < rs.ScopeSpans().Len(); j++ {
201+
ss := rs.ScopeSpans().At(j)
202+
for k := 0; k < ss.Spans().Len(); k++ {
203+
elem = ss.Spans().At(k)
204+
if !f(count, elem) {
205+
return
206+
}
207+
count++
208+
}
209+
}
156210
}
157-
return true
158-
})
159-
return count
211+
}
160212
}
161213

162-
func collectSpansFromTrace(traces ptrace.Traces) []ptrace.Span {
163-
var spans []ptrace.Span
164-
for i := 0; i < traces.ResourceSpans().Len(); i++ {
165-
rs := traces.ResourceSpans().At(i)
166-
for j := 0; j < rs.ScopeSpans().Len(); j++ {
167-
ss := rs.ScopeSpans().At(j)
168-
for k := 0; k < ss.Spans().Len(); k++ {
169-
spans = append(spans, ss.Spans().At(k))
214+
func iterResources(traces ptrace.Traces) func(func(i int, e pcommon.Resource) bool) {
215+
return func(f func(i int, e pcommon.Resource) bool) {
216+
var elem pcommon.Resource
217+
for i := 0; i < traces.ResourceSpans().Len(); i++ {
218+
rs := traces.ResourceSpans().At(i)
219+
elem = rs.Resource()
220+
if !f(i, elem) {
221+
return
170222
}
171223
}
172224
}
173-
return spans
225+
}
226+
227+
func requireAttributeCountGreaterOrEqual(t *testing.T, attributes pcommon.Map, compare int, prefixes ...string) {
228+
t.Helper()
229+
count := countAttributes(attributes, prefixes...)
230+
require.GreaterOrEqual(t, count, compare, "expected at least %d attributes, got %d", compare, count)
231+
}
232+
233+
func requireAttributeCountEqual(t *testing.T, attributes pcommon.Map, expected int, prefixes ...string) {
234+
t.Helper()
235+
count := countAttributes(attributes, prefixes...)
236+
require.GreaterOrEqual(t, expected, count, "expected at least %d attributes, got %d", expected, count)
237+
}
238+
239+
func requireAttributeEqual(t *testing.T, attributes pcommon.Map, key string, expected any) {
240+
t.Helper()
241+
val, found := attributes.Get(key)
242+
require.True(t, found, "attribute %s not found", key)
243+
require.Equal(t, expected, val.AsRaw(), "value %v expected for attribute %s but was %v", expected, key, val.AsRaw())
244+
}
245+
246+
func countAttributes(attributes pcommon.Map, prefixes ...string) int {
247+
var count int
248+
attributes.Range(func(k string, _ pcommon.Value) bool {
249+
if len(prefixes) == 0 {
250+
count++
251+
return true
252+
}
253+
254+
for _, prefix := range prefixes {
255+
if strings.HasPrefix(k, prefix) {
256+
count++
257+
}
258+
}
259+
return true
260+
})
261+
return count
174262
}
175263

176264
func ptr[T any](v T) *T {

0 commit comments

Comments
 (0)