Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
- Pass thread id to calltree generation ([#492](https://github.com/getsentry/vroom/pull/492))
- Dual mode metrics endpoint ([#493](https://github.com/getsentry/vroom/pull/493))
- Add optional generation of metrics during flamegraph aggregation ([#494](https://github.com/getsentry/vroom/pull/494))
- Annotate flamegraph with profile data ([#501](https://github.com/getsentry/vroom/pull/501))

**Bug Fixes**:

Expand Down
8 changes: 8 additions & 0 deletions internal/chunk/chunk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/getsentry/vroom/internal/nodetree"
"github.com/getsentry/vroom/internal/platform"
"github.com/getsentry/vroom/internal/testutil"
"github.com/getsentry/vroom/internal/utils"
)

func TestCallTrees(t *testing.T) {
Expand Down Expand Up @@ -47,6 +48,7 @@ func TestCallTrees(t *testing.T) {
StartNS: 10_000_000,
Frame: frame.Frame{Function: "function0"},
ProfileIDs: make(map[string]struct{}),
Profiles: make(map[utils.ExampleMetadata]struct{}),
Children: []*nodetree.Node{
{
DurationNS: 40_000_000,
Expand All @@ -58,6 +60,7 @@ func TestCallTrees(t *testing.T) {
SampleCount: 2,
Frame: frame.Frame{Function: "function1"},
ProfileIDs: make(map[string]struct{}),
Profiles: make(map[utils.ExampleMetadata]struct{}),
Children: []*nodetree.Node{
{
DurationNS: 10_000_000,
Expand All @@ -69,6 +72,7 @@ func TestCallTrees(t *testing.T) {
StartNS: 40_000_000,
Frame: frame.Frame{Function: "function2"},
ProfileIDs: make(map[string]struct{}),
Profiles: make(map[utils.ExampleMetadata]struct{}),
},
},
},
Expand Down Expand Up @@ -108,6 +112,7 @@ func TestCallTrees(t *testing.T) {
StartNS: 10_000_000,
Frame: frame.Frame{Function: "function0"},
ProfileIDs: make(map[string]struct{}),
Profiles: make(map[utils.ExampleMetadata]struct{}),
Children: []*nodetree.Node{
{
DurationNS: 30_000_000,
Expand All @@ -119,6 +124,7 @@ func TestCallTrees(t *testing.T) {
StartNS: 10_000_000,
Frame: frame.Frame{Function: "function1"},
ProfileIDs: make(map[string]struct{}),
Profiles: make(map[utils.ExampleMetadata]struct{}),
},
},
},
Expand Down Expand Up @@ -158,6 +164,7 @@ func TestCallTrees(t *testing.T) {
StartNS: 10_000_000,
Frame: frame.Frame{Function: "function0"},
ProfileIDs: make(map[string]struct{}),
Profiles: make(map[utils.ExampleMetadata]struct{}),
},
{
DurationNS: 10_000_000,
Expand All @@ -169,6 +176,7 @@ func TestCallTrees(t *testing.T) {
StartNS: 20_000_000,
Frame: frame.Frame{Function: "function1"},
ProfileIDs: make(map[string]struct{}),
Profiles: make(map[utils.ExampleMetadata]struct{}),
},
},
},
Expand Down
12 changes: 7 additions & 5 deletions internal/chunk/readjob.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,20 @@ type (
ProjectID uint64
ProfilerID string
ChunkID string
TransactionID string
ThreadID *string
Start uint64
End uint64
Result chan<- storageutil.ReadJobResult
}

ReadJobResult struct {
Err error
Chunk Chunk
ThreadID *string
Start uint64
End uint64
Err error
Chunk Chunk
TransactionID string
ThreadID *string
Start uint64
End uint64
}
)

Expand Down
111 changes: 94 additions & 17 deletions internal/flamegraph/flamegraph.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ func GetFlamegraphFromProfiles(
for pair := range callTreesQueue {
profileID := pair.First
for _, callTree := range pair.Second {
addCallTreeToFlamegraph(&flamegraphTree, callTree, profileID)
addCallTreeToFlamegraph(&flamegraphTree, callTree, annotateWithProfileID(profileID))
}
countProfAggregated++
}
Expand All @@ -157,14 +157,26 @@ func sumNodesSampleCount(nodes []*nodetree.Node) int {
return c
}

func addCallTreeToFlamegraph(flamegraphTree *[]*nodetree.Node, callTree []*nodetree.Node, profileID string) {
func annotateWithProfileID(profileID string) func(n *nodetree.Node) {
return func(n *nodetree.Node) {
n.ProfileIDs[profileID] = void
}
}

func annotateWithProfileExample(example utils.ExampleMetadata) func(n *nodetree.Node) {
return func(n *nodetree.Node) {
n.Profiles[example] = void
}
}

func addCallTreeToFlamegraph(flamegraphTree *[]*nodetree.Node, callTree []*nodetree.Node, annotate func(n *nodetree.Node)) {
for _, node := range callTree {
if existingNode := getMatchingNode(flamegraphTree, node); existingNode != nil {
existingNode.SampleCount += node.SampleCount
existingNode.DurationNS += node.DurationNS
addCallTreeToFlamegraph(&existingNode.Children, node.Children, profileID)
addCallTreeToFlamegraph(&existingNode.Children, node.Children, annotate)
if node.SampleCount > sumNodesSampleCount(node.Children) {
existingNode.ProfileIDs[profileID] = void
annotate(existingNode)
}
} else {
*flamegraphTree = append(*flamegraphTree, node)
Expand All @@ -173,40 +185,43 @@ func addCallTreeToFlamegraph(flamegraphTree *[]*nodetree.Node, callTree []*nodet
// to the right children along the branch yet,
// therefore we call a utility that walk the branch
// and does it
expandCallTreeWithProfileID(node, profileID)
expandCallTreeWithProfileID(node, annotate)
}
}
}

func expandCallTreeWithProfileID(node *nodetree.Node, profileID string) {
func expandCallTreeWithProfileID(node *nodetree.Node, annotate func(n *nodetree.Node)) {
// leaf frames: we must add the profileID
if node.Children == nil {
node.ProfileIDs[profileID] = void
annotate(node)
} else {
childrenSampleCount := 0
for _, child := range node.Children {
childrenSampleCount += child.SampleCount
expandCallTreeWithProfileID(child, profileID)
expandCallTreeWithProfileID(child, annotate)
}
// If the children's sample count is less than the current
// nodes sample count, it means there are some samples
// ending at the current node. In this case, this node
// should also contain the profile ID
if node.SampleCount > childrenSampleCount {
node.ProfileIDs[profileID] = void
annotate(node)
}
}
}

type flamegraph struct {
samples [][]int
samplesProfileIDs [][]int
samplesProfiles [][]int
sampleCounts []uint64
sampleDurationsNs []uint64
frames []speedscope.Frame
framesIndex map[string]int
profilesIDsIndex map[string]int
profilesIDs []string
profilesIndex map[utils.ExampleMetadata]int
profiles []utils.ExampleMetadata
endValue uint64
minFreq int
}
Expand All @@ -217,6 +232,7 @@ func toSpeedscope(trees []*nodetree.Node, minFreq int, projectID uint64) speedsc
framesIndex: make(map[string]int),
minFreq: minFreq,
profilesIDsIndex: make(map[string]int),
profilesIndex: make(map[utils.ExampleMetadata]int),
samples: make([][]int, 0),
sampleCounts: make([]uint64, 0),
}
Expand All @@ -229,6 +245,7 @@ func toSpeedscope(trees []*nodetree.Node, minFreq int, projectID uint64) speedsc
aggProfiles[0] = speedscope.SampledProfile{
Samples: fd.samples,
SamplesProfiles: fd.samplesProfileIDs,
SamplesExamples: fd.samplesProfiles,
Weights: fd.sampleCounts,
SampleCounts: fd.sampleCounts,
SampleDurationsNs: fd.sampleDurationsNs,
Expand All @@ -247,6 +264,7 @@ func toSpeedscope(trees []*nodetree.Node, minFreq int, projectID uint64) speedsc
Shared: speedscope.SharedData{
Frames: fd.frames,
ProfileIDs: fd.profilesIDs,
Profiles: fd.profiles,
},
Profiles: aggProfiles,
}
Expand Down Expand Up @@ -284,7 +302,13 @@ func (f *flamegraph) visitCalltree(node *nodetree.Node, currentStack *[]int) {

// base case (when we reach leaf frames)
if node.Children == nil {
f.addSample(currentStack, uint64(node.SampleCount), node.DurationNS, node.ProfileIDs)
f.addSample(
currentStack,
uint64(node.SampleCount),
node.DurationNS,
node.ProfileIDs,
node.Profiles,
)
} else {
totChildrenSampleCount := 0
var totChildrenDuration uint64
Expand All @@ -301,20 +325,33 @@ func (f *flamegraph) visitCalltree(node *nodetree.Node, currentStack *[]int) {
diffCount := node.SampleCount - totChildrenSampleCount
diffDuration := node.DurationNS - totChildrenDuration
if diffCount >= f.minFreq {
f.addSample(currentStack, uint64(diffCount), diffDuration, node.ProfileIDs)
f.addSample(
currentStack,
uint64(diffCount),
diffDuration,
node.ProfileIDs,
node.Profiles,
)
}
}
// pop last element before returning
*currentStack = (*currentStack)[:len(*currentStack)-1]
}

func (f *flamegraph) addSample(stack *[]int, count uint64, duration uint64, profileIDs map[string]struct{}) {
func (f *flamegraph) addSample(
stack *[]int,
count uint64,
duration uint64,
profileIDs map[string]struct{},
profiles map[utils.ExampleMetadata]struct{},
) {
cp := make([]int, len(*stack))
copy(cp, *stack)
f.samples = append(f.samples, cp)
f.sampleCounts = append(f.sampleCounts, count)
f.sampleDurationsNs = append(f.sampleDurationsNs, duration)
f.samplesProfileIDs = append(f.samplesProfileIDs, f.getProfileIDsIndices(profileIDs))
f.samplesProfiles = append(f.samplesProfiles, f.getProfilesIndices(profiles))
f.endValue += count
}

Expand All @@ -332,6 +369,20 @@ func (f *flamegraph) getProfileIDsIndices(profileIDs map[string]struct{}) []int
return indices
}

func (f *flamegraph) getProfilesIndices(profiles map[utils.ExampleMetadata]struct{}) []int {
indices := make([]int, 0, len(profiles))
for i := range profiles {
if idx, ok := f.profilesIndex[i]; ok {
indices = append(indices, idx)
} else {
indices = append(indices, len(f.profiles))
f.profilesIndex[i] = len(f.profiles)
f.profiles = append(f.profiles, i)
}
}
return indices
}

func GetFlamegraphFromChunks(
ctx context.Context,
organizationID uint64,
Expand Down Expand Up @@ -388,9 +439,21 @@ func GetFlamegraphFromChunks(
continue
}
intervals := []utils.Interval{interval}

annotate := annotateWithProfileExample(
utils.NewExampleFromProfilerChunk(
result.Chunk.ProjectID,
result.Chunk.ProfilerID,
result.Chunk.ID,
result.TransactionID,
result.ThreadID,
result.Start,
result.End,
),
)
for _, callTree := range callTrees {
slicedTree := sliceCallTree(&callTree, &intervals)
addCallTreeToFlamegraph(&flamegraphTree, slicedTree, result.Chunk.ID)
addCallTreeToFlamegraph(&flamegraphTree, slicedTree, annotate)
}
}
countChunksAggregated++
Expand Down Expand Up @@ -472,9 +535,12 @@ func GetFlamegraphFromCandidates(
continue
}

annotate := annotateWithProfileExample(
utils.NewExampleFromProfileID(result.Profile.ProjectID(), result.Profile.ID()),
)

for _, callTree := range profileCallTrees {
// TODO: properly pass the profile ID around
addCallTreeToFlamegraph(&flamegraphTree, callTree, "")
addCallTreeToFlamegraph(&flamegraphTree, callTree, annotate)
}
// if metrics aggregator is not null, while we're at it,
// compute the metrics as well
Expand All @@ -492,6 +558,18 @@ func GetFlamegraphFromCandidates(
continue
}

annotate := annotateWithProfileExample(
utils.NewExampleFromProfilerChunk(
result.Chunk.ProjectID,
result.Chunk.ProfilerID,
result.Chunk.ID,
result.TransactionID,
result.ThreadID,
result.Start,
result.End,
),
)

for _, callTree := range chunkCallTrees {
if result.Start > 0 && result.End > 0 {
interval := utils.Interval{
Expand All @@ -500,8 +578,7 @@ func GetFlamegraphFromCandidates(
}
callTree = sliceCallTree(&callTree, &[]utils.Interval{interval})
}
// TODO: properly pass the profile ID around
addCallTreeToFlamegraph(&flamegraphTree, callTree, "")
addCallTreeToFlamegraph(&flamegraphTree, callTree, annotate)
}
// if metrics aggregator is not null, while we're at it,
// compute the metrics as well
Expand Down
Loading