@@ -253,3 +253,301 @@ func TestProfileDuration(t *testing.T) {
253253 })
254254 }
255255}
256+ func TestGenerate_EmptyTree (t * testing.T ) {
257+ d , err := New (100 , 100 , 100 , nil )
258+ require .NoError (t , err )
259+
260+ tree := make (samples.TraceEventsTree )
261+ profiles , err := d .Generate (tree , "agent" , "v1" )
262+ require .NoError (t , err )
263+ assert .Equal (t , 0 , profiles .ResourceProfiles ().Len ())
264+ }
265+
266+ func TestGenerate_SingleContainerSingleOrigin (t * testing.T ) {
267+ d , err := New (100 , 100 , 100 , nil )
268+ require .NoError (t , err )
269+
270+ fileID := libpf .NewFileID (1 , 2 )
271+ funcName := "main"
272+ filePath := "/bin/test"
273+ d .Executables .Add (fileID , samples.ExecInfo {FileName : filePath })
274+ d .Frames .Add (libpf .NewFrameID (fileID , 0x10 ), samples.SourceInfo {
275+ FunctionName : libpf .Intern (funcName ),
276+ FilePath : libpf .Intern (filePath ),
277+ LineNumber : 42 ,
278+ })
279+
280+ traceKey := samples.TraceAndMetaKey {
281+ ExecutablePath : filePath ,
282+ Comm : "testproc" ,
283+ Pid : 123 ,
284+ Tid : 456 ,
285+ ApmServiceName : "svc" ,
286+ }
287+ events := map [libpf.Origin ]samples.KeyToEventMapping {
288+ support .TraceOriginSampling : {
289+ traceKey : & samples.TraceEvents {
290+ Files : []libpf.FileID {fileID },
291+ Linenos : []libpf.AddressOrLineno {0x10 },
292+ FrameTypes : []libpf.FrameType {libpf .GoFrame },
293+ MappingStarts : []libpf.Address {0 },
294+ MappingEnds : []libpf.Address {0 },
295+ MappingFileOffsets : []uint64 {0 },
296+ Timestamps : []uint64 {100 },
297+ EnvVars : map [string ]string {"FOO" : "BAR" },
298+ },
299+ },
300+ }
301+ tree := samples.TraceEventsTree {
302+ "container1" : events ,
303+ }
304+
305+ profiles , err := d .Generate (tree , "agent" , "v1" )
306+ require .NoError (t , err )
307+ require .Equal (t , 1 , profiles .ResourceProfiles ().Len ())
308+ rp := profiles .ResourceProfiles ().At (0 )
309+ val , _ := rp .Resource ().Attributes ().Get (string (semconv .ContainerIDKey ))
310+ assert .Equal (t , "container1" , val .Str ())
311+ assert .Equal (t , semconv .SchemaURL , rp .SchemaUrl ())
312+ require .Equal (t , 1 , rp .ScopeProfiles ().Len ())
313+ sp := rp .ScopeProfiles ().At (0 )
314+ assert .Equal (t , "agent" , sp .Scope ().Name ())
315+ assert .Equal (t , "v1" , sp .Scope ().Version ())
316+ assert .Equal (t , semconv .SchemaURL , sp .SchemaUrl ())
317+ require .Equal (t , 1 , sp .Profiles ().Len ())
318+ prof := sp .Profiles ().At (0 )
319+ assert .Equal (t , pcommon .Timestamp (100 ), prof .Time ())
320+ assert .Equal (t , pcommon .Timestamp (0 ), prof .Duration ())
321+
322+ t .Run ("Check environment variable attribute" , func (t * testing.T ) {
323+ foundFOOKey := false
324+ foundBarValue := false
325+
326+ for _ , attr := range profiles .ProfilesDictionary ().AttributeTable ().All () {
327+ key := attr .Key ()
328+ value := attr .Value ()
329+ // Check if this is an environment variable attribute
330+ if key == "process.environment_variable.FOO" {
331+ foundFOOKey = true
332+ if value .Type () == pcommon .ValueTypeStr && value .Str () == "BAR" {
333+ foundBarValue = true
334+ }
335+ }
336+ }
337+ assert .True (t , foundFOOKey ,
338+ "Attribute 'process.environment_variable.FOO' should be in the attribute table" )
339+ assert .True (t , foundBarValue ,
340+ "Environment variable value 'bar' should be in the attribute table" )
341+ })
342+ }
343+
344+ func TestGenerate_MultipleOriginsAndContainers (t * testing.T ) {
345+ d , err := New (100 , 100 , 100 , nil )
346+ require .NoError (t , err )
347+
348+ fileID := libpf .NewFileID (5 , 6 )
349+ d .Executables .Add (fileID , samples.ExecInfo {FileName : "/bin/foo" })
350+ d .Frames .Add (libpf .NewFrameID (fileID , 0x20 ), samples.SourceInfo {
351+ FunctionName : libpf .Intern ("f" ),
352+ FilePath : libpf .Intern ("/bin/foo" ),
353+ LineNumber : 1 ,
354+ })
355+
356+ traceKey := samples.TraceAndMetaKey {ExecutablePath : "/bin/foo" }
357+ events1 := map [libpf.Origin ]samples.KeyToEventMapping {
358+ support .TraceOriginSampling : {
359+ traceKey : & samples.TraceEvents {
360+ Files : []libpf.FileID {fileID },
361+ Linenos : []libpf.AddressOrLineno {0x20 },
362+ FrameTypes : []libpf.FrameType {libpf .PythonFrame },
363+ Timestamps : []uint64 {1 , 2 },
364+ },
365+ },
366+ support .TraceOriginOffCPU : {
367+ traceKey : & samples.TraceEvents {
368+ Files : []libpf.FileID {fileID },
369+ Linenos : []libpf.AddressOrLineno {0x20 },
370+ FrameTypes : []libpf.FrameType {libpf .PythonFrame },
371+ Timestamps : []uint64 {3 , 4 },
372+ OffTimes : []int64 {10 , 20 },
373+ },
374+ },
375+ }
376+ events2 := map [libpf.Origin ]samples.KeyToEventMapping {
377+ support .TraceOriginSampling : {
378+ traceKey : & samples.TraceEvents {
379+ Files : []libpf.FileID {fileID },
380+ Linenos : []libpf.AddressOrLineno {0x20 },
381+ FrameTypes : []libpf.FrameType {libpf .PythonFrame },
382+ Timestamps : []uint64 {5 },
383+ },
384+ },
385+ }
386+ tree := samples.TraceEventsTree {
387+ "c1" : events1 ,
388+ "c2" : events2 ,
389+ }
390+
391+ profiles , err := d .Generate (tree , "agent" , "v2" )
392+ require .NoError (t , err )
393+ require .Equal (t , 2 , profiles .ResourceProfiles ().Len ())
394+
395+ // Since map iteration order is not guaranteed, we need to check containers by their ID
396+ containerProfileCounts := make (map [string ]int )
397+ for i := 0 ; i < profiles .ResourceProfiles ().Len (); i ++ {
398+ rp := profiles .ResourceProfiles ().At (i )
399+ val , exists := rp .Resource ().Attributes ().Get (string (semconv .ContainerIDKey ))
400+ require .True (t , exists )
401+ containerID := val .Str ()
402+ profileCount := rp .ScopeProfiles ().At (0 ).Profiles ().Len ()
403+ containerProfileCounts [containerID ] = profileCount
404+ }
405+
406+ // c1 has both origins, so 2 profiles
407+ assert .Equal (t , 2 , containerProfileCounts ["c1" ])
408+ // c2 has only sampling, so 1 profile
409+ assert .Equal (t , 1 , containerProfileCounts ["c2" ])
410+ }
411+
412+ func TestGenerate_StringAndFunctionTablePopulation (t * testing.T ) {
413+ d , err := New (100 , 100 , 100 , nil )
414+ require .NoError (t , err )
415+
416+ fileID := libpf .NewFileID (7 , 8 )
417+ funcName := "myfunc"
418+ filePath := "/bin/bar"
419+ d .Executables .Add (fileID , samples.ExecInfo {FileName : filePath })
420+ d .Frames .Add (libpf .NewFrameID (fileID , 0x30 ), samples.SourceInfo {
421+ FunctionName : libpf .Intern (funcName ),
422+ FilePath : libpf .Intern (filePath ),
423+ LineNumber : 123 ,
424+ })
425+
426+ traceKey := samples.TraceAndMetaKey {ExecutablePath : filePath }
427+ events := map [libpf.Origin ]samples.KeyToEventMapping {
428+ support .TraceOriginSampling : {
429+ traceKey : & samples.TraceEvents {
430+ Files : []libpf.FileID {fileID },
431+ Linenos : []libpf.AddressOrLineno {0x30 },
432+ FrameTypes : []libpf.FrameType {libpf .PythonFrame },
433+ Timestamps : []uint64 {42 },
434+ },
435+ },
436+ }
437+ tree := samples.TraceEventsTree {
438+ "c" : events ,
439+ }
440+
441+ profiles , err := d .Generate (tree , "agent" , "v3" )
442+ require .NoError (t , err )
443+ dic := profiles .ProfilesDictionary ()
444+ // The string table should contain "" as first element, then function name and file path
445+ strs := dic .StringTable ().At (0 )
446+ assert .Contains (t , strs , "" )
447+ // Convert StringSlice to a Go slice for assertion
448+ var stringTableSlice []string
449+ for i := 0 ; i < dic .StringTable ().Len (); i ++ {
450+ stringTableSlice = append (stringTableSlice , dic .StringTable ().At (i ))
451+ }
452+ assert .Contains (t , stringTableSlice , funcName )
453+ assert .Contains (t , stringTableSlice , filePath )
454+ // The function table should have the function name and file path indices set
455+ require .Equal (t , 1 , dic .FunctionTable ().Len ())
456+ fn := dic .FunctionTable ().At (0 )
457+ assert .Equal (t , funcName , dic .StringTable ().At (int (fn .NameStrindex ())))
458+ assert .Equal (t , filePath , dic .StringTable ().At (int (fn .FilenameStrindex ())))
459+ }
460+
461+ func TestGenerate_NativeFrame (t * testing.T ) {
462+ d , err := New (100 , 100 , 100 , nil )
463+ require .NoError (t , err )
464+
465+ fileID := libpf .NewFileID (9 , 10 )
466+ filePath := "/usr/lib/libexample.so"
467+ d .Executables .Add (fileID , samples.ExecInfo {FileName : filePath })
468+
469+ traceKey := samples.TraceAndMetaKey {
470+ ExecutablePath : filePath ,
471+ Comm : "native_app" ,
472+ Pid : 789 ,
473+ Tid : 1011 ,
474+ }
475+ events := map [libpf.Origin ]samples.KeyToEventMapping {
476+ support .TraceOriginSampling : {
477+ traceKey : & samples.TraceEvents {
478+ Files : []libpf.FileID {fileID },
479+ Linenos : []libpf.AddressOrLineno {0x1000 },
480+ FrameTypes : []libpf.FrameType {libpf .NativeFrame },
481+ MappingStarts : []libpf.Address {0x1000 },
482+ MappingEnds : []libpf.Address {0x2000 },
483+ MappingFileOffsets : []uint64 {0x100 },
484+ Timestamps : []uint64 {789 },
485+ },
486+ },
487+ }
488+ tree := samples.TraceEventsTree {
489+ "native_container" : events ,
490+ }
491+
492+ profiles , err := d .Generate (tree , "agent" , "v1" )
493+ require .NoError (t , err )
494+ require .Equal (t , 1 , profiles .ResourceProfiles ().Len ())
495+
496+ // Check resource profile attributes
497+ rp := profiles .ResourceProfiles ().At (0 )
498+ val , exists := rp .Resource ().Attributes ().Get (string (semconv .ContainerIDKey ))
499+ require .True (t , exists )
500+ assert .Equal (t , "native_container" , val .Str ())
501+
502+ // Check scope profile
503+ require .Equal (t , 1 , rp .ScopeProfiles ().Len ())
504+ sp := rp .ScopeProfiles ().At (0 )
505+ assert .Equal (t , "agent" , sp .Scope ().Name ())
506+ assert .Equal (t , "v1" , sp .Scope ().Version ())
507+
508+ // Check profile
509+ require .Equal (t , 1 , sp .Profiles ().Len ())
510+ prof := sp .Profiles ().At (0 )
511+ assert .Equal (t , pcommon .Timestamp (789 ), prof .Time ())
512+ assert .Equal (t , pcommon .Timestamp (0 ), prof .Duration ())
513+
514+ // Verify profile contains one sample
515+ assert .Equal (t , 1 , prof .Sample ().Len ())
516+ sample := prof .Sample ().At (0 )
517+ assert .Len (t , sample .Value ().AsRaw (), 1 )
518+ assert .Equal (t , int64 (1 ), sample .Value ().At (0 )) // sampling count
519+
520+ // Check that the mapping table contains our native frame mapping
521+ // (plus the dummy mapping at index 0)
522+ dic := profiles .ProfilesDictionary ()
523+ assert .GreaterOrEqual (t , dic .MappingTable ().Len (), 2 ,
524+ "Mapping table should have dummy mapping + native frame mapping" )
525+
526+ // Find the mapping for our native frame (not the dummy one at index 0)
527+ var nativeMapping pprofile.Mapping
528+ found := false
529+ for i := 1 ; i < dic .MappingTable ().Len (); i ++ { // Skip dummy mapping at index 0
530+ mapping := dic .MappingTable ().At (i )
531+ if mapping .MemoryStart () == uint64 (0x1000 ) {
532+ nativeMapping = mapping
533+ found = true
534+ break
535+ }
536+ }
537+ require .True (t , found , "Should find mapping for native frame" )
538+
539+ // Verify mapping details
540+ assert .Equal (t , uint64 (0x1000 ), nativeMapping .MemoryStart ())
541+ assert .Equal (t , uint64 (0x2000 ), nativeMapping .MemoryLimit ())
542+ assert .Equal (t , uint64 (0x100 ), nativeMapping .FileOffset ())
543+
544+ // Verify the filename is correctly set in the mapping
545+ filenameStrIndex := nativeMapping .FilenameStrindex ()
546+ filename := dic .StringTable ().At (int (filenameStrIndex ))
547+ assert .Equal (t , filePath , filename )
548+
549+ // For native frames, function information is not populated in the function table
550+ // since it's resolved by the backend. The function table should be empty.
551+ assert .Equal (t , 0 , dic .FunctionTable ().Len (),
552+ "Function table should be empty for native frames" )
553+ }
0 commit comments