Skip to content

Commit 3c797f0

Browse files
florianlgnurizen
authored andcommitted
pdata: add tests for Generate() (open-telemetry#631)
Signed-off-by: Florian Lehner <florian.lehner@elastic.co>
1 parent df65706 commit 3c797f0

1 file changed

Lines changed: 298 additions & 0 deletions

File tree

reporter/internal/pdata/generate_test.go

Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)