From 8ea42ea719dfc765b93f7aa38e79e2a8ef1a9870 Mon Sep 17 00:00:00 2001 From: Bhavna Jindal Date: Wed, 6 Nov 2024 06:03:03 -0500 Subject: [PATCH] Add PID as an attribute in each sample (#212) --- reporter/otlp_reporter.go | 39 ++++++++++++------ reporter/otlp_reporter_test.go | 73 +++++++++++++++++++++++++++++----- 2 files changed, 90 insertions(+), 22 deletions(-) diff --git a/reporter/otlp_reporter.go b/reporter/otlp_reporter.go index 35d6b056..1cf6201c 100644 --- a/reporter/otlp_reporter.go +++ b/reporter/otlp_reporter.go @@ -70,6 +70,7 @@ type traceAndMetaKey struct { apmServiceName string // containerID is annotated based on PID information containerID string + pid int64 } // traceEvents holds known information about a trace. @@ -84,9 +85,9 @@ type traceEvents struct { } // attrKeyValue is a helper to populate Profile.attribute_table. -type attrKeyValue struct { +type attrKeyValue[T string | int64] struct { key string - value string + value T } // OTLPReporter receives and transforms information to be OTLP/profiles compliant. @@ -224,6 +225,7 @@ func (r *OTLPReporter) ReportTraceEvent(trace *libpf.Trace, meta *TraceEventMeta comm: meta.Comm, apmServiceName: meta.APMServiceName, containerID: containerID, + pid: int64(meta.PID), } if events, exists := (*traceEventsMap)[key]; exists { @@ -566,7 +568,7 @@ func (r *OTLPReporter) getProfile() (profile *profiles.Profile, startTS, endTS u // Walk every frame of the trace. for i := range traceInfo.frameTypes { - frameAttributes := addProfileAttributes(profile, []attrKeyValue{ + frameAttributes := addProfileAttributes(profile, []attrKeyValue[string]{ {key: "profile.frame.type", value: traceInfo.frameTypes[i].String()}, }, attributeMap) @@ -599,7 +601,7 @@ func (r *OTLPReporter) getProfile() (profile *profiles.Profile, startTS, endTS u fileName = execInfo.fileName } - mappingAttributes := addProfileAttributes(profile, []attrKeyValue{ + mappingAttributes := addProfileAttributes(profile, []attrKeyValue[string]{ // Once SemConv and its Go package is released with the new // semantic convention for build_id, replace these hard coded // strings. @@ -664,11 +666,13 @@ func (r *OTLPReporter) getProfile() (profile *profiles.Profile, startTS, endTS u profile.Location = append(profile.Location, loc) } - sample.Attributes = addProfileAttributes(profile, []attrKeyValue{ + sample.Attributes = append(addProfileAttributes(profile, []attrKeyValue[string]{ {key: string(semconv.ContainerIDKey), value: traceKey.containerID}, {key: string(semconv.ThreadNameKey), value: traceKey.comm}, {key: string(semconv.ServiceNameKey), value: traceKey.apmServiceName}, - }, attributeMap) + }, attributeMap), addProfileAttributes(profile, []attrKeyValue[int64]{ + {key: string(semconv.ProcessPIDKey), value: traceKey.pid}, + }, attributeMap)...) sample.LocationsLength = uint64(len(traceInfo.frameTypes)) locationIndex += sample.LocationsLength @@ -739,15 +743,26 @@ func createFunctionEntry(funcMap map[funcInfo]uint64, // addProfileAttributes adds attributes to Profile.attribute_table and returns // the indices to these attributes. -func addProfileAttributes(profile *profiles.Profile, - attributes []attrKeyValue, attributeMap map[string]uint64) []uint64 { +func addProfileAttributes[T string | int64](profile *profiles.Profile, + attributes []attrKeyValue[T], attributeMap map[string]uint64) []uint64 { indices := make([]uint64, 0, len(attributes)) - addAttr := func(attr attrKeyValue) { - if attr.value == "" { + addAttr := func(attr attrKeyValue[T]) { + var attributeCompositeKey string + var attributeValue common.AnyValue + + switch val := any(attr.value).(type) { + case string: + attributeCompositeKey = attr.key + "_" + val + attributeValue = common.AnyValue{Value: &common.AnyValue_StringValue{StringValue: val}} + case int64: + attributeCompositeKey = attr.key + "_" + strconv.Itoa(int(val)) + attributeValue = common.AnyValue{Value: &common.AnyValue_IntValue{IntValue: val}} + default: + log.Error("Unsupported attribute value type. Only string and int64 are supported.") return } - attributeCompositeKey := attr.key + "_" + attr.value + if attributeIndex, exists := attributeMap[attributeCompositeKey]; exists { indices = append(indices, attributeIndex) return @@ -756,7 +771,7 @@ func addProfileAttributes(profile *profiles.Profile, indices = append(indices, newIndex) profile.AttributeTable = append(profile.AttributeTable, &common.KeyValue{ Key: attr.key, - Value: &common.AnyValue{Value: &common.AnyValue_StringValue{StringValue: attr.value}}, + Value: &attributeValue, }) attributeMap[attributeCompositeKey] = newIndex } diff --git a/reporter/otlp_reporter_test.go b/reporter/otlp_reporter_test.go index d9c2844f..bdfc22ed 100644 --- a/reporter/otlp_reporter_test.go +++ b/reporter/otlp_reporter_test.go @@ -26,11 +26,37 @@ func TestGetSampleAttributes(t *testing.T) { comm: "", apmServiceName: "", containerID: "", + pid: 0, + }, + }, + attributeMap: make(map[string]uint64), + expectedIndices: [][]uint64{{0, 1, 2, 3}}, + expectedAttributeTable: []*common.KeyValue{ + { + Key: "container.id", + Value: &common.AnyValue{ + Value: &common.AnyValue_StringValue{StringValue: ""}, + }, + }, + { + Key: "thread.name", + Value: &common.AnyValue{ + Value: &common.AnyValue_StringValue{StringValue: ""}, + }, + }, + { + Key: "service.name", + Value: &common.AnyValue{ + Value: &common.AnyValue_StringValue{StringValue: ""}, + }, + }, + { + Key: "process.pid", + Value: &common.AnyValue{ + Value: &common.AnyValue_IntValue{IntValue: 0}, + }, }, }, - attributeMap: make(map[string]uint64), - expectedIndices: [][]uint64{make([]uint64, 0, 4)}, - expectedAttributeTable: nil, }, "duplicate": { profile: &profiles.Profile{}, @@ -40,16 +66,18 @@ func TestGetSampleAttributes(t *testing.T) { comm: "comm1", apmServiceName: "apmServiceName1", containerID: "containerID1", + pid: 1234, }, { hash: libpf.TraceHash{}, comm: "comm1", apmServiceName: "apmServiceName1", containerID: "containerID1", + pid: 1234, }, }, attributeMap: make(map[string]uint64), - expectedIndices: [][]uint64{{0, 1, 2}, {0, 1, 2}}, + expectedIndices: [][]uint64{{0, 1, 2, 3}, {0, 1, 2, 3}}, expectedAttributeTable: []*common.KeyValue{ { Key: "container.id", @@ -69,6 +97,12 @@ func TestGetSampleAttributes(t *testing.T) { Value: &common.AnyValue_StringValue{StringValue: "apmServiceName1"}, }, }, + { + Key: "process.pid", + Value: &common.AnyValue{ + Value: &common.AnyValue_IntValue{IntValue: 1234}, + }, + }, }, }, "different": { @@ -79,16 +113,18 @@ func TestGetSampleAttributes(t *testing.T) { comm: "comm1", apmServiceName: "apmServiceName1", containerID: "containerID1", + pid: 1234, }, { hash: libpf.TraceHash{}, comm: "comm2", apmServiceName: "apmServiceName2", containerID: "containerID2", + pid: 6789, }, }, attributeMap: make(map[string]uint64), - expectedIndices: [][]uint64{{0, 1, 2}, {3, 4, 5}}, + expectedIndices: [][]uint64{{0, 1, 2, 3}, {4, 5, 6, 7}}, expectedAttributeTable: []*common.KeyValue{ { Key: "container.id", @@ -108,6 +144,12 @@ func TestGetSampleAttributes(t *testing.T) { Value: &common.AnyValue_StringValue{StringValue: "apmServiceName1"}, }, }, + { + Key: "process.pid", + Value: &common.AnyValue{ + Value: &common.AnyValue_IntValue{IntValue: 1234}, + }, + }, { Key: "container.id", Value: &common.AnyValue{ @@ -126,6 +168,12 @@ func TestGetSampleAttributes(t *testing.T) { Value: &common.AnyValue_StringValue{StringValue: "apmServiceName2"}, }, }, + { + Key: "process.pid", + Value: &common.AnyValue{ + Value: &common.AnyValue_IntValue{IntValue: 6789}, + }, + }, }, }, } @@ -136,11 +184,16 @@ func TestGetSampleAttributes(t *testing.T) { t.Run(name, func(t *testing.T) { indices := make([][]uint64, 0) for _, k := range tc.k { - indices = append(indices, addProfileAttributes(tc.profile, []attrKeyValue{ - {key: string(semconv.ContainerIDKey), value: k.containerID}, - {key: string(semconv.ThreadNameKey), value: k.comm}, - {key: string(semconv.ServiceNameKey), value: k.apmServiceName}, - }, tc.attributeMap)) + indices = append(indices, append(addProfileAttributes(tc.profile, + []attrKeyValue[string]{ + {key: string(semconv.ContainerIDKey), value: k.containerID}, + {key: string(semconv.ThreadNameKey), value: k.comm}, + {key: string(semconv.ServiceNameKey), value: k.apmServiceName}, + }, tc.attributeMap), + addProfileAttributes(tc.profile, + []attrKeyValue[int64]{ + {key: string(semconv.ProcessPIDKey), value: k.pid}, + }, tc.attributeMap)...)) } require.Equal(t, tc.expectedIndices, indices) require.Equal(t, tc.expectedAttributeTable, tc.profile.AttributeTable)