Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ vanity-import-fix: $(PORTO)
@go install github.com/jcchavezs/porto/cmd/porto@latest
@porto --include-internal -w .

test: generate ebpf test-deps
test: generate ebpf test-deps rust-components
go test $(GO_FLAGS) -tags $(GO_TAGS) ./...

TESTDATA_DIRS:= \
Expand All @@ -129,7 +129,7 @@ test-deps:

TEST_INTEGRATION_BINARY_DIRS := tracer processmanager/ebpf support

integration-test-binaries: generate ebpf
integration-test-binaries: generate ebpf rust-components
$(foreach test_name, $(TEST_INTEGRATION_BINARY_DIRS), \
(go test -ldflags='-extldflags=-static' -trimpath -c \
-tags $(GO_TAGS),static_build,integration \
Expand Down
171 changes: 171 additions & 0 deletions interpreter/go/go.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package golang // import "go.opentelemetry.io/ebpf-profiler/interpreter/go"

/*
#cgo CFLAGS: -g -Wall
#include "../../rust-crates/symblib-capi/c/symblib.h"
#include <stdlib.h>
*/
import "C"

import (
"errors"
"fmt"
"hash/fnv"
"sync/atomic"
"unsafe"

"go.opentelemetry.io/ebpf-profiler/host"
"go.opentelemetry.io/ebpf-profiler/interpreter"
"go.opentelemetry.io/ebpf-profiler/libpf"
"go.opentelemetry.io/ebpf-profiler/metrics"
"go.opentelemetry.io/ebpf-profiler/remotememory"
"go.opentelemetry.io/ebpf-profiler/reporter"
"go.opentelemetry.io/ebpf-profiler/successfailurecounter"
)

var (
// compiler check to make sure the needed interfaces are satisfied
_ interpreter.Data = &goData{}
_ interpreter.Instance = &goInstance{}
)

type goData struct {
goExecutable *C.SymblibPointResolver
}

type goInstance struct {
interpreter.InstanceStubs

// Go symbolization metrics
successCount atomic.Uint64
failCount atomic.Uint64

d *goData
}

func Loader(_ interpreter.EbpfHandler, info *interpreter.LoaderInfo) (
interpreter.Data, error) {
ef, err := info.GetELF()
if err != nil {
return nil, err
}
if !ef.IsGolang() {
return nil, nil
}

exec, err := info.ExtractAsFile()
if err != nil {
return nil, err
}

executablePath := C.CString(exec)
defer C.free(unsafe.Pointer(executablePath))

gd := &goData{}

//nolint:gocritic
status := C.symblib_goruntime_new(executablePath, &gd.goExecutable)
Comment thread
florianl marked this conversation as resolved.
if status != C.SYMBLIB_OK {
return nil, fmt.Errorf("failed to create point resolver for '%s': %d",
exec, status)
}

return gd, nil
}

func (g *goData) Attach(_ interpreter.EbpfHandler, _ libpf.PID,
_ libpf.Address, _ remotememory.RemoteMemory) (interpreter.Instance, error) {
return &goInstance{
d: g,
}, nil
}

func (g *goData) Unload(_ interpreter.EbpfHandler) {
if g.goExecutable != nil {
C.symblib_goruntime_free(g.goExecutable)
g.goExecutable = nil
}
}

func (g *goInstance) GetAndResetMetrics() ([]metrics.Metric, error) {
return []metrics.Metric{
{
ID: metrics.IDGoSymbolizationSuccess,
Value: metrics.MetricValue(g.successCount.Swap(0)),
},
{
ID: metrics.IDGoSymbolizationFailure,
Value: metrics.MetricValue(g.failCount.Swap(0)),
},
}, nil
}

// Detach is a NOP for goInstance.
func (g *goInstance) Detach(_ interpreter.EbpfHandler, _ libpf.PID) error {
return nil
}

func (g *goInstance) Symbolize(symbolReporter reporter.SymbolReporter, frame *host.Frame,
Comment thread
florianl marked this conversation as resolved.
trace *libpf.Trace) error {
if !frame.Type.IsInterpType(libpf.Native) {
return interpreter.ErrMismatchInterpreterType
}
sfCounter := successfailurecounter.New(&g.successCount, &g.failCount)
defer sfCounter.DefaultToFailure()

if g.d.goExecutable == nil {
return errors.New("point resolver is out of scope")
}

var symbols *C.SymblibSlice_SymblibResolvedSymbol
defer C.symblib_slice_symblibresolved_symbol_free(symbols)

//nolint:gocritic
status := C.symblib_point_resolver_symbols_for_pc(g.d.goExecutable,
C.uint64_t(frame.Lineno), &symbols)
if status != C.SYMBLIB_OK {
return fmt.Errorf("failed to do point lookup at 0x%x: %d",
frame.Lineno, status)
}

// Access resolved symbols
symbolsSlice := unsafe.Slice((*C.SymblibResolvedSymbol)(unsafe.Pointer(symbols.data)),
symbols.len)
if len(symbolsSlice) == 0 {
return fmt.Errorf("failed to symbolize 0x%x", frame.Lineno)
}

frameFileBytes := []byte(frame.File.StringNoQuotes())
for i := 0; i < len(symbolsSlice); i++ {
lineNo := libpf.SourceLineno(symbolsSlice[i].line_number)
funcName := C.GoString(symbolsSlice[i].function_name)
sourceFile := C.GoString(symbolsSlice[i].file_name)

// The fnv hash Write() method calls cannot fail, so it's safe to ignore the errors.
h := fnv.New128a()
_, _ = h.Write(frameFileBytes)
_, _ = h.Write([]byte(funcName))
_, _ = h.Write([]byte(sourceFile))
fileID, err := libpf.FileIDFromBytes(h.Sum(nil))
if err != nil {
return fmt.Errorf("failed to create a file ID: %v", err)
}

frameID := libpf.NewFrameID(fileID, libpf.AddressOrLineno(lineNo))

trace.AppendFrameID(libpf.GoFrame, frameID)

symbolReporter.FrameMetadata(&reporter.FrameMetadataArgs{
FrameID: frameID,
FunctionName: funcName,
SourceFile: sourceFile,
SourceLine: lineNo,
})
}

sfCounter.ReportSuccess()
return nil
}
11 changes: 11 additions & 0 deletions interpreter/go/go_amd64.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//go:build amd64

// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package golang // import "go.opentelemetry.io/ebpf-profiler/interpreter/go"

/*
#cgo LDFLAGS: ${SRCDIR}/../../target/x86_64-unknown-linux-musl/release/libsymblib_capi.a
*/
import "C"
11 changes: 11 additions & 0 deletions interpreter/go/go_arm64.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//go:build arm64

// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package golang // import "go.opentelemetry.io/ebpf-profiler/interpreter/go"

/*
#cgo LDFLAGS: ${SRCDIR}/../../target/aarch64-unknown-linux-musl/release/libsymblib_capi.a
*/
import "C"
2 changes: 2 additions & 0 deletions libpf/frametype.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ const (
V8Frame FrameType = support.FrameMarkerV8
// DotnetFrame identifies the Dotnet interpreter frames.
DotnetFrame FrameType = support.FrameMarkerDotnet
// GoFrame identifies Go frames.
GoFrame FrameType = support.FrameMarkerGo
// AbortFrame identifies frames that report that further unwinding was aborted due to an error.
AbortFrame FrameType = support.FrameMarkerAbort
)
Expand Down
3 changes: 3 additions & 0 deletions libpf/interpretertype.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ const (
V8 InterpreterType = support.FrameMarkerV8
// Dotnet identifies the Dotnet interpreter.
Dotnet InterpreterType = support.FrameMarkerDotnet
// Go identifies Go code.
Go InterpreterType = support.FrameMarkerGo
)

// Pseudo-interpreters without a corresponding frame type.
Expand Down Expand Up @@ -65,6 +67,7 @@ var interpreterTypeToString = map[InterpreterType]string{
V8: "v8js",
Dotnet: "dotnet",
APMInt: "apm-integration",
Go: "go",
}

var stringToInterpreterType = make(map[string]InterpreterType, len(interpreterTypeToString))
Expand Down
8 changes: 7 additions & 1 deletion metrics/ids.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions metrics/metrics.json
Original file line number Diff line number Diff line change
Expand Up @@ -1987,5 +1987,19 @@
"name": "ErrProcParse",
"field": "agent.errors.proc_parse",
"id": 275
},
{
"description": "Number of successfully symbolized Go frames",
"type": "counter",
"name": "GoSymbolizationSuccess",
"field": "agent.go.symbolization.successes",
"id": 276
},
{
"description": "Number of Go frames that failed symbolization",
"type": "counter",
"name": "GoSymbolizationFailure",
"field": "agent.go.symbolization.failures",
"id": 277
}
]
4 changes: 4 additions & 0 deletions processmanager/execinfomanager/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"go.opentelemetry.io/ebpf-profiler/interpreter"
"go.opentelemetry.io/ebpf-profiler/interpreter/apmint"
"go.opentelemetry.io/ebpf-profiler/interpreter/dotnet"
golang "go.opentelemetry.io/ebpf-profiler/interpreter/go"
"go.opentelemetry.io/ebpf-profiler/interpreter/hotspot"
"go.opentelemetry.io/ebpf-profiler/interpreter/nodev8"
"go.opentelemetry.io/ebpf-profiler/interpreter/perl"
Expand Down Expand Up @@ -124,6 +125,9 @@ func NewExecutableInfoManager(
if includeTracers.Has(types.DotnetTracer) {
interpreterLoaders = append(interpreterLoaders, dotnet.Loader)
}
if includeTracers.Has(types.GoTracer) {
interpreterLoaders = append(interpreterLoaders, golang.Loader)
}

interpreterLoaders = append(interpreterLoaders, apmint.Loader)

Expand Down
6 changes: 6 additions & 0 deletions processmanager/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,12 @@ func (pm *ProcessManager) ConvertTrace(trace *host.Trace) (newTrace *libpf.Trace
}
}

