Skip to content

Commit a6990cf

Browse files
committed
Propagate process context to profiles
1 parent c1760f0 commit a6990cf

9 files changed

Lines changed: 392 additions & 12 deletions

File tree

libpf/trace.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ package libpf // import "go.opentelemetry.io/ebpf-profiler/libpf"
55

66
import (
77
"unique"
8+
9+
"go.opentelemetry.io/collector/pdata/pcommon"
810
)
911

1012
// FrameMappingFileData represents a backing file for a memory mapping.
@@ -120,6 +122,7 @@ type EbpfTrace struct {
120122
NumFrames int
121123
EnvVars map[String]String
122124
CustomLabels map[String]String
125+
Resource *pcommon.Resource
123126
KernelFrames Frames
124127
FrameData []uint64
125128
FrameDataBuf [3072]uint64

processcontext/processcontext.go

Lines changed: 158 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,17 @@ import (
77
"encoding/binary"
88
"errors"
99
"fmt"
10+
"net/url"
11+
"strings"
1012
"structs"
1113
"unsafe"
1214

15+
"go.opentelemetry.io/collector/pdata/pcommon"
16+
semconv "go.opentelemetry.io/otel/semconv/v1.34.0"
17+
commonpb "go.opentelemetry.io/proto/otlp/common/v1"
1318
"google.golang.org/protobuf/proto"
1419

20+
"go.opentelemetry.io/ebpf-profiler/internal/log"
1521
"go.opentelemetry.io/ebpf-profiler/libpf"
1622
"go.opentelemetry.io/ebpf-profiler/libpf/pfunsafe"
1723
processcontextpb "go.opentelemetry.io/ebpf-profiler/processcontext/v1development"
@@ -47,6 +53,12 @@ const (
4753

4854
// Offset of the MonotonicPublishedAtNs field in the header struct
4955
monotonicPublishedAtNsOffset = libpf.Address(unsafe.Offsetof(header{}.MonotonicPublishedAtNs))
56+
57+
// resourceAttrKey is the environment variable name OpenTelemetry Resource information will be read from.
58+
resourceAttrKey = "OTEL_RESOURCE_ATTRIBUTES"
59+
60+
// svcNameKey is the environment variable name that Service Name information will be read from.
61+
svcNameKey = "OTEL_SERVICE_NAME"
5062
)
5163

5264
var (
@@ -61,8 +73,9 @@ var (
6173
)
6274

6375
type Info struct {
64-
Context *processcontextpb.ProcessContext
65-
PublishedAtNs uint64
76+
Resource *pcommon.Resource
77+
ExtraAttributes *pcommon.Map
78+
PublishedAtNs uint64
6679
}
6780

6881
// header represents the 32-byte memory region header per OTEP #4719.
@@ -200,11 +213,151 @@ func readPayload(rm remotememory.RemoteMemory, hdr header) (Info, error) {
200213
return Info{}, fmt.Errorf("failed to unmarshal ProcessContext: %w", err)
201214
}
202215

203-
return Info{Context: ctx, PublishedAtNs: hdr.MonotonicPublishedAtNs}, nil
216+
var resource *pcommon.Resource
217+
if ctx.Resource != nil {
218+
r := pcommon.NewResource()
219+
for _, attr := range ctx.Resource.Attributes {
220+
convertAnyValue(attr.Value).MoveTo(r.Attributes().PutEmpty(attr.Key))
221+
}
222+
resource = &r
223+
}
224+
225+
var extraAttributes *pcommon.Map
226+
if ctx.ExtraAttributes != nil {
227+
m := pcommon.NewMap()
228+
for _, attr := range ctx.ExtraAttributes {
229+
convertAnyValue(attr.Value).MoveTo(m.PutEmpty(attr.Key))
230+
}
231+
extraAttributes = &m
232+
}
233+
return Info{Resource: resource, ExtraAttributes: extraAttributes, PublishedAtNs: hdr.MonotonicPublishedAtNs}, nil
204234
}
205235

206236
func (p *Info) ClearExtraAttributes() {
207-
if p.Context != nil {
208-
p.Context.ExtraAttributes = nil
237+
// if p.Context != nil {
238+
// p.Context.ExtraAttributes = nil
239+
// }
240+
}
241+
242+
// convertAnyValue converts a commonpb.AnyValue to a pcommon.Value,
243+
// handling all value types including nested maps and arrays.
244+
func convertAnyValue(src *commonpb.AnyValue) pcommon.Value {
245+
if src == nil {
246+
return pcommon.NewValueEmpty()
247+
}
248+
switch v := src.Value.(type) {
249+
case *commonpb.AnyValue_StringValue:
250+
return pcommon.NewValueStr(v.StringValue)
251+
case *commonpb.AnyValue_BoolValue:
252+
return pcommon.NewValueBool(v.BoolValue)
253+
case *commonpb.AnyValue_IntValue:
254+
return pcommon.NewValueInt(v.IntValue)
255+
case *commonpb.AnyValue_DoubleValue:
256+
return pcommon.NewValueDouble(v.DoubleValue)
257+
case *commonpb.AnyValue_BytesValue:
258+
val := pcommon.NewValueBytes()
259+
val.Bytes().FromRaw(v.BytesValue)
260+
return val
261+
case *commonpb.AnyValue_ArrayValue:
262+
val := pcommon.NewValueSlice()
263+
if v.ArrayValue != nil {
264+
sl := val.Slice()
265+
sl.EnsureCapacity(len(v.ArrayValue.Values))
266+
for _, item := range v.ArrayValue.Values {
267+
convertAnyValue(item).MoveTo(sl.AppendEmpty())
268+
}
269+
}
270+
return val
271+
case *commonpb.AnyValue_KvlistValue:
272+
val := pcommon.NewValueMap()
273+
if v.KvlistValue != nil {
274+
m := val.Map()
275+
m.EnsureCapacity(len(v.KvlistValue.Values))
276+
for _, kv := range v.KvlistValue.Values {
277+
convertAnyValue(kv.Value).MoveTo(m.PutEmpty(kv.Key))
278+
}
279+
}
280+
return val
281+
default:
282+
return pcommon.NewValueEmpty()
283+
}
284+
}
285+
286+
func (p *Info) addResourceStringAttribute(key string, value string) {
287+
if p.Resource == nil {
288+
r := pcommon.NewResource()
289+
p.Resource = &r
290+
}
291+
// Only add the attribute if it is not already present.
292+
if _, ok := p.Resource.Attributes().Get(key); ok {
293+
return
294+
}
295+
p.Resource.Attributes().PutStr(key, value)
296+
}
297+
298+
// AddEnvVars adds the given env vars to the ProcessContext as resource attributes.
299+
// OTEL_SERVICE_NAME is mapped to the service.name attribute.
300+
// OTEL_RESOURCE_ATTRIBUTES is parsed as comma-separated key=value pairs with
301+
// percent-encoded keys and values per the OTel resource SDK specification.
302+
// OTEL_SERVICE_NAME takes precedence over service.name in OTEL_RESOURCE_ATTRIBUTES.
303+
func (p *Info) AddEnvVars(envVars map[libpf.String]libpf.String) {
304+
// Process OTEL_SERVICE_NAME first so it takes precedence over any
305+
// service.name key inside OTEL_RESOURCE_ATTRIBUTES (addResourceAttribute
306+
// skips keys that are already present).
307+
if value, ok := envVars[libpf.Intern(svcNameKey)]; ok {
308+
p.addResourceStringAttribute(string(semconv.ServiceNameKey), value.String())
309+
}
310+
if value, ok := envVars[libpf.Intern(resourceAttrKey)]; ok {
311+
p.parseResourceAttributes(value.String())
312+
}
313+
}
314+
315+
// parseResourceAttributes parses the OTEL_RESOURCE_ATTRIBUTES env var value
316+
// as comma-separated key=value pairs where keys and values are percent-encoded.
317+
// On any decoding error the entire value is discarded.
318+
func (p *Info) parseResourceAttributes(raw string) {
319+
if raw == "" {
320+
return
321+
}
322+
// Parse into a temporary slice first so that on error we discard everything
323+
// per the OTel spec.
324+
type kv struct{ key, value string }
325+
var pairs []kv
326+
for pair := range strings.SplitSeq(raw, ",") {
327+
k, v, ok := strings.Cut(pair, "=")
328+
if !ok {
329+
log.Debugf("OTEL_RESOURCE_ATTRIBUTES: discarding invalid value: missing '=' in %q", pair)
330+
return
331+
}
332+
key, err := url.PathUnescape(k)
333+
if err != nil {
334+
log.Debugf("OTEL_RESOURCE_ATTRIBUTES: discarding invalid value: %v", err)
335+
return
336+
}
337+
value, err := url.PathUnescape(v)
338+
if err != nil {
339+
log.Debugf("OTEL_RESOURCE_ATTRIBUTES: discarding invalid value: %v", err)
340+
return
341+
}
342+
pairs = append(pairs, kv{key, value})
343+
}
344+
for _, pair := range pairs {
345+
p.addResourceStringAttribute(pair.key, pair.value)
346+
}
347+
}
348+
349+
func ResourceToContextKey(resource *pcommon.Resource) libpf.String {
350+
if resource == nil {
351+
return libpf.NullString
352+
}
353+
// Per semantic conventions, triplet of service.namespace, service.name, service.instance.id
354+
// must be globally unique.
355+
// https://github.com/open-telemetry/semantic-conventions/blob/main/docs/registry/attributes/service.md
356+
serviceNamespace, namespaceOk := resource.Attributes().Get(string(semconv.ServiceNamespaceKey))
357+
serviceName, nameOk := resource.Attributes().Get(string(semconv.ServiceNameKey))
358+
serviceInstanceID, instanceIdOk := resource.Attributes().Get(string(semconv.ServiceInstanceIDKey))
359+
if !namespaceOk || !nameOk || !instanceIdOk {
360+
return libpf.NullString
209361
}
362+
return libpf.Intern(fmt.Sprintf("%s:%s:%s", serviceNamespace.Str(), serviceName.Str(), serviceInstanceID.Str()))
210363
}

0 commit comments

Comments
 (0)