mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-07-30 14:22:01 +08:00
215 lines
6.7 KiB
Go
215 lines
6.7 KiB
Go
package tracedetail
|
|
|
|
import (
|
|
"slices"
|
|
"sort"
|
|
|
|
"github.com/SigNoz/signoz/pkg/query-service/model"
|
|
)
|
|
|
|
var (
|
|
SPAN_LIMIT_PER_REQUEST_FOR_WATERFALL float64 = 500
|
|
)
|
|
|
|
type Interval struct {
|
|
StartTime uint64
|
|
Duration uint64
|
|
Service string
|
|
}
|
|
|
|
func mergeIntervals(intervals []Interval) []Interval {
|
|
if len(intervals) == 0 {
|
|
return nil
|
|
}
|
|
|
|
var merged []Interval
|
|
current := intervals[0]
|
|
|
|
for i := 1; i < len(intervals); i++ {
|
|
next := intervals[i]
|
|
if current.StartTime+current.Duration >= next.StartTime {
|
|
endTime := max(current.StartTime+current.Duration, next.StartTime+next.Duration)
|
|
current.Duration = endTime - current.StartTime
|
|
} else {
|
|
merged = append(merged, current)
|
|
current = next
|
|
}
|
|
}
|
|
// Add the last interval
|
|
merged = append(merged, current)
|
|
|
|
return merged
|
|
}
|
|
|
|
func ContainsWaterfallSpan(slice []*model.Span, item *model.Span) bool {
|
|
for _, v := range slice {
|
|
if v.SpanID == item.SpanID {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func findIndexForSelectedSpanFromPreOrder(spans []*model.Span, selectedSpanId string) int {
|
|
var selectedSpanIndex = -1
|
|
|
|
for index, span := range spans {
|
|
if span.SpanID == selectedSpanId {
|
|
selectedSpanIndex = index
|
|
break
|
|
}
|
|
}
|
|
|
|
return selectedSpanIndex
|
|
}
|
|
|
|
func getPathFromRootToSelectedSpanId(node *model.Span, selectedSpanId string, uncollapsedSpans []string, isSelectedSpanIDUnCollapsed bool) (bool, []string) {
|
|
spansFromRootToNode := []string{}
|
|
|
|
if node.SpanID == selectedSpanId {
|
|
if isSelectedSpanIDUnCollapsed {
|
|
spansFromRootToNode = append(spansFromRootToNode, node.SpanID)
|
|
}
|
|
return true, spansFromRootToNode
|
|
}
|
|
|
|
isPresentInSubtreeForTheNode := false
|
|
for _, child := range node.Children {
|
|
isPresentInThisSubtree, _spansFromRootToNode := getPathFromRootToSelectedSpanId(child, selectedSpanId, uncollapsedSpans, isSelectedSpanIDUnCollapsed)
|
|
// if the interested node is present in the given subtree then add the span node to uncollapsed node list
|
|
if isPresentInThisSubtree {
|
|
if !slices.Contains(uncollapsedSpans, node.SpanID) {
|
|
spansFromRootToNode = append(spansFromRootToNode, node.SpanID)
|
|
}
|
|
isPresentInSubtreeForTheNode = true
|
|
spansFromRootToNode = append(spansFromRootToNode, _spansFromRootToNode...)
|
|
}
|
|
}
|
|
return isPresentInSubtreeForTheNode, spansFromRootToNode
|
|
}
|
|
|
|
func traverseTrace(span *model.Span, uncollapsedSpans []string, level uint64, isPartOfPreOrder bool, hasSibling bool, selectedSpanId string) []*model.Span {
|
|
preOrderTraversal := []*model.Span{}
|
|
|
|
// sort the children to maintain the order across requests
|
|
sort.Slice(span.Children, func(i, j int) bool {
|
|
if span.Children[i].TimeUnixNano == span.Children[j].TimeUnixNano {
|
|
return span.Children[i].Name < span.Children[j].Name
|
|
}
|
|
return span.Children[i].TimeUnixNano < span.Children[j].TimeUnixNano
|
|
})
|
|
|
|
span.SubTreeNodeCount = 0
|
|
nodeWithoutChildren := model.Span{
|
|
SpanID: span.SpanID,
|
|
TraceID: span.TraceID,
|
|
ServiceName: span.ServiceName,
|
|
TimeUnixNano: span.TimeUnixNano,
|
|
Name: span.Name,
|
|
Kind: int32(span.Kind),
|
|
DurationNano: span.DurationNano,
|
|
HasError: span.HasError,
|
|
StatusMessage: span.StatusMessage,
|
|
StatusCodeString: span.StatusCodeString,
|
|
SpanKind: span.SpanKind,
|
|
References: span.References,
|
|
Events: span.Events,
|
|
TagMap: span.TagMap,
|
|
Children: make([]*model.Span, 0),
|
|
HasChildren: len(span.Children) > 0,
|
|
Level: level,
|
|
HasSiblings: hasSibling,
|
|
SubTreeNodeCount: 0,
|
|
}
|
|
|
|
if isPartOfPreOrder {
|
|
preOrderTraversal = append(preOrderTraversal, &nodeWithoutChildren)
|
|
}
|
|
|
|
for index, child := range span.Children {
|
|
_childTraversal := traverseTrace(child, uncollapsedSpans, level+1, isPartOfPreOrder && slices.Contains(uncollapsedSpans, span.SpanID), index != (len(span.Children)-1), selectedSpanId)
|
|
preOrderTraversal = append(preOrderTraversal, _childTraversal...)
|
|
nodeWithoutChildren.SubTreeNodeCount += child.SubTreeNodeCount + 1
|
|
span.SubTreeNodeCount += child.SubTreeNodeCount + 1
|
|
}
|
|
|
|
nodeWithoutChildren.SubTreeNodeCount += 1
|
|
return preOrderTraversal
|
|
|
|
}
|
|
|
|
func CalculateServiceTime(serviceIntervals map[string][]Interval) map[string]uint64 {
|
|
totalTimes := make(map[string]uint64)
|
|
|
|
for service, serviceIntervals := range serviceIntervals {
|
|
sort.Slice(serviceIntervals, func(i, j int) bool {
|
|
return serviceIntervals[i].StartTime < serviceIntervals[j].StartTime
|
|
})
|
|
mergedIntervals := mergeIntervals(serviceIntervals)
|
|
totalTime := uint64(0)
|
|
for _, interval := range mergedIntervals {
|
|
totalTime += interval.Duration
|
|
}
|
|
totalTimes[service] = totalTime
|
|
}
|
|
|
|
return totalTimes
|
|
}
|
|
|
|
func GetSelectedSpans(uncollapsedSpans []string, selectedSpanID string, traceRoots []*model.Span, spanIdToSpanNodeMap map[string]*model.Span, isSelectedSpanIDUnCollapsed bool) ([]*model.Span, []string, string, string) {
|
|
|
|
var preOrderTraversal = make([]*model.Span, 0)
|
|
var rootServiceName, rootServiceEntryPoint string
|
|
updatedUncollapsedSpans := uncollapsedSpans
|
|
|
|
selectedSpanIndex := -1
|
|
for _, rootSpanID := range traceRoots {
|
|
if rootNode, exists := spanIdToSpanNodeMap[rootSpanID.SpanID]; exists {
|
|
_, spansFromRootToNode := getPathFromRootToSelectedSpanId(rootNode, selectedSpanID, updatedUncollapsedSpans, isSelectedSpanIDUnCollapsed)
|
|
updatedUncollapsedSpans = append(updatedUncollapsedSpans, spansFromRootToNode...)
|
|
|
|
_preOrderTraversal := traverseTrace(rootNode, updatedUncollapsedSpans, 0, true, false, selectedSpanID)
|
|
_selectedSpanIndex := findIndexForSelectedSpanFromPreOrder(_preOrderTraversal, selectedSpanID)
|
|
|
|
if _selectedSpanIndex != -1 {
|
|
selectedSpanIndex = _selectedSpanIndex + len(preOrderTraversal)
|
|
}
|
|
|
|
preOrderTraversal = append(preOrderTraversal, _preOrderTraversal...)
|
|
|
|
if rootServiceName == "" {
|
|
rootServiceName = rootNode.ServiceName
|
|
}
|
|
|
|
if rootServiceEntryPoint == "" {
|
|
rootServiceEntryPoint = rootNode.Name
|
|
}
|
|
}
|
|
}
|
|
|
|
// if we couldn't find the selectedSpan in the trace then defaulting the selected index to 0
|
|
if selectedSpanIndex == -1 && selectedSpanID != "" {
|
|
selectedSpanIndex = 0
|
|
}
|
|
|
|
// get the 0.4*[span limit] before the interested span index
|
|
startIndex := selectedSpanIndex - int(SPAN_LIMIT_PER_REQUEST_FOR_WATERFALL*0.4)
|
|
// get the 0.6*[span limit] after the intrested span index
|
|
endIndex := selectedSpanIndex + int(SPAN_LIMIT_PER_REQUEST_FOR_WATERFALL*0.6)
|
|
|
|
// adjust the sliding window according to the available left and right spaces.
|
|
if startIndex < 0 {
|
|
endIndex = endIndex - startIndex
|
|
startIndex = 0
|
|
}
|
|
if endIndex > len(preOrderTraversal) {
|
|
startIndex = startIndex - (endIndex - len(preOrderTraversal))
|
|
endIndex = len(preOrderTraversal)
|
|
}
|
|
if startIndex < 0 {
|
|
startIndex = 0
|
|
}
|
|
|
|
return preOrderTraversal[startIndex:endIndex], updatedUncollapsedSpans, rootServiceName, rootServiceEntryPoint
|
|
}
|