// Attempt symbolization of native frames. It is best effort and
// provides non-symbolized frames if no native symbolizer is active.
if err := pm.symbolizeFrame(i, trace, newTrace); err == nil {
continue
}

fileID, ok := pm.FileIDMapper.Get(frame.File)
if !ok {
log.Debugf(
Expand Down
2 changes: 2 additions & 0 deletions support/ebpf/frametypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
#define FRAME_MARKER_PHP_JIT 0x9
// Indicates a Dotnet frame
#define FRAME_MARKER_DOTNET 0xA
// Indicates a Go frame
#define FRAME_MARKER_GO 0xB

// Indicates a frame containing information about a critical unwinding error
// that caused further unwinding to be aborted.
Expand Down
1 change: 1 addition & 0 deletions support/types.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions support/types_def.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const (
FrameMarkerPerl = C.FRAME_MARKER_PERL
FrameMarkerV8 = C.FRAME_MARKER_V8
FrameMarkerDotnet = C.FRAME_MARKER_DOTNET
FrameMarkerGo = C.FRAME_MARKER_GO
FrameMarkerAbort = C.FRAME_MARKER_ABORT
)

Expand Down
63 changes: 63 additions & 0 deletions tools/coredump/testdata/amd64/go-1.24.1-hello.json.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
{
"coredump-ref": "ad99fdc13a9fd30c511ae87fbd2f0d4ba8c16af65a691cce39dc9031f04b26f4",
"threads": [
{
"lwp": 2683,
"frames": [
"internal/runtime/syscall.Syscall6+0 in /usr/local/go/src/internal/runtime/syscall/asm_linux_amd64.s:36",
"syscall.RawSyscall6+0 in /usr/local/go/src/syscall/syscall_linux.go:66",
"syscall.Syscall+0 in /usr/local/go/src/syscall/syscall_linux.go:86",
"syscall.write+0 in /usr/local/go/src/syscall/zsyscall_linux_amd64.go:964",
"internal/poll.(*FD).Write+0 in /usr/local/go/src/syscall/syscall_unix.go:211",
"os.(*File).Write+0 in /usr/local/go/src/os/file.go:196",
"fmt.Fprintln+0 in /usr/local/go/src/fmt/print.go:305",
"main.hello+0 in /home/ec2-user/testsources/go/hello.go:14",
"main.main+0 in /home/ec2-user/testsources/go/hello.go:50",
"runtime.main+0 in /usr/local/go/src/internal/runtime/atomic/types.go:194",
"runtime.goexit+0 in /usr/local/go/src/runtime/asm_amd64.s:1701"
]
},
{
"lwp": 2684,
"frames": [
"runtime.usleep+0 in /usr/local/go/src/runtime/sys_linux_amd64.s:135",
"runtime.sysmon+0 in /usr/local/go/src/runtime/proc.go:6108",
"runtime.sysmon+0 in /usr/local/go/src/runtime/proc.go:6108",
"runtime.mstart1+0 in /usr/local/go/src/runtime/proc.go:1855",
"runtime.mstart0+0 in /usr/local/go/src/runtime/proc.go:1817",
"runtime.mstart+0 in /usr/local/go/src/runtime/asm_amd64.s:396"
]
},
{
"lwp": 2685,
"frames": [
"runtime.futex+0 in /usr/local/go/src/runtime/sys_linux_amd64.s:558",
"runtime.futexsleep+0 in /usr/local/go/src/runtime/os_linux.go:75",
"runtime.notesleep+0 in /usr/local/go/src/runtime/lock_futex.go:48",
"runtime.stopm+0 in /usr/local/go/src/runtime/proc.go:1888",
"runtime.findRunnable+0 in /usr/local/go/src/runtime/proc.go:3279",
"runtime.schedule+0 in /usr/local/go/src/runtime/proc.go:4017",
"runtime.goschedImpl+0 in /usr/local/go/src/runtime/proc.go:4176",
"runtime.gopreempt_m+0 in /usr/local/go/src/runtime/proc.go:4193",
"runtime.mcall+0 in /usr/local/go/src/runtime/asm_amd64.s:463"
]
},
{
"lwp": 2686,
"frames": [
"runtime.futex+0 in /usr/local/go/src/runtime/sys_linux_amd64.s:558",
"runtime.futexsleep+0 in /usr/local/go/src/runtime/os_linux.go:75",
"runtime.notesleep+0 in /usr/local/go/src/runtime/lock_futex.go:48",
"runtime.stopm+0 in /usr/local/go/src/runtime/proc.go:1888",
"runtime.exitsyscall0+0 in /usr/local/go/src/runtime/proc.go:4875",
"runtime.mcall+0 in /usr/local/go/src/runtime/asm_amd64.s:463"
]
}
],
"modules": [
{
"ref": "cfe34afe8ed0115e552d0e94cf7bd46186deb8c3775b310204f194a0b5f67cd5",
"local-path": "/home/ec2-user/testsources/go/hello"
}
]
}
Loading