Skip to content

Commit c0a283b

Browse files
committed
[WP] Implementation of Otel 4947 thread context: dd-trace-go test
1 parent 82e7be2 commit c0a283b

2 files changed

Lines changed: 175 additions & 0 deletions

File tree

pkg/security/tests/span_test.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"os/exec"
1515
"runtime"
1616
"strconv"
17+
"strings"
1718
"testing"
1819

1920
"github.com/stretchr/testify/assert"
@@ -307,3 +308,82 @@ func TestGoSpan(t *testing.T) {
307308
})
308309
})
309310
}
311+
312+
// TestDDTraceGoSpan tests the full dd-trace-go integration: dd-trace-go creates
313+
// a real span which internally sets pprof labels ("span id", "local root span id"),
314+
// and the eBPF Go labels reader extracts them from the goroutine's label storage.
315+
func TestDDTraceGoSpan(t *testing.T) {
316+
SkipIfNotAvailable(t)
317+
318+
ruleDefs := []*rules.RuleDefinition{
319+
{
320+
ID: "test_ddtrace_span_rule_open",
321+
Expression: `open.file.path == "{{.Root}}/test-ddtrace-span"`,
322+
},
323+
}
324+
325+
test, err := newTestModule(t, nil, ruleDefs)
326+
if err != nil {
327+
t.Fatal(err)
328+
}
329+
defer test.Close()
330+
331+
goSyscallTester, err := loadSyscallTester(t, test, "syscall_go_tester")
332+
if err != nil {
333+
t.Fatal(err)
334+
}
335+
336+
t.Run("ddtrace_span", func(t *testing.T) {
337+
test.RunMultiMode(t, "open", func(t *testing.T, _ wrapperType, cmdFunc func(cmd string, args []string, envs []string) *exec.Cmd) {
338+
testFile, _, err := test.Path("test-ddtrace-span")
339+
if err != nil {
340+
t.Fatal(err)
341+
}
342+
defer os.Remove(testFile)
343+
344+
args := []string{
345+
"-ddtrace-span-test",
346+
"-ddtrace-span-file-path", testFile,
347+
}
348+
envs := []string{}
349+
350+
// Capture the tester's stdout to extract the span IDs
351+
// that dd-trace-go generated at runtime.
352+
var expectedSpanID, expectedLocalRootSpanID uint64
353+
354+
test.WaitSignalFromRule(t, func() error {
355+
cmd := cmdFunc(goSyscallTester, args, envs)
356+
out, err := cmd.CombinedOutput()
357+
358+
if err != nil {
359+
return fmt.Errorf("%s: %w", out, err)
360+
}
361+
362+
// Parse the span IDs from the tester's output.
363+
for _, line := range strings.Split(string(out), "\n") {
364+
if strings.HasPrefix(line, "ddtrace_span_id=") {
365+
val := strings.TrimPrefix(line, "ddtrace_span_id=")
366+
expectedSpanID, _ = strconv.ParseUint(strings.TrimSpace(val), 10, 64)
367+
}
368+
if strings.HasPrefix(line, "ddtrace_local_root_span_id=") {
369+
val := strings.TrimPrefix(line, "ddtrace_local_root_span_id=")
370+
expectedLocalRootSpanID, _ = strconv.ParseUint(strings.TrimSpace(val), 10, 64)
371+
}
372+
}
373+
374+
if expectedSpanID == 0 {
375+
return fmt.Errorf("failed to parse ddtrace_span_id from output: %s", out)
376+
}
377+
378+
return nil
379+
}, func(event *model.Event, rule *rules.Rule) {
380+
assertTriggeredRule(t, rule, "test_ddtrace_span_rule_open")
381+
382+
assert.Equal(t, expectedSpanID, event.SpanContext.SpanID,
383+
"span ID should match the dd-trace-go generated value")
384+
assert.Equal(t, expectedLocalRootSpanID, event.SpanContext.TraceID.Lo,
385+
"trace ID lo should match the dd-trace-go local root span ID")
386+
}, "test_ddtrace_span_rule_open")
387+
})
388+
})
389+
}

