diff --git a/.github/workflows/unit-test-on-pull-request.yml b/.github/workflows/unit-test-on-pull-request.yml index 5a2b4ec63..0a4f13412 100644 --- a/.github/workflows/unit-test-on-pull-request.yml +++ b/.github/workflows/unit-test-on-pull-request.yml @@ -58,6 +58,8 @@ jobs: uses: ./.github/workflows/env - name: Tests run: make test TARGET_ARCH=${{ matrix.target_arch }} + - name: sudo tests + run: make sudo-tests build-integration-test-binaries: name: Build integration test binaries (${{ matrix.target_arch }}) diff --git a/Makefile b/Makefile index bccfdf4cf..474488162 100644 --- a/Makefile +++ b/Makefile @@ -76,6 +76,16 @@ linter-version: test: generate ebpf test-deps go test $(GO_FLAGS) ./... +SUDOTEST_DIRS := ./customlabelstest + +target/release/custom-labels-example: + cargo build --release --bin custom-labels-example + +sudo-tests: generate ebpf target/release/custom-labels-example + $(foreach test_dir, $(SUDOTEST_DIRS), \ + sudo go test $(GO_FLAGS) -tags $(GO_TAGS) "$(test_dir)" \ + ) + TESTDATA_DIRS:= \ nativeunwind/elfunwindinfo/testdata \ libpf/pfelf/testdata \ diff --git a/customlabelstest/customlabels_test.go b/customlabelstest/customlabels_test.go new file mode 100644 index 000000000..e5c3a0987 --- /dev/null +++ b/customlabelstest/customlabels_test.go @@ -0,0 +1,80 @@ +package customlabelstest + +import ( + "context" + "os/exec" + "regexp" + "testing" + "time" + + "github.com/stretchr/testify/require" + "github.com/open-telemetry/opentelemetry-ebpf-profiler/testutils" + tracertypes "github.com/open-telemetry/opentelemetry-ebpf-profiler/tracer/types" +) + +func TestNativeCustomLabels(t *testing.T) { + if !testutils.IsRoot() { + t.Skip("root privileges required") + } + + r := &testutils.MockReporter{} + enabledTracers, _ := tracertypes.Parse("all") + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + traceCh, _ := testutils.StartTracer(ctx, t, enabledTracers, r) + + errCh := make(chan error, 1) + + cmd := exec.CommandContext(ctx, "../target/release/custom-labels-example") + err := cmd.Start() + require.NoError(t, err, "run 'cargo build --release --bin custom-labels-example' first") + + go func() { + err := cmd.Wait() + errCh <- err + }() + + stopCh := time.After(10 * time.Second) + + re := regexp.MustCompile(`^[a-zA-Z0-9]{16}$`) + good := false +Loop: + for { + select { + case trace, ok := <-traceCh: + if !ok { + break Loop + } + if trace == nil { + continue + } + if len(trace.CustomLabels) > 0 { + var gotL1, gotL2 bool + for k, v := range trace.CustomLabels { + switch k { + case "l1": + gotL1 = true + require.True(t, re.MatchString(v)) + t.Logf("got l1, value is %s", v) + case "l2": + gotL2 = true + require.True(t, re.MatchString(v)) + t.Logf("got l2, value is %s", v) + default: + require.Failf(t, "fail", "got unexpected label: %s=%s", k, v) + } + } + if gotL1 && gotL2 { + good = true + break Loop + } + } + case err := <-errCh: + require.Failf(t, "fail", "Failed to run custom-labels-example, err = %v", err) + case <-stopCh: + require.Fail(t, "fail", "Failed to get labels after ten seconds") + } + } + require.True(t, good) +} diff --git a/host/host.go b/host/host.go index d4a38992d..df3da36df 100644 --- a/host/host.go +++ b/host/host.go @@ -55,4 +55,5 @@ type Trace struct { TID libpf.PID APMTraceID libpf.APMTraceID APMTransactionID libpf.APMTransactionID + CustomLabels map[string]string } diff --git a/interpreter/customlabels/customlabels.go b/interpreter/customlabels/customlabels.go new file mode 100644 index 000000000..e0faffd1f --- /dev/null +++ b/interpreter/customlabels/customlabels.go @@ -0,0 +1,156 @@ +package customlabels + +// #include +// #include "../../support/ebpf/types.h" +import "C" +import ( + "debug/elf" + "errors" + "fmt" + "regexp" + "unsafe" + + "github.com/open-telemetry/opentelemetry-ebpf-profiler/interpreter" + "github.com/open-telemetry/opentelemetry-ebpf-profiler/libpf" + "github.com/open-telemetry/opentelemetry-ebpf-profiler/libpf/pfelf" + "github.com/open-telemetry/opentelemetry-ebpf-profiler/remotememory" +) + +const ( + abiVersionExport = "custom_labels_abi_version" + tlsExport = "custom_labels_current_set" +) + +var dsoRegex = regexp.MustCompile(`.*/libcustomlabels.*\.so|.*/customlabels\.node`) + +type data struct { + abiVersionElfVA libpf.Address + tlsAddr libpf.Address + isSharedLibrary bool +} + +var _ interpreter.Data = &data{} + +func roundUp(multiple, value uint64) uint64 { + if multiple == 0 { + return value + } + return (value + multiple - 1) / multiple * multiple +} + +func Loader(_ interpreter.EbpfHandler, info *interpreter.LoaderInfo) (interpreter.Data, error) { + ef, err := info.GetELF() + if err != nil { + return nil, err + } + + abiVersionSym, err := ef.LookupSymbol(abiVersionExport) + if err != nil { + if errors.Is(err, pfelf.ErrSymbolNotFound) { + return nil, nil + } + + return nil, err + } + + if abiVersionSym.Size != 4 { + return nil, fmt.Errorf("abi version export has wrong size %d", abiVersionSym.Size) + } + + // If this is the libcustomlabels.so library, we are using + // global-dynamic TLS model and have to look up the TLS descriptor. + // Otherwise, assume we're the main binary and just look up the + // symbol. + isSharedLibrary := dsoRegex.MatchString(info.FileName()) + var tlsAddr libpf.Address + if isSharedLibrary { + // Resolve thread info TLS export. + tlsDescs, err := ef.TLSDescriptors() + if err != nil { + return nil, errors.New("failed to extract TLS descriptors") + } + var ok bool + tlsAddr, ok = tlsDescs[tlsExport] + if !ok { + return nil, errors.New("failed to locate TLS descriptor for custom labels") + } + } else { + tlsSym, err := ef.LookupSymbol(tlsExport) + if err != nil { + return nil, err + } + if ef.Machine == elf.EM_AARCH64 { + tlsAddr = libpf.Address(tlsSym.Address + 16) + } else if ef.Machine == elf.EM_X86_64 { + // Symbol addresses are relative to the start of the + // thread-local storage image, but the thread pointer points to the _end_ + // of the image. So we need to find the size of the image in order to know where the + // beginning is. + // + // The image is just .tdata followed by .tbss, + // but we also have to respect the alignment. + tbss, err := ef.Tbss() + if err != nil { + return nil, err + } + tdata, err := ef.Tdata() + var tdataSize uint64 + if err != nil { + // No Tdata is ok, it's the same as size 0 + if err != pfelf.ErrNoTdata { + return nil, err + } + } else { + tdataSize = tdata.Size + } + imageSize := roundUp(tbss.Addralign, tdataSize) + tbss.Size + tlsAddr = libpf.Address(int64(tlsSym.Address) - int64(imageSize)) + } else { + return nil, fmt.Errorf("unrecognized machine: %s", ef.Machine.String()) + } + } + + d := data{ + abiVersionElfVA: libpf.Address(abiVersionSym.Address), + tlsAddr: tlsAddr, + isSharedLibrary: isSharedLibrary, + } + return &d, nil +} + +type instance struct { + interpreter.InstanceStubs +} + +func (d data) Attach(ebpf interpreter.EbpfHandler, pid libpf.PID, + bias libpf.Address, rm remotememory.RemoteMemory) (interpreter.Instance, error) { + abiVersion, err := rm.Uint32Checked(bias + d.abiVersionElfVA) + if err != nil { + return nil, fmt.Errorf("failed to read custom labels ABI version: %w", err) + } + + if abiVersion != 1 { + return nil, fmt.Errorf("unsupported custom labels ABI version: %d"+ + " (only 1 is supported)", abiVersion) + } + + var tlsOffset uint64 + if d.isSharedLibrary { + // Read TLS offset from the TLS descriptor + tlsOffset = rm.Uint64(bias + d.tlsAddr + 8) + } else { + // We're in the main executable: TLS offset is known statically. + tlsOffset = uint64(d.tlsAddr) + } + + procInfo := C.NativeCustomLabelsProcInfo{tls_offset: C.u64(tlsOffset)} + if err := ebpf.UpdateProcData(libpf.CustomLabels, pid, unsafe.Pointer(&procInfo)); err != nil { + return nil, err + } + + return &instance{}, nil +} + +func (i *instance) Detach(ebpf interpreter.EbpfHandler, pid libpf.PID) error { + return ebpf.DeleteProcData(libpf.CustomLabels, pid) +} diff --git a/libpf/interpretertype.go b/libpf/interpretertype.go index 48db6474d..a9938e078 100644 --- a/libpf/interpretertype.go +++ b/libpf/interpretertype.go @@ -40,6 +40,9 @@ const ( // APMInt identifies the pseudo-interpreter for the APM integration. APMInt InterpreterType = 0x100 + + // CustomLabels identifies the pseudo-interpreter for native custom labels support. + CustomLabels InterpreterType = 0x102 ) // Frame converts the interpreter type into the corresponding frame type. diff --git a/libpf/pfelf/file.go b/libpf/pfelf/file.go index 12a04b73a..51c8bb46e 100644 --- a/libpf/pfelf/file.go +++ b/libpf/pfelf/file.go @@ -54,6 +54,12 @@ var ErrSymbolNotFound = errors.New("symbol not found") // ErrNotELF is returned when the file is not an ELF var ErrNotELF = errors.New("not an ELF file") +// ErrNoTbss is returned when the tbss section cannot be found +var ErrNoTbss = errors.New("no thread-local uninitialized data section (tbss)") + +// ErrNoTdata is returned when the tdata section cannot be found +var ErrNoTdata = errors.New("no thread-local initialized data section (tdata)") + // File represents an open ELF file type File struct { // closer is called internally when resources for this File are to be released @@ -413,6 +419,32 @@ func (f *File) Section(name string) *Section { return nil } +// Tbss gets the thread-local uninitialized data section +func (f *File) Tbss() (*Section, error) { + if err := f.LoadSections(); err != nil { + return nil, err + } + for _, sec := range f.Sections { + if sec.Type == elf.SHT_NOBITS && sec.Flags&elf.SHF_TLS != 0 { + return &sec, nil + } + } + return nil, ErrNoTbss +} + +// Tdata gets the thread-local initialized data section +func (f *File) Tdata() (*Section, error) { + if err := f.LoadSections(); err != nil { + return nil, err + } + for _, sec := range f.Sections { + if sec.Type == elf.SHT_PROGBITS && sec.Flags&elf.SHF_TLS != 0 { + return &sec, nil + } + } + return nil, ErrNoTdata +} + // ReadVirtualMemory reads bytes from given virtual address func (f *File) ReadVirtualMemory(p []byte, addr int64) (int, error) { if len(p) == 0 { diff --git a/libpf/trace.go b/libpf/trace.go index da17fd575..8626571aa 100644 --- a/libpf/trace.go +++ b/libpf/trace.go @@ -14,6 +14,7 @@ type Trace struct { MappingEnd []Address MappingFileOffsets []uint64 Hash TraceHash + CustomLabels map[string]string } // AppendFrame appends a frame to the columnar frame array without mapping information. diff --git a/processmanager/ebpf/ebpf.go b/processmanager/ebpf/ebpf.go index 19cb54a73..f25a6a591 100644 --- a/processmanager/ebpf/ebpf.go +++ b/processmanager/ebpf/ebpf.go @@ -103,6 +103,7 @@ type ebpfMapsImpl struct { rubyProcs *cebpf.Map v8Procs *cebpf.Map apmIntProcs *cebpf.Map + clProcs *cebpf.Map // Stackdelta and process related eBPF maps exeIDToStackDeltaMaps []*cebpf.Map @@ -203,6 +204,12 @@ func LoadMaps(ctx context.Context, maps map[string]*cebpf.Map) (EbpfHandler, err } impl.apmIntProcs = apmIntProcs + clProcs, ok := maps["cl_procs"] + if !ok { + log.Fatalf("Map cl_procs is not available") + } + impl.clProcs = clProcs + impl.stackDeltaPageToInfo, ok = maps["stack_delta_page_to_info"] if !ok { log.Fatalf("Map stack_delta_page_to_info is not available") @@ -294,6 +301,8 @@ func (impl *ebpfMapsImpl) getInterpreterTypeMap(typ libpf.InterpreterType) (*ceb return impl.v8Procs, nil case libpf.APMInt: return impl.apmIntProcs, nil + case libpf.CustomLabels: + return impl.clProcs, nil default: return nil, fmt.Errorf("type %d is not (yet) supported", typ) } diff --git a/processmanager/execinfomanager/manager.go b/processmanager/execinfomanager/manager.go index ffbc90d2a..4339a5720 100644 --- a/processmanager/execinfomanager/manager.go +++ b/processmanager/execinfomanager/manager.go @@ -18,6 +18,7 @@ import ( "github.com/open-telemetry/opentelemetry-ebpf-profiler/host" "github.com/open-telemetry/opentelemetry-ebpf-profiler/interpreter" "github.com/open-telemetry/opentelemetry-ebpf-profiler/interpreter/apmint" + "github.com/open-telemetry/opentelemetry-ebpf-profiler/interpreter/customlabels" "github.com/open-telemetry/opentelemetry-ebpf-profiler/interpreter/dotnet" "github.com/open-telemetry/opentelemetry-ebpf-profiler/interpreter/hotspot" "github.com/open-telemetry/opentelemetry-ebpf-profiler/interpreter/nodev8" @@ -100,6 +101,7 @@ func NewExecutableInfoManager( sdp nativeunwind.StackDeltaProvider, ebpf pmebpf.EbpfHandler, includeTracers types.IncludedTracers, + collectCustomLabels bool, ) (*ExecutableInfoManager, error) { // Initialize interpreter loaders. interpreterLoaders := make([]interpreter.Loader, 0) @@ -126,6 +128,9 @@ func NewExecutableInfoManager( } interpreterLoaders = append(interpreterLoaders, apmint.Loader) + if collectCustomLabels { + interpreterLoaders = append(interpreterLoaders, customlabels.Loader) + } deferredFileIDs, err := lru.NewSynced[host.FileID, libpf.Void](deferredFileIDSize, func(id host.FileID) uint32 { return uint32(id) }) diff --git a/processmanager/manager.go b/processmanager/manager.go index 5a18225f2..8914804d2 100644 --- a/processmanager/manager.go +++ b/processmanager/manager.go @@ -67,7 +67,7 @@ var ( // implementation. func New(ctx context.Context, includeTracers types.IncludedTracers, monitorInterval time.Duration, ebpf pmebpf.EbpfHandler, fileIDMapper FileIDMapper, symbolReporter reporter.SymbolReporter, - sdp nativeunwind.StackDeltaProvider, filterErrorFrames bool) (*ProcessManager, error) { + sdp nativeunwind.StackDeltaProvider, filterErrorFrames bool, collectCustomLabels bool) (*ProcessManager, error) { if fileIDMapper == nil { var err error fileIDMapper, err = newFileIDMapper(lruFileIDCacheSize) @@ -83,7 +83,7 @@ func New(ctx context.Context, includeTracers types.IncludedTracers, monitorInter } elfInfoCache.SetLifetime(elfInfoCacheTTL) - em, err := eim.NewExecutableInfoManager(sdp, ebpf, includeTracers) + em, err := eim.NewExecutableInfoManager(sdp, ebpf, includeTracers, collectCustomLabels) if err != nil { return nil, fmt.Errorf("unable to create ExecutableInfoManager: %v", err) } @@ -216,9 +216,10 @@ func (pm *ProcessManager) ConvertTrace(trace *host.Trace) (newTrace *libpf.Trace traceLen := len(trace.Frames) newTrace = &libpf.Trace{ - Files: make([]libpf.FileID, 0, traceLen), - Linenos: make([]libpf.AddressOrLineno, 0, traceLen), - FrameTypes: make([]libpf.FrameType, 0, traceLen), + Files: make([]libpf.FileID, 0, traceLen), + Linenos: make([]libpf.AddressOrLineno, 0, traceLen), + FrameTypes: make([]libpf.FrameType, 0, traceLen), + CustomLabels: trace.CustomLabels, } for i := 0; i < traceLen; i++ { diff --git a/processmanager/manager_test.go b/processmanager/manager_test.go index e5c6b8ef1..4d9411c05 100644 --- a/processmanager/manager_test.go +++ b/processmanager/manager_test.go @@ -317,7 +317,7 @@ func TestInterpreterConvertTrace(t *testing.T) { nil, &symbolReporterMockup{}, nil, - true) + true, true) require.NoError(t, err) newTrace := manager.ConvertTrace(testcase.trace) @@ -406,7 +406,7 @@ func TestNewMapping(t *testing.T) { NewMapFileIDMapper(), symRepMockup, &dummyProvider, - true) + true, true) require.NoError(t, err) // Replace the internal hooks for the tests. These hooks catch the @@ -596,7 +596,7 @@ func TestProcExit(t *testing.T) { NewMapFileIDMapper(), repMockup, &dummyProvider, - true) + true, true) require.NoError(t, err) defer cancel() diff --git a/remotememory/remotememory.go b/remotememory/remotememory.go index 6ab4ee960..5d3a3f5c1 100644 --- a/remotememory/remotememory.go +++ b/remotememory/remotememory.go @@ -71,6 +71,15 @@ func (rm RemoteMemory) Uint32(addr libpf.Address) uint32 { return binary.LittleEndian.Uint32(buf[:]) } +// Uint32Checked reads a 32-bit unsigned integer from remote memory +func (rm RemoteMemory) Uint32Checked(addr libpf.Address) (uint32, error) { + var buf [4]byte + if err := rm.Read(addr, buf[:]); err != nil { + return 0, err + } + return binary.LittleEndian.Uint32(buf[:]), nil +} + // Uint64 reads a 64-bit unsigned integer from remote memory func (rm RemoteMemory) Uint64(addr libpf.Address) uint64 { var buf [8]byte diff --git a/rust-crates/custom-labels-example/Cargo.lock b/rust-crates/custom-labels-example/Cargo.lock new file mode 100644 index 000000000..7517159df --- /dev/null +++ b/rust-crates/custom-labels-example/Cargo.lock @@ -0,0 +1,406 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "bindgen" +version = "0.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", +] + +[[package]] +name = "bitflags" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cc" +version = "1.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c736e259eea577f443d5c86c304f9f4ae0295c43f3ba05c21f1d66b5f06001af" +dependencies = [ + "shlex", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "custom-labels" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c53a5b6673472ca0d1fb09362e0c7477412058cc73eba0c0c81276f69a8e7879" +dependencies = [ + "bindgen", + "cc", + "libc", +] + +[[package]] +name = "custom-labels-example" +version = "0.0.0" +dependencies = [ + "custom-labels", + "rand", +] + +[[package]] +name = "either" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7914353092ddf589ad78f25c5c1c21b7f80b0ff8621e7c814c3485b5306da9d" + +[[package]] +name = "getrandom" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +dependencies = [ + "cfg-if", + "libc", + "wasi", + "windows-targets", +] + +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "libc" +version = "0.2.170" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" + +[[package]] +name = "libloading" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +dependencies = [ + "cfg-if", + "windows-targets", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy 0.7.35", +] + +[[package]] +name = "proc-macro2" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +dependencies = [ + "rand_chacha", + "rand_core", + "zerocopy 0.8.20", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a509b1a2ffbe92afab0e55c8fd99dea1c280e8171bd2d88682bb20bc41cbc2c" +dependencies = [ + "getrandom", + "zerocopy 0.8.20", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "syn" +version = "2.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" + +[[package]] +name = "wasi" +version = "0.13.3+wasi-0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen-rt" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +dependencies = [ + "bitflags", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dde3bb8c68a8f3f1ed4ac9221aad6b10cece3e60a8e2ea54a6a2dec806d0084c" +dependencies = [ + "zerocopy-derive 0.8.20", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eea57037071898bf96a6da35fd626f4f27e9cee3ead2a6c703cf09d472b2e700" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/rust-crates/custom-labels-example/Cargo.toml b/rust-crates/custom-labels-example/Cargo.toml new file mode 100644 index 000000000..a3c8df97a --- /dev/null +++ b/rust-crates/custom-labels-example/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "custom-labels-example" +edition = "2021" +# Once we have the Rust workspace stuff from upstream, set these to true. +# +# version.workspace = true +# rust-version.workspace = true +# license.workspace = true + +[dependencies] +custom-labels = "0.4.0" +rand = "0.9.0" + +[build-dependencies] +custom-labels = "0.4.0" \ No newline at end of file diff --git a/rust-crates/custom-labels-example/build.rs b/rust-crates/custom-labels-example/build.rs new file mode 100644 index 000000000..be4c68863 --- /dev/null +++ b/rust-crates/custom-labels-example/build.rs @@ -0,0 +1,3 @@ +fn main() { + custom_labels::build::emit_build_instructions(); +} diff --git a/rust-crates/custom-labels-example/src/main.rs b/rust-crates/custom-labels-example/src/main.rs new file mode 100644 index 000000000..87d779df7 --- /dev/null +++ b/rust-crates/custom-labels-example/src/main.rs @@ -0,0 +1,27 @@ +use std::time::{Duration, Instant}; + +use rand::distr::Alphanumeric; +use rand::Rng; + +fn rand_str() -> String { + String::from_utf8( + rand::rng() + .sample_iter(&Alphanumeric) + .take(16) + .collect::>(), + ) + .unwrap() +} + +fn main() { + let start = Instant::now(); + + custom_labels::with_label("l1", rand_str(), || { + custom_labels::with_label("l2", rand_str(), || loop { + if start.elapsed() >= Duration::from_secs(10) { + println!("PASS"); + return; + } + }) + }); +} diff --git a/support/ebpf/interpreter_dispatcher.ebpf.c b/support/ebpf/interpreter_dispatcher.ebpf.c index e2c062d74..6639a3277 100644 --- a/support/ebpf/interpreter_dispatcher.ebpf.c +++ b/support/ebpf/interpreter_dispatcher.ebpf.c @@ -6,6 +6,7 @@ #include "types.h" #include "tracemgmt.h" #include "tsd.h" +#include "util.h" // Begin shared maps @@ -125,6 +126,105 @@ bpf_map_def SEC("maps") apm_int_procs = { .max_entries = 128, }; +bpf_map_def SEC("maps") cl_procs = { + .type = BPF_MAP_TYPE_HASH, + .key_size = sizeof(pid_t), + .value_size = sizeof(NativeCustomLabelsProcInfo), + .max_entries = 128, +}; + +static inline __attribute__((__always_inline__)) bool +get_native_custom_labels(PerCPURecord *record, NativeCustomLabelsProcInfo *proc) +{ + u64 tsd_base; + if (tsd_get_base((void **)&tsd_base) != 0) { + increment_metric(metricID_UnwindNativeCustomLabelsErrReadTsdBase); + DEBUG_PRINT("cl: failed to get TSD base for native custom labels"); + return false; + } + + u64 offset = tsd_base + proc->tls_offset; + DEBUG_PRINT("cl: native custom labels data at 0x%llx", offset); + + NativeCustomLabelsSet *p_current_set; + int err; + if ((err = bpf_probe_read_user(&p_current_set, sizeof(void *), (void *)(offset)))) { + increment_metric(metricID_UnwindNativeCustomLabelsErrReadData); + DEBUG_PRINT("Failed to read custom labels current set pointer: %d", err); + return false; + } + + if (!p_current_set) { + DEBUG_PRINT("Null labelset"); + record->trace.custom_labels.len = 0; + return true; + } + + NativeCustomLabelsSet current_set; + if ((err = bpf_probe_read_user(¤t_set, sizeof(current_set), p_current_set))) { + increment_metric(metricID_UnwindNativeCustomLabelsErrReadData); + DEBUG_PRINT("cl: failed to read custom labels data: %d", err); + return false; + } + + DEBUG_PRINT("cl: native custom labels count: %lu", current_set.count); + + unsigned ct = 0; + CustomLabelsArray *out = &record->trace.custom_labels; + +#pragma unroll + for (int i = 0; i < MAX_CUSTOM_LABELS; i++) { + if (i >= current_set.count) + break; + NativeCustomLabel *lbl_ptr = current_set.storage + i; + if ((err = bpf_probe_read_user( + &record->nativeCustomLabel, sizeof(NativeCustomLabel), (void *)(lbl_ptr)))) { + increment_metric(metricID_UnwindNativeCustomLabelsErrReadData); + DEBUG_PRINT("cl: failed to read label storage struct: %d", err); + return false; + } + NativeCustomLabel *lbl = &record->nativeCustomLabel; + if (!lbl->key.buf) + continue; + CustomLabel *out_lbl = &out->labels[ct]; + unsigned klen = MIN(lbl->key.len, CUSTOM_LABEL_MAX_KEY_LEN - 1); + if ((err = bpf_probe_read_user(out_lbl->key, klen, (void *)lbl->key.buf))) { + increment_metric(metricID_UnwindNativeCustomLabelsErrReadKey); + DEBUG_PRINT("cl: failed to read label key: %d", err); + goto exit; + } + unsigned vlen = MIN(lbl->value.len, CUSTOM_LABEL_MAX_VAL_LEN - 1); + if ((err = bpf_probe_read_user(out_lbl->val, vlen, (void *)lbl->value.buf))) { + increment_metric(metricID_UnwindNativeCustomLabelsErrReadValue); + DEBUG_PRINT("cl: failed to read label value: %d", err); + goto exit; + } + ++ct; + } +exit: + out->len = ct; + increment_metric(metricID_UnwindNativeCustomLabelsReadSuccesses); + return true; +} + +static inline __attribute__((__always_inline__)) void +maybe_add_native_custom_labels(PerCPURecord *record) +{ + u32 pid = record->trace.pid; + NativeCustomLabelsProcInfo *proc = bpf_map_lookup_elem(&cl_procs, &pid); + if (!proc) { + DEBUG_PRINT("cl: %d does not support native custom labels", pid); + return; + } + DEBUG_PRINT("cl: trace is within a process with native custom labels enabled"); + bool success = get_native_custom_labels(record, proc); + if (success) + increment_metric(metricID_UnwindNativeCustomLabelsAddSuccesses); + else + increment_metric(metricID_UnwindNativeCustomLabelsAddErrors); +} + + static inline __attribute__((__always_inline__)) void maybe_add_apm_info(Trace *trace) { u32 pid = trace->pid; // verifier needs this to be on stack on 4.15 kernel @@ -180,6 +280,7 @@ int unwind_stop(struct pt_regs *ctx) { Trace *trace = &record->trace; UnwindState *state = &record->state; + maybe_add_native_custom_labels(record); maybe_add_apm_info(trace); // If the stack is otherwise empty, push an error for that: we should diff --git a/support/ebpf/native_stack_trace.ebpf.c b/support/ebpf/native_stack_trace.ebpf.c index d9c56f3b7..89041aeab 100644 --- a/support/ebpf/native_stack_trace.ebpf.c +++ b/support/ebpf/native_stack_trace.ebpf.c @@ -511,6 +511,7 @@ static ErrorCode unwind_one_frame(u64 pid, u32 frame_idx, struct UnwindState *st state->fp = rt_regs[29]; state->lr = normalize_pac_ptr(rt_regs[30]); state->r22 = rt_regs[22]; + // state->r28 = rt_regs[28]; // ADDED FOR GO?? is it necessary for custom? state->return_address = false; DEBUG_PRINT("signal frame"); goto frame_ok; diff --git a/support/ebpf/tracemgmt.h b/support/ebpf/tracemgmt.h index d2c58d535..6c02adb55 100644 --- a/support/ebpf/tracemgmt.h +++ b/support/ebpf/tracemgmt.h @@ -200,6 +200,7 @@ static inline PerCPURecord *get_pristine_per_cpu_record() #elif defined(__aarch64__) record->state.lr = 0; record->state.r22 = 0; + // record->state.r28 = 0; // ADDED FOR GO? is it necessary? #endif record->state.return_address = false; record->state.error_metric = -1; @@ -224,6 +225,13 @@ static inline PerCPURecord *get_pristine_per_cpu_record() trace->apm_trace_id.as_int.lo = 0; trace->apm_transaction_id.as_int = 0; + // is this maybe just for GO?? + u64 *labels_space = (u64 *)&trace->custom_labels; +#pragma unroll + for (int i = 0; i < sizeof(CustomLabelsArray) / 8; i++) { + labels_space[i] = 0; + } + return record; } diff --git a/support/ebpf/tracer.ebpf.amd64 b/support/ebpf/tracer.ebpf.amd64 index d7928e5b4..e221a98a6 100644 Binary files a/support/ebpf/tracer.ebpf.amd64 and b/support/ebpf/tracer.ebpf.amd64 differ diff --git a/support/ebpf/types.h b/support/ebpf/types.h index ee4aed2d2..33a6a37ee 100644 --- a/support/ebpf/types.h +++ b/support/ebpf/types.h @@ -274,10 +274,10 @@ enum { // number of failures to get TSD base for APM correlation metricID_UnwindApmIntErrReadTsdBase, - // number of failures read the APM correlation pointer + // number of failures to read the APM correlation pointer metricID_UnwindApmIntErrReadCorrBufPtr, - // number of failures read the APM correlation buffer + // number of failures to read the APM correlation buffer metricID_UnwindApmIntErrReadCorrBuf, // number of successful reads of APM correlation info @@ -301,6 +301,27 @@ enum { // number of failures to unwind code object due to its large size metricID_UnwindDotnetErrCodeTooLarge, + // number of failures to get TSD base for native custom labels + metricID_UnwindNativeCustomLabelsErrReadTsdBase, + + // number of failures to read native custom labels thread-local object + metricID_UnwindNativeCustomLabelsErrReadData, + + // number of failures to read native custom labels key buffer + metricID_UnwindNativeCustomLabelsErrReadKey, + + // number of failures to read native custom labels value buffer + metricID_UnwindNativeCustomLabelsErrReadValue, + + // number of successful reads of native custom labels + metricID_UnwindNativeCustomLabelsReadSuccesses, + + // total number of failures to add native custom labels + metricID_UnwindNativeCustomLabelsAddErrors, + + // total number of successes adding native custom labels + metricID_UnwindNativeCustomLabelsAddSuccesses, + // // Metric IDs above are for counters (cumulative values) // @@ -512,6 +533,41 @@ typedef struct __attribute__((packed)) ApmCorrelationBuf { ApmSpanID transaction_id; } ApmCorrelationBuf; +#define CUSTOM_LABEL_MAX_KEY_LEN COMM_LEN +// Big enough to hold UUIDs, etc. +#define CUSTOM_LABEL_MAX_VAL_LEN 48 + +typedef struct CustomLabel { + char key[CUSTOM_LABEL_MAX_KEY_LEN]; + char val[CUSTOM_LABEL_MAX_VAL_LEN]; +} CustomLabel; + +typedef struct NativeCustomLabelsString { + size_t len; + const unsigned char *buf; +} NativeCustomLabelsString; + +typedef struct NativeCustomLabel { + NativeCustomLabelsString key; + NativeCustomLabelsString value; +} NativeCustomLabel; + +typedef struct NativeCustomLabelsThreadLocalData { + NativeCustomLabel *storage; + size_t count; + size_t capacity; +} NativeCustomLabelsSet; + +#define MAX_CUSTOM_LABELS 10 + +typedef struct CustomLabelsArray { + unsigned len; + CustomLabel labels[MAX_CUSTOM_LABELS]; +} CustomLabelsArray; + +#define MAX_CUSTOM_LABELS 10 + + // Container for a stack trace typedef struct Trace { // The process ID @@ -528,6 +584,8 @@ typedef struct Trace { ApmSpanID apm_transaction_id; // APM trace ID or all-zero if not present. ApmTraceID apm_trace_id; + // Custom Labels + CustomLabelsArray custom_labels; // The kernel stack ID. s32 kernel_stack_id; // The number of frames in the stack. @@ -554,7 +612,7 @@ typedef struct UnwindState { u64 rax, r9, r11, r13, r15; #elif defined(__aarch64__) // Current register values for named registers - u64 lr, r22; + u64 lr, r22, r28; #endif // The executable ID/hash associated with PC @@ -651,6 +709,10 @@ typedef struct PythonUnwindScratchSpace { u8 code[192]; } PythonUnwindScratchSpace; +typedef struct CustomLabelsState { + void *go_m_ptr; +} CustomLabelsState; + // Per-CPU info for the stack being built. This contains the stack as well as // meta-data on the number of eBPF tail-calls used so far to construct it. typedef struct PerCPURecord { @@ -662,6 +724,8 @@ typedef struct PerCPURecord { PerlUnwindState perlUnwindState; // The current Python unwinder state. PythonUnwindState pythonUnwindState; + // State for Go and Native custom labels + CustomLabelsState customLabelsState; // The current PHP unwinder state. PHPUnwindState phpUnwindState; // The current Ruby unwinder state. @@ -675,6 +739,8 @@ typedef struct PerCPURecord { V8UnwindScratchSpace v8UnwindScratch; // Scratch space for the Python unwinder PythonUnwindScratchSpace pythonUnwindScratch; + // Native labels scratch space + NativeCustomLabel nativeCustomLabel; }; // Mask to indicate which unwinders are complete u32 unwindersDone; @@ -854,4 +920,8 @@ typedef struct ApmIntProcInfo { u64 tls_offset; } ApmIntProcInfo; +typedef struct NativeCustomLabelsProcInfo { + u64 tls_offset; +} NativeCustomLabelsProcInfo; + #endif diff --git a/support/ebpf/util.h b/support/ebpf/util.h new file mode 100644 index 000000000..c260544fd --- /dev/null +++ b/support/ebpf/util.h @@ -0,0 +1,7 @@ +#ifndef OPTI_UTIL_H +#define OPTI_UTIL_H + +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) + +#endif diff --git a/testutils/helpers.go b/testutils/helpers.go new file mode 100644 index 000000000..dd7418098 --- /dev/null +++ b/testutils/helpers.go @@ -0,0 +1,127 @@ +package testutils + +import ( + "bufio" + "context" + "errors" + "io" + "os" + "strings" + "testing" + "time" + + log "github.com/sirupsen/logrus" + "github.com/stretchr/testify/require" + "github.com/open-telemetry/opentelemetry-ebpf-profiler/host" + "github.com/open-telemetry/opentelemetry-ebpf-profiler/libpf" + "github.com/open-telemetry/opentelemetry-ebpf-profiler/reporter" + "github.com/open-telemetry/opentelemetry-ebpf-profiler/tracer" + tracertypes "github.com/open-telemetry/opentelemetry-ebpf-profiler/tracer/types" +) + +type MockIntervals struct{} + +func (f MockIntervals) MonitorInterval() time.Duration { return 1 * time.Second } +func (f MockIntervals) TracePollInterval() time.Duration { return 250 * time.Millisecond } +func (f MockIntervals) PIDCleanupInterval() time.Duration { return 1 * time.Second } + +type MockReporter struct{} + +func (f MockReporter) ExecutableKnown(_ libpf.FileID) bool { + return true +} + +func (f MockReporter) ExecutableMetadata(_ *reporter.ExecutableMetadataArgs) { +} + +func (f MockReporter) FrameKnown(_ libpf.FrameID) bool { + return true +} + +func (f MockReporter) FrameMetadata(_ *reporter.FrameMetadataArgs) {} + +func StartTracer(ctx context.Context, t *testing.T, et tracertypes.IncludedTracers, + r reporter.SymbolReporter) (chan *host.Trace, *tracer.Tracer) { + trc, err := tracer.NewTracer(ctx, &tracer.Config{ + CollectCustomLabels: true, + Reporter: r, + Intervals: &MockIntervals{}, + IncludeTracers: et, + SamplesPerSecond: 20, + ProbabilisticInterval: 100, + ProbabilisticThreshold: 100, + }) + require.NoError(t, err) + + go readTracePipe(ctx) + + trc.StartPIDEventProcessor(ctx) + + err = trc.AttachTracer() + require.NoError(t, err) + + log.Info("Attached tracer program") + + err = trc.EnableProfiling() + require.NoError(t, err) + + err = trc.AttachSchedMonitor() + require.NoError(t, err) + + traceCh := make(chan *host.Trace) + + // Spawn monitors for the various result maps + err = trc.StartMapMonitors(ctx, traceCh) + require.NoError(t, err) + + return traceCh, trc +} + +func getTracePipe() (*os.File, error) { + for _, mnt := range []string{ + "/sys/kernel/debug/tracing", + "/sys/kernel/tracing", + "/tracing", + "/trace"} { + t, err := os.Open(mnt + "/trace_pipe") + if err == nil { + return t, nil + } + log.Errorf("Could not open trace_pipe at %s: %s", mnt, err) + } + return nil, os.ErrNotExist +} + +func readTracePipe(ctx context.Context) { + tp, err := getTracePipe() + if err != nil { + log.Warning("Could not open trace_pipe, check that debugfs is mounted") + return + } + + // When we're done kick ReadString out of blocked I/O. + go func() { + <-ctx.Done() + tp.Close() + }() + + r := bufio.NewReader(tp) + for { + line, err := r.ReadString('\n') + if err != nil { + if errors.Is(err, io.EOF) { + continue + } + log.Error(err) + return + } + line = strings.TrimSpace(line) + if line != "" { + log.Infof("%s", line) + } + } +} + +func IsRoot() bool { + return os.Geteuid() == 0 +} diff --git a/tools/coredump/coredump.go b/tools/coredump/coredump.go index f85c7f953..7bbb5bc55 100644 --- a/tools/coredump/coredump.go +++ b/tools/coredump/coredump.go @@ -196,7 +196,7 @@ func ExtractTraces(ctx context.Context, pr process.Process, debug bool, includeTracers, _ := tracertypes.Parse("all") manager, err := pm.New(todo, includeTracers, monitorInterval, &coredumpEbpfMaps, - pm.NewMapFileIDMapper(), symCache, elfunwindinfo.NewStackDeltaProvider(), false) + pm.NewMapFileIDMapper(), symCache, elfunwindinfo.NewStackDeltaProvider(), false, true) if err != nil { return nil, fmt.Errorf("failed to get Interpreter manager: %v", err) } diff --git a/tracer/tracer.go b/tracer/tracer.go index b23b0278c..a7799b00c 100644 --- a/tracer/tracer.go +++ b/tracer/tracer.go @@ -150,6 +150,9 @@ type Config struct { FilterErrorFrames bool // KernelVersionCheck indicates whether the kernel version should be checked. KernelVersionCheck bool + // CollectCustomLabels determines whether to collect custom labels in + // languages that support them. + CollectCustomLabels bool // BPFVerifierLogLevel is the log level of the eBPF verifier output. BPFVerifierLogLevel uint32 // BPFVerifierLogSize is the size in bytes that will be allocated for the eBPF verifier output. @@ -282,7 +285,7 @@ func NewTracer(ctx context.Context, cfg *Config) (*Tracer, error) { processManager, err := pm.New(ctx, cfg.IncludeTracers, cfg.Intervals.MonitorInterval(), ebpfHandler, nil, cfg.Reporter, elfunwindinfo.NewStackDeltaProvider(), - cfg.FilterErrorFrames) + cfg.FilterErrorFrames, cfg.CollectCustomLabels) if err != nil { return nil, fmt.Errorf("failed to create processManager: %v", err) } @@ -927,6 +930,16 @@ func (t *Tracer) loadBpfTrace(raw []byte) *host.Trace { } } + if ptr.custom_labels.len > 0 { + trace.CustomLabels = make(map[string]string, int(ptr.custom_labels.len)) + for i := 0; i < int(ptr.custom_labels.len); i++ { + lbl := ptr.custom_labels.labels[i] + key := C.GoString((*C.char)(unsafe.Pointer(&lbl.key))) + val := C.GoString((*C.char)(unsafe.Pointer(&lbl.val))) + trace.CustomLabels[key] = val + } + } + // If there are no kernel frames, or reading them failed, we are responsible // for allocating the columnar frame array. if len(trace.Frames) == 0 {