Skip to content

Commit aeb3d34

Browse files
committed
feat: Propate the uprobe link into sample type
With #651 it is possible to attach uprobes, but they currently are merged together. This PR introduces the use of cookies (available from kernel 5.15+) to keep track of the links that lead to the events being generated. It now uses the symbol part of the uprobe link and propages it into the sample type. That is something that we currently don't do well enough. So let's look at this example: ``` --uprobe-link /nix/store/r7pnxs3cfl3qxwacj38iakpm5v1ch6lz-glibc-2.40-66/lib/libc.so.6:malloc --uprobe-link /nix/store/r7pnxs3cfl3qxwacj38iakpm5v1ch6lz-glibc-2.40-66/lib/libc.so.6:open --uprobe-link /proc/4501/root/usr/lib/aarch64-linux-gnu/libc.so.6:open --uprobe-link /proc/4501/root/usr/lib/aarch64-linux-gnu/libc.so.6:malloc ``` There would be resulting two sample types - `uprobe_malloc_events:count` - `uprobe_open_events:count`
1 parent 7cdeb21 commit aeb3d34

File tree

13 files changed

+121
-53
lines changed

13 files changed

+121
-53
lines changed

cli_flags.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313

1414
"go.opentelemetry.io/ebpf-profiler/internal/controller"
1515
"go.opentelemetry.io/ebpf-profiler/tracer"
16+
"go.opentelemetry.io/ebpf-profiler/tracer/types"
1617
)
1718