pkg/security/tests/syscall_tester/go/syscall_go_tester.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@ import (
1717
"net/http"
1818
"os"
1919
"os/exec"
20+
"runtime"
2021
"runtime/pprof"
2122
"strconv"
2223
"syscall"
2324
"time"
2425
"unsafe"
2526

27+
"github.com/DataDog/dd-trace-go/v2/ddtrace/tracer"
2628
manager "github.com/DataDog/ebpf-manager"
2729
"github.com/syndtr/gocapability/capability"
2830
"github.com/vishvananda/netlink"
@@ -55,6 +57,8 @@ var (
5557
goSpanSpanID string
5658
goSpanLocalRootSpanID string
5759
goSpanFilePath string
60+
ddtraceSpanTest bool
61+
ddtraceSpanFilePath string
5862
)
5963

6064
//go:embed ebpf_probe.o
@@ -340,6 +344,89 @@ func RunGoSpanTest(spanID, localRootSpanID, filePath string) error {
340344
return nil
341345
}
342346

347+
// RunDDTraceSpanTest uses dd-trace-go to create a real span, which sets pprof
348+
// labels automatically via the profiler code hotspots integration. This tests
349+
// the full dd-trace-go → pprof labels → eBPF Go labels reader pipeline.
350+
func RunDDTraceSpanTest(filePath string) error {
351+
// Create and seal a tracer-info memfd with tracer_language="go".
352+
type TracerMeta struct {
353+
SchemaVersion uint8 `msgpack:"schema_version"`
354+
TracerLanguage string `msgpack:"tracer_language"`
355+
TracerVersion string `msgpack:"tracer_version"`
356+
Hostname string `msgpack:"hostname"`
357+
ServiceName string `msgpack:"service_name"`
358+
}
359+
meta := TracerMeta{
360+
SchemaVersion: 2,
361+
TracerLanguage: "go",
362+
TracerVersion: "0.0.1-test",
363+
Hostname: "test",
364+
ServiceName: "ddtrace-test",
365+
}
366+
data, err := msgpack.Marshal(&meta)
367+
if err != nil {
368+
return fmt.Errorf("msgpack marshal: %w", err)
369+
}
370+
371+
fd, err := unix.MemfdCreate("datadog-tracer-info-ddtrace0", unix.MFD_ALLOW_SEALING)
372+
if err != nil {
373+
return fmt.Errorf("memfd_create: %w", err)
374+
}
375+
defer unix.Close(fd)
376+
377+
if _, err := unix.Write(fd, data); err != nil {
378+
return fmt.Errorf("memfd write: %w", err)
379+
}
380+
const fAddSeals = 1033
381+
const fSealWrite = 0x0008
382+
const fSealShrink = 0x0002
383+
const fSealGrow = 0x0004
384+
if _, _, errno := syscall.Syscall(syscall.SYS_FCNTL, uintptr(fd), fAddSeals, fSealWrite|fSealShrink|fSealGrow); errno != 0 {
385+
return fmt.Errorf("memfd seal: %w", errno)
386+
}
387+
388+
// Wait for the agent to process the memfd seal event and populate the BPF map.
389+
time.Sleep(500 * time.Millisecond)
390+
391+
// Start dd-trace-go with:
392+
// - WithTestDefaults: uses a dummy transport (no real agent needed)
393+
// - WithProfilerCodeHotspots: enables "span id" and "local root span id" pprof labels
394+
// - WithService: set a service name
395+
tracer.Start(
396+
tracer.WithTestDefaults(nil),
397+
tracer.WithProfilerCodeHotspots(true),
398+
tracer.WithService("ddtrace-test"),
399+
tracer.WithLogStartup(false),
400+
)
401+
defer tracer.Stop()
402+
403+
// Create a span. dd-trace-go will automatically set pprof labels
404+
// "span id" and "local root span id" on the current goroutine.
405+
span, ctx := tracer.StartSpanFromContext(context.Background(), "test.operation")
406+
407+
// Print the span ID and local root span ID so the test can parse and verify them.
408+
spanID := span.Context().SpanID()
409+
localRootSpanID := span.Root().Context().SpanID()
410+
fmt.Printf("ddtrace_span_id=%d\n", spanID)
411+
fmt.Printf("ddtrace_local_root_span_id=%d\n", localRootSpanID)
412+
413+
_ = ctx
414+
runtime.LockOSThread()
415+
defer runtime.UnlockOSThread()
416+
417+
// Trigger the file open that the CWS rule is watching.
418+
f, err := os.Create(filePath)
419+
if err != nil {
420+
span.Finish()
421+
return fmt.Errorf("create file: %w", err)
422+
}
423+
f.Close()
424+
os.Remove(filePath)
425+
426+
span.Finish()
427+
return nil
428+
}
429+
343430
func main() {
344431
flag.BoolVar(&bpfLoad, "load-bpf", false, "load the eBPF programs")
345432
flag.BoolVar(&bpfClone, "clone-bpf", false, "clone maps")
@@ -360,6 +447,8 @@ func main() {
360447
flag.StringVar(&goSpanSpanID, "go-span-span-id", "", "span ID for the Go span test (decimal string)")
361448
flag.StringVar(&goSpanLocalRootSpanID, "go-span-local-root-span-id", "", "local root span ID for the Go span test (decimal string)")
362449
flag.StringVar(&goSpanFilePath, "go-span-file-path", "", "file path to open for the Go span test")
450+
flag.BoolVar(&ddtraceSpanTest, "ddtrace-span-test", false, "when set, runs the dd-trace-go span test")
451+
flag.StringVar(&ddtraceSpanFilePath, "ddtrace-span-file-path", "", "file path to open for the dd-trace-go span test")
363452

364453
flag.Parse()
365454

@@ -426,4 +515,10 @@ func main() {
426515
panic(err)
427516
}
428517
}
518+
519+
if ddtraceSpanTest {
520+
if err := RunDDTraceSpanTest(ddtraceSpanFilePath); err != nil {
521+
panic(err)
522+
}
523+
}
429524
}

0 commit comments

Comments
 (0)