Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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