mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-12-31 15:06:26 +00:00
wip
This commit is contained in:
parent
c01bc0282a
commit
fc6a923c5e
10 changed files with 166 additions and 20 deletions
|
@ -19,6 +19,8 @@ according to [these docs](https://docs.victoriametrics.com/victorialogs/quicksta
|
||||||
|
|
||||||
## tip
|
## tip
|
||||||
|
|
||||||
|
* FEATURE: allow omitting `stats` prefix in [`stats` pipe](https://docs.victoriametrics.com/victorialogs/logsql/#stats-pipe). For example, `_time:5m | count() rows` is a valid query now. It is equivalent to `_time:5m | stats count() as rows`.
|
||||||
|
* FEATURE: allow omitting `filter` prefix in [`filter` pipe](https://docs.victoriametrics.com/victorialogs/logsql/#filter-pipe) if the filter doesn't clash with [pipe names](#https://docs.victoriametrics.com/victorialogs/logsql/#pipes). For example, `_time:5m | stats by (host) count() rows | rows:>1000` is a valid query now. It is equivalent to `_time:5m | stats by (host) count() rows | filter rows:>1000`.
|
||||||
* FEATURE: allow [`head` pipe](https://docs.victoriametrics.com/victorialogs/logsql/#limit-pipe) without number. For example, `error | head`. In this case 10 last values are returned as `head` Unix command does by default.
|
* FEATURE: allow [`head` pipe](https://docs.victoriametrics.com/victorialogs/logsql/#limit-pipe) without number. For example, `error | head`. In this case 10 last values are returned as `head` Unix command does by default.
|
||||||
* FEATURE: allow using [comparison filters](https://docs.victoriametrics.com/victorialogs/logsql/#range-comparison-filters) with strings. For example, `some_text_field:>="foo"` matches [log entries](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model) with `some_text_field` field values bigger or equal to `foo`.
|
* FEATURE: allow using [comparison filters](https://docs.victoriametrics.com/victorialogs/logsql/#range-comparison-filters) with strings. For example, `some_text_field:>="foo"` matches [log entries](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model) with `some_text_field` field values bigger or equal to `foo`.
|
||||||
|
|
||||||
|
|
|
@ -1391,8 +1391,7 @@ See also:
|
||||||
|
|
||||||
### filter pipe
|
### filter pipe
|
||||||
|
|
||||||
Sometimes it is needed to apply additional filters on the calculated results. This can be done with `| filter ...` [pipe](#pipes).
|
The `| filter ...` [pipe](#pipes) allows filtering the selected logs entries with arbitrary [filters](#filters).
|
||||||
The `filter` pipe can contain arbitrary [filters](#filters).
|
|
||||||
|
|
||||||
For example, the following query returns `host` [field](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model) values
|
For example, the following query returns `host` [field](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model) values
|
||||||
if the number of log messages with the `error` [word](#word) for them over the last hour exceeds `1_000`:
|
if the number of log messages with the `error` [word](#word) for them over the last hour exceeds `1_000`:
|
||||||
|
@ -1401,6 +1400,13 @@ if the number of log messages with the `error` [word](#word) for them over the l
|
||||||
_time:1h error | stats by (host) count() logs_count | filter logs_count:> 1_000
|
_time:1h error | stats by (host) count() logs_count | filter logs_count:> 1_000
|
||||||
```
|
```
|
||||||
|
|
||||||
|
It is allowed to omit `filter` prefix if the used filters do not clash with [pipe names](#pipes).
|
||||||
|
So the following query is equivalent to the previous one:
|
||||||
|
|
||||||
|
```logsql
|
||||||
|
_time:1h error | stats by (host) count() logs_count | logs_count:> 1_000
|
||||||
|
```
|
||||||
|
|
||||||
See also:
|
See also:
|
||||||
|
|
||||||
- [`stats` pipe](#stats-pipe)
|
- [`stats` pipe](#stats-pipe)
|
||||||
|
@ -1761,6 +1767,12 @@ For example, the following query calculates the following stats for logs over th
|
||||||
_time:5m | stats count() logs_total, count_uniq(_stream) streams_total
|
_time:5m | stats count() logs_total, count_uniq(_stream) streams_total
|
||||||
```
|
```
|
||||||
|
|
||||||
|
It is allowed to omit `stats` prefix for convenience. So the following query is equivalent to the previous one:
|
||||||
|
|
||||||
|
```logsql
|
||||||
|
_time:5m | count() logs_total, count_uniq(_stream) streams_total
|
||||||
|
```
|
||||||
|
|
||||||
See also:
|
See also:
|
||||||
|
|
||||||
- [stats by fields](#stats-by-fields)
|
- [stats by fields](#stats-by-fields)
|
||||||
|
|
|
@ -252,7 +252,7 @@ func (q *Query) AddCountByTimePipe(step, off int64, fields []string) {
|
||||||
s := fmt.Sprintf("stats by (%s) count() hits", byFieldsStr)
|
s := fmt.Sprintf("stats by (%s) count() hits", byFieldsStr)
|
||||||
lex := newLexer(s)
|
lex := newLexer(s)
|
||||||
|
|
||||||
ps, err := parsePipeStats(lex)
|
ps, err := parsePipeStats(lex, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Panicf("BUG: unexpected error when parsing [%s]: %s", s, err)
|
logger.Panicf("BUG: unexpected error when parsing [%s]: %s", s, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1094,7 +1094,7 @@ func TestParseQueryFailure(t *testing.T) {
|
||||||
f("")
|
f("")
|
||||||
f("|")
|
f("|")
|
||||||
f("foo|")
|
f("foo|")
|
||||||
f("foo|bar")
|
f("foo|bar(")
|
||||||
f("foo and")
|
f("foo and")
|
||||||
f("foo OR ")
|
f("foo OR ")
|
||||||
f("not")
|
f("not")
|
||||||
|
@ -1163,7 +1163,7 @@ func TestParseQueryFailure(t *testing.T) {
|
||||||
f(`very long query with error aaa ffdfd fdfdfd fdfd:( ffdfdfdfdfd`)
|
f(`very long query with error aaa ffdfd fdfdfd fdfd:( ffdfdfdfdfd`)
|
||||||
|
|
||||||
// query with unexpected tail
|
// query with unexpected tail
|
||||||
f(`foo | bar`)
|
f(`foo | bar(`)
|
||||||
|
|
||||||
// unexpected comma
|
// unexpected comma
|
||||||
f(`foo,bar`)
|
f(`foo,bar`)
|
||||||
|
@ -1284,9 +1284,9 @@ func TestParseQueryFailure(t *testing.T) {
|
||||||
// missing pipe keyword
|
// missing pipe keyword
|
||||||
f(`foo |`)
|
f(`foo |`)
|
||||||
|
|
||||||
// unknown pipe keyword
|
// invlaid pipe
|
||||||
f(`foo | bar`)
|
f(`foo | bar(`)
|
||||||
f(`foo | fields bar | baz`)
|
f(`foo | fields bar | baz(`)
|
||||||
|
|
||||||
// missing field in fields pipe
|
// missing field in fields pipe
|
||||||
f(`foo | fields`)
|
f(`foo | fields`)
|
||||||
|
|
|
@ -86,6 +86,8 @@ func parsePipes(lex *lexer) ([]pipe, error) {
|
||||||
lex.nextToken()
|
lex.nextToken()
|
||||||
case lex.isKeyword(")", ""):
|
case lex.isKeyword(")", ""):
|
||||||
return pipes, nil
|
return pipes, nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unexpected token after [%s]: %q; expecting '|' or ')'", pipes[len(pipes)-1], lex.token)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -123,7 +125,7 @@ func parsePipe(lex *lexer) (pipe, error) {
|
||||||
}
|
}
|
||||||
return pf, nil
|
return pf, nil
|
||||||
case lex.isKeyword("filter"):
|
case lex.isKeyword("filter"):
|
||||||
pf, err := parsePipeFilter(lex)
|
pf, err := parsePipeFilter(lex, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot parse 'filter' pipe: %w", err)
|
return nil, fmt.Errorf("cannot parse 'filter' pipe: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -177,7 +179,7 @@ func parsePipe(lex *lexer) (pipe, error) {
|
||||||
}
|
}
|
||||||
return ps, nil
|
return ps, nil
|
||||||
case lex.isKeyword("stats"):
|
case lex.isKeyword("stats"):
|
||||||
ps, err := parsePipeStats(lex)
|
ps, err := parsePipeStats(lex, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot parse 'stats' pipe: %w", err)
|
return nil, fmt.Errorf("cannot parse 'stats' pipe: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -207,6 +209,22 @@ func parsePipe(lex *lexer) (pipe, error) {
|
||||||
}
|
}
|
||||||
return pu, nil
|
return pu, nil
|
||||||
default:
|
default:
|
||||||
|
lexState := lex.backupState()
|
||||||
|
|
||||||
|
// Try parsing stats pipe without 'stats' keyword
|
||||||
|
ps, err := parsePipeStats(lex, false)
|
||||||
|
if err == nil {
|
||||||
|
return ps, nil
|
||||||
|
}
|
||||||
|
lex.restoreState(lexState)
|
||||||
|
|
||||||
|
// Try parsing filter pipe without 'filter' keyword
|
||||||
|
pf, err := parsePipeFilter(lex, false)
|
||||||
|
if err == nil {
|
||||||
|
return pf, nil
|
||||||
|
}
|
||||||
|
lex.restoreState(lexState)
|
||||||
|
|
||||||
return nil, fmt.Errorf("unexpected pipe %q", lex.token)
|
return nil, fmt.Errorf("unexpected pipe %q", lex.token)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -108,11 +108,13 @@ func (pfp *pipeFilterProcessor) flush() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parsePipeFilter(lex *lexer) (*pipeFilter, error) {
|
func parsePipeFilter(lex *lexer, needFilterKeyword bool) (*pipeFilter, error) {
|
||||||
if !lex.isKeyword("filter") {
|
if needFilterKeyword {
|
||||||
return nil, fmt.Errorf("expecting 'filter'; got %q", lex.token)
|
if !lex.isKeyword("filter") {
|
||||||
|
return nil, fmt.Errorf("expecting 'filter'; got %q", lex.token)
|
||||||
|
}
|
||||||
|
lex.nextToken()
|
||||||
}
|
}
|
||||||
lex.nextToken()
|
|
||||||
|
|
||||||
f, err := parseFilter(lex)
|
f, err := parseFilter(lex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -32,6 +32,14 @@ func TestPipeFilter(t *testing.T) {
|
||||||
expectPipeResults(t, pipeStr, rows, rowsExpected)
|
expectPipeResults(t, pipeStr, rows, rowsExpected)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// filter mismatch, missing 'filter' prefix
|
||||||
|
f("abc", [][]Field{
|
||||||
|
{
|
||||||
|
{"_msg", `{"foo":"bar"}`},
|
||||||
|
{"a", `test`},
|
||||||
|
},
|
||||||
|
}, [][]Field{})
|
||||||
|
|
||||||
// filter mismatch
|
// filter mismatch
|
||||||
f("filter abc", [][]Field{
|
f("filter abc", [][]Field{
|
||||||
{
|
{
|
||||||
|
@ -40,6 +48,19 @@ func TestPipeFilter(t *testing.T) {
|
||||||
},
|
},
|
||||||
}, [][]Field{})
|
}, [][]Field{})
|
||||||
|
|
||||||
|
// filter match, missing 'filter' prefix
|
||||||
|
f("foo", [][]Field{
|
||||||
|
{
|
||||||
|
{"_msg", `{"foo":"bar"}`},
|
||||||
|
{"a", `test`},
|
||||||
|
},
|
||||||
|
}, [][]Field{
|
||||||
|
{
|
||||||
|
{"_msg", `{"foo":"bar"}`},
|
||||||
|
{"a", `test`},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
// filter match
|
// filter match
|
||||||
f("filter foo", [][]Field{
|
f("filter foo", [][]Field{
|
||||||
{
|
{
|
||||||
|
|
|
@ -62,7 +62,7 @@ func (pr *pipeRename) hasFilterInWithQuery() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pr *pipeRename) initFilterInValues(cache map[string][]string, getFieldValuesFunc getFieldValuesFunc) (pipe, error) {
|
func (pr *pipeRename) initFilterInValues(_ map[string][]string, _ getFieldValuesFunc) (pipe, error) {
|
||||||
return pr, nil
|
return pr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -537,13 +537,14 @@ func (psp *pipeStatsProcessor) flush() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parsePipeStats(lex *lexer) (*pipeStats, error) {
|
func parsePipeStats(lex *lexer, needStatsKeyword bool) (*pipeStats, error) {
|
||||||
if !lex.isKeyword("stats") {
|
if needStatsKeyword {
|
||||||
return nil, fmt.Errorf("expecting 'stats'; got %q", lex.token)
|
if !lex.isKeyword("stats") {
|
||||||
|
return nil, fmt.Errorf("expecting 'stats'; got %q", lex.token)
|
||||||
|
}
|
||||||
|
lex.nextToken()
|
||||||
}
|
}
|
||||||
|
|
||||||
lex.nextToken()
|
|
||||||
|
|
||||||
var ps pipeStats
|
var ps pipeStats
|
||||||
if lex.isKeyword("by", "(") {
|
if lex.isKeyword("by", "(") {
|
||||||
if lex.isKeyword("by") {
|
if lex.isKeyword("by") {
|
||||||
|
|
|
@ -39,6 +39,70 @@ func TestPipeStats(t *testing.T) {
|
||||||
expectPipeResults(t, pipeStr, rows, rowsExpected)
|
expectPipeResults(t, pipeStr, rows, rowsExpected)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// missing 'stats' keyword
|
||||||
|
f("count(*) as rows", [][]Field{
|
||||||
|
{
|
||||||
|
{"_msg", `abc`},
|
||||||
|
{"a", `2`},
|
||||||
|
{"b", `3`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{"_msg", `def`},
|
||||||
|
{"a", `1`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{"a", `2`},
|
||||||
|
{"b", `54`},
|
||||||
|
},
|
||||||
|
}, [][]Field{
|
||||||
|
{
|
||||||
|
{"rows", "3"},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// missing 'stats' keyword
|
||||||
|
f("count() as rows, count() if (a:2) rows2", [][]Field{
|
||||||
|
{
|
||||||
|
{"_msg", `abc`},
|
||||||
|
{"a", `2`},
|
||||||
|
{"b", `3`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{"_msg", `def`},
|
||||||
|
{"a", `1`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{"a", `2`},
|
||||||
|
{"b", `54`},
|
||||||
|
},
|
||||||
|
}, [][]Field{
|
||||||
|
{
|
||||||
|
{"rows", "3"},
|
||||||
|
{"rows2", "2"},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
f("stats count() as rows, count() if (a:2) rows2", [][]Field{
|
||||||
|
{
|
||||||
|
{"_msg", `abc`},
|
||||||
|
{"a", `2`},
|
||||||
|
{"b", `3`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{"_msg", `def`},
|
||||||
|
{"a", `1`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{"a", `2`},
|
||||||
|
{"b", `54`},
|
||||||
|
},
|
||||||
|
}, [][]Field{
|
||||||
|
{
|
||||||
|
{"rows", "3"},
|
||||||
|
{"rows2", "2"},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
f("stats count(*) as rows", [][]Field{
|
f("stats count(*) as rows", [][]Field{
|
||||||
{
|
{
|
||||||
{"_msg", `abc`},
|
{"_msg", `abc`},
|
||||||
|
@ -141,6 +205,32 @@ func TestPipeStats(t *testing.T) {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// missing 'stats' keyword
|
||||||
|
f("by (a) count(*) as rows", [][]Field{
|
||||||
|
{
|
||||||
|
{"_msg", `abc`},
|
||||||
|
{"a", `2`},
|
||||||
|
{"b", `3`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{"_msg", `def`},
|
||||||
|
{"a", `1`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{"a", `2`},
|
||||||
|
{"b", `54`},
|
||||||
|
},
|
||||||
|
}, [][]Field{
|
||||||
|
{
|
||||||
|
{"a", "1"},
|
||||||
|
{"rows", "1"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{"a", "2"},
|
||||||
|
{"rows", "2"},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
f("stats by (a) count(*) as rows", [][]Field{
|
f("stats by (a) count(*) as rows", [][]Field{
|
||||||
{
|
{
|
||||||
{"_msg", `abc`},
|
{"_msg", `abc`},
|
||||||
|
|
Loading…
Reference in a new issue