Compare commits

...

7 Commits

Author SHA1 Message Date
Nikhil Soni
2df5a528bc chore: fix config key in error messages 2026-06-08 23:46:06 +05:30
Nikhil Soni
5c23ad53dd refactor: simplify BFS in getAllLevels method 2026-06-08 23:41:42 +05:30
Nikhil Soni
9b247c622e refactor: simplify sampling method 2026-06-08 23:41:42 +05:30
Nikhil Soni
254d888c3f refactor: simplify GetSelectedLevels on flamegraph 2026-06-08 23:41:42 +05:30
Nikhil Soni
c3b0d1812a chore: remove unwanted absctraction 2026-06-08 23:41:42 +05:30
Nikhil Soni
5241f34756 Revert "chore: extract out flamegraph building logic for easier review"
This reverts commit f9222c9930.
2026-06-08 23:41:42 +05:30
Nikhil Soni
b53fb35c32 feat: add config for flamegraph 2026-06-08 23:41:42 +05:30
2 changed files with 103 additions and 7 deletions

View File

@@ -59,19 +59,19 @@ func (c Config) Validate() error {
return errors.NewInvalidInputf(errors.CodeInvalidInput, "traces.waterfall.max_limit_to_select_all_spans must be positive")
}
if c.Flamegraph.MaxSelectedLevels <= 0 {
return errors.NewInvalidInputf(errors.CodeInvalidInput, "tracedetail.flamegraph.level_limit must be positive, got %d", c.Flamegraph.MaxSelectedLevels)
return errors.NewInvalidInputf(errors.CodeInvalidInput, "traces.flamegraph.level_limit must be positive, got %d", c.Flamegraph.MaxSelectedLevels)
}
if c.Flamegraph.MaxSpansPerLevel <= 0 {
return errors.NewInvalidInputf(errors.CodeInvalidInput, "tracedetail.flamegraph.spans_per_level must be positive, got %d", c.Flamegraph.MaxSpansPerLevel)
return errors.NewInvalidInputf(errors.CodeInvalidInput, "traces.flamegraph.spans_per_level must be positive, got %d", c.Flamegraph.MaxSpansPerLevel)
}
if c.Flamegraph.SamplingTopLatencySpansCount < 0 {
return errors.NewInvalidInputf(errors.CodeInvalidInput, "tracedetail.flamegraph.top_latency_count cannot be negative, got %d", c.Flamegraph.SamplingTopLatencySpansCount)
return errors.NewInvalidInputf(errors.CodeInvalidInput, "traces.flamegraph.top_latency_count cannot be negative, got %d", c.Flamegraph.SamplingTopLatencySpansCount)
}
if c.Flamegraph.SamplingBucketCount <= 0 {
return errors.NewInvalidInputf(errors.CodeInvalidInput, "tracedetail.flamegraph.bucket_count must be positive, got %d", c.Flamegraph.SamplingBucketCount)
return errors.NewInvalidInputf(errors.CodeInvalidInput, "traces.flamegraph.bucket_count must be positive, got %d", c.Flamegraph.SamplingBucketCount)
}
if c.Flamegraph.SelectAllSpansLimit == 0 {
return errors.NewInvalidInputf(errors.CodeInvalidInput, "tracedetail.flamegraph.max_limit_to_select_all_spans must be positive")
return errors.NewInvalidInputf(errors.CodeInvalidInput, "traces.flamegraph.max_limit_to_select_all_spans must be positive")
}
return nil
}

View File

@@ -41,12 +41,52 @@ func NewFlamegraphTraceFromStorable(spans []StorableSpan, selectFields []telemet
}
func (t *FlamegraphTrace) GetAllLevels() [][]*FlamegraphSpan {
return nil
var result [][]*FlamegraphSpan
for _, root := range t.roots {
currentLevel := []*FlamegraphSpan{root}
for depth := int64(0); len(currentLevel) > 0; depth++ {
var nextLevel []*FlamegraphSpan
for _, node := range currentLevel {
node.Level = depth
nextLevel = append(nextLevel, node.Children...)
}
result = append(result, currentLevel)
currentLevel = nextLevel
}
}
return result
}
// GetSelectedLevels returns the window of levels around selectedSpanID with sampling applied to dense levels.
func (t *FlamegraphTrace) GetSelectedLevels(selectedSpanID string, levelLimit, spansPerLevel, topLatencyCount, bucketCount int) []FlamegraphLevel {
return nil
allLevels := t.GetAllLevels()
selectedIndex := getLevelIndex(allLevels, selectedSpanID)
// 40% window above level with selected span and 60% below that
beforeSelectedLevel := int(float64(levelLimit) * 0.4)
startLevel := max(0, selectedIndex-beforeSelectedLevel)
endLevel := min(len(allLevels), startLevel+levelLimit)
result := make([]FlamegraphLevel, 0, endLevel-startLevel)
for i := startLevel; i < endLevel; i++ {
spans := allLevels[i]
sampled := spans
if len(spans) > spansPerLevel {
sampled = t.sampleLevel(spans, selectedSpanID, i == selectedIndex, topLatencyCount, bucketCount)
}
if len(sampled) == 0 {
continue
}
spanIDs := make([]string, len(sampled))
for j, s := range sampled {
spanIDs[j] = s.SpanID
}
result = append(result, FlamegraphLevel{Level: spans[0].Level, SpanIDs: spanIDs})
}
return result
}
func (t *FlamegraphTrace) EnrichSelectedSpans(selectedSpans []FlamegraphLevel, fullSpans []StorableSpan, selectFields []telemetrytypes.TelemetryFieldKey) [][]*FlamegraphSpan {
@@ -101,6 +141,62 @@ func (t *FlamegraphTrace) buildSpanTree() {
})
}
func (t *FlamegraphTrace) sampleLevel(spans []*FlamegraphSpan, selectedSpanID string, isSelectedLevel bool, topLatencyCount, bucketCount int) []*FlamegraphSpan {
sorted := make([]*FlamegraphSpan, len(spans))
copy(sorted, spans)
sort.Slice(sorted, func(i, j int) bool {
return sorted[i].DurationNano > sorted[j].DurationNano
})
topK := min(topLatencyCount, len(sorted))
sampled := make([]*FlamegraphSpan, topK, topK+1)
copy(sampled, sorted[:topK])
if isSelectedLevel {
for _, span := range sorted {
if span.SpanID == selectedSpanID {
sampled = append(sampled, span)
break
}
}
}
return append(sampled, t.bucketSampleSpans(sorted, bucketCount)...)
}
func (t *FlamegraphTrace) bucketSampleSpans(sorted []*FlamegraphSpan, bucketCount int) []*FlamegraphSpan {
bucketSize := (t.endTime - t.startTime) / uint64(bucketCount)
if bucketSize == 0 {
bucketSize = 1
}
buckets := make([][]*FlamegraphSpan, bucketCount)
for _, span := range sorted {
if span.Timestamp < t.startTime || span.Timestamp > t.endTime {
continue
}
idx := min(int((span.Timestamp-t.startTime)/bucketSize), bucketCount-1)
if len(buckets[idx]) < 2 {
buckets[idx] = append(buckets[idx], span)
}
}
var result []*FlamegraphSpan
for _, bucket := range buckets {
result = append(result, bucket...)
}
return result
}
func getLevelIndex(levels [][]*FlamegraphSpan, spanID string) int {
for i, lvl := range levels {
for _, span := range lvl {
if span.SpanID == spanID {
return i
}
}
}
return 0
}
func flamegraphSpanIndex(spans []*FlamegraphSpan, spanID string) int {
for i, s := range spans {
if s.SpanID == spanID {