diff --git a/pkg/query-service/app/traces/v4/query_builder.go b/pkg/query-service/app/traces/v4/query_builder.go index 80d39b9016a..3f62540fe1d 100644 --- a/pkg/query-service/app/traces/v4/query_builder.go +++ b/pkg/query-service/app/traces/v4/query_builder.go @@ -93,8 +93,8 @@ func buildTracesFilterQuery(fs *v3.FilterSet) (string, error) { if fs != nil && len(fs.Items) != 0 { for _, item := range fs.Items { - // skip if it's a resource attribute - if item.Key.Type == v3.AttributeKeyTypeResource { + // skip if it's a resource attribute or Span search scope attribute + if item.Key.Type == v3.AttributeKeyTypeResource || item.Key.Type == v3.AttributeKeyTypeSpanSearchScope { continue } @@ -213,6 +213,31 @@ func orderByAttributeKeyTags(panelType v3.PanelType, items []v3.OrderBy, tags [] return str } +func buildSpanScopeQuery(fs *v3.FilterSet) (string, error) { + var query string + if fs == nil || len(fs.Items) == 0 { + return "", nil + } + for _, item := range fs.Items { + // skip anything other than Span Search scope attribute + if item.Key.Type != v3.AttributeKeyTypeSpanSearchScope { + continue + } + keyName := strings.ToLower(item.Key.Key) + + if keyName == constants.SpanSearchScopeRoot { + query = "parent_span_id = '' " + return query, nil + } else if keyName == constants.SpanSearchScopeEntryPoint { + query = "((name, `resource_string_service$$name`) IN ( SELECT DISTINCT name, serviceName from " + constants.SIGNOZ_TRACE_DBNAME + "." + constants.SIGNOZ_TOP_LEVEL_OPERATIONS_TABLENAME + " )) " + return query, nil + } else { + return "", fmt.Errorf("invalid scope item type: %s", item.Key.Type) + } + } + return "", nil +} + func buildTracesQuery(start, end, step int64, mq *v3.BuilderQuery, panelType v3.PanelType, options v3.QBOptions) (string, error) { tracesStart := utils.GetEpochNanoSecs(start) tracesEnd := utils.GetEpochNanoSecs(end) @@ -248,6 +273,11 @@ func buildTracesQuery(start, end, step int64, mq *v3.BuilderQuery, panelType v3. filterSubQuery = filterSubQuery + " AND (resource_fingerprint GLOBAL IN " + resourceSubQuery + ")" } + spanScopeSubQuery, err := buildSpanScopeQuery(mq.Filters) + if spanScopeSubQuery != "" { + filterSubQuery = filterSubQuery + " AND " + spanScopeSubQuery + } + // timerange will be sent in epoch millisecond selectLabels := getSelectLabels(mq.GroupBy) if selectLabels != "" { @@ -274,8 +304,8 @@ func buildTracesQuery(start, end, step int64, mq *v3.BuilderQuery, panelType v3. if len(mq.SelectColumns) == 0 { return "", fmt.Errorf("select columns cannot be empty for panelType %s", panelType) } - // add it to the select labels selectLabels = getSelectLabels(mq.SelectColumns) + // add it to the select labels queryNoOpTmpl := fmt.Sprintf("SELECT timestamp as timestamp_datetime, spanID, traceID,%s ", selectLabels) + "from " + constants.SIGNOZ_TRACE_DBNAME + "." + constants.SIGNOZ_SPAN_INDEX_V3 + " where %s %s" + "%s" query = fmt.Sprintf(queryNoOpTmpl, timeFilter, filterSubQuery, orderBy) } else { diff --git a/pkg/query-service/app/traces/v4/query_builder_test.go b/pkg/query-service/app/traces/v4/query_builder_test.go index a9b055b67f5..50b83fa9d94 100644 --- a/pkg/query-service/app/traces/v4/query_builder_test.go +++ b/pkg/query-service/app/traces/v4/query_builder_test.go @@ -552,6 +552,70 @@ func Test_buildTracesQuery(t *testing.T) { want: "SELECT timestamp as timestamp_datetime, spanID, traceID, name as `name` from signoz_traces.distributed_signoz_index_v3 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " + "AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) order by timestamp ASC", }, + { + name: "test noop list view with entry_point_spans", + args: args{ + panelType: v3.PanelTypeList, + start: 1680066360726210000, + end: 1680066458000000000, + mq: &v3.BuilderQuery{ + AggregateOperator: v3.AggregateOperatorNoOp, + Filters: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{{Key: v3.AttributeKey{Key: "isEntryPoint", Type: v3.AttributeKeyTypeSpanSearchScope, IsColumn: false}, Value: true, Operator: v3.FilterOperatorEqual}}}, + SelectColumns: []v3.AttributeKey{{Key: "name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}}, + OrderBy: []v3.OrderBy{{ColumnName: "timestamp", Order: "ASC"}}, + }, + }, + want: "SELECT timestamp as timestamp_datetime, spanID, traceID, name as `name` from signoz_traces.distributed_signoz_index_v3 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " + + "AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) AND ((name, `resource_string_service$$name`) IN ( SELECT DISTINCT name, serviceName from signoz_traces.distributed_top_level_operations )) order by timestamp ASC", + }, + { + name: "test noop list view with root_spans", + args: args{ + panelType: v3.PanelTypeList, + start: 1680066360726210000, + end: 1680066458000000000, + mq: &v3.BuilderQuery{ + AggregateOperator: v3.AggregateOperatorNoOp, + Filters: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{{Key: v3.AttributeKey{Key: "isRoot", Type: v3.AttributeKeyTypeSpanSearchScope, IsColumn: false}, Value: true, Operator: v3.FilterOperatorEqual}}}, + SelectColumns: []v3.AttributeKey{{Key: "name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}}, + OrderBy: []v3.OrderBy{{ColumnName: "timestamp", Order: "ASC"}}, + }, + }, + want: "SELECT timestamp as timestamp_datetime, spanID, traceID, name as `name` from signoz_traces.distributed_signoz_index_v3 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " + + "AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) AND parent_span_id = '' order by timestamp ASC", + }, + { + name: "test noop list view with root_spans and entry_point_spans both existing", + args: args{ + panelType: v3.PanelTypeList, + start: 1680066360726210000, + end: 1680066458000000000, + mq: &v3.BuilderQuery{ + AggregateOperator: v3.AggregateOperatorNoOp, + Filters: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{{Key: v3.AttributeKey{Key: "isRoot", Type: v3.AttributeKeyTypeSpanSearchScope, IsColumn: false}, Value: true, Operator: v3.FilterOperatorEqual}, {Key: v3.AttributeKey{Key: "isEntryPoint", Type: v3.AttributeKeyTypeSpanSearchScope, IsColumn: false}, Value: true, Operator: v3.FilterOperatorEqual}}}, + SelectColumns: []v3.AttributeKey{{Key: "name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}}, + OrderBy: []v3.OrderBy{{ColumnName: "timestamp", Order: "ASC"}}, + }, + }, + want: "SELECT timestamp as timestamp_datetime, spanID, traceID, name as `name` from signoz_traces.distributed_signoz_index_v3 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " + + "AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) AND parent_span_id = '' order by timestamp ASC", + }, + { + name: "test noop list view with root_spans with other attributes", + args: args{ + panelType: v3.PanelTypeList, + start: 1680066360726210000, + end: 1680066458000000000, + mq: &v3.BuilderQuery{ + AggregateOperator: v3.AggregateOperatorNoOp, + Filters: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{{Key: v3.AttributeKey{Key: "isRoot", Type: v3.AttributeKeyTypeSpanSearchScope, IsColumn: false}, Value: true, Operator: v3.FilterOperatorEqual}, {Key: v3.AttributeKey{Key: "service.name", Type: v3.AttributeKeyTypeResource, IsColumn: true, DataType: v3.AttributeKeyDataTypeString}, Value: "cartservice", Operator: v3.FilterOperatorEqual}}}, + SelectColumns: []v3.AttributeKey{{Key: "name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}}, + OrderBy: []v3.OrderBy{{ColumnName: "timestamp", Order: "ASC"}}, + }, + }, + want: "SELECT timestamp as timestamp_datetime, spanID, traceID, name as `name` from signoz_traces.distributed_signoz_index_v3 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " + + "AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) AND (resource_fingerprint GLOBAL IN (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (seen_at_ts_bucket_start >= 1680064560) AND (seen_at_ts_bucket_start <= 1680066458) AND simpleJSONExtractString(labels, 'service.name') = 'cartservice' AND labels like '%service.name%cartservice%')) AND parent_span_id = '' order by timestamp ASC", + }, { name: "test noop list view-without ts", args: args{ diff --git a/pkg/query-service/constants/constants.go b/pkg/query-service/constants/constants.go index 242b2cd4a39..373552e8719 100644 --- a/pkg/query-service/constants/constants.go +++ b/pkg/query-service/constants/constants.go @@ -54,6 +54,9 @@ const DurationSort = "DurationSort" const TimestampSort = "TimestampSort" const PreferRPM = "PreferRPM" +const SpanSearchScopeRoot = "isroot" +const SpanSearchScopeEntryPoint = "isentrypoint" + func GetAlertManagerApiPrefix() string { if os.Getenv("ALERTMANAGER_API_PREFIX") != "" { return os.Getenv("ALERTMANAGER_API_PREFIX") @@ -248,6 +251,7 @@ const ( SIGNOZ_TIMESERIES_v4_1DAY_LOCAL_TABLENAME = "time_series_v4_1day" SIGNOZ_TIMESERIES_v4_1WEEK_LOCAL_TABLENAME = "time_series_v4_1week" SIGNOZ_TIMESERIES_v4_1DAY_TABLENAME = "distributed_time_series_v4_1day" + SIGNOZ_TOP_LEVEL_OPERATIONS_TABLENAME = "distributed_top_level_operations" ) var TimeoutExcludedRoutes = map[string]bool{ diff --git a/pkg/query-service/model/v3/v3.go b/pkg/query-service/model/v3/v3.go index 49fd1b1d35e..b9570fc52e4 100644 --- a/pkg/query-service/model/v3/v3.go +++ b/pkg/query-service/model/v3/v3.go @@ -322,6 +322,7 @@ const ( AttributeKeyTypeTag AttributeKeyType = "tag" AttributeKeyTypeResource AttributeKeyType = "resource" AttributeKeyTypeInstrumentationScope AttributeKeyType = "scope" + AttributeKeyTypeSpanSearchScope AttributeKeyType = "spanSearchScope" ) func (t AttributeKeyType) String() string {