fix: interpret missing point as zero value in formula evaluation (#4668)

This commit is contained in:
Srikanth Chekuri 2024-03-11 23:47:19 +05:30 committed by GitHub
parent 5a2d729ba9
commit b4d12966f3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 218 additions and 130 deletions

View File

@ -87,23 +87,6 @@ func joinAndCalculate(results []*v3.Result, uniqueLabelSet map[string]string, ex
} }
} }
vars := expression.Vars()
var doesNotHaveAllVars bool
for _, v := range vars {
if _, ok := seriesMap[v]; !ok {
doesNotHaveAllVars = true
break
}
}
// There is no series that matches the label set from all queries
// TODO: Does the lack of a series from one query mean that the result should be nil?
// Or should we interpret the series as having a value of 0 at all timestamps?
// The current behaviour with ClickHouse is to show no data
if doesNotHaveAllVars {
return nil, nil
}
resultSeries := &v3.Series{ resultSeries := &v3.Series{
Labels: uniqueLabelSet, Labels: uniqueLabelSet,
} }

View File

@ -235,7 +235,39 @@ func TestProcessResults(t *testing.T) {
}, },
}, },
want: &v3.Result{ want: &v3.Result{
Series: []*v3.Series{}, Series: []*v3.Series{
{
Labels: map[string]string{
"service_name": "frontend",
"operation": "GET /api",
},
Points: []v3.Point{
{
Timestamp: 1,
Value: 10,
},
{
Timestamp: 2,
Value: 20,
},
},
},
{
Labels: map[string]string{
"service_name": "redis",
},
Points: []v3.Point{
{
Timestamp: 1,
Value: 30,
},
{
Timestamp: 3,
Value: 40,
},
},
},
},
}, },
}, },
} }
@ -350,6 +382,21 @@ func TestProcessResultsErrorRate(t *testing.T) {
}, },
want: &v3.Result{ want: &v3.Result{
Series: []*v3.Series{ Series: []*v3.Series{
{
Labels: map[string]string{
"service_name": "frontend",
},
Points: []v3.Point{
{
Timestamp: 1,
Value: 0,
},
{
Timestamp: 2,
Value: 0,
},
},
},
{ {
Labels: map[string]string{ Labels: map[string]string{
"service_name": "redis", "service_name": "redis",
@ -365,6 +412,21 @@ func TestProcessResultsErrorRate(t *testing.T) {
}, },
}, },
}, },
{
Labels: map[string]string{
"service_name": "route",
},
Points: []v3.Point{
{
Timestamp: 1,
Value: 0,
},
{
Timestamp: 2,
Value: 0,
},
},
},
}, },
}, },
}, },
@ -906,14 +968,7 @@ func TestFormula(t *testing.T) {
}, },
}, },
}, },
want: &v3.Result{}, want: &v3.Result{
},
{
name: "Group keys on both sides are overlapping but do not match exactly",
expression: "A/B",
results: []*v3.Result{
{
QueryName: "A",
Series: []*v3.Series{ Series: []*v3.Series{
{ {
Labels: map[string]string{ Labels: map[string]string{
@ -923,23 +978,23 @@ func TestFormula(t *testing.T) {
Points: []v3.Point{ Points: []v3.Point{
{ {
Timestamp: 1, Timestamp: 1,
Value: 10, Value: math.Inf(0),
}, },
{ {
Timestamp: 2, Timestamp: 2,
Value: 20, Value: math.Inf(0),
}, },
{ {
Timestamp: 4, Timestamp: 4,
Value: 40, Value: math.Inf(0),
}, },
{ {
Timestamp: 5, Timestamp: 5,
Value: 50, Value: math.Inf(0),
}, },
{ {
Timestamp: 7, Timestamp: 7,
Value: 70, Value: math.Inf(0),
}, },
}, },
}, },
@ -951,88 +1006,81 @@ func TestFormula(t *testing.T) {
Points: []v3.Point{ Points: []v3.Point{
{ {
Timestamp: 1, Timestamp: 1,
Value: 12, Value: math.Inf(0),
}, },
{ {
Timestamp: 2, Timestamp: 2,
Value: 45, Value: math.Inf(0),
}, },
{ {
Timestamp: 3, Timestamp: 3,
Value: 30, Value: math.Inf(0),
}, },
{ {
Timestamp: 4, Timestamp: 4,
Value: 40, Value: math.Inf(0),
}, },
{ {
Timestamp: 5, Timestamp: 5,
Value: 50, Value: math.Inf(0),
}, },
}, },
}, },
},
},
{
QueryName: "B",
Series: []*v3.Series{
{ {
Labels: map[string]string{ Labels: map[string]string{
"os.type": "linux", "host_name": "ip-10-420-69-1",
"state": "running", "state": "not_running_chalamet",
}, },
Points: []v3.Point{ Points: []v3.Point{
{ {
Timestamp: 1, Timestamp: 1,
Value: 22, Value: 0,
}, },
{ {
Timestamp: 2, Timestamp: 2,
Value: 65, Value: 0,
}, },
{ {
Timestamp: 3, Timestamp: 3,
Value: 30, Value: 0,
}, },
{ {
Timestamp: 4, Timestamp: 4,
Value: 40, Value: 0,
}, },
{ {
Timestamp: 5, Timestamp: 5,
Value: 50, Value: 0,
}, },
}, },
}, },
{ {
Labels: map[string]string{ Labels: map[string]string{
"os.type": "windows", "host_name": "ip-10-420-69-2",
"state": "busy", "state": "busy",
}, },
Points: []v3.Point{ Points: []v3.Point{
{ {
Timestamp: 1, Timestamp: 1,
Value: 22, Value: 0,
}, },
{ {
Timestamp: 2, Timestamp: 2,
Value: 65, Value: 0,
}, },
{ {
Timestamp: 4, Timestamp: 4,
Value: 40, Value: 0,
}, },
{ {
Timestamp: 5, Timestamp: 5,
Value: 50, Value: 0,
}, },
}, },
}, },
}, },
}, },
}, },
want: &v3.Result{},
},
{ {
name: "Group keys on the left side are a superset of the right side", name: "Group keys on the left side are a superset of the right side",
expression: "A/B", expression: "A/B",
@ -1193,6 +1241,59 @@ func TestFormula(t *testing.T) {
}, },
}, },
}, },
{
Labels: map[string]string{
"host_name": "ip-10-420-69-2",
"state": "idle",
"os.type": "linux",
},
Points: []v3.Point{
{
Timestamp: 1,
Value: math.Inf(0),
},
{
Timestamp: 2,
Value: math.Inf(0),
},
{
Timestamp: 3,
Value: math.Inf(0),
},
{
Timestamp: 4,
Value: math.Inf(0),
},
{
Timestamp: 5,
Value: math.Inf(0),
},
},
},
{
Labels: map[string]string{
"state": "busy",
"os.type": "linux",
},
Points: []v3.Point{
{
Timestamp: 1,
Value: 0,
},
{
Timestamp: 2,
Value: 0,
},
{
Timestamp: 4,
Value: 0,
},
{
Timestamp: 5,
Value: 0,
},
},
},
}, },
}, },
}, },
@ -1454,18 +1555,22 @@ func TestFormula(t *testing.T) {
expression, err := govaluate.NewEvaluableExpression(tt.expression) expression, err := govaluate.NewEvaluableExpression(tt.expression)
if err != nil { if err != nil {
t.Errorf("Error parsing expression: %v", err) t.Errorf("Error parsing expression: %v", err)
return
} }
got, err := processResults(tt.results, expression) got, err := processResults(tt.results, expression)
if err != nil { if err != nil {
t.Errorf("Error processing results: %v", err) t.Errorf("Error processing results: %v", err)
return
} }
if len(got.Series) != len(tt.want.Series) { if len(got.Series) != len(tt.want.Series) {
t.Errorf("processResults(): number of series - got = %v, want %v", len(got.Series), len(tt.want.Series)) t.Errorf("processResults(): number of series - got = %v, want %v", len(got.Series), len(tt.want.Series))
return
} }
for i := range got.Series { for i := range got.Series {
if len(got.Series[i].Points) != len(tt.want.Series[i].Points) { if len(got.Series[i].Points) != len(tt.want.Series[i].Points) {
t.Errorf("processResults(): number of points - got = %v, want %v", len(got.Series[i].Points), len(tt.want.Series[i].Points)) t.Errorf("processResults(): number of points - got = %v, want %v", len(got.Series[i].Points), len(tt.want.Series[i].Points))
return
} }
for j := range got.Series[i].Points { for j := range got.Series[i].Points {
if got.Series[i].Points[j].Value != tt.want.Series[i].Points[j].Value { if got.Series[i].Points[j].Value != tt.want.Series[i].Points[j].Value {