Skip to content

Commit ff0c9b5

Browse files
committed
Consolidate reads and user more LRU caches
1 parent 2521ea1 commit ff0c9b5

File tree

1 file changed

+130
-66
lines changed

1 file changed

+130
-66
lines changed

interpreter/beam/beam.go

Lines changed: 130 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,16 @@ package beam // import "go.opentelemetry.io/ebpf-profiler/interpreter/beam"
99
// that share the same bytecode, such as Elixir and Gleam.
1010

1111
import (
12+
"encoding/binary"
1213
"fmt"
14+
"hash/crc32"
1315
"regexp"
1416
"strings"
1517
"unsafe"
1618

1719
"github.com/elastic/go-freelru"
1820
"go.opentelemetry.io/ebpf-profiler/internal/log"
21+
"go.opentelemetry.io/ebpf-profiler/libpf/hash"
1922

2023
"go.opentelemetry.io/ebpf-profiler/host"
2124
"go.opentelemetry.io/ebpf-profiler/interpreter"
@@ -103,16 +106,23 @@ type beamData struct {
103106
}
104107
}
105108

109+
type beamMfa struct {
110+
module uint32
111+
function uint32
112+
arity uint32
113+
}
114+
106115
type beamInstance struct {
107116
interpreter.InstanceStubs
108117

109-
pid libpf.PID
110-
data *beamData
111-
rm remotememory.RemoteMemory
112-
rangesPtr libpf.Address
113-
atomTable libpf.Address
114-
atomCache map[uint32]string
115-
stringCache *freelru.LRU[libpf.Address, libpf.String]
118+
pid libpf.PID
119+
data *beamData
120+
rm remotememory.RemoteMemory
121+
rangesPtr libpf.Address
122+
atomTable libpf.Address
123+
atomCache *freelru.LRU[uint32, libpf.String]
124+
mfaNameCache *freelru.LRU[beamMfa, libpf.String]
125+
stringCache *freelru.LRU[libpf.Address, libpf.String]
116126

117127
// prefixes is indexed by the prefix added to ebpf maps (to be cleaned up) to its generation
118128
prefixes map[lpm.Prefix]uint32
@@ -230,6 +240,7 @@ func Loader(ebpf interpreter.EbpfHandler, info *interpreter.LoaderInfo) (interpr
230240
vms.beamCodeHeader.lineTable = 72
231241
vms.ertsCodeInfo.sizeOf = 40
232242
vms.ertsCodeInfo.mfa = 16
243+
vms.ertsCodeMfa.sizeOf = 24
233244
vms.ertsCodeMfa.module = 0
234245
vms.ertsCodeMfa.function = 8
235246
vms.ertsCodeMfa.arity = 16
@@ -283,21 +294,41 @@ func (d *beamData) Attach(ebpf interpreter.EbpfHandler, pid libpf.PID, bias libp
283294
return nil, err
284295
}
285296

297+
atomCache, err := freelru.New[uint32, libpf.String](
298+
interpreter.LruFunctionCacheSize, hash.Uint32)
299+
if err != nil {
300+
return nil, err
301+
}
302+
303+
hashMFA := func(key beamMfa) uint32 {
304+
data := make([]byte, 12)
305+
binary.LittleEndian.PutUint32(data[0:4], key.module)
306+
binary.LittleEndian.PutUint32(data[4:8], key.function)
307+
binary.LittleEndian.PutUint32(data[8:12], key.arity)
308+
return crc32.ChecksumIEEE(data)
309+
}
310+
mfaNameCache, err := freelru.New[beamMfa, libpf.String](
311+
interpreter.LruFunctionCacheSize, hashMFA)
312+
if err != nil {
313+
return nil, err
314+
}
315+
286316
stringCache, err := freelru.New[libpf.Address, libpf.String](
287317
interpreter.LruFunctionCacheSize, libpf.Address.Hash32)
288318
if err != nil {
289319
return nil, err
290320
}
291321

292322
return &beamInstance{
293-
pid: pid,
294-
data: d,
295-
rm: rm,
296-
prefixes: make(map[lpm.Prefix]uint32),
297-
rangesPtr: bias + libpf.Address(d.r),
298-
atomTable: bias + libpf.Address(d.ertsAtomTable),
299-
atomCache: make(map[uint32]string),
300-
stringCache: stringCache,
323+
pid: pid,
324+
data: d,
325+
rm: rm,
326+
prefixes: make(map[lpm.Prefix]uint32),
327+
rangesPtr: bias + libpf.Address(d.r),
328+
atomTable: bias + libpf.Address(d.ertsAtomTable),
329+
atomCache: atomCache,
330+
mfaNameCache: mfaNameCache,
331+
stringCache: stringCache,
301332
}, nil
302333
}
303334

@@ -357,60 +388,74 @@ func (i *beamInstance) Symbolize(frame *host.Frame, frames *libpf.Frames) error
357388
codeHeader := libpf.Address(frame.File)
358389
pc := libpf.Address(frame.Lineno)
359390

360-
log.Debugf("BEAM symbolizing pc: 0x%x", pc)
361-
362-
functionIndex, moduleID, functionID, arity, err := i.findMFA(pc, codeHeader)
391+
functionIndex, mfa, err := i.findMFA(pc, codeHeader)
363392
if err != nil {
364393
return err
365394
}
366395

367-
moduleName, err := i.lookupAtom(moduleID)
368-
if err != nil {
369-
return err
370-
}
371-
functionName, err := i.lookupAtom(functionID)
372-
if err != nil {
373-
return err
374-
}
375-
376-
mfaName := ""
377-
if strings.HasPrefix(moduleName, "Elixir.") {
378-
// This is an Elixir module, so format the function using Elixir syntax (without the "Elixir." prefix)
379-
mfaName = fmt.Sprintf("%s.%s/%d", moduleName[7:], functionName, arity)
396+
var mfaName libpf.String
397+
if value, ok := i.mfaNameCache.Get(mfa); ok {
398+
mfaName = value
380399
} else {
381-
// Assume it's Erlang and format it using Erlang syntax
382-
mfaName = fmt.Sprintf("%s:%s/%d", moduleName, functionName, arity)
400+
moduleName, err := i.lookupAtom(mfa.module)
401+
if err != nil {
402+
return err
403+
}
404+
functionName, err := i.lookupAtom(mfa.function)
405+
if err != nil {
406+
return err
407+
}
408+
409+
if strings.HasPrefix(moduleName.String(), "Elixir.") {
410+
// This is an Elixir module, so format the function using Elixir syntax (without the "Elixir." prefix)
411+
mfaName = libpf.Intern(fmt.Sprintf("%s.%s/%d", moduleName.String()[7:], functionName, mfa.arity))
412+
} else {
413+
// Assume it's Erlang and format it using Erlang syntax
414+
mfaName = libpf.Intern(fmt.Sprintf("%s:%s/%d", moduleName, functionName, mfa.arity))
415+
}
416+
417+
i.mfaNameCache.Add(mfa, mfaName)
383418
}
384419

385420
fileName, lineNumber, err := i.findFileLocation(codeHeader, functionIndex, pc)
386-
if err != nil {
387-
return err
421+
if err == nil {
422+
log.Debugf("BEAM Found function %s at %s:%d", mfaName, fileName, lineNumber)
423+
frames.Append(&libpf.Frame{
424+
Type: libpf.BEAMFrame,
425+
FunctionName: mfaName,
426+
SourceFile: fileName,
427+
SourceLine: libpf.SourceLineno(lineNumber),
428+
})
429+
} else {
430+
log.Debugf("BEAM Found function %s", mfaName)
431+
frames.Append(&libpf.Frame{
432+
Type: libpf.BEAMFrame,
433+
FunctionName: mfaName,
434+
})
388435
}
389436

390-
log.Debugf("BEAM Found function %s at %s:%d", mfaName, fileName, lineNumber)
391-
frames.Append(&libpf.Frame{
392-
Type: libpf.BEAMFrame,
393-
FunctionName: libpf.Intern(mfaName),
394-
SourceFile: fileName,
395-
SourceLine: libpf.SourceLineno(lineNumber),
396-
})
397-
398437
return nil
399438
}
400439

401-
func (i *beamInstance) findMFA(pc libpf.Address, codeHeader libpf.Address) (functionIndex uint64, moduleID uint32, functionID uint32, arity uint32, err error) {
440+
func (i *beamInstance) findMFA(pc libpf.Address, codeHeader libpf.Address) (functionIndex uint64, mfa beamMfa, err error) {
402441
vms := i.data.vmStructs
403442

404443
numFunctions := i.rm.Uint32(codeHeader + libpf.Address(vms.beamCodeHeader.numFunctions))
405444
functions := codeHeader + libpf.Address(vms.beamCodeHeader.functions)
406445

446+
midBuffer := make([]byte, 16)
447+
407448
ertsCodeInfo := libpf.Address(0)
408449
lowIdx := uint64(0)
409450
highIdx := uint64(numFunctions) - 1
410451
for lowIdx < highIdx {
411452
midIdx := lowIdx + (highIdx-lowIdx)/2
412-
midStart := i.rm.Ptr(functions + libpf.Address(midIdx*8))
413-
midEnd := i.rm.Ptr(functions + libpf.Address((midIdx+1)*8))
453+
err := i.rm.Read(functions+libpf.Address(midIdx*8), midBuffer)
454+
if err != nil {
455+
return 0, beamMfa{}, fmt.Errorf("BEAM unable to read codeHeader.functions[%d] for codeHeader 0x%x", midIdx, codeHeader)
456+
}
457+
midStart := nopanicslicereader.Ptr(midBuffer, 0)
458+
midEnd := nopanicslicereader.Ptr(midBuffer, 8)
414459
if pc < midStart {
415460
highIdx = midIdx
416461
} else if pc >= midEnd {
@@ -419,16 +464,20 @@ func (i *beamInstance) findMFA(pc libpf.Address, codeHeader libpf.Address) (func
419464
ertsCodeInfo = midStart
420465
functionIndex = midIdx
421466

422-
mfa := ertsCodeInfo + libpf.Address(vms.ertsCodeInfo.mfa)
423-
moduleID := i.rm.Uint32(mfa + libpf.Address(vms.ertsCodeMfa.module))
424-
functionID := i.rm.Uint32(mfa + libpf.Address(vms.ertsCodeMfa.function))
425-
arity := i.rm.Uint32(mfa + libpf.Address(vms.ertsCodeMfa.arity))
467+
data := make([]byte, vms.ertsCodeMfa.sizeOf)
468+
err = i.rm.Read(ertsCodeInfo+libpf.Address(vms.ertsCodeInfo.mfa), data)
469+
if err != nil {
470+
return 0, beamMfa{}, fmt.Errorf("BEAM unable to look up MFA at for ertsCodeInfo 0x%x", ertsCodeInfo)
471+
}
472+
mfa.module = nopanicslicereader.Uint32(data, uint(vms.ertsCodeMfa.module))
473+
mfa.function = nopanicslicereader.Uint32(data, uint(vms.ertsCodeMfa.function))
474+
mfa.arity = nopanicslicereader.Uint32(data, uint(vms.ertsCodeMfa.arity))
426475

427-
return functionIndex, moduleID, functionID, arity, nil
476+
return functionIndex, mfa, nil
428477
}
429478
}
430479

431-
return 0, 0, 0, 0, fmt.Errorf("BEAM unable to find the MFA for PC 0x%x in expected code range", pc)
480+
return 0, beamMfa{}, fmt.Errorf("BEAM unable to find the MFA for PC 0x%x in expected code range", pc)
432481
}
433482

434483
func (i *beamInstance) findFileLocation(codeHeader libpf.Address, functionIndex uint64, pc libpf.Address) (fileName libpf.String, lineNumber uint64, err error) {
@@ -437,21 +486,35 @@ func (i *beamInstance) findFileLocation(codeHeader libpf.Address, functionIndex
437486
lineTable := i.rm.Ptr(codeHeader + libpf.Address(vms.beamCodeHeader.lineTable))
438487
functionTable := lineTable + libpf.Address(vms.beamCodeLineTab.funcTab)
439488

440-
lineLow := i.rm.Ptr(functionTable + libpf.Address(8*functionIndex))
441-
lineHigh := i.rm.Ptr(functionTable + libpf.Address(8*(functionIndex+1)))
489+
lineRange := make([]byte, 16)
490+
err = i.rm.Read(functionTable+libpf.Address(8*functionIndex), lineRange)
491+
if err != nil {
492+
return libpf.NullString, 0, fmt.Errorf("BEAM failed to read function table info")
493+
}
494+
lineLow := nopanicslicereader.Ptr(lineRange, 0)
495+
lineHigh := nopanicslicereader.Ptr(lineRange, 8)
442496

497+
lineMidBuffer := make([]byte, 16)
443498
// We need to align the lineMid values on 8-byte address boundaries
444499
bitmask := libpf.Address(^(uint64(0xf)))
445500
for lineHigh > lineLow {
446501
lineMid := lineLow + ((lineHigh-lineLow)/2)&bitmask
447-
448-
if pc < i.rm.Ptr(lineMid) {
502+
err := i.rm.Read(lineMid, lineMidBuffer)
503+
if err != nil {
504+
return libpf.NullString, 0, fmt.Errorf("BEAM failed to read line table")
505+
}
506+
if pc < nopanicslicereader.Ptr(lineMidBuffer, 0) {
449507
lineHigh = lineMid
450-
} else if pc < i.rm.Ptr(lineMid+libpf.Address(8)) {
508+
} else if pc < nopanicslicereader.Ptr(lineMidBuffer, 8) {
451509
firstLine := i.rm.Ptr(functionTable)
452510
locIndex := uint32((lineMid - firstLine) / 8)
453-
locSize := i.rm.Uint32(lineTable + libpf.Address(vms.beamCodeLineTab.locSize))
454-
locTab := i.rm.Ptr(lineTable + libpf.Address(vms.beamCodeLineTab.locTab))
511+
lineTab := make([]byte, vms.beamCodeLineTab.sizeOf)
512+
err = i.rm.Read(lineTable, lineTab)
513+
if err != nil {
514+
return libpf.NullString, 0, fmt.Errorf("BEAM failed to read line table info")
515+
}
516+
locSize := nopanicslicereader.Uint32(lineTab, uint(vms.beamCodeLineTab.locSize))
517+
locTab := nopanicslicereader.Ptr(lineTab, uint(vms.beamCodeLineTab.locTab))
455518
locAddr := locTab + libpf.Address(locSize*locIndex)
456519
loc := uint64(0)
457520
if locSize == 2 {
@@ -472,8 +535,8 @@ func (i *beamInstance) findFileLocation(codeHeader libpf.Address, functionIndex
472535
return libpf.NullString, 0, fmt.Errorf("BEAM unable to find file and line number")
473536
}
474537

475-
func (i *beamInstance) lookupAtom(index uint32) (string, error) {
476-
if value, ok := i.atomCache[index]; ok {
538+
func (i *beamInstance) lookupAtom(index uint32) (libpf.String, error) {
539+
if value, ok := i.atomCache.Get(index); ok {
477540
return value, nil
478541
}
479542

@@ -490,7 +553,7 @@ func (i *beamInstance) lookupAtom(index uint32) (string, error) {
490553
case "27":
491554
err := i.rm.Read(i.rm.Ptr(entry+libpf.Address(vms.atom.name)), name)
492555
if err != nil {
493-
return "", fmt.Errorf("BEAM Unable to lookup atom with index %d: %v", index, err)
556+
return libpf.NullString, fmt.Errorf("BEAM Unable to lookup atom with index %d: %v", index, err)
494557
}
495558
case "28":
496559
// Implementation based on https://github.com/erlang/otp/blob/OTP-28.0.2/erts/etc/unix/etp-commands.in#L657-L674
@@ -500,15 +563,16 @@ func (i *beamInstance) lookupAtom(index uint32) (string, error) {
500563
if subtag == uint64(i.data.etpHeapBitsSubtag) {
501564
err := i.rm.Read(unboxed+libpf.Address(vms.erlHeapBits.data), name)
502565
if err != nil {
503-
return "", fmt.Errorf("BEAM Unable to lookup atom with index %d (ErlHeapBits tag): %v", index, err)
566+
return libpf.NullString, fmt.Errorf("BEAM Unable to lookup atom with index %d (ErlHeapBits tag): %v", index, err)
504567
}
505568
} else {
506-
return "", fmt.Errorf("BEAM Unable to lookup atom with index %d: expected boxed value subtag 0x%x, found 0x%x", index, i.data.etpHeapBitsSubtag, subtag)
569+
return libpf.NullString, fmt.Errorf("BEAM Unable to lookup atom with index %d: expected boxed value subtag 0x%x, found 0x%x", index, i.data.etpHeapBitsSubtag, subtag)
507570
}
508571
}
509572

510-
i.atomCache[index] = string(name)
511-
return string(name), nil
573+
nameString := libpf.Intern(string(name))
574+
i.atomCache.Add(index, nameString)
575+
return nameString, nil
512576
}
513577

514578
func (i *beamInstance) readErlangString(eterm libpf.Address, maxLength uint64) libpf.String {

0 commit comments

Comments
 (0)