Skip to content

Commit d5869d5

Browse files
authored
Enable auto/sdk in otel global (#1405)
* Enable auto/sdk for the OTel global probe Set the auto/sdk flag in the global API to true when newSpan is called. * Update the otelglobal e2e test Verify the auto/sdk probe is being used. * Add a changelog entry * Unload the newSpan probe after first call * Update internal/pkg/instrumentation/bpf/go.opentelemetry.io/otel/traceglobal/probe.go * Add package constraints to otelglobal uprobes * Remove converter.uprobeNewStartMu * Test SpanFromContext is also supported The span embedded within a context will be from `auto/sdk`. This means that #974 is resolved by this change.
1 parent 50f8105 commit d5869d5

File tree

17 files changed

+272
-45
lines changed

17 files changed

+272
-45
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ OpenTelemetry Go Automatic Instrumentation adheres to [Semantic Versioning](http
1111
### Added
1212
- Update `httpPlusdb` demo with adding `OTEL_GO_AUTO_PARSE_DB_STATEMENT` env variable ([#1523](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/1523))
1313
- Support `SELECT`, `INSERT`, `UPDATE`, and `DELETE` for database span names and `db.operation.name` attribute. ([#1253](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/1253))
14+
- Support the full tracing API with the `otelglobal` probe. ([#1405](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/1405))
1415
- Support `go.opentelemetry.io/[email protected]`. ([#1417](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/1417))
1516
- Support `google.golang.org/grpc` `1.69.0`. ([#1417](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/1417))
1617
- Update `google.golang.org/grpc` probe to work with versions `>= 1.69.0`. ([#1431](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/1431))

internal/pkg/instrumentation/bpf/database/sql/probe.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ func New(logger *slog.Logger, version string) probe.Probe {
5353
Val: shouldIncludeDBStatement(),
5454
},
5555
},
56-
Uprobes: []probe.Uprobe{
56+
Uprobes: []*probe.Uprobe{
5757
{
5858
Sym: "database/sql.(*DB).queryDC",
5959
EntryProbe: "uprobe_queryDC",

internal/pkg/instrumentation/bpf/github.com/segmentio/kafka-go/consumer/probe.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ func New(logger *slog.Logger, version string) probe.Probe {
6969
Val: structfield.NewID("github.com/segmentio/kafka-go", "github.com/segmentio/kafka-go", "ReaderConfig", "GroupID"),
7070
},
7171
},
72-
Uprobes: []probe.Uprobe{
72+
Uprobes: []*probe.Uprobe{
7373
{
7474
Sym: "github.com/segmentio/kafka-go.(*Reader).FetchMessage",
7575
EntryProbe: "uprobe_FetchMessage",

internal/pkg/instrumentation/bpf/github.com/segmentio/kafka-go/producer/probe.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ func New(logger *slog.Logger, version string) probe.Probe {
5757
Val: structfield.NewID("github.com/segmentio/kafka-go", "github.com/segmentio/kafka-go", "Message", "Time"),
5858
},
5959
},
60-
Uprobes: []probe.Uprobe{
60+
Uprobes: []*probe.Uprobe{
6161
{
6262
Sym: "github.com/segmentio/kafka-go.(*Writer).WriteMessages",
6363
EntryProbe: "uprobe_WriteMessages",

internal/pkg/instrumentation/bpf/go.opentelemetry.io/auto/sdk/probe.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ func New(logger *slog.Logger) probe.Probe {
6060
),
6161
},
6262
},
63-
Uprobes: []probe.Uprobe{
63+
Uprobes: []*probe.Uprobe{
6464
{
6565
Sym: "go.opentelemetry.io/auto/sdk.(*tracer).start",
6666
EntryProbe: "uprobe_Tracer_start",
@@ -86,25 +86,25 @@ type converter struct {
8686
logger *slog.Logger
8787
}
8888

89-
func (c *converter) decodeEvent(record perf.Record) (event, error) {
89+
func (c *converter) decodeEvent(record perf.Record) (*event, error) {
9090
reader := bytes.NewReader(record.RawSample)
9191

9292
var e event
9393
err := binary.Read(reader, binary.LittleEndian, &e.Size)
9494
if err != nil {
9595
c.logger.Error("failed to decode size", "error", err)
96-
return event{}, err
96+
return nil, err
9797
}
9898
c.logger.Debug("decoded size", "size", e.Size)
9999

100100
e.SpanData = make([]byte, e.Size)
101101
_, err = reader.Read(e.SpanData)
102102
if err != nil {
103103
c.logger.Error("failed to read span data", "error", err)
104-
return event{}, err
104+
return nil, err
105105
}
106106
c.logger.Debug("decoded span data", "size", e.Size)
107-
return e, nil
107+
return &e, nil
108108
}
109109

110110
func (c *converter) processFn(e *event) ptrace.ScopeSpans {

internal/pkg/instrumentation/bpf/go.opentelemetry.io/otel/traceglobal/bpf/probe.bpf.c

+38
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ char __license[] SEC("license") = "Dual MIT/GPL";
2323
#define MAX_BUCKETS 8
2424
#define MAX_TRACERS 64
2525

26+
// Records state of our write to auto-instrumentation flag.
27+
bool wrote_flag = false;
28+
2629
struct span_description_t {
2730
char buf[MAX_STATUS_DESCRIPTION_LEN];
2831
};
@@ -42,7 +45,12 @@ typedef struct tracer_id {
4245
char schema_url[MAX_TRACER_SCHEMA_URL_LEN];
4346
} tracer_id_t;
4447

48+
struct control_t {
49+
u32 kind; // Required to be 1.
50+
};
51+
4552
struct otel_span_t {
53+
u32 kind; // Required to be 0.
4654
BASE_SPAN_PROPERTIES
4755
struct span_name_t span_name;
4856
otel_status_t status;
@@ -388,6 +396,36 @@ static __always_inline long fill_tracer_id(tracer_id_t *tracer_id, go_tracer_ptr
388396
return 0;
389397
}
390398

399+
// This instrumentation attaches uprobe to the following function:
400+
// func (t *tracer) newSpan(ctx context.Context, autoSpan *bool, name string, opts []trace.SpanStartOption) (context.Context, trace.Span) {
401+
// https://github.com/open-telemetry/opentelemetry-go/blob/ac386f383cdfc14f546b4e55e8726a0a45e8a409/internal/global/trace.go#L161
402+
SEC("uprobe/newSpan")
403+
int uprobe_newStart(struct pt_regs *ctx) {
404+
if (wrote_flag) {
405+
// Already wrote flag value.
406+
return 0;
407+
}
408+
409+
void *flag_ptr = get_argument(ctx, 4);
410+
if (flag_ptr == NULL) {
411+
bpf_printk("invalid flag_ptr: NULL");
412+
return -1;
413+
}
414+
415+
bool true_value = true;
416+
long res = bpf_probe_write_user(flag_ptr, &true_value, sizeof(bool));
417+
if (res != 0) {
418+
bpf_printk("failed to write bool flag value: %ld", res);
419+
return -2;
420+
}
421+
422+
wrote_flag = true;
423+
424+
// Signal this uprobe should be unloaded.
425+
struct control_t ctrl = {1};
426+
return bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, (void *)(&ctrl), sizeof(struct control_t));
427+
}
428+
391429
// This instrumentation attaches uprobe to the following function:
392430
// func (t *tracer) Start(ctx context.Context, name string, opts ...trace.SpanStartOption) (context.Context, trace.Span)
393431
// https://github.com/open-telemetry/opentelemetry-go/blob/98b32a6c3a87fbee5d34c063b9096f416b250897/internal/global/trace.go#L149

internal/pkg/instrumentation/bpf/go.opentelemetry.io/otel/traceglobal/bpf_arm64_bpfel.go

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/pkg/instrumentation/bpf/go.opentelemetry.io/otel/traceglobal/bpf_x86_bpfel.go

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/pkg/instrumentation/bpf/go.opentelemetry.io/otel/traceglobal/probe.go

+94-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package global
55

66
import (
7+
"bytes"
78
"encoding/binary"
89
"fmt"
910
"log/slog"
@@ -18,6 +19,7 @@ import (
1819
"go.opentelemetry.io/auto/internal/pkg/process"
1920
"go.opentelemetry.io/auto/internal/pkg/structfield"
2021

22+
"github.com/cilium/ebpf/perf"
2123
"github.com/hashicorp/go-version"
2224
"golang.org/x/sys/unix"
2325

@@ -33,6 +35,27 @@ import (
3335
const (
3436
// pkg is the package being instrumented.
3537
pkg = "go.opentelemetry.io/otel/internal/global"
38+
39+
// Minimum version of go.opentelemetry.io/otel that supports using the
40+
// go.opentelemetry.io/auto/sdk in the global API.
41+
minAutoSDK = "1.33.0"
42+
)
43+
44+
var (
45+
otelWithAutoSDK = probe.PackageConstrainst{
46+
Package: "go.opentelemetry.io/otel",
47+
Constraints: version.MustConstraints(
48+
version.NewConstraint(fmt.Sprintf(">= %s", minAutoSDK)),
49+
),
50+
FailureMode: probe.FailureModeIgnore,
51+
}
52+
otelWithoutAutoSDK = probe.PackageConstrainst{
53+
Package: "go.opentelemetry.io/otel",
54+
Constraints: version.MustConstraints(
55+
version.NewConstraint(fmt.Sprintf("< %s", minAutoSDK)),
56+
),
57+
FailureMode: probe.FailureModeIgnore,
58+
}
3659
)
3760

3861
// New returns a new [probe.Probe].
@@ -41,6 +64,19 @@ func New(logger *slog.Logger) probe.Probe {
4164
SpanKind: trace.SpanKindClient,
4265
InstrumentedPkg: pkg,
4366
}
67+
68+
uprobeNewStart := &probe.Uprobe{
69+
Sym: "go.opentelemetry.io/otel/internal/global.(*tracer).newSpan",
70+
EntryProbe: "uprobe_newStart",
71+
PackageConstrainsts: []probe.PackageConstrainst{
72+
otelWithAutoSDK,
73+
},
74+
}
75+
76+
c := &converter{
77+
logger: logger,
78+
uprobeNewStart: uprobeNewStart,
79+
}
4480
return &probe.TraceProducer[bpfObjects, event]{
4581
Base: probe.Base[bpfObjects, event]{
4682
ID: id,
@@ -107,38 +143,94 @@ func New(logger *slog.Logger) probe.Probe {
107143
tracerIDContainsSchemaURL{},
108144
tracerIDContainsScopeAttributes{},
109145
},
110-
Uprobes: []probe.Uprobe{
146+
Uprobes: []*probe.Uprobe{
147+
uprobeNewStart,
111148
{
112149
Sym: "go.opentelemetry.io/otel/internal/global.(*tracer).Start",
113150
EntryProbe: "uprobe_Start",
114151
ReturnProbe: "uprobe_Start_Returns",
152+
PackageConstrainsts: []probe.PackageConstrainst{
153+
otelWithoutAutoSDK,
154+
},
115155
},
116156
{
117157
Sym: "go.opentelemetry.io/otel/internal/global.(*nonRecordingSpan).End",
118158
EntryProbe: "uprobe_End",
159+
PackageConstrainsts: []probe.PackageConstrainst{
160+
otelWithoutAutoSDK,
161+
},
119162
},
120163
{
121164
Sym: "go.opentelemetry.io/otel/internal/global.(*nonRecordingSpan).SetAttributes",
122165
EntryProbe: "uprobe_SetAttributes",
123166
FailureMode: probe.FailureModeIgnore,
167+
PackageConstrainsts: []probe.PackageConstrainst{
168+
otelWithoutAutoSDK,
169+
},
124170
},
125171
{
126172
Sym: "go.opentelemetry.io/otel/internal/global.(*nonRecordingSpan).SetStatus",
127173
EntryProbe: "uprobe_SetStatus",
128174
FailureMode: probe.FailureModeIgnore,
175+
PackageConstrainsts: []probe.PackageConstrainst{
176+
otelWithoutAutoSDK,
177+
},
129178
},
130179
{
131180
Sym: "go.opentelemetry.io/otel/internal/global.(*nonRecordingSpan).SetName",
132181
EntryProbe: "uprobe_SetName",
133182
FailureMode: probe.FailureModeIgnore,
183+
PackageConstrainsts: []probe.PackageConstrainst{
184+
otelWithoutAutoSDK,
185+
},
134186
},
135187
},
136-
SpecFn: loadBpf,
188+
SpecFn: loadBpf,
189+
ProcessRecord: c.decodeEvent,
137190
},
138191
ProcessFn: processFn,
139192
}
140193
}
141194

195+
type recordKind uint32
196+
197+
const (
198+
recordKindTelemetry recordKind = iota
199+
recordKindConrol
200+
)
201+
202+
type converter struct {
203+
logger *slog.Logger
204+
205+
uprobeNewStart *probe.Uprobe
206+
}
207+
208+
func (c *converter) decodeEvent(record perf.Record) (*event, error) {
209+
reader := bytes.NewReader(record.RawSample)
210+
211+
var kind recordKind
212+
err := binary.Read(reader, binary.LittleEndian, &kind)
213+
if err != nil {
214+
return nil, err
215+
}
216+
217+
var e *event
218+
switch kind {
219+
case recordKindTelemetry:
220+
e = new(event)
221+
reader.Reset(record.RawSample)
222+
err = binary.Read(reader, binary.LittleEndian, e)
223+
case recordKindConrol:
224+
if c.uprobeNewStart != nil {
225+
err = c.uprobeNewStart.Close()
226+
c.uprobeNewStart = nil
227+
}
228+
default:
229+
err = fmt.Errorf("unknown record kind: %d", kind)
230+
}
231+
return e, err
232+
}
233+
142234
// tracerIDContainsSchemaURL is a Probe Const defining whether the tracer key contains schemaURL.
143235
type tracerIDContainsSchemaURL struct{}
144236

0 commit comments

Comments
 (0)