Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Correct inline existing functions in flamegraphs #485

Merged
merged 2 commits into from Dec 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
87 changes: 35 additions & 52 deletions pkg/storage/flamegraph_flat.go
Expand Up @@ -48,13 +48,13 @@ func (fp *FlatProfile) Samples() map[string]*Sample {

func GenerateFlamegraphFlat(ctx context.Context, tracer trace.Tracer, metaStore metastore.ProfileMetaStore, p InstantFlatProfile) (*pb.Flamegraph, error) {
rootNode := &pb.FlamegraphNode{}
cur := rootNode
current := rootNode

var height int32
samples := p.Samples()

locationUUIDSeen := map[string]struct{}{}
locationUUIDs := [][]byte{}
for _, s := range p.Samples() {
for _, s := range samples {
for _, l := range s.Location {
if _, seen := locationUUIDSeen[string(l.ID[:])]; !seen {
locationUUIDSeen[string(l.ID[:])] = struct{}{}
Expand All @@ -68,68 +68,51 @@ func GenerateFlamegraphFlat(ctx context.Context, tracer trace.Tracer, metaStore
return nil, fmt.Errorf("get locations by ids: %w", err)
}

for _, s := range p.Samples() {
var height int32

for _, s := range samples {
if int32(len(s.Location)) > height {
height = int32(len(s.Location))
}

// Reverse walking the location as stacked location are like 3 > 2 > 1 > 0 where 0 is the root.
for i := len(s.Location) - 1; i >= 0; i-- {
location := locationsMap[string(s.Location[i].ID[:])] // use the fully populated location
nextID := location.ID

index := sort.Search(len(cur.Children), func(i int) bool {
cmp := bytes.Compare(cur.Children[i].GetMeta().GetLocation().GetId(), nextID[:])
return cmp == 0 || cmp == 1
})

if index < len(cur.Children) && bytes.Equal(cur.Children[index].GetMeta().GetLocation().GetId(), nextID[:]) {
// The node already exists in the flamegraph, we can simply add the value and diff value and continue with this child's children next.
cur = cur.Children[index]
cur.Cumulative += s.Value
cur.Diff += s.DiffValue
} else {
nodes := locationToTreeNodes(location)
if len(nodes) > 1 {
// There are multiple inlined nodes.
// We iterate over these nodes, adding value and diff value,
// and make the first node the leaf whereas the last is the root of this subtree.
// In the end we insert the last node (sub tree root node) as child to the cur children.
// We set the leaf node (first node) as cur to continue inserting the next nodes with it.
for i := len(nodes) - 1; i >= 0; i-- {
node := nodes[i]
node.Cumulative += s.Value
node.Diff += s.DiffValue
if i > 0 {
node.Children = []*pb.FlamegraphNode{nodes[i-1]}
} else {
node.Children = nil
}
}

newChildren := make([]*pb.FlamegraphNode, len(cur.Children)+1)
copy(newChildren, cur.Children[:index])
newChildren[index] = nodes[len(nodes)-1] // Add the root of these nodes to the current children
copy(newChildren[index+1:], cur.Children[index:])
cur.Children = newChildren

cur = nodes[0] // Continue with the leaf of these nodes

nodes := locationToTreeNodes(location)
for j := len(nodes) - 1; j >= 0; j-- {
node := nodes[j]

index := sort.Search(len(current.GetChildren()), func(i int) bool {
cmp := bytes.Compare(current.GetChildren()[i].GetMeta().GetLocation().GetId(), node.GetMeta().GetLocation().GetId())
return cmp == 0 || cmp == 1
})

if index < len(current.GetChildren()) && bytes.Equal(
current.GetChildren()[index].GetMeta().GetLocation().GetId(),
node.GetMeta().GetLocation().GetId(),
) {
// Insert onto existing node
current = current.Children[index]
current.Cumulative += s.Value
current.Diff += s.DiffValue
} else {
// There is only one node to insert and the node hasn't been in the flame graph before.
// The value and diff value are added to the node, and
// we insert the node to the correct index within the node's current children.
node := nodes[0]
// Insert new node
node.Cumulative += s.Value
node.Diff += s.DiffValue

newChildren := make([]*pb.FlamegraphNode, len(cur.Children)+1)
copy(newChildren, cur.Children[:index])
newChildren := make([]*pb.FlamegraphNode, len(current.Children)+1)
copy(newChildren, current.Children[:index])

newChildren[index] = node
copy(newChildren[index+1:], cur.Children[index:])
cur.Children = newChildren
copy(newChildren[index+1:], current.Children[index:])
current.Children = newChildren

current = node

cur = node
// There is a case where locationToTreeNodes returns the node pointing to its parent,
// resulting in an endless loop. We remove all possible children and add them later ourselves.
current.Children = nil
}
}
}
Expand All @@ -139,7 +122,7 @@ func GenerateFlamegraphFlat(ctx context.Context, tracer trace.Tracer, metaStore
rootNode.Diff += s.DiffValue

// For next sample start at the root again
cur = rootNode
current = rootNode
}

flamegraph := &pb.Flamegraph{Root: &pb.FlamegraphRootNode{}}
Expand Down
136 changes: 136 additions & 0 deletions pkg/storage/flamegraph_flat_test.go
Expand Up @@ -412,3 +412,139 @@ func TestGenerateFlamegraphWithInlined(t *testing.T) {
},
}, fg)
}

func TestGenerateFlamegraphWithInlinedExisting(t *testing.T) {
ctx := context.Background()
logger := log.NewNopLogger()
reg := prometheus.NewRegistry()
tracer := trace.NewNoopTracerProvider().Tracer("")

store := metastore.NewBadgerMetastore(logger, reg, tracer, NewLinearUUIDGenerator())

functions := []*profile.Function{
{ID: 72, Name: "net.(*netFD).accept", SystemName: "net.(*netFD).accept", Filename: "net/fd_unix.go"},
{ID: 53, Name: "internal/poll.(*FD).Accept", SystemName: "internal/poll.(*FD).Accept", Filename: "internal/poll/fd_unix.go"},
{ID: 12, Name: "internal/poll.(*pollDesc).waitRead", SystemName: "internal/poll.(*pollDesc).waitRead", Filename: "internal/poll/fd_poll_runtime.go"},
{ID: 4, Name: "internal/poll.(*pollDesc).wait", SystemName: "internal/poll.(*pollDesc).wait", Filename: "internal/poll/fd_poll_runtime.go"},
}
locations := []*profile.Location{
{ID: 4, Address: 94658718830132, Line: []profile.Line{{Line: 173, Function: functions[0]}}},
{ID: 16, Address: 94658718611115, Line: []profile.Line{
{Line: 89, Function: functions[1]},
{Line: 402, Function: functions[2]},
}},
{ID: 50, Address: 94658718597969, Line: []profile.Line{{Line: 84, Function: functions[3]}}},
}
samples := []*profile.Sample{
{
Location: []*profile.Location{locations[2], locations[1], locations[0]},
Value: []int64{1},
},
{
Location: []*profile.Location{locations[1], locations[0]},
Value: []int64{2},
},
}
p := &profile.Profile{
SampleType: []*profile.ValueType{{Type: "", Unit: ""}},
PeriodType: &profile.ValueType{Type: "", Unit: ""},
Sample: samples,
Location: locations,
Function: functions,
}

fp, err := FlatProfileFromPprof(ctx, logger, store, p, 0)
require.NoError(t, err)

fg, err := GenerateFlamegraphFlat(ctx, tracer, store, fp)
require.NoError(t, err)

expected := &pb.Flamegraph{
Total: 3,
Height: 4,
Root: &pb.FlamegraphRootNode{
Cumulative: 3,
Children: []*pb.FlamegraphNode{{
Cumulative: 3,
Meta: &pb.FlamegraphNodeMeta{
Location: &metapb.Location{
Id: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7},
Address: 94658718830132,
},
Line: &metapb.Line{
FunctionId: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6},
Line: 173,
},
Function: &metapb.Function{
Id: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6},
StartLine: 0,
Name: "net.(*netFD).accept",
SystemName: "net.(*netFD).accept",
Filename: "net/fd_unix.go",
},
},
Children: []*pb.FlamegraphNode{{
Cumulative: 3,
Meta: &pb.FlamegraphNodeMeta{
Location: &metapb.Location{
Id: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5},
Address: 94658718611115,
},
Line: &metapb.Line{
FunctionId: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3},
Line: 89,
},
Function: &metapb.Function{
Id: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3},
StartLine: 0,
Name: "internal/poll.(*FD).Accept",
SystemName: "internal/poll.(*FD).Accept",
Filename: "internal/poll/fd_unix.go",
},
},
Children: []*pb.FlamegraphNode{{
Cumulative: 3,
Meta: &pb.FlamegraphNodeMeta{
Location: &metapb.Location{
Id: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5},
Address: 94658718611115,
},
Function: &metapb.Function{
Id: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4},
Name: "internal/poll.(*pollDesc).waitRead",
SystemName: "internal/poll.(*pollDesc).waitRead",
Filename: "internal/poll/fd_poll_runtime.go",
},
Line: &metapb.Line{
FunctionId: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4},
Line: 402,
},
},
Children: []*pb.FlamegraphNode{{
Cumulative: 1,
Meta: &pb.FlamegraphNodeMeta{
Location: &metapb.Location{
Id: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2},
Address: 94658718597969,
},
Function: &metapb.Function{
Id: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
Name: "internal/poll.(*pollDesc).wait",
SystemName: "internal/poll.(*pollDesc).wait",
Filename: "internal/poll/fd_poll_runtime.go",
},
Line: &metapb.Line{
FunctionId: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
Line: 84,
},
},
Children: nil,
}},
}},
}},
}},
},
}

require.Equal(t, expected, fg)
}