Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 1 addition & 3 deletions collector/internal/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,7 @@ func NewController(cfg *controller.Config,
GRPCConnectionTimeout: intervals.GRPCConnectionTimeout(),
ReportInterval: intervals.ReportInterval(),
ExecutablesCacheElements: 16384,
// Next step: Calculate FramesCacheElements from numCores and samplingRate.
FramesCacheElements: 131072,
SamplesPerSecond: cfg.SamplesPerSecond,
SamplesPerSecond: cfg.SamplesPerSecond,
}, nextConsumer)
if err != nil {
return nil, err
Expand Down
1 change: 1 addition & 0 deletions host/host.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,5 @@ type Trace struct {
CPU int
EnvVars map[string]string
CustomLabels map[string]string
KernelFrames libpf.Frames
}
4 changes: 0 additions & 4 deletions interpreter/dotnet/dotnet.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,6 @@ import (
log "github.com/sirupsen/logrus"

"go.opentelemetry.io/ebpf-profiler/interpreter"
"go.opentelemetry.io/ebpf-profiler/libpf"
)

const (
Expand All @@ -125,9 +124,6 @@ var (
// regex for the core language runtime
dotnetRegex = regexp.MustCompile(`/(\d+)\.(\d+).(\d+)/libcoreclr.so$`)

// The FileID used for Dotnet stub frames. Same FileID as in other interpreters.
stubsFileID = libpf.NewStubFileID(libpf.DotnetFrame)

// compiler check to make sure the needed interfaces are satisfied
_ interpreter.Data = &dotnetData{}
_ interpreter.Instance = &dotnetInstance{}
Expand Down
80 changes: 28 additions & 52 deletions interpreter/dotnet/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ package dotnet // import "go.opentelemetry.io/ebpf-profiler/interpreter/dotnet"

import (
"fmt"
"hash/fnv"
"path"
"slices"
"strings"
Expand Down Expand Up @@ -134,8 +133,6 @@ type dotnetInstance struct {
// bias is the ELF DSO load bias
bias libpf.Address

codeTypeMethodIDs [codeReadyToRun]libpf.AddressOrLineno

// Internal class instance pointers
codeRangeListPtr libpf.Address
precodeStubManagerPtr libpf.Address
Expand All @@ -159,24 +156,11 @@ type dotnetInstance struct {
addrToMethod *freelru.LRU[libpf.Address, *dotnetMethod]
}

// calculateAndSymbolizeStubID calculates a stub LineID, and symbolizes it if needed
func (i *dotnetInstance) insertAndSymbolizeStubFrame(symbolReporter reporter.SymbolReporter,
trace *libpf.Trace, codeType uint) {
name := "[stub: " + codeName[codeType] + "]"
lineID := i.codeTypeMethodIDs[codeType]
if lineID == 0 {
h := fnv.New128a()
_, _ = h.Write([]byte(name))
nameHash := h.Sum(nil)
lineID = libpf.AddressOrLineno(npsr.Uint64(nameHash, 0))
i.codeTypeMethodIDs[codeType] = lineID
}

frameID := libpf.NewFrameID(stubsFileID, lineID)
trace.AppendFrameID(libpf.DotnetFrame, frameID)
symbolReporter.FrameMetadata(&reporter.FrameMetadataArgs{
FrameID: frameID,
FunctionName: libpf.Intern(name),
// appendStubFrame appends a stub frame of given type
func (i *dotnetInstance) appendStubFrame(frames *libpf.Frames, codeType uint) {
frames.Append(&libpf.Frame{
Type: libpf.DotnetFrame,
FunctionName: libpf.Intern("[stub: " + codeName[codeType] + "]"),
})
}

Expand Down Expand Up @@ -736,8 +720,7 @@ func (i *dotnetInstance) GetAndResetMetrics() ([]metrics.Metric, error) {
}, nil
}

func (i *dotnetInstance) Symbolize(symbolReporter reporter.SymbolReporter,
frame *host.Frame, trace *libpf.Trace) error {
func (i *dotnetInstance) Symbolize(frame *host.Frame, frames *libpf.Frames) error {
if !frame.Type.IsInterpType(libpf.Dotnet) {
return interpreter.ErrMismatchInterpreterType
}
Expand All @@ -761,25 +744,25 @@ func (i *dotnetInstance) Symbolize(symbolReporter reporter.SymbolReporter,
// PC is executing:
// - on non-leaf frames, it is the return address
// - on leaf frames, it is the address after the CALL machine opcode
lineID := libpf.AddressOrLineno(pcOffset)
frameID := libpf.NewFrameID(module.fileID, lineID)
trace.AppendFrameID(libpf.DotnetFrame, frameID)
if !symbolReporter.FrameKnown(frameID) {
methodName := module.resolveR2RMethodName(pcOffset)
symbolReporter.FrameMetadata(&reporter.FrameMetadataArgs{
FrameID: frameID,
FunctionName: methodName,
SourceFile: module.simpleName,
})
}
frames.Append(&libpf.Frame{
Type: libpf.DotnetFrame,
FileID: module.fileID,
AddressOrLineno: libpf.AddressOrLineno(pcOffset),
FunctionName: module.resolveR2RMethodName(pcOffset),
SourceFile: module.simpleName,
})
case codeJIT:
// JITted frame in anonymous mapping
method, err := i.getMethod(codeHeaderPtr)
if err != nil {
return err
}
if method.index == 0 || method.classification == mcDynamic {
i.appendStubFrame(frames, codeDynamic)
break
}

ilOffset := method.mapPCOffsetToILOffset(pcOffset, frame.ReturnAddress)
fileID := method.module.fileID

// The Line ID format is:
// 4 bits Set to 0xf to indicate JIT frame.
Expand All @@ -788,25 +771,18 @@ func (i *dotnetInstance) Symbolize(symbolReporter reporter.SymbolReporter,
// pointing to CALL instruction if the debug info was accurate.
lineID := libpf.AddressOrLineno(0xf0000000+method.index)<<32 +
libpf.AddressOrLineno(ilOffset)

if method.index == 0 || method.classification == mcDynamic {
i.insertAndSymbolizeStubFrame(symbolReporter, trace, codeDynamic)
} else {
frameID := libpf.NewFrameID(fileID, lineID)
trace.AppendFrameID(libpf.DotnetFrame, frameID)
if !symbolReporter.FrameKnown(frameID) {
methodName := method.module.resolveMethodName(method.index)
symbolReporter.FrameMetadata(&reporter.FrameMetadataArgs{
FrameID: frameID,
SourceFile: method.module.simpleName,
FunctionName: methodName,
FunctionOffset: ilOffset,
})
}
}
methodName := method.module.resolveMethodName(method.index)
frames.Append(&libpf.Frame{
Type: libpf.DotnetFrame,
FileID: method.module.fileID,
AddressOrLineno: lineID,
SourceFile: method.module.simpleName,
FunctionName: methodName,
FunctionOffset: ilOffset,
})
default:
// Stub code
i.insertAndSymbolizeStubFrame(symbolReporter, trace, frameType)
i.appendStubFrame(frames, frameType)
}

sfCounter.ReportSuccess()
Expand Down
32 changes: 8 additions & 24 deletions interpreter/go/go.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ package golang // import "go.opentelemetry.io/ebpf-profiler/interpreter/go"

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

"go.opentelemetry.io/ebpf-profiler/host"
Expand All @@ -14,7 +13,6 @@ import (
"go.opentelemetry.io/ebpf-profiler/metrics"
"go.opentelemetry.io/ebpf-profiler/nativeunwind/elfunwindinfo"
"go.opentelemetry.io/ebpf-profiler/remotememory"
"go.opentelemetry.io/ebpf-profiler/reporter"
"go.opentelemetry.io/ebpf-profiler/successfailurecounter"
)

Expand Down Expand Up @@ -94,8 +92,7 @@ func (g *goInstance) Detach(_ interpreter.EbpfHandler, _ libpf.PID) error {
return nil
}

func (g *goInstance) Symbolize(symbolReporter reporter.SymbolReporter, frame *host.Frame,
trace *libpf.Trace) error {
func (g *goInstance) Symbolize(frame *host.Frame, frames *libpf.Frames) error {
if !frame.Type.IsInterpType(libpf.Native) {
return interpreter.ErrMismatchInterpreterType
}
Expand All @@ -107,27 +104,14 @@ func (g *goInstance) Symbolize(symbolReporter reporter.SymbolReporter, frame *ho
return fmt.Errorf("failed to symbolize 0x%x", frame.Lineno)
}

// The fnv hash Write() method calls cannot fail, so it's safe to ignore the errors.
h := fnv.New128a()
_, _ = h.Write([]byte(frame.File.StringNoQuotes()))
_, _ = h.Write([]byte(fn))
_, _ = 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: libpf.Intern(fn),
SourceFile: libpf.Intern(sourceFile),
SourceLine: libpf.SourceLineno(lineNo),
frames.Append(&libpf.Frame{
Type: libpf.GoFrame,
//TODO: File: convert the frame.File (host.FileID) to libpf.FileID here
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As Go frames are treated like any other interpreted frame, I think we can skip File here. WDYT?

Suggested change
//TODO: File: convert the frame.File (host.FileID) to libpf.FileID here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes sense to provide it here as a follow up as it provides additional information and this is a file backed mapping.

AddressOrLineno: frame.Lineno,
FunctionName: libpf.Intern(fn),
SourceFile: libpf.Intern(sourceFile),
SourceLine: libpf.SourceLineno(lineNo),
})

sfCounter.ReportSuccess()
return nil
}
63 changes: 14 additions & 49 deletions interpreter/go/go_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,53 +12,9 @@ import (
"go.opentelemetry.io/ebpf-profiler/libpf/pfelf"
"go.opentelemetry.io/ebpf-profiler/process"
"go.opentelemetry.io/ebpf-profiler/remotememory"
"go.opentelemetry.io/ebpf-profiler/reporter"
"go.opentelemetry.io/ebpf-profiler/util"
)

// mockReporter implements reporter.SymbolReporter for testing
type mockReporter struct {
b *testing.B
frameMetadata map[libpf.FrameID]*reporter.FrameMetadataArgs
}

func newMockReporter(b *testing.B) *mockReporter {
return &mockReporter{
b: b,
frameMetadata: make(map[libpf.FrameID]*reporter.FrameMetadataArgs),
}
}

func (m *mockReporter) CompareFunctionName(fn string) {
if len(m.frameMetadata) != 1 {
m.b.Fatalf("Expected a single entry but got %d", len(m.frameMetadata))
}
for _, v := range m.frameMetadata {
// The returned anonymous function has the suffic 'func1'.
// Therefore check only for a matching prefix.
if !strings.HasPrefix(v.FunctionName.String(), fn) {
m.b.Fatalf("Expected '%s()' but got '%s()'", fn, v.FunctionName)
}
}
}

func (m *mockReporter) FrameMetadata(args *reporter.FrameMetadataArgs) {
m.frameMetadata[args.FrameID] = args
}

func (m *mockReporter) ExecutableMetadata(args *reporter.ExecutableMetadataArgs) {
// Not used in this test
}

func (m *mockReporter) FrameKnown(frameID libpf.FrameID) bool {
_, exists := m.frameMetadata[frameID]
return exists
}

func (m *mockReporter) ExecutableKnown(fileID libpf.FileID) bool {
return false
}

func BenchmarkGolang(b *testing.B) {
pc, _, _, ok := runtime.Caller(1)
if !ok {
Expand All @@ -80,7 +36,6 @@ func BenchmarkGolang(b *testing.B) {
}
loaderInfo := interpreter.NewLoaderInfo(hostFileID, elfRef, []util.Range{})
rm := remotememory.NewProcessVirtualMemory(libpfPID)
symReporter := newMockReporter(b)

b.ResetTimer()
b.ReportAllocs()
Expand All @@ -95,16 +50,26 @@ func BenchmarkGolang(b *testing.B) {
b.Fatalf("Failed to create instance: %v", err)
}

trace := libpf.Trace{}
frames := libpf.Frames{}

if err := gI.Symbolize(symReporter, &host.Frame{
if err := gI.Symbolize(&host.Frame{
File: hostFileID,
Lineno: libpf.AddressOrLineno(pc),
Type: libpf.FrameType(libpf.Native),
}, &trace); err != nil {
}, &frames); err != nil {
b.Fatalf("Failed to symbolize 0x%x: %v", pc, err)
}

symReporter.CompareFunctionName(fn.Name())
if len(frames) != 1 {
b.Fatalf("Expected a single entry but got %d", len(frames))
}
for _, uniqueFrame := range frames {
f := uniqueFrame.Value()
// The returned anonymous function has the suffic 'func1'.
// Therefore check only for a matching prefix.
if !strings.HasPrefix(f.FunctionName.String(), fn.Name()) {
b.Fatalf("Expected '%s()' but got '%s()'", fn, f.FunctionName)
}
}
}
}
22 changes: 10 additions & 12 deletions interpreter/hotspot/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -361,23 +361,21 @@ func (d *hotspotData) Attach(_ interpreter.EbpfHandler, _ libpf.PID, bias libpf.
}
// In total there are about 100 to 200 intrinsics. We don't expect to encounter
// everyone single one. So we use a small cache size here than LruFunctionCacheSize.
addrToStubNameID, err :=
freelru.New[libpf.Address, libpf.AddressOrLineno](128,
libpf.Address.Hash32)
addrToStubName, err := freelru.New[libpf.Address, libpf.String](128, libpf.Address.Hash32)
if err != nil {
return nil, err
}

return &hotspotInstance{
d: d,
rm: rm,
bias: bias,
addrToSymbol: addrToSymbol,
addrToMethod: addrToMethod,
addrToJITInfo: addrToJITInfo,
addrToStubNameID: addrToStubNameID,
prefixes: libpf.Set[lpm.Prefix]{},
stubs: map[libpf.Address]StubRoutine{},
d: d,
rm: rm,
bias: bias,
addrToSymbol: addrToSymbol,
addrToMethod: addrToMethod,
addrToJITInfo: addrToJITInfo,
addrToStubName: addrToStubName,
prefixes: libpf.Set[lpm.Prefix]{},
stubs: map[libpf.Address]StubRoutine{},
}, nil
}

Expand Down
4 changes: 0 additions & 4 deletions interpreter/hotspot/hotspot.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,6 @@ import (
log "github.com/sirupsen/logrus"

"go.opentelemetry.io/ebpf-profiler/interpreter"
"go.opentelemetry.io/ebpf-profiler/libpf"
)

var (
Expand All @@ -122,9 +121,6 @@ var (
hiddenClassRegex = regexp.MustCompile(`\+0x[0-9a-f]{16}`)
hiddenClassMask = "+<hidden>"

// The FileID used for intrinsic stub frames
hotspotStubsFileID = libpf.NewStubFileID(libpf.HotSpotFrame)

_ interpreter.Data = &hotspotData{}
_ interpreter.Instance = &hotspotInstance{}
)
Expand Down
Loading