fix: support canDefaultZero for logs and traces (#8973)

* fix: support canDefaultZero for logs and traces

* fix: remove increase

* fix: move changes to req.go

* fix: add tood

* fix: address comments
This commit is contained in:
Nityananda Gohain 2025-09-02 15:24:55 +05:30 committed by GitHub
parent 5d9247f591
commit 052fb8b703
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 148 additions and 3 deletions

View File

@ -326,9 +326,8 @@ func (q *querier) processTimeSeriesFormula(
}
}
canDefaultZero := req.GetQueriesSupportingZeroDefault()
// Create formula evaluator
// TODO(srikanthccv): add conditional default zero
canDefaultZero := make(map[string]bool)
evaluator, err := qbtypes.NewFormulaEvaluator(formula.Expression, canDefaultZero)
if err != nil {
q.logger.ErrorContext(ctx, "failed to create formula evaluator", "error", err, "formula", formula.Name)
@ -478,7 +477,7 @@ func (q *querier) processScalarFormula(
}
}
canDefaultZero := make(map[string]bool)
canDefaultZero := req.GetQueriesSupportingZeroDefault()
evaluator, err := qbtypes.NewFormulaEvaluator(formula.Expression, canDefaultZero)
if err != nil {
q.logger.ErrorContext(ctx, "failed to create formula evaluator", "error", err, "formula", formula.Name)

View File

@ -403,3 +403,37 @@ type FormatOptions struct {
FillGaps bool `json:"fillGaps,omitempty"`
FormatTableResultForUI bool `json:"formatTableResultForUI,omitempty"`
}
func (r *QueryRangeRequest) GetQueriesSupportingZeroDefault() map[string]bool {
canDefaultZeroAgg := func(expr string) bool {
expr = strings.ToLower(expr)
// only pure additive/counting operations should default to zero,
// while statistical/analytical operations should show gaps when there's no data to analyze.
// TODO: use newExprVisitor for getting the function used in the expression
if strings.HasPrefix(expr, "count(") ||
strings.HasPrefix(expr, "count_distinct(") ||
strings.HasPrefix(expr, "sum(") ||
strings.HasPrefix(expr, "rate(") {
return true
}
return false
}
canDefaultZero := make(map[string]bool)
for _, q := range r.CompositeQuery.Queries {
if q.Type == QueryTypeBuilder {
if query, ok := q.Spec.(QueryBuilderQuery[TraceAggregation]); ok {
if len(query.Aggregations) == 1 && canDefaultZeroAgg(query.Aggregations[0].Expression) {
canDefaultZero[query.Name] = true
}
} else if query, ok := q.Spec.(QueryBuilderQuery[LogAggregation]); ok {
if len(query.Aggregations) == 1 && canDefaultZeroAgg(query.Aggregations[0].Expression) {
canDefaultZero[query.Name] = true
}
}
}
}
return canDefaultZero
}

View File

@ -2,6 +2,7 @@ package querybuildertypesv5
import (
"encoding/json"
"reflect"
"testing"
"time"
@ -1529,3 +1530,114 @@ func TestValidateUniqueTraceOperator(t *testing.T) {
})
}
}
func TestQueryRangeRequest_GetQueriesSupportingZeroDefault(t *testing.T) {
tests := []struct {
name string
CompositeQuery CompositeQuery
want map[string]bool
}{
{
name: "test count on traces - support zeroDefault",
CompositeQuery: CompositeQuery{
Queries: []QueryEnvelope{
{
Type: QueryTypeBuilder,
Spec: QueryBuilderQuery[TraceAggregation]{
Name: "A",
Signal: telemetrytypes.SignalTraces,
Filter: &Filter{
Expression: "service.name = demo",
},
Aggregations: []TraceAggregation{
{
Expression: "count()",
},
},
},
},
{
Type: QueryTypeBuilder,
Spec: QueryBuilderQuery[TraceAggregation]{
Name: "B",
Signal: telemetrytypes.SignalTraces,
Aggregations: []TraceAggregation{
{
Expression: "count()",
},
},
},
},
{
Type: QueryTypeFormula,
Spec: QueryBuilderFormula{
Expression: "A / B * 100",
},
},
},
},
want: map[string]bool{
"A": true,
"B": true,
},
},
{
name: "test rate on logs - support zeroDefault",
CompositeQuery: CompositeQuery{
Queries: []QueryEnvelope{
{
Type: QueryTypeBuilder,
Spec: QueryBuilderQuery[LogAggregation]{
Name: "A",
Signal: telemetrytypes.SignalTraces,
Filter: &Filter{
Expression: "service.name = demo",
},
Aggregations: []LogAggregation{
{
Expression: "rate()",
},
},
},
},
},
},
want: map[string]bool{
"A": true,
},
},
{
name: "test min on logs - doesn't support zeroDefault",
CompositeQuery: CompositeQuery{
Queries: []QueryEnvelope{
{
Type: QueryTypeBuilder,
Spec: QueryBuilderQuery[LogAggregation]{
Name: "A",
Signal: telemetrytypes.SignalTraces,
Filter: &Filter{
Expression: "service.name = demo",
},
Aggregations: []LogAggregation{
{
Expression: "min(duration)",
},
},
},
},
},
},
want: map[string]bool{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &QueryRangeRequest{
CompositeQuery: tt.CompositeQuery,
}
if got := r.GetQueriesSupportingZeroDefault(); !reflect.DeepEqual(got, tt.want) {
t.Errorf("QueryRangeRequest.GetQueriesSupportingZeroDefault() = %v, want %v", got, tt.want)
}
})
}
}