@@ -25,9 +25,12 @@ import (
2525 "go.opentelemetry.io/ebpf-profiler/libpf"
2626 "go.opentelemetry.io/ebpf-profiler/libpf/pfelf"
2727 "go.opentelemetry.io/ebpf-profiler/libpf/pfunsafe"
28+ "go.opentelemetry.io/ebpf-profiler/lpm"
2829 "go.opentelemetry.io/ebpf-profiler/metrics"
2930 npsr "go.opentelemetry.io/ebpf-profiler/nopanicslicereader"
31+ "go.opentelemetry.io/ebpf-profiler/process"
3032 "go.opentelemetry.io/ebpf-profiler/remotememory"
33+ "go.opentelemetry.io/ebpf-profiler/reporter"
3134 "go.opentelemetry.io/ebpf-profiler/successfailurecounter"
3235 "go.opentelemetry.io/ebpf-profiler/support"
3336 "go.opentelemetry.io/ebpf-profiler/util"
@@ -104,14 +107,16 @@ var (
104107 // regex to extract a version from a string
105108 rubyVersionRegex = regexp .MustCompile (`^(\d+)\.(\d+)\.(\d+)$` )
106109
107- unknownCfunc = libpf .Intern ("<unknown cfunc>" )
108- cfuncDummyFile = libpf .Intern ("<cfunc>" )
109- rubyGcFrame = libpf .Intern ("(garbage collection)" )
110- rubyGcRunning = libpf .Intern ("(running)" )
111- rubyGcMarking = libpf .Intern ("(marking)" )
112- rubyGcSweeping = libpf .Intern ("(sweeping)" )
113- rubyGcCompacting = libpf .Intern ("(compacting)" )
114- rubyGcDummyFile = libpf .Intern ("<gc>" )
110+ unknownCfunc = libpf .Intern ("<unknown cfunc>" )
111+ cfuncDummyFile = libpf .Intern ("<cfunc>" )
112+ rubyGcFrame = libpf .Intern ("(garbage collection)" )
113+ rubyGcRunning = libpf .Intern ("(running)" )
114+ rubyGcMarking = libpf .Intern ("(marking)" )
115+ rubyGcSweeping = libpf .Intern ("(sweeping)" )
116+ rubyGcCompacting = libpf .Intern ("(compacting)" )
117+ rubyGcDummyFile = libpf .Intern ("<gc>" )
118+ rubyJitDummyFrame = libpf .Intern ("<unknown jit code>" )
119+ rubyJitDummyFile = libpf .Intern ("<jitted code>" )
115120 // compiler check to make sure the needed interfaces are satisfied
116121 _ interpreter.Data = & rubyData {}
117122 _ interpreter.Instance = & rubyInstance {}
@@ -376,6 +381,8 @@ func (r *rubyData) Attach(ebpf interpreter.EbpfHandler, pid libpf.PID, bias libp
376381 procInfo : & cdata ,
377382 globalSymbolsAddr : r .globalSymbolsAddr + bias ,
378383 addrToString : addrToString ,
384+ mappings : make (map [process.RawMapping ]* uint32 ),
385+ prefixes : make (map [lpm.Prefix ]* uint32 ),
379386 memPool : sync.Pool {
380387 New : func () any {
381388 buf := make ([]byte , 512 )
@@ -425,6 +432,7 @@ type rubyInstance struct {
425432
426433 // lastId is a cached copy index of the final entry in the global symbol table
427434 lastId uint32
435+
428436 // globalSymbolsAddr is the offset of the global symbol table, for looking up ruby symbolic ids
429437 globalSymbolsAddr libpf.Address
430438
@@ -437,6 +445,13 @@ type rubyInstance struct {
437445 // maxSize is the largest number we did see in the last reporting interval for size
438446 // in getRubyLineNo.
439447 maxSize atomic.Uint32
448+
449+ // mappings is indexed by the Mapping to its generation
450+ mappings map [process.RawMapping ]* uint32
451+ // prefixes is indexed by the prefix added to ebpf maps (to be cleaned up) to its generation
452+ prefixes map [lpm.Prefix ]* uint32
453+ // mappingGeneration is the current generation (so old entries can be pruned)
454+ mappingGeneration uint32
440455}
441456
442457func (r * rubyInstance ) Detach (ebpf interpreter.EbpfHandler , pid libpf.PID ) error {
@@ -1115,6 +1130,15 @@ func (r *rubyInstance) Symbolize(ef libpf.EbpfFrame, frames *libpf.Frames, _ lib
11151130 SourceLine : 0 ,
11161131 })
11171132 return nil
1133+ case support .RubyFrameTypeJit :
1134+ label := rubyJitDummyFrame
1135+ frames .Append (& libpf.Frame {
1136+ Type : libpf .RubyFrame ,
1137+ FunctionName : label ,
1138+ SourceFile : rubyJitDummyFile ,
1139+ SourceLine : 0 ,
1140+ })
1141+ return nil
11181142 default :
11191143 return fmt .Errorf ("Unable to get CME or ISEQ from frame address (%d)" , frameAddrType )
11201144 }
@@ -1244,6 +1268,92 @@ func profileFrameFullLabel(classPath, label, baseLabel, methodName libpf.String,
12441268 return libpf .Intern (profileLabel )
12451269}
12461270
1271+ func (r * rubyInstance ) SynchronizeMappings (ebpf interpreter.EbpfHandler ,
1272+ _ reporter.ExecutableReporter , pr process.Process , mappings []process.RawMapping ) error {
1273+ var jitMapping * process.RawMapping
1274+
1275+ pid := pr .PID ()
1276+ jitFound := false
1277+ r .mappingGeneration ++
1278+
1279+ log .Debugf ("Synchronizing ruby mappings" )
1280+
1281+ for idx := range mappings {
1282+ m := & mappings [idx ]
1283+ if ! m .IsExecutable () || ! m .IsAnonymous () {
1284+ continue
1285+ }
1286+ // If prctl is allowed, ruby should label the memory region
1287+ // always prefer that
1288+ if strings .Contains (m .Path , "jit_reserve_addr_space" ) {
1289+ jitMapping = m
1290+ jitFound = true
1291+ }
1292+ // Use the first executable anon region we find if it isn't labeled
1293+ // If we find more, prefer ones earlier in memory or larger in size
1294+ if ! jitFound && (jitMapping == nil || m .Vaddr < jitMapping .Vaddr || m .Length > jitMapping .Length ) {
1295+ // Don't set jitFound here as it is a heuristic, we aren't sure
1296+ // could be on a system without linux config flag to allow prctl to label memoy
1297+ jitMapping = m
1298+ }
1299+
1300+ if _ , exists := r .mappings [* m ]; exists {
1301+ * r .mappings [* m ] = r .mappingGeneration
1302+ continue
1303+ }
1304+
1305+ // Generate a new uint32 pointer which is shared for mapping and the prefixes it owns
1306+ // so updating the mapping above will reflect to prefixes also.
1307+ mappingGeneration := r .mappingGeneration
1308+ r .mappings [* m ] = & mappingGeneration
1309+
1310+ // Just assume all anonymous and executable mappings are Ruby for now
1311+ log .Debugf ("Enabling Ruby interpreter for %#x/%#x" , m .Vaddr , m .Length )
1312+
1313+ prefixes , err := lpm .CalculatePrefixList (m .Vaddr , m .Vaddr + m .Length )
1314+ if err != nil {
1315+ return fmt .Errorf ("new anonymous mapping lpm failure %#x/%#x" , m .Vaddr , m .Length )
1316+ }
1317+
1318+ for _ , prefix := range prefixes {
1319+ _ , exists := r .prefixes [prefix ]
1320+ if ! exists {
1321+ err := ebpf .UpdatePidInterpreterMapping (pid , prefix , support .ProgUnwindRuby , 0 , 0 )
1322+ if err != nil {
1323+ return err
1324+ }
1325+ }
1326+ r .prefixes [prefix ] = & mappingGeneration
1327+ }
1328+ }
1329+ if jitMapping != nil && (r .procInfo .Jit_start != jitMapping .Vaddr || r .procInfo .Jit_end != jitMapping .Vaddr + jitMapping .Length ) {
1330+ r .procInfo .Jit_start = jitMapping .Vaddr
1331+ r .procInfo .Jit_end = jitMapping .Vaddr + jitMapping .Length
1332+ if err := ebpf .UpdateProcData (libpf .Ruby , pr .PID (), unsafe .Pointer (r .procInfo )); err != nil {
1333+ return err
1334+ }
1335+ log .Debugf ("Added jit mapping %08x ruby proc info, %08x" , r .procInfo .Jit_start , r .procInfo .Jit_end )
1336+ }
1337+ // Remove prefixes not seen
1338+ for prefix , generationPtr := range r .prefixes {
1339+ if * generationPtr == r .mappingGeneration {
1340+ continue
1341+ }
1342+ log .Debugf ("Delete Ruby prefix %#v" , prefix )
1343+ _ = ebpf .DeletePidInterpreterMapping (pid , prefix )
1344+ delete (r .prefixes , prefix )
1345+ }
1346+ for m , generationPtr := range r .mappings {
1347+ if * generationPtr == r .mappingGeneration {
1348+ continue
1349+ }
1350+ log .Debugf ("Disabling Ruby for %#x/%#x" , m .Vaddr , m .Length )
1351+ delete (r .mappings , m )
1352+ }
1353+
1354+ return nil
1355+ }
1356+
12471357func (r * rubyInstance ) GetAndResetMetrics () ([]metrics.Metric , error ) {
12481358 addrToStringStats := r .addrToString .ResetMetrics ()
12491359
0 commit comments