diff --git a/lib/logstorage/stats_count_empty_test.go b/lib/logstorage/stats_count_empty_test.go index 7cde7389f..bd9963620 100644 --- a/lib/logstorage/stats_count_empty_test.go +++ b/lib/logstorage/stats_count_empty_test.go @@ -42,8 +42,7 @@ func TestStatsCountEmpty(t *testing.T) { {"_msg", `def`}, {"a", `1`}, }, - { - }, + {}, { {"a", `3`}, {"b", `54`}, @@ -64,8 +63,7 @@ func TestStatsCountEmpty(t *testing.T) { {"_msg", `def`}, {"a", `1`}, }, - { - }, + {}, { {"a", `3`}, {"b", `54`}, @@ -86,8 +84,7 @@ func TestStatsCountEmpty(t *testing.T) { {"_msg", `def`}, {"a", `1`}, }, - { - }, + {}, { {"aa", `3`}, {"bb", `54`}, @@ -207,8 +204,7 @@ func TestStatsCountEmpty(t *testing.T) { {"a", `1`}, {"c", "3"}, }, - { - }, + {}, { {"a", `3`}, {"b", `5`}, diff --git a/lib/logstorage/stats_count_test.go b/lib/logstorage/stats_count_test.go index 2ff03ef02..c8728e23e 100644 --- a/lib/logstorage/stats_count_test.go +++ b/lib/logstorage/stats_count_test.go @@ -42,8 +42,7 @@ func TestStatsCount(t *testing.T) { {"_msg", `def`}, {"a", `1`}, }, - { - }, + {}, { {"a", `3`}, {"b", `54`}, @@ -64,8 +63,7 @@ func TestStatsCount(t *testing.T) { {"_msg", `def`}, {"a", `1`}, }, - { - }, + {}, { {"a", `3`}, {"b", `54`}, @@ -86,8 +84,7 @@ func TestStatsCount(t *testing.T) { {"_msg", `def`}, {"a", `1`}, }, - { - }, + {}, { {"aa", `3`}, {"bb", `54`}, @@ -208,8 +205,7 @@ func TestStatsCount(t *testing.T) { {"a", `1`}, {"c", "3"}, }, - { - }, + {}, { {"a", `3`}, {"b", `5`}, diff --git a/lib/logstorage/stats_max.go b/lib/logstorage/stats_max.go index 5eed2e748..66a63a16e 100644 --- a/lib/logstorage/stats_max.go +++ b/lib/logstorage/stats_max.go @@ -154,6 +154,9 @@ func (smp *statsMaxProcessor) updateStateBytes(b []byte) { } func (smp *statsMaxProcessor) updateStateString(v string) { + if v == "" { + // Skip empty strings + } if smp.hasMax && !lessString(smp.max, v) { return } @@ -163,7 +166,7 @@ func (smp *statsMaxProcessor) updateStateString(v string) { func (smp *statsMaxProcessor) finalizeStats() string { if !smp.hasMax { - return "NaN" + return "" } return smp.max } diff --git a/lib/logstorage/stats_max_test.go b/lib/logstorage/stats_max_test.go new file mode 100644 index 000000000..b4e45a8f5 --- /dev/null +++ b/lib/logstorage/stats_max_test.go @@ -0,0 +1,366 @@ +package logstorage + +import ( + "testing" +) + +func TestParseStatsMaxSuccess(t *testing.T) { + f := func(pipeStr string) { + t.Helper() + expectParseStatsFuncSuccess(t, pipeStr) + } + + f(`max(*)`) + f(`max(a)`) + f(`max(a, b)`) +} + +func TestParseStatsMaxFailure(t *testing.T) { + f := func(pipeStr string) { + t.Helper() + expectParseStatsFuncFailure(t, pipeStr) + } + + f(`max`) + f(`max(a b)`) + f(`max(x) y`) +} + +func TestStatsMax(t *testing.T) { + f := func(pipeStr string, rows, rowsExpected [][]Field) { + t.Helper() + expectPipeResults(t, pipeStr, rows, rowsExpected) + } + + f("stats max(*) as x", [][]Field{ + { + {"_msg", `abc`}, + {"a", `2`}, + {"b", `3`}, + }, + { + {"_msg", `def`}, + {"a", `1`}, + }, + { + {"a", `3`}, + {"b", `54`}, + }, + }, [][]Field{ + { + {"x", "def"}, + }, + }) + + f("stats max(a) as x", [][]Field{ + { + {"_msg", `abc`}, + {"a", `2`}, + {"b", `3`}, + }, + { + {"_msg", `def`}, + {"a", `1`}, + }, + { + {"a", `3`}, + {"b", `54`}, + }, + }, [][]Field{ + { + {"x", "3"}, + }, + }) + + f("stats max(a, b) as x", [][]Field{ + { + {"_msg", `abc`}, + {"a", `2`}, + {"b", `3`}, + }, + { + {"_msg", `def`}, + {"a", `1`}, + }, + { + {"a", `3`}, + {"b", `54`}, + {"c", "1232"}, + }, + }, [][]Field{ + { + {"x", "54"}, + }, + }) + + f("stats max(b) as x", [][]Field{ + { + {"_msg", `abc`}, + {"a", `2`}, + {"b", `3`}, + }, + { + {"_msg", `def`}, + {"a", `1`}, + }, + { + {"a", `3`}, + {"b", `54`}, + }, + }, [][]Field{ + { + {"x", "54"}, + }, + }) + + f("stats max(c) as x", [][]Field{ + { + {"_msg", `abc`}, + {"a", `2`}, + {"b", `3`}, + }, + { + {"_msg", `def`}, + {"a", `1`}, + }, + { + {"a", `3`}, + {"b", `54`}, + }, + }, [][]Field{ + { + {"x", ""}, + }, + }) + + f("stats max(a) if (b:*) as x", [][]Field{ + { + {"_msg", `abc`}, + {"a", `2`}, + {"b", `3`}, + }, + { + {"_msg", `def`}, + {"a", `3432`}, + }, + { + {"a", `3`}, + {"b", `54`}, + }, + }, [][]Field{ + { + {"x", "3"}, + }, + }) + + f("stats by (b) max(a) if (b:*) as x", [][]Field{ + { + {"_msg", `abc`}, + {"a", `2`}, + {"b", `3`}, + }, + { + {"_msg", `def`}, + {"a", `1`}, + {"b", "3"}, + }, + { + {"a", `3`}, + {"c", `54`}, + }, + }, [][]Field{ + { + {"b", "3"}, + {"x", "2"}, + }, + { + {"b", ""}, + {"x", ""}, + }, + }) + + f("stats by (a) max(b) as x", [][]Field{ + { + {"_msg", `abc`}, + {"a", `1`}, + {"b", `3`}, + }, + { + {"_msg", `def`}, + {"a", `1`}, + }, + { + {"a", `3`}, + {"b", `5`}, + }, + { + {"a", `3`}, + {"b", `7`}, + }, + }, [][]Field{ + { + {"a", "1"}, + {"x", "3"}, + }, + { + {"a", "3"}, + {"x", "7"}, + }, + }) + + f("stats by (a) max(*) as x", [][]Field{ + { + {"_msg", `abc`}, + {"a", `1`}, + {"b", `3`}, + }, + { + {"_msg", `def`}, + {"a", `1`}, + {"c", "10"}, + }, + { + {"a", `3`}, + {"b", `5`}, + }, + { + {"a", `3`}, + {"b", `7`}, + }, + }, [][]Field{ + { + {"a", "1"}, + {"x", "def"}, + }, + { + {"a", "3"}, + {"x", "7"}, + }, + }) + + f("stats by (a) max(c) as x", [][]Field{ + { + {"_msg", `abc`}, + {"a", `1`}, + {"b", `3`}, + }, + { + {"_msg", `def`}, + {"a", `1`}, + }, + { + {"a", `3`}, + {"c", `foo`}, + }, + { + {"a", `3`}, + {"b", `7`}, + }, + }, [][]Field{ + { + {"a", "1"}, + {"x", ""}, + }, + { + {"a", "3"}, + {"x", "foo"}, + }, + }) + + f("stats by (a) max(a, b, c) as x", [][]Field{ + { + {"_msg", `abc`}, + {"a", `1`}, + {"b", `34`}, + }, + { + {"_msg", `def`}, + {"a", `1`}, + {"c", "3"}, + }, + { + {"a", `3`}, + {"b", `5`}, + }, + { + {"a", `3`}, + {"b", `7`}, + }, + }, [][]Field{ + { + {"a", "1"}, + {"x", "34"}, + }, + { + {"a", "3"}, + {"x", "7"}, + }, + }) + + f("stats by (a, b) max(a) as x", [][]Field{ + { + {"_msg", `abc`}, + {"a", `1`}, + {"b", `3`}, + }, + { + {"_msg", `def`}, + {"a", `1`}, + {"c", "3"}, + }, + { + {"a", `3`}, + {"b", `5`}, + }, + }, [][]Field{ + { + {"a", "1"}, + {"b", "3"}, + {"x", "1"}, + }, + { + {"a", "1"}, + {"b", ""}, + {"x", "1"}, + }, + { + {"a", "3"}, + {"b", "5"}, + {"x", "3"}, + }, + }) + + f("stats by (a, b) max(c) as x", [][]Field{ + { + {"_msg", `abc`}, + {"a", `1`}, + {"b", `3`}, + }, + { + {"_msg", `def`}, + {"a", `1`}, + {"c", "foo"}, + }, + { + {"a", `3`}, + {"b", `5`}, + {"c", "4"}, + }, + }, [][]Field{ + { + {"a", "1"}, + {"b", "3"}, + {"x", ""}, + }, + { + {"a", "1"}, + {"b", ""}, + {"x", "foo"}, + }, + { + {"a", "3"}, + {"b", "5"}, + {"x", "4"}, + }, + }) +} diff --git a/lib/logstorage/stats_min.go b/lib/logstorage/stats_min.go index f82d85e6b..285b4eaa4 100644 --- a/lib/logstorage/stats_min.go +++ b/lib/logstorage/stats_min.go @@ -154,6 +154,10 @@ func (smp *statsMinProcessor) updateStateBytes(b []byte) { } func (smp *statsMinProcessor) updateStateString(v string) { + if v == "" { + // Skip empty strings + return + } if smp.hasMin && !lessString(v, smp.min) { return } @@ -163,7 +167,7 @@ func (smp *statsMinProcessor) updateStateString(v string) { func (smp *statsMinProcessor) finalizeStats() string { if !smp.hasMin { - return "NaN" + return "" } return smp.min } diff --git a/lib/logstorage/stats_min_test.go b/lib/logstorage/stats_min_test.go new file mode 100644 index 000000000..adda694ff --- /dev/null +++ b/lib/logstorage/stats_min_test.go @@ -0,0 +1,366 @@ +package logstorage + +import ( + "testing" +) + +func TestParseStatsMinSuccess(t *testing.T) { + f := func(pipeStr string) { + t.Helper() + expectParseStatsFuncSuccess(t, pipeStr) + } + + f(`min(*)`) + f(`min(a)`) + f(`min(a, b)`) +} + +func TestParseStatsMinFailure(t *testing.T) { + f := func(pipeStr string) { + t.Helper() + expectParseStatsFuncFailure(t, pipeStr) + } + + f(`min`) + f(`min(a b)`) + f(`min(x) y`) +} + +func TestStatsMin(t *testing.T) { + f := func(pipeStr string, rows, rowsExpected [][]Field) { + t.Helper() + expectPipeResults(t, pipeStr, rows, rowsExpected) + } + + f("stats min(*) as x", [][]Field{ + { + {"_msg", `abc`}, + {"a", `2`}, + {"b", `3`}, + }, + { + {"_msg", `def`}, + {"a", `1`}, + }, + { + {"a", `3`}, + {"b", `54`}, + }, + }, [][]Field{ + { + {"x", "1"}, + }, + }) + + f("stats min(a) as x", [][]Field{ + { + {"_msg", `abc`}, + {"a", `2`}, + {"b", `3`}, + }, + { + {"_msg", `def`}, + {"a", `1`}, + }, + { + {"a", `3`}, + {"b", `54`}, + }, + }, [][]Field{ + { + {"x", "1"}, + }, + }) + + f("stats min(a, b) as x", [][]Field{ + { + {"_msg", `abc`}, + {"a", `2`}, + {"b", `3`}, + }, + { + {"_msg", `def`}, + {"a", `1`}, + }, + { + {"a", `3`}, + {"b", `54`}, + {"c", "1232"}, + }, + }, [][]Field{ + { + {"x", "1"}, + }, + }) + + f("stats min(b) as x", [][]Field{ + { + {"_msg", `abc`}, + {"a", `2`}, + {"b", `3`}, + }, + { + {"_msg", `def`}, + {"a", `1`}, + }, + { + {"a", `3`}, + {"b", `54`}, + }, + }, [][]Field{ + { + {"x", "3"}, + }, + }) + + f("stats min(c) as x", [][]Field{ + { + {"_msg", `abc`}, + {"a", `2`}, + {"b", `3`}, + }, + { + {"_msg", `def`}, + {"a", `1`}, + }, + { + {"a", `3`}, + {"b", `54`}, + }, + }, [][]Field{ + { + {"x", ""}, + }, + }) + + f("stats min(a) if (b:*) as x", [][]Field{ + { + {"_msg", `abc`}, + {"a", `2`}, + {"b", `3`}, + }, + { + {"_msg", `def`}, + {"a", `1`}, + }, + { + {"a", `3`}, + {"b", `54`}, + }, + }, [][]Field{ + { + {"x", "2"}, + }, + }) + + f("stats by (b) min(a) if (b:*) as x", [][]Field{ + { + {"_msg", `abc`}, + {"a", `2`}, + {"b", `3`}, + }, + { + {"_msg", `def`}, + {"a", `-12.34`}, + {"b", "3"}, + }, + { + {"a", `3`}, + {"c", `54`}, + }, + }, [][]Field{ + { + {"b", "3"}, + {"x", "-12.34"}, + }, + { + {"b", ""}, + {"x", ""}, + }, + }) + + f("stats by (a) min(b) as x", [][]Field{ + { + {"_msg", `abc`}, + {"a", `1`}, + {"b", `3`}, + }, + { + {"_msg", `def`}, + {"a", `1`}, + }, + { + {"a", `3`}, + {"b", `5`}, + }, + { + {"a", `3`}, + {"b", `7`}, + }, + }, [][]Field{ + { + {"a", "1"}, + {"x", "3"}, + }, + { + {"a", "3"}, + {"x", "5"}, + }, + }) + + f("stats by (a) min(*) as x", [][]Field{ + { + {"_msg", `abc`}, + {"a", `1`}, + {"b", `3`}, + }, + { + {"_msg", `def`}, + {"a", `1`}, + {"c", "-34"}, + }, + { + {"a", `3`}, + {"b", `5`}, + }, + { + {"a", `3`}, + {"b", `7`}, + }, + }, [][]Field{ + { + {"a", "1"}, + {"x", "-34"}, + }, + { + {"a", "3"}, + {"x", "3"}, + }, + }) + + f("stats by (a) min(c) as x", [][]Field{ + { + {"_msg", `abc`}, + {"a", `1`}, + {"b", `3`}, + }, + { + {"_msg", `def`}, + {"a", `1`}, + }, + { + {"a", `3`}, + {"c", `foo`}, + }, + { + {"a", `3`}, + {"b", `7`}, + }, + }, [][]Field{ + { + {"a", "1"}, + {"x", ""}, + }, + { + {"a", "3"}, + {"x", "foo"}, + }, + }) + + f("stats by (a) min(a, b, c) as x", [][]Field{ + { + {"_msg", `abc`}, + {"a", `1`}, + {"b", `34`}, + }, + { + {"_msg", `def`}, + {"a", `1`}, + {"c", "3"}, + }, + { + {"a", `3`}, + {"b", `5`}, + }, + { + {"a", `3`}, + {"b", `7`}, + }, + }, [][]Field{ + { + {"a", "1"}, + {"x", "1"}, + }, + { + {"a", "3"}, + {"x", "3"}, + }, + }) + + f("stats by (a, b) min(a) as x", [][]Field{ + { + {"_msg", `abc`}, + {"a", `1`}, + {"b", `3`}, + }, + { + {"_msg", `def`}, + {"a", `1`}, + {"c", "3"}, + }, + { + {"a", `3`}, + {"b", `5`}, + }, + }, [][]Field{ + { + {"a", "1"}, + {"b", "3"}, + {"x", "1"}, + }, + { + {"a", "1"}, + {"b", ""}, + {"x", "1"}, + }, + { + {"a", "3"}, + {"b", "5"}, + {"x", "3"}, + }, + }) + + f("stats by (a, b) min(c) as x", [][]Field{ + { + {"_msg", `abc`}, + {"a", `1`}, + {"b", `3`}, + }, + { + {"_msg", `def`}, + {"a", `1`}, + {"c", "foo"}, + }, + { + {"a", `3`}, + {"b", `5`}, + {"c", "4"}, + }, + }, [][]Field{ + { + {"a", "1"}, + {"b", "3"}, + {"x", ""}, + }, + { + {"a", "1"}, + {"b", ""}, + {"x", "foo"}, + }, + { + {"a", "3"}, + {"b", "5"}, + {"x", "4"}, + }, + }) +}