mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-12-31 15:06:26 +00:00
wip
This commit is contained in:
parent
02f30898e1
commit
c4b68956fe
3 changed files with 528 additions and 0 deletions
|
@ -577,6 +577,12 @@ func parseStatsFunc(lex *lexer) (statsFunc, error) {
|
||||||
return nil, fmt.Errorf("cannot parse 'count_uniq' func: %w", err)
|
return nil, fmt.Errorf("cannot parse 'count_uniq' func: %w", err)
|
||||||
}
|
}
|
||||||
return sus, nil
|
return sus, nil
|
||||||
|
case lex.isKeyword("fields_max"):
|
||||||
|
sms, err := parseStatsFieldsMax(lex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot parse 'fields_max' func: %w", err)
|
||||||
|
}
|
||||||
|
return sms, nil
|
||||||
case lex.isKeyword("fields_min"):
|
case lex.isKeyword("fields_min"):
|
||||||
sms, err := parseStatsFieldsMin(lex)
|
sms, err := parseStatsFieldsMin(lex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
236
lib/logstorage/stats_fields_max.go
Normal file
236
lib/logstorage/stats_fields_max.go
Normal file
|
@ -0,0 +1,236 @@
|
||||||
|
package logstorage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
||||||
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
type statsFieldsMax struct {
|
||||||
|
srcField string
|
||||||
|
|
||||||
|
resultFields []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sm *statsFieldsMax) String() string {
|
||||||
|
s := "fields_max(" + quoteTokenIfNeeded(sm.srcField)
|
||||||
|
if len(sm.resultFields) > 0 {
|
||||||
|
s += ", " + fieldNamesString(sm.resultFields)
|
||||||
|
}
|
||||||
|
s += ")"
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sm *statsFieldsMax) updateNeededFields(neededFields fieldsSet) {
|
||||||
|
neededFields.add(sm.srcField)
|
||||||
|
neededFields.addFields(sm.resultFields)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sm *statsFieldsMax) newStatsProcessor() (statsProcessor, int) {
|
||||||
|
smp := &statsFieldsMaxProcessor{
|
||||||
|
sm: sm,
|
||||||
|
}
|
||||||
|
return smp, int(unsafe.Sizeof(*smp))
|
||||||
|
}
|
||||||
|
|
||||||
|
type statsFieldsMaxProcessor struct {
|
||||||
|
sm *statsFieldsMax
|
||||||
|
|
||||||
|
max string
|
||||||
|
|
||||||
|
fields []Field
|
||||||
|
}
|
||||||
|
|
||||||
|
func (smp *statsFieldsMaxProcessor) updateStatsForAllRows(br *blockResult) int {
|
||||||
|
stateSizeIncrease := 0
|
||||||
|
|
||||||
|
c := br.getColumnByName(smp.sm.srcField)
|
||||||
|
if c.isConst {
|
||||||
|
v := c.valuesEncoded[0]
|
||||||
|
stateSizeIncrease += smp.updateState(v, br, 0)
|
||||||
|
return stateSizeIncrease
|
||||||
|
}
|
||||||
|
if c.isTime {
|
||||||
|
bb := bbPool.Get()
|
||||||
|
bb.B = marshalTimestampRFC3339NanoString(bb.B[:0], br.timestamps[0])
|
||||||
|
v := bytesutil.ToUnsafeString(bb.B)
|
||||||
|
stateSizeIncrease += smp.updateState(v, br, 0)
|
||||||
|
bbPool.Put(bb)
|
||||||
|
return stateSizeIncrease
|
||||||
|
}
|
||||||
|
|
||||||
|
needUpdateState := false
|
||||||
|
switch c.valueType {
|
||||||
|
case valueTypeString:
|
||||||
|
needUpdateState = true
|
||||||
|
case valueTypeDict:
|
||||||
|
for _, v := range c.dictValues {
|
||||||
|
if smp.needUpdateStateString(v) {
|
||||||
|
needUpdateState = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case valueTypeUint8, valueTypeUint16, valueTypeUint32, valueTypeUint64:
|
||||||
|
bb := bbPool.Get()
|
||||||
|
bb.B = marshalUint64String(bb.B[:0], c.maxValue)
|
||||||
|
needUpdateState = smp.needUpdateStateBytes(bb.B)
|
||||||
|
bbPool.Put(bb)
|
||||||
|
case valueTypeFloat64:
|
||||||
|
f := math.Float64frombits(c.maxValue)
|
||||||
|
bb := bbPool.Get()
|
||||||
|
bb.B = marshalFloat64String(bb.B[:0], f)
|
||||||
|
needUpdateState = smp.needUpdateStateBytes(bb.B)
|
||||||
|
bbPool.Put(bb)
|
||||||
|
case valueTypeIPv4:
|
||||||
|
bb := bbPool.Get()
|
||||||
|
bb.B = marshalIPv4String(bb.B[:0], uint32(c.maxValue))
|
||||||
|
needUpdateState = smp.needUpdateStateBytes(bb.B)
|
||||||
|
bbPool.Put(bb)
|
||||||
|
case valueTypeTimestampISO8601:
|
||||||
|
bb := bbPool.Get()
|
||||||
|
bb.B = marshalTimestampISO8601String(bb.B[:0], int64(c.maxValue))
|
||||||
|
needUpdateState = smp.needUpdateStateBytes(bb.B)
|
||||||
|
bbPool.Put(bb)
|
||||||
|
default:
|
||||||
|
logger.Panicf("BUG: unknown valueType=%d", c.valueType)
|
||||||
|
}
|
||||||
|
|
||||||
|
if needUpdateState {
|
||||||
|
values := c.getValues(br)
|
||||||
|
for i, v := range values {
|
||||||
|
stateSizeIncrease += smp.updateState(v, br, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return stateSizeIncrease
|
||||||
|
}
|
||||||
|
|
||||||
|
func (smp *statsFieldsMaxProcessor) updateStatsForRow(br *blockResult, rowIdx int) int {
|
||||||
|
stateSizeIncrease := 0
|
||||||
|
|
||||||
|
c := br.getColumnByName(smp.sm.srcField)
|
||||||
|
if c.isConst {
|
||||||
|
v := c.valuesEncoded[0]
|
||||||
|
stateSizeIncrease += smp.updateState(v, br, rowIdx)
|
||||||
|
return stateSizeIncrease
|
||||||
|
}
|
||||||
|
if c.isTime {
|
||||||
|
bb := bbPool.Get()
|
||||||
|
bb.B = marshalTimestampRFC3339NanoString(bb.B[:0], br.timestamps[rowIdx])
|
||||||
|
v := bytesutil.ToUnsafeString(bb.B)
|
||||||
|
stateSizeIncrease += smp.updateState(v, br, rowIdx)
|
||||||
|
bbPool.Put(bb)
|
||||||
|
return stateSizeIncrease
|
||||||
|
}
|
||||||
|
|
||||||
|
v := c.getValueAtRow(br, rowIdx)
|
||||||
|
stateSizeIncrease += smp.updateState(v, br, rowIdx)
|
||||||
|
|
||||||
|
return stateSizeIncrease
|
||||||
|
}
|
||||||
|
|
||||||
|
func (smp *statsFieldsMaxProcessor) mergeState(sfp statsProcessor) {
|
||||||
|
src := sfp.(*statsFieldsMaxProcessor)
|
||||||
|
if smp.needUpdateStateString(src.max) {
|
||||||
|
smp.max = src.max
|
||||||
|
smp.fields = src.fields
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (smp *statsFieldsMaxProcessor) needUpdateStateBytes(b []byte) bool {
|
||||||
|
v := bytesutil.ToUnsafeString(b)
|
||||||
|
return smp.needUpdateStateString(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (smp *statsFieldsMaxProcessor) needUpdateStateString(v string) bool {
|
||||||
|
if v == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return smp.max == "" || lessString(smp.max, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (smp *statsFieldsMaxProcessor) updateState(v string, br *blockResult, rowIdx int) int {
|
||||||
|
stateSizeIncrease := 0
|
||||||
|
|
||||||
|
if !smp.needUpdateStateString(v) {
|
||||||
|
// There is no need in updating state
|
||||||
|
return stateSizeIncrease
|
||||||
|
}
|
||||||
|
|
||||||
|
stateSizeIncrease -= len(smp.max)
|
||||||
|
stateSizeIncrease += len(v)
|
||||||
|
smp.max = strings.Clone(v)
|
||||||
|
|
||||||
|
fields := smp.fields
|
||||||
|
for _, f := range fields {
|
||||||
|
stateSizeIncrease -= len(f.Name) + len(f.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
clear(fields)
|
||||||
|
fields = fields[:0]
|
||||||
|
if len(smp.sm.resultFields) == 0 {
|
||||||
|
cs := br.getColumns()
|
||||||
|
for _, c := range cs {
|
||||||
|
v := c.getValueAtRow(br, rowIdx)
|
||||||
|
fields = append(fields, Field{
|
||||||
|
Name: strings.Clone(c.name),
|
||||||
|
Value: strings.Clone(v),
|
||||||
|
})
|
||||||
|
stateSizeIncrease += len(c.name) + len(v)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, field := range smp.sm.resultFields {
|
||||||
|
c := br.getColumnByName(field)
|
||||||
|
v := c.getValueAtRow(br, rowIdx)
|
||||||
|
fields = append(fields, Field{
|
||||||
|
Name: strings.Clone(c.name),
|
||||||
|
Value: strings.Clone(v),
|
||||||
|
})
|
||||||
|
stateSizeIncrease += len(c.name) + len(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
smp.fields = fields
|
||||||
|
|
||||||
|
return stateSizeIncrease
|
||||||
|
}
|
||||||
|
|
||||||
|
func (smp *statsFieldsMaxProcessor) finalizeStats() string {
|
||||||
|
bb := bbPool.Get()
|
||||||
|
bb.B = marshalFieldsToJSON(bb.B, smp.fields)
|
||||||
|
result := string(bb.B)
|
||||||
|
bbPool.Put(bb)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseStatsFieldsMax(lex *lexer) (*statsFieldsMax, error) {
|
||||||
|
if !lex.isKeyword("fields_max") {
|
||||||
|
return nil, fmt.Errorf("unexpected func; got %q; want 'fields_max'", lex.token)
|
||||||
|
}
|
||||||
|
lex.nextToken()
|
||||||
|
fields, err := parseFieldNamesInParens(lex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot parse 'fields_max' args: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(fields) == 0 {
|
||||||
|
return nil, fmt.Errorf("missing first arg for 'fields_max' func - source field")
|
||||||
|
}
|
||||||
|
|
||||||
|
srcField := fields[0]
|
||||||
|
resultFields := fields[1:]
|
||||||
|
if slices.Contains(resultFields, "*") {
|
||||||
|
resultFields = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
sm := &statsFieldsMax{
|
||||||
|
srcField: srcField,
|
||||||
|
resultFields: resultFields,
|
||||||
|
}
|
||||||
|
return sm, nil
|
||||||
|
}
|
286
lib/logstorage/stats_fields_max_test.go
Normal file
286
lib/logstorage/stats_fields_max_test.go
Normal file
|
@ -0,0 +1,286 @@
|
||||||
|
package logstorage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseStatsFieldsMaxSuccess(t *testing.T) {
|
||||||
|
f := func(pipeStr string) {
|
||||||
|
t.Helper()
|
||||||
|
expectParseStatsFuncSuccess(t, pipeStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
f(`fields_max(foo)`)
|
||||||
|
f(`fields_max(foo, bar)`)
|
||||||
|
f(`fields_max(foo, bar, baz)`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseStatsFieldsMaxFailure(t *testing.T) {
|
||||||
|
f := func(pipeStr string) {
|
||||||
|
t.Helper()
|
||||||
|
expectParseStatsFuncFailure(t, pipeStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
f(`fields_max`)
|
||||||
|
f(`fields_max()`)
|
||||||
|
f(`fields_max(x) bar`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStatsFieldsMax(t *testing.T) {
|
||||||
|
f := func(pipeStr string, rows, rowsExpected [][]Field) {
|
||||||
|
t.Helper()
|
||||||
|
expectPipeResults(t, pipeStr, rows, rowsExpected)
|
||||||
|
}
|
||||||
|
|
||||||
|
f("stats fields_max(a) as x", [][]Field{
|
||||||
|
{
|
||||||
|
{"_msg", `abc`},
|
||||||
|
{"a", `2`},
|
||||||
|
{"b", `3`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{"_msg", `def`},
|
||||||
|
{"a", `1`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{"a", `3`},
|
||||||
|
{"b", `54`},
|
||||||
|
},
|
||||||
|
}, [][]Field{
|
||||||
|
{
|
||||||
|
{"x", `{"a":"3","b":"54"}`},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
f("stats fields_max(foo) as x", [][]Field{
|
||||||
|
{
|
||||||
|
{"_msg", `abc`},
|
||||||
|
{"a", `2`},
|
||||||
|
{"b", `3`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{"_msg", `def`},
|
||||||
|
{"a", `1`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{"a", `3`},
|
||||||
|
{"b", `54`},
|
||||||
|
},
|
||||||
|
}, [][]Field{
|
||||||
|
{
|
||||||
|
{"x", `{}`},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
f("stats fields_max(b, a) as x", [][]Field{
|
||||||
|
{
|
||||||
|
{"_msg", `abc`},
|
||||||
|
{"a", `2`},
|
||||||
|
{"b", `3`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{"_msg", `def`},
|
||||||
|
{"a", `1`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{"a", `3`},
|
||||||
|
{"b", `54`},
|
||||||
|
{"c", "1232"},
|
||||||
|
},
|
||||||
|
}, [][]Field{
|
||||||
|
{
|
||||||
|
{"x", `{"a":"3"}`},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
f("stats fields_max(b, a, x, b) as x", [][]Field{
|
||||||
|
{
|
||||||
|
{"_msg", `abc`},
|
||||||
|
{"a", `2`},
|
||||||
|
{"b", `3`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{"_msg", `def`},
|
||||||
|
{"a", `1`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{"a", `3`},
|
||||||
|
{"b", `54`},
|
||||||
|
{"c", "1232"},
|
||||||
|
},
|
||||||
|
}, [][]Field{
|
||||||
|
{
|
||||||
|
{"x", `{"a":"3","x":"","b":"54"}`},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
f("stats fields_max(a) if (b:*) as x", [][]Field{
|
||||||
|
{
|
||||||
|
{"_msg", `abc`},
|
||||||
|
{"a", `2`},
|
||||||
|
{"b", `3`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{"_msg", `def`},
|
||||||
|
{"a", `1`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{"a", `3`},
|
||||||
|
{"b", `54`},
|
||||||
|
},
|
||||||
|
}, [][]Field{
|
||||||
|
{
|
||||||
|
{"x", `{"a":"3","b":"54"}`},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
f("stats by (b) fields_max(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", `{"_msg":"abc","a":"2","b":"3"}`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{"b", ""},
|
||||||
|
{"x", `{}`},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
f("stats by (a) fields_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", `{"_msg":"abc","a":"1","b":"3"}`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{"a", "3"},
|
||||||
|
{"x", `{"a":"3","b":"7"}`},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
f("stats by (a) fields_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", `{"a":"3","c":"foo"}`},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
f("stats by (a) fields_max(b, c) as x", [][]Field{
|
||||||
|
{
|
||||||
|
{"_msg", `abc`},
|
||||||
|
{"a", `1`},
|
||||||
|
{"b", `34`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{"_msg", `def`},
|
||||||
|
{"a", `1`},
|
||||||
|
{"c", "3"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{"a", `3`},
|
||||||
|
{"b", `5`},
|
||||||
|
{"c", "foo"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{"a", `3`},
|
||||||
|
{"b", `7`},
|
||||||
|
{"c", "bar"},
|
||||||
|
},
|
||||||
|
}, [][]Field{
|
||||||
|
{
|
||||||
|
{"a", "1"},
|
||||||
|
{"x", `{"c":""}`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{"a", "3"},
|
||||||
|
{"x", `{"c":"bar"}`},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
f("stats by (a, b) fields_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", `{"_msg":"def","a":"1","c":"foo","b":""}`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{"a", "3"},
|
||||||
|
{"b", "5"},
|
||||||
|
{"x", `{"a":"3","b":"5","c":"4"}`},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
Loading…
Reference in a new issue