2022-11-24 18:18:19 +05:30
package db
import (
"errors"
"strconv"
"go.signoz.io/signoz/ee/query-service/model"
basemodel "go.signoz.io/signoz/pkg/query-service/model"
"go.uber.org/zap"
)
// SmartTraceAlgorithm is an algorithm to find the target span and build a tree of spans around it with the given levelUp and levelDown parameters and the given spanLimit
func SmartTraceAlgorithm ( payload [ ] basemodel . SearchSpanResponseItem , targetSpanId string , levelUp int , levelDown int , spanLimit int ) ( [ ] basemodel . SearchSpansResult , error ) {
var spans [ ] * model . SpanForTraceDetails
2024-05-27 17:20:45 +05:30
// if targetSpanId is null or not present then randomly select a span as targetSpanId
if ( targetSpanId == "" || targetSpanId == "null" ) && len ( payload ) > 0 {
targetSpanId = payload [ 0 ] . SpanID
}
2022-11-24 18:18:19 +05:30
// Build a slice of spans from the payload
for _ , spanItem := range payload {
var parentID string
if len ( spanItem . References ) > 0 && spanItem . References [ 0 ] . RefType == "CHILD_OF" {
parentID = spanItem . References [ 0 ] . SpanId
}
span := & model . SpanForTraceDetails {
TimeUnixNano : spanItem . TimeUnixNano ,
SpanID : spanItem . SpanID ,
TraceID : spanItem . TraceID ,
ServiceName : spanItem . ServiceName ,
Name : spanItem . Name ,
Kind : spanItem . Kind ,
DurationNano : spanItem . DurationNano ,
TagMap : spanItem . TagMap ,
ParentID : parentID ,
Events : spanItem . Events ,
HasError : spanItem . HasError ,
}
spans = append ( spans , span )
}
// Build span trees from the spans
roots , err := buildSpanTrees ( & spans )
if err != nil {
return nil , err
}
targetSpan := & model . SpanForTraceDetails { }
// Find the target span in the span trees
for _ , root := range roots {
targetSpan , err = breadthFirstSearch ( root , targetSpanId )
if targetSpan != nil {
break
}
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error during BreadthFirstSearch()" , zap . Error ( err ) )
2022-11-24 18:18:19 +05:30
return nil , err
}
}
// If the target span is not found, return span not found error
if targetSpan == nil {
2024-06-11 20:10:38 +05:30
return nil , errors . New ( "span not found" )
2022-11-24 18:18:19 +05:30
}
// Build the final result
parents := [ ] * model . SpanForTraceDetails { }
// Get the parent spans of the target span up to the given levelUp parameter and spanLimit
preParent := targetSpan
for i := 0 ; i < levelUp + 1 ; i ++ {
if i == levelUp {
preParent . ParentID = ""
}
if spanLimit - len ( preParent . Children ) <= 0 {
parents = append ( parents , preParent )
parents = append ( parents , preParent . Children [ : spanLimit ] ... )
spanLimit -= ( len ( preParent . Children [ : spanLimit ] ) + 1 )
preParent . ParentID = ""
break
}
parents = append ( parents , preParent )
parents = append ( parents , preParent . Children ... )
spanLimit -= ( len ( preParent . Children ) + 1 )
preParent = preParent . ParentSpan
if preParent == nil {
break
}
}
// Get the child spans of the target span until the given levelDown and spanLimit
preParents := [ ] * model . SpanForTraceDetails { targetSpan }
children := [ ] * model . SpanForTraceDetails { }
for i := 0 ; i < levelDown && len ( preParents ) != 0 && spanLimit > 0 ; i ++ {
parents := [ ] * model . SpanForTraceDetails { }
for _ , parent := range preParents {
if spanLimit - len ( parent . Children ) <= 0 {
children = append ( children , parent . Children [ : spanLimit ] ... )
spanLimit -= len ( parent . Children [ : spanLimit ] )
break
}
children = append ( children , parent . Children ... )
parents = append ( parents , parent . Children ... )
}
preParents = parents
}
// Store the final list of spans in the resultSpanSet map to avoid duplicates
resultSpansSet := make ( map [ * model . SpanForTraceDetails ] struct { } )
resultSpansSet [ targetSpan ] = struct { } { }
for _ , parent := range parents {
resultSpansSet [ parent ] = struct { } { }
}
for _ , child := range children {
resultSpansSet [ child ] = struct { } { }
}
searchSpansResult := [ ] basemodel . SearchSpansResult { {
2024-06-11 20:10:38 +05:30
Columns : [ ] string { "__time" , "SpanId" , "TraceId" , "ServiceName" , "Name" , "Kind" , "DurationNano" , "TagsKeys" , "TagsValues" , "References" , "Events" , "HasError" } ,
Events : make ( [ ] [ ] interface { } , len ( resultSpansSet ) ) ,
2024-05-27 17:20:45 +05:30
IsSubTree : true ,
2022-11-24 18:18:19 +05:30
} ,
}
// Convert the resultSpansSet map to searchSpansResult
i := 0 // index for spans
for item := range resultSpansSet {
references := [ ] basemodel . OtelSpanRef {
{
TraceId : item . TraceID ,
SpanId : item . ParentID ,
RefType : "CHILD_OF" ,
} ,
}
referencesStringArray := [ ] string { }
for _ , item := range references {
referencesStringArray = append ( referencesStringArray , item . ToString ( ) )
}
keys := make ( [ ] string , 0 , len ( item . TagMap ) )
values := make ( [ ] string , 0 , len ( item . TagMap ) )
for k , v := range item . TagMap {
keys = append ( keys , k )
values = append ( values , v )
}
if item . Events == nil {
item . Events = [ ] string { }
}
searchSpansResult [ 0 ] . Events [ i ] = [ ] interface { } {
item . TimeUnixNano ,
item . SpanID ,
item . TraceID ,
item . ServiceName ,
item . Name ,
strconv . Itoa ( int ( item . Kind ) ) ,
strconv . FormatInt ( item . DurationNano , 10 ) ,
keys ,
values ,
referencesStringArray ,
item . Events ,
item . HasError ,
}
i ++ // increment index
}
return searchSpansResult , nil
}
// buildSpanTrees builds trees of spans from a list of spans.
func buildSpanTrees ( spansPtr * [ ] * model . SpanForTraceDetails ) ( [ ] * model . SpanForTraceDetails , error ) {
// Build a map of spanID to span for fast lookup
var roots [ ] * model . SpanForTraceDetails
spans := * spansPtr
mapOfSpans := make ( map [ string ] * model . SpanForTraceDetails , len ( spans ) )
for _ , span := range spans {
if span . ParentID == "" {
roots = append ( roots , span )
}
mapOfSpans [ span . SpanID ] = span
}
// Build the span tree by adding children to the parent spans
for _ , span := range spans {
if span . ParentID == "" {
continue
}
parent := mapOfSpans [ span . ParentID ]
// If the parent span is not found, add current span to list of roots
if parent == nil {
2024-03-27 00:07:29 +05:30
// zap.L().Debug("Parent Span not found parent_id: ", span.ParentID)
2022-11-24 18:18:19 +05:30
roots = append ( roots , span )
span . ParentID = ""
continue
}
span . ParentSpan = parent
parent . Children = append ( parent . Children , span )
}
return roots , nil
}
// breadthFirstSearch performs a breadth-first search on the span tree to find the target span.
func breadthFirstSearch ( spansPtr * model . SpanForTraceDetails , targetId string ) ( * model . SpanForTraceDetails , error ) {
queue := [ ] * model . SpanForTraceDetails { spansPtr }
visited := make ( map [ string ] bool )
for len ( queue ) > 0 {
current := queue [ 0 ]
visited [ current . SpanID ] = true
queue = queue [ 1 : ]
if current . SpanID == targetId {
return current , nil
}
for _ , child := range current . Children {
2024-06-11 20:10:38 +05:30
if ok := visited [ child . SpanID ] ; ! ok {
2022-11-24 18:18:19 +05:30
queue = append ( queue , child )
}
}
}
return nil , nil
}