1819
const (
@@ -131,7 +132,11 @@ func parseArgs() (*controller.Config, error) {
131132

132133
fs.StringVar(&args.IncludeEnvVars, "env-vars", defaultEnvVarsValue, envVarsHelp)
133134

134-
fs.Func("uprobe-link", probeLinkHelper, func(link string) error {
135+
fs.Func("uprobe-link", probeLinkHelper, func(linkStr string) error {
136+
link, err := types.ParseUProbeLink(linkStr)
137+
if err != nil {
138+
return err
139+
}
135140
args.UProbeLinks = append(args.UProbeLinks, link)
136141
return nil
137142
})

internal/controller/config.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111

1212
"go.opentelemetry.io/ebpf-profiler/reporter"
1313
"go.opentelemetry.io/ebpf-profiler/tracer"
14+
"go.opentelemetry.io/ebpf-profiler/tracer/types"
1415
)
1516

1617
type Config struct {
@@ -32,7 +33,7 @@ type Config struct {
3233
VerboseMode bool
3334
Version bool
3435
OffCPUThreshold float64
35-
UProbeLinks []string
36+
UProbeLinks []types.UProbeLink
3637
LoadProbe bool
3738

3839
Reporter reporter.Reporter

main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ func mainWithExitCode() exitCode {
117117
GRPCConnectionTimeout: intervals.GRPCConnectionTimeout(),
118118
ReportInterval: intervals.ReportInterval(),
119119
SamplesPerSecond: cfg.SamplesPerSecond,
120+
UProbeLinks: cfg.UProbeLinks,
120121
})
121122
if err != nil {
122123
log.Error(err)

reporter/base_reporter.go

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,18 @@ func (b *baseReporter) Stop() {
4141
}
4242

4343
func (b *baseReporter) ReportTraceEvent(trace *libpf.Trace, meta *samples.TraceEventMeta) error {
44-
switch meta.Origin {
44+
origin := samples.Origin{
45+
Origin: meta.Origin,
46+
}
47+
switch origin.Origin {
4548
case support.TraceOriginSampling:
4649
case support.TraceOriginOffCPU:
4750
case support.TraceOriginUProbe:
51+
uprobeIdx := int(meta.OffTime - 1)
52+
meta.OffTime = 0
53+
if uprobeIdx >= 0 && uprobeIdx < len(b.cfg.UProbeLinks) {
54+
origin.ProbeLinkName = b.cfg.UProbeLinks[uprobeIdx].Symbol
55+
}
4856
default:
4957
return fmt.Errorf("skip reporting trace for %d origin: %w", meta.Origin,
5058
errUnknownOrigin)
@@ -72,21 +80,21 @@ func (b *baseReporter) ReportTraceEvent(trace *libpf.Trace, meta *samples.TraceE
7280

7381
if _, exists := (*eventsTree)[samples.ContainerID(containerID)]; !exists {
7482
(*eventsTree)[samples.ContainerID(containerID)] =
75-
make(map[libpf.Origin]samples.KeyToEventMapping)
83+
make(map[samples.Origin]samples.KeyToEventMapping)
7684
}
7785

78-
if _, exists := (*eventsTree)[samples.ContainerID(containerID)][meta.Origin]; !exists {
79-
(*eventsTree)[samples.ContainerID(containerID)][meta.Origin] =
86+
if _, exists := (*eventsTree)[samples.ContainerID(containerID)][origin]; !exists {
87+
(*eventsTree)[samples.ContainerID(containerID)][origin] =
8088
make(samples.KeyToEventMapping)
8189
}
8290

83-
if events, exists := (*eventsTree)[samples.ContainerID(containerID)][meta.Origin][key]; exists {
91+
if events, exists := (*eventsTree)[samples.ContainerID(containerID)][origin][key]; exists {
8492
events.Timestamps = append(events.Timestamps, uint64(meta.Timestamp))
8593
events.OffTimes = append(events.OffTimes, meta.OffTime)
86-
(*eventsTree)[samples.ContainerID(containerID)][meta.Origin][key] = events
94+
(*eventsTree)[samples.ContainerID(containerID)][origin][key] = events
8795
return nil
8896
}
89-
(*eventsTree)[samples.ContainerID(containerID)][meta.Origin][key] = &samples.TraceEvents{
97+
(*eventsTree)[samples.ContainerID(containerID)][origin][key] = &samples.TraceEvents{
9098
Frames: trace.Frames,
9199
Timestamps: []uint64{uint64(meta.Timestamp)},
92100
OffTimes: []int64{meta.OffTime},

reporter/config.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"google.golang.org/grpc"
1010

1111
"go.opentelemetry.io/ebpf-profiler/reporter/samples"
12+
"go.opentelemetry.io/ebpf-profiler/tracer/types"
1213
)
1314

1415
type Config struct {
@@ -47,4 +48,6 @@ type Config struct {
4748
// GRPCDialOptions allows passing additional gRPC dial options when establishing
4849
// the connection to the collector. These options are appended after the default options.
4950
GRPCDialOptions []grpc.DialOption
51+
52+
UProbeLinks []types.UProbeLink
5053
}

reporter/internal/pdata/generate.go

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import (
77
"fmt"
88
"math"
99
"path/filepath"
10+
"slices"
11+
"strings"
1012
"time"
1113

1214
log "github.com/sirupsen/logrus"
@@ -79,11 +81,19 @@ func (p *Pdata) Generate(tree samples.TraceEventsTree,
7981
sp.Scope().SetVersion(agentVersion)
8082
sp.SetSchemaUrl(semconv.SchemaURL)
8183

82-
for _, origin := range []libpf.Origin{
83-
support.TraceOriginSampling,
84-
support.TraceOriginOffCPU,
85-
support.TraceOriginUProbe,
86-
} {
84+
origins := make([]samples.Origin, len(originToEvents))
85+
for o := range originToEvents {
86+
origins = append(origins, o)
87+
}
88+
slices.SortFunc(origins, func(a, b samples.Origin) int {
89+
if v := a.Origin - b.Origin; v != 0 {
90+
return int(v)
91+
}
92+
93+
return strings.Compare(a.ProbeLinkName, b.ProbeLinkName)
94+
})
95+
96+
for _, origin := range origins {
8797
if len(originToEvents[origin]) == 0 {
8898
// Do not append empty profiles.
8999
continue
@@ -129,12 +139,12 @@ func (p *Pdata) setProfile(
129139
mappingSet orderedset.OrderedSet[uniqueMapping],
130140
stackSet orderedset.OrderedSet[stackInfo],
131141
locationSet orderedset.OrderedSet[locationInfo],
132-
origin libpf.Origin,
142+
origin samples.Origin,
133143
events map[samples.TraceAndMetaKey]*samples.TraceEvents,
134144
profile pprofile.Profile,
135145
) error {
136146
st := profile.SampleType()
137-
switch origin {
147+
switch origin.Origin {
138148
case support.TraceOriginSampling:
139149
profile.SetPeriod(1e9 / int64(p.samplesPerSecond))
140150
pt := profile.PeriodType()
@@ -147,11 +157,15 @@ func (p *Pdata) setProfile(
147157
st.SetTypeStrindex(stringSet.Add("events"))
148158
st.SetUnitStrindex(stringSet.Add("nanoseconds"))
149159
case support.TraceOriginUProbe:
150-
st.SetTypeStrindex(stringSet.Add("events"))
160+
if origin.ProbeLinkName != "" {
161+
st.SetTypeStrindex(stringSet.Add(fmt.Sprintf("uprobe_%s_events", origin.ProbeLinkName)))
162+
} else {
163+
st.SetTypeStrindex(stringSet.Add("uprobe_events"))
164+
}
151165
st.SetUnitStrindex(stringSet.Add("count"))
152166
default:
153167
// Should never happen
154-
return fmt.Errorf("generating profile for unsupported origin %d", origin)
168+
return fmt.Errorf("generating profile for unsupported origin %d", origin.Origin)
155169
}
156170

157171
startTS, endTS := uint64(math.MaxUint64), uint64(0)
@@ -164,7 +178,7 @@ func (p *Pdata) setProfile(
164178
}
165179

166180
sample.TimestampsUnixNano().FromRaw(traceInfo.Timestamps)
167-
if origin == support.TraceOriginOffCPU {
181+
if origin.Origin == support.TraceOriginOffCPU {
168182
sample.Values().Append(traceInfo.OffTimes...)
169183
}
170184

reporter/internal/pdata/generate_test.go

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -146,21 +146,21 @@ func newTestFrames(extraFrame bool) libpf.Frames {
146146
func TestFunctionTableOrder(t *testing.T) {
147147
for _, tt := range []struct {
148148
name string
149-
events map[libpf.Origin]samples.KeyToEventMapping
149+
events map[samples.Origin]samples.KeyToEventMapping
150150

151151
wantFunctionTable []string
152152
expectedResourceProfiles int
153153
}{
154154
{
155155
name: "no events",
156-
events: map[libpf.Origin]samples.KeyToEventMapping{},
156+
events: map[samples.Origin]samples.KeyToEventMapping{},
157157
wantFunctionTable: []string{""},
158158
expectedResourceProfiles: 0,
159159
}, {
160160
name: "single executable",
161161
expectedResourceProfiles: 1,
162-
events: map[libpf.Origin]samples.KeyToEventMapping{
163-
support.TraceOriginSampling: map[samples.TraceAndMetaKey]*samples.TraceEvents{
162+
events: map[samples.Origin]samples.KeyToEventMapping{
163+
samples.Origin{Origin: support.TraceOriginSampling}: map[samples.TraceAndMetaKey]*samples.TraceEvents{
164164
{Pid: 1}: {
165165
Frames: newTestFrames(false),
166166
Timestamps: []uint64{1, 2, 3, 4, 5},
@@ -210,12 +210,12 @@ func TestFunctionTableOrder(t *testing.T) {
210210
func TestProfileDuration(t *testing.T) {
211211
for _, tt := range []struct {
212212
name string
213-
events map[libpf.Origin]samples.KeyToEventMapping
213+
events map[samples.Origin]samples.KeyToEventMapping
214214
}{
215215
{
216216
name: "profile duration",
217-
events: map[libpf.Origin]samples.KeyToEventMapping{
218-
support.TraceOriginSampling: map[samples.TraceAndMetaKey]*samples.TraceEvents{
217+
events: map[samples.Origin]samples.KeyToEventMapping{
218+
samples.Origin{Origin: support.TraceOriginSampling}: map[samples.TraceAndMetaKey]*samples.TraceEvents{
219219
{Pid: 1}: {
220220
Timestamps: []uint64{2, 1, 3, 4, 7},
221221
},
@@ -286,8 +286,8 @@ func TestGenerate_SingleContainerSingleOrigin(t *testing.T) {
286286
Tid: 456,
287287
ApmServiceName: "svc",
288288
}
289-
events := map[libpf.Origin]samples.KeyToEventMapping{
290-
support.TraceOriginSampling: {
289+
events := map[samples.Origin]samples.KeyToEventMapping{
290+
samples.Origin{Origin: support.TraceOriginSampling}: {
291291
traceKey: &samples.TraceEvents{
292292
Frames: singleFrameTrace(libpf.GoFrame, mappingFile,
293293
0x10, funcName, filePath, 42),
@@ -351,23 +351,23 @@ func TestGenerate_MultipleOriginsAndContainers(t *testing.T) {
351351
traceKey := samples.TraceAndMetaKey{ExecutablePath: "/bin/foo"}
352352
frames := singleFrameTrace(libpf.PythonFrame, mappingFile, 0x20, "f", "/bin/foo", 1)
353353

354-
events1 := map[libpf.Origin]samples.KeyToEventMapping{
355-
support.TraceOriginSampling: {
354+
events1 := map[samples.Origin]samples.KeyToEventMapping{
355+
samples.Origin{Origin: support.TraceOriginSampling}: {
356356
traceKey: &samples.TraceEvents{
357357
Frames: frames,
358358
Timestamps: []uint64{1, 2},
359359
},
360360
},
361-
support.TraceOriginOffCPU: {
361+
samples.Origin{Origin: support.TraceOriginOffCPU}: {
362362
traceKey: &samples.TraceEvents{
363363
Frames: frames,
364364
Timestamps: []uint64{3, 4},
365365
OffTimes: []int64{10, 20},
366366
},
367367
},
368368
}
369-
events2 := map[libpf.Origin]samples.KeyToEventMapping{
370-
support.TraceOriginSampling: {
369+
events2 := map[samples.Origin]samples.KeyToEventMapping{
370+
samples.Origin{Origin: support.TraceOriginSampling}: {
371371
traceKey: &samples.TraceEvents{
372372
Frames: frames,
373373
Timestamps: []uint64{5},
@@ -412,8 +412,8 @@ func TestGenerate_StringAndFunctionTablePopulation(t *testing.T) {
412412
})
413413

414414
traceKey := samples.TraceAndMetaKey{ExecutablePath: filePath}
415-
events := map[libpf.Origin]samples.KeyToEventMapping{
416-
support.TraceOriginSampling: {
415+
events := map[samples.Origin]samples.KeyToEventMapping{
416+
samples.Origin{Origin: support.TraceOriginSampling}: {
417417
traceKey: &samples.TraceEvents{
418418
Frames: singleFrameTrace(libpf.PythonFrame, mappingFile, 0x30,
419419
funcName, filePath, 123),
@@ -476,8 +476,8 @@ func TestGenerate_NativeFrame(t *testing.T) {
476476
Pid: 789,
477477
Tid: 1011,
478478
}
479-
events := map[libpf.Origin]samples.KeyToEventMapping{
480-
support.TraceOriginSampling: {
479+
events := map[samples.Origin]samples.KeyToEventMapping{
480+
samples.Origin{Origin: support.TraceOriginSampling}: {
481481
traceKey: &samples.TraceEvents{
482482
Frames: singleFrameNative(mappingFile, 0x1000, 0x1000, 0x2000, 0x100),
483483
Timestamps: []uint64{123, 456, 789},
@@ -554,15 +554,15 @@ func TestGenerate_NativeFrame(t *testing.T) {
554554
func TestStackTableOrder(t *testing.T) {
555555
for _, tt := range []struct {
556556
name string
557-
events map[libpf.Origin]samples.KeyToEventMapping
557+
events map[samples.Origin]samples.KeyToEventMapping
558558

559559
wantStackTable [][]int32
560560
expectedLocationTableLen int
561561
}{
562562
{
563563
name: "single stack",
564-
events: map[libpf.Origin]samples.KeyToEventMapping{
565-
support.TraceOriginSampling: map[samples.TraceAndMetaKey]*samples.TraceEvents{
564+
events: map[samples.Origin]samples.KeyToEventMapping{
565+
samples.Origin{Origin: support.TraceOriginSampling}: map[samples.TraceAndMetaKey]*samples.TraceEvents{
566566
{}: {
567567
Frames: newTestFrames(false),
568568
Timestamps: []uint64{1, 2, 3, 4, 5},
@@ -576,16 +576,16 @@ func TestStackTableOrder(t *testing.T) {
576576
},
577577
{
578578
name: "multiple stacks",
579-
events: map[libpf.Origin]samples.KeyToEventMapping{
580-
support.TraceOriginSampling: map[samples.TraceAndMetaKey]*samples.TraceEvents{
579+
events: map[samples.Origin]samples.KeyToEventMapping{
580+
samples.Origin{Origin: support.TraceOriginSampling}: map[samples.TraceAndMetaKey]*samples.TraceEvents{
581581
{Pid: 1}: {
582582
Frames: newTestFrames(false),
583583
Timestamps: []uint64{1, 2, 3, 4, 5},
584584
},
585585
},
586586
// This test relies on an implementation detail for ordering of results:
587587
// it assumes that support.TraceOriginSampling events are processed first
588-
support.TraceOriginOffCPU: map[samples.TraceAndMetaKey]*samples.TraceEvents{
588+
samples.Origin{Origin: support.TraceOriginOffCPU}: map[samples.TraceAndMetaKey]*samples.TraceEvents{
589589
{Pid: 2}: {
590590
Frames: newTestFrames(true),
591591
Timestamps: []uint64{7, 8, 9, 10, 11, 12},

reporter/samples/samples.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,22 @@ type TraceAndMetaKey struct {
4747
// Executable path is retrieved from /proc/PID/exe
4848
ExecutablePath string
4949

50+
// Name of the hook that collected this probe, relevant for e.g. for UProbe links
51+
ProbeLinkName string
52+
5053
// ExtraMeta stores extra meta info that may have been produced by a
5154
// `SampleAttrProducer` instance. May be nil.
5255
ExtraMeta any
5356
}
5457

58+
type Origin struct {
59+
Origin libpf.Origin
60+
ProbeLinkName string
61+
}
62+
5563
// TraceEventsTree stores samples and their related metadata in a tree-like
5664
// structure optimized for the OTel Profiling protocol representation.
57-
type TraceEventsTree map[ContainerID]map[libpf.Origin]KeyToEventMapping
65+
type TraceEventsTree map[ContainerID]map[Origin]KeyToEventMapping
5866

5967
// ContainerID represents an extracted key from /proc/<PID>/cgroup.
6068
type ContainerID string

support/ebpf/bpfdefs.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,8 @@ static long (*bpf_probe_read_user)(void *dst, int size, const void *unsafe_ptr)
129129
static long (*bpf_probe_read_kernel)(void *dst, int size, const void *unsafe_ptr) = (void *)
130130
BPF_FUNC_probe_read_kernel;
131131

132+
static long (*const bpf_get_attach_cookie)(void *ctx) = (void *)BPF_FUNC_get_attach_cookie;
133+
132134
// The sizeof in bpf_trace_printk() must include \0, else no output
133135
// is generated. The \n is not needed on 5.8+ kernels, but definitely on
134136
// 5.4 kernels.

support/ebpf/tracer.ebpf.arm64

7.83 KB
Binary file not shown.

0 commit comments

Comments
 (0)