mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-12-31 15:06:26 +00:00
wip
This commit is contained in:
parent
caf1304ee4
commit
77e2d0be60
21 changed files with 1996 additions and 362 deletions
|
@ -1069,7 +1069,7 @@ See the [Roadmap](https://docs.victoriametrics.com/VictoriaLogs/Roadmap.html) fo
|
||||||
|
|
||||||
## Stats
|
## Stats
|
||||||
|
|
||||||
LogsQL supports calculating the following stats:
|
LogsQL supports calculating the following stats functions:
|
||||||
|
|
||||||
- The number of matching log entries. Examples:
|
- The number of matching log entries. Examples:
|
||||||
- `error | stats count() as errors_total` returns the number of [log messages](https://docs.victoriametrics.com/victorialogs/keyconcepts/#message-field) with the `error` [word](#word).
|
- `error | stats count() as errors_total` returns the number of [log messages](https://docs.victoriametrics.com/victorialogs/keyconcepts/#message-field) with the `error` [word](#word).
|
||||||
|
@ -1117,6 +1117,48 @@ LogsQL supports calculating the following stats:
|
||||||
across [log messages](https://docs.victoriametrics.com/victorialogs/keyconcepts/#message-field) with the `GET` [word](#word), grouped
|
across [log messages](https://docs.victoriametrics.com/victorialogs/keyconcepts/#message-field) with the `GET` [word](#word), grouped
|
||||||
by `path` [field](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model) value.
|
by `path` [field](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model) value.
|
||||||
|
|
||||||
|
### Grouping stats by buckets
|
||||||
|
|
||||||
|
#### Time buckets
|
||||||
|
|
||||||
|
Stats can be bucketed by [`_time`](https://docs.victoriametrics.com/victorialogs/keyconcepts/#time-field) with the `_time:bucket_duration` syntax inside `by(...)` clause.
|
||||||
|
For example, the following query returns per-minute number of log messages with the `error` [word](#word) for the last 10 minutes:
|
||||||
|
|
||||||
|
```logsql
|
||||||
|
_time:10m error | stats by (_time:1m) count() errors_per_minute
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Numeric buckets
|
||||||
|
|
||||||
|
Stats can be bucketed by any numeric [log field](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model) with the `field_name:bucket_size` syntax inside `by(...)` clause.
|
||||||
|
For example, the following query returns the number of log messages with the `status=200` [phrase](#phrase-filter) bucketed by `request_duration_seconds` numeric field with `0.5` step:
|
||||||
|
|
||||||
|
```logsql
|
||||||
|
_time:10m "status=200" | stats by (request_duration_seconds:0.5) count() requests
|
||||||
|
```
|
||||||
|
|
||||||
|
The `bucket_size` can contain the following convenient suffixes:
|
||||||
|
|
||||||
|
- `KB` - the `bucket_size` is multiplied by `1000` in this case. For example, `10KB`.
|
||||||
|
- `MB` - the `bucket_size` is multiplied by `1_000_000` in this case. For example, `10MB`.
|
||||||
|
- `GB` - the `bucket_size` is multiplied by `1_000_000_000` in this case. For example, `10GB`.
|
||||||
|
- `TB` - the `bucket_size` is multiplied by `1_000_000_000_000` in this case. For example, `10TB`.
|
||||||
|
- `KiB` - the `bucket_size` is multiplied by `1024` in this case. For example, `10KiB`.
|
||||||
|
- `MiB` - the `bucket_size` is multiplied by `1024*1024` in this case. For example, `10MiB`.
|
||||||
|
- `GiB` - the `bucket_size` is multiplied by `1024*1024*1024` in this case. For example, `10GiB`.
|
||||||
|
- `TiB` - the `bucket_size` is multiplied by `1024*1024*1024*1024` in this case. For example, `10TiB`.
|
||||||
|
|
||||||
|
#### IPv4 mask buckets
|
||||||
|
|
||||||
|
Stats can be bucketed by [log fields](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model) with [IPv4 addresses](https://en.wikipedia.org/wiki/IP_address)
|
||||||
|
via the `ip_field_name:/network_mask` syntax inside `by(...)` clause. For example, the following query returns the number of log entries per `/24` subnetwork during the last 10 minutes:
|
||||||
|
|
||||||
|
```logsql
|
||||||
|
_time:10m | stats by (ip:/24) count() requests_per_subnet
|
||||||
|
```
|
||||||
|
|
||||||
|
### Calculating multiple stats
|
||||||
|
|
||||||
Stats calculations can be combined. For example, the following query calculates the number of log messages with the `error` [word](#word),
|
Stats calculations can be combined. For example, the following query calculates the number of log messages with the `error` [word](#word),
|
||||||
the number of unique values for `ip` [field](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model) and the sum of `duration`
|
the number of unique values for `ip` [field](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model) and the sum of `duration`
|
||||||
[field](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model), grouped by `namespace` [field](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model):
|
[field](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model), grouped by `namespace` [field](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model):
|
||||||
|
@ -1128,6 +1170,8 @@ error | stats by (namespace)
|
||||||
sum(duration) as duration_sum
|
sum(duration) as duration_sum
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Stats TODO
|
||||||
|
|
||||||
LogsQL will support calculating the following additional stats based on the [log fields](https://docs.victoriametrics.com/VictoriaLogs/keyConcepts.html#data-model)
|
LogsQL will support calculating the following additional stats based on the [log fields](https://docs.victoriametrics.com/VictoriaLogs/keyConcepts.html#data-model)
|
||||||
and fields created by [transformations](#transformations):
|
and fields created by [transformations](#transformations):
|
||||||
|
|
||||||
|
|
|
@ -711,10 +711,10 @@ type timestampsHeader struct {
|
||||||
// blockSize is the size of the timestamps block inside timestampsFilename file
|
// blockSize is the size of the timestamps block inside timestampsFilename file
|
||||||
blockSize uint64
|
blockSize uint64
|
||||||
|
|
||||||
// minTimestamp is the mimumum timestamp seen in the block
|
// minTimestamp is the mimumum timestamp seen in the block in nanoseconds
|
||||||
minTimestamp int64
|
minTimestamp int64
|
||||||
|
|
||||||
// maxTimestamp is the maximum timestamp seen in the block
|
// maxTimestamp is the maximum timestamp seen in the block in nanoseconds
|
||||||
maxTimestamp int64
|
maxTimestamp int64
|
||||||
|
|
||||||
// marshalType is the type used for encoding the timestamps block
|
// marshalType is the type used for encoding the timestamps block
|
||||||
|
|
|
@ -1,15 +1,18 @@
|
||||||
package logstorage
|
package logstorage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/binary"
|
||||||
"math"
|
"math"
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
||||||
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/decimal"
|
||||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/encoding"
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/encoding"
|
||||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// blockResult holds results for a single block of log entries.
|
||||||
|
//
|
||||||
|
// It is expected that its contents is accessed only from a single goroutine at a time.
|
||||||
type blockResult struct {
|
type blockResult struct {
|
||||||
// buf holds all the bytes behind the requested column values in the block.
|
// buf holds all the bytes behind the requested column values in the block.
|
||||||
buf []byte
|
buf []byte
|
||||||
|
@ -335,6 +338,582 @@ func (br *blockResult) addConstColumn(name, value string) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (br *blockResult) getBucketedColumnValues(c *blockResultColumn, bucketSize float64) []string {
|
||||||
|
if c.isConst {
|
||||||
|
return br.getBucketedConstValues(c.encodedValues[0], bucketSize)
|
||||||
|
}
|
||||||
|
if c.isTime {
|
||||||
|
return br.getBucketedTimestampValues(bucketSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch c.valueType {
|
||||||
|
case valueTypeString:
|
||||||
|
return br.getBucketedStringValues(c.encodedValues, bucketSize)
|
||||||
|
case valueTypeDict:
|
||||||
|
return br.getBucketedDictValues(c.encodedValues, c.dictValues, bucketSize)
|
||||||
|
case valueTypeUint8:
|
||||||
|
return br.getBucketedUint8Values(c.encodedValues, bucketSize)
|
||||||
|
case valueTypeUint16:
|
||||||
|
return br.getBucketedUint16Values(c.encodedValues, bucketSize)
|
||||||
|
case valueTypeUint32:
|
||||||
|
return br.getBucketedUint32Values(c.encodedValues, bucketSize)
|
||||||
|
case valueTypeUint64:
|
||||||
|
return br.getBucketedUint64Values(c.encodedValues, bucketSize)
|
||||||
|
case valueTypeFloat64:
|
||||||
|
return br.getBucketedFloat64Values(c.encodedValues, bucketSize)
|
||||||
|
case valueTypeIPv4:
|
||||||
|
return br.getBucketedIPv4Values(c.encodedValues, bucketSize)
|
||||||
|
case valueTypeTimestampISO8601:
|
||||||
|
return br.getBucketedTimestampISO8601Values(c.encodedValues, bucketSize)
|
||||||
|
default:
|
||||||
|
logger.Panicf("BUG: unknown valueType=%d", c.valueType)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (br *blockResult) getBucketedConstValues(v string, bucketSize float64) []string {
|
||||||
|
if v == "" {
|
||||||
|
// Fast path - return a slice of empty strings without constructing the slice.
|
||||||
|
return getEmptyStrings(len(br.timestamps))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slower path - construct slice of identical values with the len(br.timestamps)
|
||||||
|
|
||||||
|
valuesBuf := br.valuesBuf
|
||||||
|
valuesBufLen := len(valuesBuf)
|
||||||
|
|
||||||
|
v = br.getBucketedValue(v, bucketSize)
|
||||||
|
for range br.timestamps {
|
||||||
|
valuesBuf = append(valuesBuf, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
br.valuesBuf = valuesBuf
|
||||||
|
|
||||||
|
return valuesBuf[valuesBufLen:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (br *blockResult) getBucketedTimestampValues(bucketSize float64) []string {
|
||||||
|
buf := br.buf
|
||||||
|
valuesBuf := br.valuesBuf
|
||||||
|
valuesBufLen := len(valuesBuf)
|
||||||
|
|
||||||
|
timestamps := br.timestamps
|
||||||
|
var s string
|
||||||
|
|
||||||
|
if bucketSize <= 1 {
|
||||||
|
for i := range timestamps {
|
||||||
|
if i > 0 && timestamps[i-1] == timestamps[i] {
|
||||||
|
valuesBuf = append(valuesBuf, s)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
bufLen := len(buf)
|
||||||
|
buf = marshalTimestampRFC3339Nano(buf, timestamps[i])
|
||||||
|
s = bytesutil.ToUnsafeString(buf[bufLen:])
|
||||||
|
valuesBuf = append(valuesBuf, s)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bucketSizeInt := int64(bucketSize)
|
||||||
|
var prevTimestamp int64
|
||||||
|
for i := range timestamps {
|
||||||
|
if i > 0 && timestamps[i-1] == timestamps[i] {
|
||||||
|
valuesBuf = append(valuesBuf, s)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
timestamp := timestamps[i]
|
||||||
|
timestamp -= timestamp % bucketSizeInt
|
||||||
|
if i > 0 && timestamp == prevTimestamp {
|
||||||
|
valuesBuf = append(valuesBuf, s)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
prevTimestamp = timestamp
|
||||||
|
bufLen := len(buf)
|
||||||
|
buf = marshalTimestampRFC3339Nano(buf, timestamp)
|
||||||
|
s = bytesutil.ToUnsafeString(buf[bufLen:])
|
||||||
|
valuesBuf = append(valuesBuf, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
br.buf = buf
|
||||||
|
br.valuesBuf = valuesBuf
|
||||||
|
|
||||||
|
return valuesBuf[valuesBufLen:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (br *blockResult) getBucketedStringValues(values []string, bucketSize float64) []string {
|
||||||
|
if bucketSize <= 0 {
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
|
||||||
|
valuesBuf := br.valuesBuf
|
||||||
|
valuesBufLen := len(valuesBuf)
|
||||||
|
|
||||||
|
var s string
|
||||||
|
for i := range values {
|
||||||
|
if i > 0 && values[i-1] == values[i] {
|
||||||
|
valuesBuf = append(valuesBuf, s)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
s = br.getBucketedValue(values[i], bucketSize)
|
||||||
|
valuesBuf = append(valuesBuf, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
br.valuesBuf = valuesBuf
|
||||||
|
|
||||||
|
return valuesBuf[valuesBufLen:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (br *blockResult) getBucketedDictValues(encodedValues, dictValues []string, bucketSize float64) []string {
|
||||||
|
valuesBuf := br.valuesBuf
|
||||||
|
valuesBufLen := len(valuesBuf)
|
||||||
|
|
||||||
|
dictValues = br.getBucketedStringValues(dictValues, bucketSize)
|
||||||
|
for _, v := range encodedValues {
|
||||||
|
dictIdx := v[0]
|
||||||
|
valuesBuf = append(valuesBuf, dictValues[dictIdx])
|
||||||
|
}
|
||||||
|
|
||||||
|
br.valuesBuf = valuesBuf
|
||||||
|
|
||||||
|
return valuesBuf[valuesBufLen:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (br *blockResult) getBucketedUint8Values(encodedValues []string, bucketSize float64) []string {
|
||||||
|
buf := br.buf
|
||||||
|
valuesBuf := br.valuesBuf
|
||||||
|
valuesBufLen := len(valuesBuf)
|
||||||
|
|
||||||
|
var s string
|
||||||
|
|
||||||
|
if bucketSize <= 1 || bucketSize >= (1<<8) {
|
||||||
|
for i, v := range encodedValues {
|
||||||
|
if i > 0 && encodedValues[i-1] == encodedValues[i] {
|
||||||
|
valuesBuf = append(valuesBuf, s)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
n := uint64(v[0])
|
||||||
|
bufLen := len(buf)
|
||||||
|
buf = marshalUint64(buf, n)
|
||||||
|
s = bytesutil.ToUnsafeString(buf[bufLen:])
|
||||||
|
valuesBuf = append(valuesBuf, s)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bucketSizeInt := uint64(bucketSize)
|
||||||
|
var nPrev uint64
|
||||||
|
for i, v := range encodedValues {
|
||||||
|
if i > 0 && encodedValues[i-1] == encodedValues[i] {
|
||||||
|
valuesBuf = append(valuesBuf, s)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
n := uint64(v[0])
|
||||||
|
n -= n % bucketSizeInt
|
||||||
|
if i > 0 && n == nPrev {
|
||||||
|
valuesBuf = append(valuesBuf, s)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
nPrev = n
|
||||||
|
bufLen := len(buf)
|
||||||
|
buf = marshalUint64(buf, n)
|
||||||
|
s = bytesutil.ToUnsafeString(buf[bufLen:])
|
||||||
|
valuesBuf = append(valuesBuf, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
br.valuesBuf = valuesBuf
|
||||||
|
br.buf = buf
|
||||||
|
|
||||||
|
return br.valuesBuf[valuesBufLen:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (br *blockResult) getBucketedUint16Values(encodedValues []string, bucketSize float64) []string {
|
||||||
|
buf := br.buf
|
||||||
|
valuesBuf := br.valuesBuf
|
||||||
|
valuesBufLen := len(valuesBuf)
|
||||||
|
|
||||||
|
var s string
|
||||||
|
|
||||||
|
if bucketSize <= 1 || bucketSize >= (1<<16) {
|
||||||
|
for i, v := range encodedValues {
|
||||||
|
if i > 0 && encodedValues[i-1] == encodedValues[i] {
|
||||||
|
valuesBuf = append(valuesBuf, s)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
b := bytesutil.ToUnsafeBytes(v)
|
||||||
|
n := uint64(encoding.UnmarshalUint16(b))
|
||||||
|
bufLen := len(buf)
|
||||||
|
buf = marshalUint64(buf, n)
|
||||||
|
s = bytesutil.ToUnsafeString(buf[bufLen:])
|
||||||
|
valuesBuf = append(valuesBuf, s)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bucketSizeInt := uint64(bucketSize)
|
||||||
|
var nPrev uint64
|
||||||
|
for i, v := range encodedValues {
|
||||||
|
if i > 0 && encodedValues[i-1] == encodedValues[i] {
|
||||||
|
valuesBuf = append(valuesBuf, s)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
b := bytesutil.ToUnsafeBytes(v)
|
||||||
|
n := uint64(encoding.UnmarshalUint16(b))
|
||||||
|
n -= n % bucketSizeInt
|
||||||
|
if i > 0 && n == nPrev {
|
||||||
|
valuesBuf = append(valuesBuf, s)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
nPrev = n
|
||||||
|
bufLen := len(buf)
|
||||||
|
buf = marshalUint64(buf, n)
|
||||||
|
s = bytesutil.ToUnsafeString(buf[bufLen:])
|
||||||
|
valuesBuf = append(valuesBuf, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
br.valuesBuf = valuesBuf
|
||||||
|
br.buf = buf
|
||||||
|
|
||||||
|
return br.valuesBuf[valuesBufLen:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (br *blockResult) getBucketedUint32Values(encodedValues []string, bucketSize float64) []string {
|
||||||
|
buf := br.buf
|
||||||
|
valuesBuf := br.valuesBuf
|
||||||
|
valuesBufLen := len(valuesBuf)
|
||||||
|
|
||||||
|
var s string
|
||||||
|
|
||||||
|
if bucketSize <= 1 || bucketSize >= (1<<32) {
|
||||||
|
for i, v := range encodedValues {
|
||||||
|
if i > 0 && encodedValues[i-1] == encodedValues[i] {
|
||||||
|
valuesBuf = append(valuesBuf, s)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
b := bytesutil.ToUnsafeBytes(v)
|
||||||
|
n := uint64(encoding.UnmarshalUint32(b))
|
||||||
|
bufLen := len(buf)
|
||||||
|
buf = marshalUint64(buf, n)
|
||||||
|
s = bytesutil.ToUnsafeString(buf[bufLen:])
|
||||||
|
valuesBuf = append(valuesBuf, s)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bucketSizeInt := uint64(bucketSize)
|
||||||
|
var nPrev uint64
|
||||||
|
for i, v := range encodedValues {
|
||||||
|
if i > 0 && encodedValues[i-1] == encodedValues[i] {
|
||||||
|
valuesBuf = append(valuesBuf, s)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
b := bytesutil.ToUnsafeBytes(v)
|
||||||
|
n := uint64(encoding.UnmarshalUint32(b))
|
||||||
|
n -= n % bucketSizeInt
|
||||||
|
if i > 0 && n == nPrev {
|
||||||
|
valuesBuf = append(valuesBuf, s)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
nPrev = n
|
||||||
|
bufLen := len(buf)
|
||||||
|
buf = marshalUint64(buf, n)
|
||||||
|
s = bytesutil.ToUnsafeString(buf[bufLen:])
|
||||||
|
valuesBuf = append(valuesBuf, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
br.valuesBuf = valuesBuf
|
||||||
|
br.buf = buf
|
||||||
|
|
||||||
|
return br.valuesBuf[valuesBufLen:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (br *blockResult) getBucketedUint64Values(encodedValues []string, bucketSize float64) []string {
|
||||||
|
buf := br.buf
|
||||||
|
valuesBuf := br.valuesBuf
|
||||||
|
valuesBufLen := len(valuesBuf)
|
||||||
|
|
||||||
|
var s string
|
||||||
|
|
||||||
|
if bucketSize <= 1 || bucketSize >= (1<<64) {
|
||||||
|
for i, v := range encodedValues {
|
||||||
|
if i > 0 && encodedValues[i-1] == encodedValues[i] {
|
||||||
|
valuesBuf = append(valuesBuf, s)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
b := bytesutil.ToUnsafeBytes(v)
|
||||||
|
n := encoding.UnmarshalUint64(b)
|
||||||
|
bufLen := len(buf)
|
||||||
|
buf = marshalUint64(buf, n)
|
||||||
|
s = bytesutil.ToUnsafeString(buf[bufLen:])
|
||||||
|
valuesBuf = append(valuesBuf, s)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bucketSizeInt := uint64(bucketSize)
|
||||||
|
var nPrev uint64
|
||||||
|
for i, v := range encodedValues {
|
||||||
|
if i > 0 && encodedValues[i-1] == encodedValues[i] {
|
||||||
|
valuesBuf = append(valuesBuf, s)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
b := bytesutil.ToUnsafeBytes(v)
|
||||||
|
n := encoding.UnmarshalUint64(b)
|
||||||
|
n -= n % bucketSizeInt
|
||||||
|
if i > 0 && n == nPrev {
|
||||||
|
valuesBuf = append(valuesBuf, s)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
nPrev = n
|
||||||
|
bufLen := len(buf)
|
||||||
|
buf = marshalUint64(buf, n)
|
||||||
|
s = bytesutil.ToUnsafeString(buf[bufLen:])
|
||||||
|
valuesBuf = append(valuesBuf, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
br.valuesBuf = valuesBuf
|
||||||
|
br.buf = buf
|
||||||
|
|
||||||
|
return br.valuesBuf[valuesBufLen:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (br *blockResult) getBucketedFloat64Values(encodedValues []string, bucketSize float64) []string {
|
||||||
|
buf := br.buf
|
||||||
|
valuesBuf := br.valuesBuf
|
||||||
|
valuesBufLen := len(valuesBuf)
|
||||||
|
|
||||||
|
var s string
|
||||||
|
|
||||||
|
if bucketSize <= 0 {
|
||||||
|
for i, v := range encodedValues {
|
||||||
|
if i > 0 && encodedValues[i-1] == encodedValues[i] {
|
||||||
|
valuesBuf = append(valuesBuf, s)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
b := bytesutil.ToUnsafeBytes(v)
|
||||||
|
n := encoding.UnmarshalUint64(b)
|
||||||
|
f := math.Float64frombits(n)
|
||||||
|
|
||||||
|
bufLen := len(buf)
|
||||||
|
buf = marshalFloat64(buf, f)
|
||||||
|
s = bytesutil.ToUnsafeString(buf[bufLen:])
|
||||||
|
valuesBuf = append(valuesBuf, s)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_, e := decimal.FromFloat(bucketSize)
|
||||||
|
p10 := math.Pow10(int(-e))
|
||||||
|
bucketSizeP10 := int64(bucketSize * p10)
|
||||||
|
var fPrev float64
|
||||||
|
for i, v := range encodedValues {
|
||||||
|
if i > 0 && encodedValues[i-1] == encodedValues[i] {
|
||||||
|
valuesBuf = append(valuesBuf, s)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
b := bytesutil.ToUnsafeBytes(v)
|
||||||
|
n := encoding.UnmarshalUint64(b)
|
||||||
|
f := math.Float64frombits(n)
|
||||||
|
|
||||||
|
// emulate f % bucketSize for float64 values
|
||||||
|
fP10 := int64(f * p10)
|
||||||
|
fP10 -= fP10 % bucketSizeP10
|
||||||
|
f = float64(fP10) / p10
|
||||||
|
|
||||||
|
if i > 0 && f == fPrev {
|
||||||
|
valuesBuf = append(valuesBuf, s)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fPrev = f
|
||||||
|
bufLen := len(buf)
|
||||||
|
buf = marshalFloat64(buf, f)
|
||||||
|
s = bytesutil.ToUnsafeString(buf[bufLen:])
|
||||||
|
valuesBuf = append(valuesBuf, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
br.valuesBuf = valuesBuf
|
||||||
|
br.buf = buf
|
||||||
|
|
||||||
|
return br.valuesBuf[valuesBufLen:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (br *blockResult) getBucketedIPv4Values(encodedValues []string, bucketSize float64) []string {
|
||||||
|
buf := br.buf
|
||||||
|
valuesBuf := br.valuesBuf
|
||||||
|
valuesBufLen := len(valuesBuf)
|
||||||
|
|
||||||
|
var s string
|
||||||
|
|
||||||
|
if bucketSize <= 1 {
|
||||||
|
for i, v := range encodedValues {
|
||||||
|
if i > 0 && encodedValues[i-1] == encodedValues[i] {
|
||||||
|
valuesBuf = append(valuesBuf, s)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
bufLen := len(buf)
|
||||||
|
buf = toIPv4String(buf, v)
|
||||||
|
s = bytesutil.ToUnsafeString(buf[bufLen:])
|
||||||
|
valuesBuf = append(valuesBuf, s)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bucketSizeInt := uint32(bucketSize)
|
||||||
|
var nPrev uint32
|
||||||
|
for i, v := range encodedValues {
|
||||||
|
if i > 0 && encodedValues[i-1] == encodedValues[i] {
|
||||||
|
valuesBuf = append(valuesBuf, s)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
b := bytesutil.ToUnsafeBytes(v)
|
||||||
|
n := binary.BigEndian.Uint32(b)
|
||||||
|
n -= n % bucketSizeInt
|
||||||
|
if i > 0 && n == nPrev {
|
||||||
|
valuesBuf = append(valuesBuf, s)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
nPrev = n
|
||||||
|
bufLen := len(buf)
|
||||||
|
buf = marshalIPv4(buf, n)
|
||||||
|
s = bytesutil.ToUnsafeString(buf[bufLen:])
|
||||||
|
valuesBuf = append(valuesBuf, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
br.valuesBuf = valuesBuf
|
||||||
|
br.buf = buf
|
||||||
|
|
||||||
|
return valuesBuf[valuesBufLen:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (br *blockResult) getBucketedTimestampISO8601Values(encodedValues []string, bucketSize float64) []string {
|
||||||
|
buf := br.buf
|
||||||
|
valuesBuf := br.valuesBuf
|
||||||
|
valuesBufLen := len(valuesBuf)
|
||||||
|
|
||||||
|
var s string
|
||||||
|
|
||||||
|
if bucketSize <= 1 {
|
||||||
|
for i, v := range encodedValues {
|
||||||
|
if i > 0 && encodedValues[i-1] == encodedValues[i] {
|
||||||
|
valuesBuf = append(valuesBuf, s)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
b := bytesutil.ToUnsafeBytes(v)
|
||||||
|
n := encoding.UnmarshalUint64(b)
|
||||||
|
|
||||||
|
bufLen := len(buf)
|
||||||
|
buf = marshalTimestampISO8601(buf, int64(n))
|
||||||
|
s = bytesutil.ToUnsafeString(buf[bufLen:])
|
||||||
|
valuesBuf = append(valuesBuf, s)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bucketSizeInt := uint64(bucketSize)
|
||||||
|
var nPrev uint64
|
||||||
|
bb := bbPool.Get()
|
||||||
|
for i, v := range encodedValues {
|
||||||
|
if i > 0 && encodedValues[i-1] == encodedValues[i] {
|
||||||
|
valuesBuf = append(valuesBuf, s)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
b := bytesutil.ToUnsafeBytes(v)
|
||||||
|
n := encoding.UnmarshalUint64(b)
|
||||||
|
n -= n % bucketSizeInt
|
||||||
|
if i > 0 && n == nPrev {
|
||||||
|
valuesBuf = append(valuesBuf, s)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
nPrev = n
|
||||||
|
bufLen := len(buf)
|
||||||
|
buf = marshalTimestampISO8601(buf, int64(n))
|
||||||
|
s = bytesutil.ToUnsafeString(buf[bufLen:])
|
||||||
|
valuesBuf = append(valuesBuf, s)
|
||||||
|
}
|
||||||
|
bbPool.Put(bb)
|
||||||
|
}
|
||||||
|
|
||||||
|
br.valuesBuf = valuesBuf
|
||||||
|
br.buf = buf
|
||||||
|
|
||||||
|
return valuesBuf[valuesBufLen:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// getBucketedValue returns bucketed s according to the given bucketSize.
|
||||||
|
func (br *blockResult) getBucketedValue(s string, bucketSize float64) string {
|
||||||
|
if bucketSize <= 0 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
if len(s) == 0 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
c := s[0]
|
||||||
|
if (c < '0' || c > '9') && c != '-' {
|
||||||
|
// Fast path - the value cannot be bucketed, since it starts with unexpected chars.
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
if f, ok := tryParseFloat64(s); ok {
|
||||||
|
// emulate f % bucketSize for float64 values
|
||||||
|
_, e := decimal.FromFloat(bucketSize)
|
||||||
|
p10 := math.Pow10(int(-e))
|
||||||
|
fP10 := int64(f * p10)
|
||||||
|
fP10 -= fP10 % int64(bucketSize*p10)
|
||||||
|
f = float64(fP10) / p10
|
||||||
|
|
||||||
|
bufLen := len(br.buf)
|
||||||
|
br.buf = marshalFloat64(br.buf, f)
|
||||||
|
return bytesutil.ToUnsafeString(br.buf[bufLen:])
|
||||||
|
}
|
||||||
|
|
||||||
|
if nsecs, ok := tryParseTimestampISO8601(s); ok {
|
||||||
|
nsecs -= nsecs % int64(bucketSize)
|
||||||
|
bufLen := len(br.buf)
|
||||||
|
br.buf = marshalTimestampISO8601(br.buf, nsecs)
|
||||||
|
return bytesutil.ToUnsafeString(br.buf[bufLen:])
|
||||||
|
}
|
||||||
|
|
||||||
|
if nsecs, ok := tryParseTimestampRFC3339Nano(s); ok {
|
||||||
|
nsecs -= nsecs % int64(bucketSize)
|
||||||
|
bufLen := len(br.buf)
|
||||||
|
br.buf = marshalTimestampRFC3339Nano(br.buf, nsecs)
|
||||||
|
return bytesutil.ToUnsafeString(br.buf[bufLen:])
|
||||||
|
}
|
||||||
|
|
||||||
|
if n, ok := tryParseIPv4(s); ok {
|
||||||
|
n -= n % uint32(bucketSize)
|
||||||
|
bufLen := len(br.buf)
|
||||||
|
br.buf = marshalIPv4(br.buf, n)
|
||||||
|
return bytesutil.ToUnsafeString(br.buf[bufLen:])
|
||||||
|
}
|
||||||
|
|
||||||
|
if nsecs, ok := tryParseDuration(s); ok {
|
||||||
|
nsecs -= nsecs % int64(bucketSize)
|
||||||
|
bufLen := len(br.buf)
|
||||||
|
br.buf = marshalDuration(br.buf, nsecs)
|
||||||
|
return bytesutil.ToUnsafeString(br.buf[bufLen:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Couldn't parse s, so return it as is.
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
func (br *blockResult) addEmptyStringColumn(columnName string) {
|
func (br *blockResult) addEmptyStringColumn(columnName string) {
|
||||||
br.cs = append(br.cs, blockResultColumn{
|
br.cs = append(br.cs, blockResultColumn{
|
||||||
name: columnName,
|
name: columnName,
|
||||||
|
@ -378,7 +957,9 @@ func (br *blockResult) getColumnByName(columnName string) blockResultColumn {
|
||||||
}
|
}
|
||||||
|
|
||||||
cs := br.getColumns()
|
cs := br.getColumns()
|
||||||
for i := range cs {
|
|
||||||
|
// iterate columns in reverse order, so overridden column results are returned instead of original column results.
|
||||||
|
for i := len(cs) - 1; i >= 0; i-- {
|
||||||
if cs[i].name == columnName {
|
if cs[i].name == columnName {
|
||||||
return cs[i]
|
return cs[i]
|
||||||
}
|
}
|
||||||
|
@ -429,15 +1010,6 @@ func (br *blockResult) truncateRows(keepRows int) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (br *blockResult) appendColumnValues(dst [][]string, columnNames []string) [][]string {
|
|
||||||
for _, columnName := range columnNames {
|
|
||||||
c := br.getColumnByName(columnName)
|
|
||||||
values := c.getValues(br)
|
|
||||||
dst = append(dst, values)
|
|
||||||
}
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
type blockResultColumn struct {
|
type blockResultColumn struct {
|
||||||
// name is column name.
|
// name is column name.
|
||||||
name string
|
name string
|
||||||
|
@ -455,15 +1027,21 @@ type blockResultColumn struct {
|
||||||
// valueType is the type of non-cost value
|
// valueType is the type of non-cost value
|
||||||
valueType valueType
|
valueType valueType
|
||||||
|
|
||||||
// dictValues contain dictionary values for valueTypeDict column
|
// dictValues contains dictionary values for valueTypeDict column
|
||||||
dictValues []string
|
dictValues []string
|
||||||
|
|
||||||
// encodedValues contain encoded values for non-const column
|
// encodedValues contains encoded values for non-const column
|
||||||
encodedValues []string
|
encodedValues []string
|
||||||
|
|
||||||
// values contain decoded values after getValues() call for the given column
|
// values contains decoded values after getValues() call
|
||||||
values []string
|
values []string
|
||||||
|
|
||||||
|
// bucketedValues contains values after getBucketedValues() call
|
||||||
|
bucketedValues []string
|
||||||
|
|
||||||
|
// bucketSize contains bucketSize for bucketedValues
|
||||||
|
bucketSize float64
|
||||||
|
|
||||||
// buf and valuesBuf are used by addValue() in order to re-use memory across resetRows().
|
// buf and valuesBuf are used by addValue() in order to re-use memory across resetRows().
|
||||||
buf []byte
|
buf []byte
|
||||||
valuesBuf []string
|
valuesBuf []string
|
||||||
|
@ -557,129 +1135,31 @@ func (c *blockResultColumn) getValueAtRow(br *blockResult, rowIdx int) string {
|
||||||
return values[rowIdx]
|
return values[rowIdx]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getValues returns values for the given column, bucketed according to bucketSize.
|
||||||
|
//
|
||||||
|
// The returned values are valid until br.reset() is called.
|
||||||
|
func (c *blockResultColumn) getBucketedValues(br *blockResult, bucketSize float64) []string {
|
||||||
|
if bucketSize <= 0 {
|
||||||
|
return c.getValues(br)
|
||||||
|
}
|
||||||
|
if values := c.bucketedValues; values != nil && c.bucketSize == bucketSize {
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
|
||||||
|
c.bucketedValues = br.getBucketedColumnValues(c, bucketSize)
|
||||||
|
c.bucketSize = bucketSize
|
||||||
|
return c.bucketedValues
|
||||||
|
}
|
||||||
|
|
||||||
// getValues returns values for the given column.
|
// getValues returns values for the given column.
|
||||||
//
|
//
|
||||||
// The returned values are valid until br.reset() is called.
|
// The returned values are valid until br.reset() is called.
|
||||||
func (c *blockResultColumn) getValues(br *blockResult) []string {
|
func (c *blockResultColumn) getValues(br *blockResult) []string {
|
||||||
if c.values != nil {
|
if values := c.values; values != nil {
|
||||||
return c.values
|
return values
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := br.buf
|
c.values = br.getBucketedColumnValues(c, 0)
|
||||||
valuesBuf := br.valuesBuf
|
|
||||||
valuesBufLen := len(valuesBuf)
|
|
||||||
|
|
||||||
if c.isConst {
|
|
||||||
v := c.encodedValues[0]
|
|
||||||
if v == "" {
|
|
||||||
// Fast path - return a slice of empty strings without constructing it.
|
|
||||||
c.values = getEmptyStrings(len(br.timestamps))
|
|
||||||
return c.values
|
|
||||||
}
|
|
||||||
|
|
||||||
// Slower path - construct slice of identical values with the len(br.timestamps)
|
|
||||||
for range br.timestamps {
|
|
||||||
valuesBuf = append(valuesBuf, v)
|
|
||||||
}
|
|
||||||
c.values = valuesBuf[valuesBufLen:]
|
|
||||||
br.valuesBuf = valuesBuf
|
|
||||||
return c.values
|
|
||||||
}
|
|
||||||
if c.isTime {
|
|
||||||
for _, timestamp := range br.timestamps {
|
|
||||||
t := time.Unix(0, timestamp).UTC()
|
|
||||||
bufLen := len(buf)
|
|
||||||
buf = t.AppendFormat(buf, time.RFC3339Nano)
|
|
||||||
s := bytesutil.ToUnsafeString(buf[bufLen:])
|
|
||||||
valuesBuf = append(valuesBuf, s)
|
|
||||||
}
|
|
||||||
c.values = valuesBuf[valuesBufLen:]
|
|
||||||
br.buf = buf
|
|
||||||
br.valuesBuf = valuesBuf
|
|
||||||
return c.values
|
|
||||||
}
|
|
||||||
|
|
||||||
appendValue := func(v string) {
|
|
||||||
bufLen := len(buf)
|
|
||||||
buf = append(buf, v...)
|
|
||||||
s := bytesutil.ToUnsafeString(buf[bufLen:])
|
|
||||||
valuesBuf = append(valuesBuf, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch c.valueType {
|
|
||||||
case valueTypeString:
|
|
||||||
c.values = c.encodedValues
|
|
||||||
return c.values
|
|
||||||
case valueTypeDict:
|
|
||||||
dictValues := c.dictValues
|
|
||||||
for _, v := range c.encodedValues {
|
|
||||||
dictIdx := v[0]
|
|
||||||
appendValue(dictValues[dictIdx])
|
|
||||||
}
|
|
||||||
case valueTypeUint8:
|
|
||||||
bb := bbPool.Get()
|
|
||||||
for _, v := range c.encodedValues {
|
|
||||||
n := uint64(v[0])
|
|
||||||
bb.B = strconv.AppendUint(bb.B[:0], n, 10)
|
|
||||||
appendValue(bytesutil.ToUnsafeString(bb.B))
|
|
||||||
}
|
|
||||||
bbPool.Put(bb)
|
|
||||||
case valueTypeUint16:
|
|
||||||
bb := bbPool.Get()
|
|
||||||
for _, v := range c.encodedValues {
|
|
||||||
b := bytesutil.ToUnsafeBytes(v)
|
|
||||||
n := uint64(encoding.UnmarshalUint16(b))
|
|
||||||
bb.B = strconv.AppendUint(bb.B[:0], n, 10)
|
|
||||||
appendValue(bytesutil.ToUnsafeString(bb.B))
|
|
||||||
}
|
|
||||||
bbPool.Put(bb)
|
|
||||||
case valueTypeUint32:
|
|
||||||
bb := bbPool.Get()
|
|
||||||
for _, v := range c.encodedValues {
|
|
||||||
b := bytesutil.ToUnsafeBytes(v)
|
|
||||||
n := uint64(encoding.UnmarshalUint32(b))
|
|
||||||
bb.B = strconv.AppendUint(bb.B[:0], n, 10)
|
|
||||||
appendValue(bytesutil.ToUnsafeString(bb.B))
|
|
||||||
}
|
|
||||||
bbPool.Put(bb)
|
|
||||||
case valueTypeUint64:
|
|
||||||
bb := bbPool.Get()
|
|
||||||
for _, v := range c.encodedValues {
|
|
||||||
b := bytesutil.ToUnsafeBytes(v)
|
|
||||||
n := encoding.UnmarshalUint64(b)
|
|
||||||
bb.B = strconv.AppendUint(bb.B[:0], n, 10)
|
|
||||||
appendValue(bytesutil.ToUnsafeString(bb.B))
|
|
||||||
}
|
|
||||||
bbPool.Put(bb)
|
|
||||||
case valueTypeFloat64:
|
|
||||||
bb := bbPool.Get()
|
|
||||||
for _, v := range c.encodedValues {
|
|
||||||
bb.B = toFloat64String(bb.B[:0], v)
|
|
||||||
appendValue(bytesutil.ToUnsafeString(bb.B))
|
|
||||||
}
|
|
||||||
bbPool.Put(bb)
|
|
||||||
case valueTypeIPv4:
|
|
||||||
bb := bbPool.Get()
|
|
||||||
for _, v := range c.encodedValues {
|
|
||||||
bb.B = toIPv4String(bb.B[:0], v)
|
|
||||||
appendValue(bytesutil.ToUnsafeString(bb.B))
|
|
||||||
}
|
|
||||||
bbPool.Put(bb)
|
|
||||||
case valueTypeTimestampISO8601:
|
|
||||||
bb := bbPool.Get()
|
|
||||||
for _, v := range c.encodedValues {
|
|
||||||
bb.B = toTimestampISO8601String(bb.B[:0], v)
|
|
||||||
appendValue(bytesutil.ToUnsafeString(bb.B))
|
|
||||||
}
|
|
||||||
bbPool.Put(bb)
|
|
||||||
default:
|
|
||||||
logger.Panicf("BUG: unknown valueType=%d", c.valueType)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.values = valuesBuf[valuesBufLen:]
|
|
||||||
br.buf = buf
|
|
||||||
br.valuesBuf = valuesBuf
|
|
||||||
|
|
||||||
return c.values
|
return c.values
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -84,12 +84,12 @@ func (fe *filterExact) apply(bs *blockSearch, bm *bitmap) {
|
||||||
|
|
||||||
func matchTimestampISO8601ByExactValue(bs *blockSearch, ch *columnHeader, bm *bitmap, value string, tokens []string) {
|
func matchTimestampISO8601ByExactValue(bs *blockSearch, ch *columnHeader, bm *bitmap, value string, tokens []string) {
|
||||||
n, ok := tryParseTimestampISO8601(value)
|
n, ok := tryParseTimestampISO8601(value)
|
||||||
if !ok || n < ch.minValue || n > ch.maxValue {
|
if !ok || n < int64(ch.minValue) || n > int64(ch.maxValue) {
|
||||||
bm.resetBits()
|
bm.resetBits()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
bb := bbPool.Get()
|
bb := bbPool.Get()
|
||||||
bb.B = encoding.MarshalUint64(bb.B, n)
|
bb.B = encoding.MarshalUint64(bb.B, uint64(n))
|
||||||
matchBinaryValue(bs, ch, bm, bb.B, tokens)
|
matchBinaryValue(bs, ch, bm, bb.B, tokens)
|
||||||
bbPool.Put(bb)
|
bbPool.Put(bb)
|
||||||
}
|
}
|
||||||
|
|
|
@ -242,7 +242,7 @@ func (fi *filterIn) initTimestampISO8601Values() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
bufLen := len(buf)
|
bufLen := len(buf)
|
||||||
buf = encoding.MarshalUint64(buf, n)
|
buf = encoding.MarshalUint64(buf, uint64(n))
|
||||||
s := bytesutil.ToUnsafeString(buf[bufLen:])
|
s := bytesutil.ToUnsafeString(buf[bufLen:])
|
||||||
m[s] = struct{}{}
|
m[s] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package logstorage
|
package logstorage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
||||||
|
@ -192,12 +191,12 @@ func matchMinMaxValueLen(ch *columnHeader, minLen, maxLen uint64) bool {
|
||||||
bb := bbPool.Get()
|
bb := bbPool.Get()
|
||||||
defer bbPool.Put(bb)
|
defer bbPool.Put(bb)
|
||||||
|
|
||||||
bb.B = strconv.AppendUint(bb.B[:0], ch.minValue, 10)
|
bb.B = marshalUint64(bb.B[:0], ch.minValue)
|
||||||
s := bytesutil.ToUnsafeString(bb.B)
|
s := bytesutil.ToUnsafeString(bb.B)
|
||||||
if maxLen < uint64(len(s)) {
|
if maxLen < uint64(len(s)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
bb.B = strconv.AppendUint(bb.B[:0], ch.maxValue, 10)
|
bb.B = marshalUint64(bb.B[:0], ch.maxValue)
|
||||||
s = bytesutil.ToUnsafeString(bb.B)
|
s = bytesutil.ToUnsafeString(bb.B)
|
||||||
return minLen <= uint64(len(s))
|
return minLen <= uint64(len(s))
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ package logstorage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
@ -323,7 +322,7 @@ func toUint8String(bs *blockSearch, bb *bytesutil.ByteBuffer, v string) string {
|
||||||
logger.Panicf("FATAL: %s: unexpected length for binary representation of uint8 number: got %d; want 1", bs.partPath(), len(v))
|
logger.Panicf("FATAL: %s: unexpected length for binary representation of uint8 number: got %d; want 1", bs.partPath(), len(v))
|
||||||
}
|
}
|
||||||
n := uint64(v[0])
|
n := uint64(v[0])
|
||||||
bb.B = strconv.AppendUint(bb.B[:0], n, 10)
|
bb.B = marshalUint64(bb.B[:0], n)
|
||||||
return bytesutil.ToUnsafeString(bb.B)
|
return bytesutil.ToUnsafeString(bb.B)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -333,7 +332,7 @@ func toUint16String(bs *blockSearch, bb *bytesutil.ByteBuffer, v string) string
|
||||||
}
|
}
|
||||||
b := bytesutil.ToUnsafeBytes(v)
|
b := bytesutil.ToUnsafeBytes(v)
|
||||||
n := uint64(encoding.UnmarshalUint16(b))
|
n := uint64(encoding.UnmarshalUint16(b))
|
||||||
bb.B = strconv.AppendUint(bb.B[:0], n, 10)
|
bb.B = marshalUint64(bb.B[:0], n)
|
||||||
return bytesutil.ToUnsafeString(bb.B)
|
return bytesutil.ToUnsafeString(bb.B)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -343,7 +342,7 @@ func toUint32String(bs *blockSearch, bb *bytesutil.ByteBuffer, v string) string
|
||||||
}
|
}
|
||||||
b := bytesutil.ToUnsafeBytes(v)
|
b := bytesutil.ToUnsafeBytes(v)
|
||||||
n := uint64(encoding.UnmarshalUint32(b))
|
n := uint64(encoding.UnmarshalUint32(b))
|
||||||
bb.B = strconv.AppendUint(bb.B[:0], n, 10)
|
bb.B = marshalUint64(bb.B[:0], n)
|
||||||
return bytesutil.ToUnsafeString(bb.B)
|
return bytesutil.ToUnsafeString(bb.B)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -353,6 +352,6 @@ func toUint64String(bs *blockSearch, bb *bytesutil.ByteBuffer, v string) string
|
||||||
}
|
}
|
||||||
b := bytesutil.ToUnsafeBytes(v)
|
b := bytesutil.ToUnsafeBytes(v)
|
||||||
n := encoding.UnmarshalUint64(b)
|
n := encoding.UnmarshalUint64(b)
|
||||||
bb.B = strconv.AppendUint(bb.B[:0], n, 10)
|
bb.B = marshalUint64(bb.B[:0], n)
|
||||||
return bytesutil.ToUnsafeString(bb.B)
|
return bytesutil.ToUnsafeString(bb.B)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,13 @@ package logstorage
|
||||||
//
|
//
|
||||||
// It is expressed as `_time:(start, end]` in LogsQL.
|
// It is expressed as `_time:(start, end]` in LogsQL.
|
||||||
type filterTime struct {
|
type filterTime struct {
|
||||||
|
// mintimestamp is the minimum timestamp in nanoseconds to find
|
||||||
minTimestamp int64
|
minTimestamp int64
|
||||||
|
|
||||||
|
// maxTimestamp is the maximum timestamp in nanoseconds to find
|
||||||
maxTimestamp int64
|
maxTimestamp int64
|
||||||
|
|
||||||
|
// stringRepr is string representation of the filter
|
||||||
stringRepr string
|
stringRepr string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -219,7 +219,7 @@ func ParseQuery(s string) (*Query, error) {
|
||||||
|
|
||||||
f, err := parseFilter(lex)
|
f, err := parseFilter(lex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%w; context: %s", err, lex.context())
|
return nil, fmt.Errorf("%w; context: [%s]", err, lex.context())
|
||||||
}
|
}
|
||||||
q := &Query{
|
q := &Query{
|
||||||
f: f,
|
f: f,
|
||||||
|
@ -227,12 +227,12 @@ func ParseQuery(s string) (*Query, error) {
|
||||||
|
|
||||||
pipes, err := parsePipes(lex)
|
pipes, err := parsePipes(lex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%w; context: %s", err, lex.context())
|
return nil, fmt.Errorf("%w; context: [%s]", err, lex.context())
|
||||||
}
|
}
|
||||||
q.pipes = pipes
|
q.pipes = pipes
|
||||||
|
|
||||||
if !lex.isEnd() {
|
if !lex.isEnd() {
|
||||||
return nil, fmt.Errorf("unexpected unparsed tail; context: %s", lex.context())
|
return nil, fmt.Errorf("unexpected unparsed tail; context: [%s]", lex.context())
|
||||||
}
|
}
|
||||||
|
|
||||||
return q, nil
|
return q, nil
|
||||||
|
@ -344,25 +344,25 @@ func parseGenericFilter(lex *lexer, fieldName string) (filter, error) {
|
||||||
case lex.isKeyword(",", ")", "[", "]"):
|
case lex.isKeyword(",", ")", "[", "]"):
|
||||||
return nil, fmt.Errorf("unexpected token %q", lex.token)
|
return nil, fmt.Errorf("unexpected token %q", lex.token)
|
||||||
}
|
}
|
||||||
phrase := getCompoundPhrase(lex, fieldName == "")
|
phrase := getCompoundPhrase(lex, fieldName != "")
|
||||||
return parseFilterForPhrase(lex, phrase, fieldName)
|
return parseFilterForPhrase(lex, phrase, fieldName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getCompoundPhrase(lex *lexer, stopOnColon bool) string {
|
func getCompoundPhrase(lex *lexer, allowColon bool) string {
|
||||||
phrase := lex.token
|
phrase := lex.token
|
||||||
rawPhrase := lex.rawToken
|
rawPhrase := lex.rawToken
|
||||||
lex.nextToken()
|
lex.nextToken()
|
||||||
suffix := getCompoundSuffix(lex, stopOnColon)
|
suffix := getCompoundSuffix(lex, allowColon)
|
||||||
if suffix == "" {
|
if suffix == "" {
|
||||||
return phrase
|
return phrase
|
||||||
}
|
}
|
||||||
return rawPhrase + suffix
|
return rawPhrase + suffix
|
||||||
}
|
}
|
||||||
|
|
||||||
func getCompoundSuffix(lex *lexer, stopOnColon bool) string {
|
func getCompoundSuffix(lex *lexer, allowColon bool) string {
|
||||||
s := ""
|
s := ""
|
||||||
stopTokens := []string{"*", ",", "(", ")", "[", "]", "|", ""}
|
stopTokens := []string{"*", ",", "(", ")", "[", "]", "|", ""}
|
||||||
if stopOnColon {
|
if !allowColon {
|
||||||
stopTokens = append(stopTokens, ":")
|
stopTokens = append(stopTokens, ":")
|
||||||
}
|
}
|
||||||
for !lex.isSkippedSpace && !lex.isKeyword(stopTokens...) {
|
for !lex.isSkippedSpace && !lex.isKeyword(stopTokens...) {
|
||||||
|
@ -495,7 +495,7 @@ func parseFuncArgMaybePrefix(lex *lexer, funcName, fieldName string, callback fu
|
||||||
phrase := lex.token
|
phrase := lex.token
|
||||||
lex.nextToken()
|
lex.nextToken()
|
||||||
if !lex.isKeyword("(") {
|
if !lex.isKeyword("(") {
|
||||||
phrase += getCompoundSuffix(lex, fieldName == "")
|
phrase += getCompoundSuffix(lex, fieldName != "")
|
||||||
return parseFilterForPhrase(lex, phrase, fieldName)
|
return parseFilterForPhrase(lex, phrase, fieldName)
|
||||||
}
|
}
|
||||||
if !lex.mustNextToken() {
|
if !lex.mustNextToken() {
|
||||||
|
@ -676,7 +676,7 @@ func parseFilterRange(lex *lexer, fieldName string) (filter, error) {
|
||||||
case lex.isKeyword("["):
|
case lex.isKeyword("["):
|
||||||
includeMinValue = true
|
includeMinValue = true
|
||||||
default:
|
default:
|
||||||
phrase := funcName + getCompoundSuffix(lex, fieldName == "")
|
phrase := funcName + getCompoundSuffix(lex, fieldName != "")
|
||||||
return parseFilterForPhrase(lex, phrase, fieldName)
|
return parseFilterForPhrase(lex, phrase, fieldName)
|
||||||
}
|
}
|
||||||
if !lex.mustNextToken() {
|
if !lex.mustNextToken() {
|
||||||
|
@ -765,7 +765,7 @@ func parseFuncArgs(lex *lexer, fieldName string, callback func(args []string) (f
|
||||||
funcName := lex.token
|
funcName := lex.token
|
||||||
lex.nextToken()
|
lex.nextToken()
|
||||||
if !lex.isKeyword("(") {
|
if !lex.isKeyword("(") {
|
||||||
phrase := funcName + getCompoundSuffix(lex, fieldName == "")
|
phrase := funcName + getCompoundSuffix(lex, fieldName != "")
|
||||||
return parseFilterForPhrase(lex, phrase, fieldName)
|
return parseFilterForPhrase(lex, phrase, fieldName)
|
||||||
}
|
}
|
||||||
if !lex.mustNextToken() {
|
if !lex.mustNextToken() {
|
||||||
|
@ -824,9 +824,9 @@ func parseFilterTimeWithOffset(lex *lexer) (*filterTime, error) {
|
||||||
return nil, fmt.Errorf("missing offset for _time filter %s", ft)
|
return nil, fmt.Errorf("missing offset for _time filter %s", ft)
|
||||||
}
|
}
|
||||||
s := getCompoundToken(lex)
|
s := getCompoundToken(lex)
|
||||||
d, err := promutils.ParseDuration(s)
|
d, ok := tryParseDuration(s)
|
||||||
if err != nil {
|
if !ok {
|
||||||
return nil, fmt.Errorf("cannot parse offset for _time filter %s: %w", ft, err)
|
return nil, fmt.Errorf("cannot parse offset %q for _time filter %s: %w", s, ft, err)
|
||||||
}
|
}
|
||||||
offset := int64(d)
|
offset := int64(d)
|
||||||
ft.minTimestamp -= offset
|
ft.minTimestamp -= offset
|
||||||
|
@ -862,9 +862,9 @@ func parseFilterTime(lex *lexer) (*filterTime, error) {
|
||||||
return ft, nil
|
return ft, nil
|
||||||
}
|
}
|
||||||
// Parse _time:duration, which transforms to '_time:(now-duration, now]'
|
// Parse _time:duration, which transforms to '_time:(now-duration, now]'
|
||||||
d, err := promutils.ParseDuration(s)
|
d, ok := tryParseDuration(s)
|
||||||
if err != nil {
|
if !ok {
|
||||||
return nil, fmt.Errorf("cannot parse duration in _time filter: %w", err)
|
return nil, fmt.Errorf("cannot parse duration %q in _time filter", s)
|
||||||
}
|
}
|
||||||
if d < 0 {
|
if d < 0 {
|
||||||
d = -d
|
d = -d
|
||||||
|
|
|
@ -862,6 +862,10 @@ func TestParseQuerySuccess(t *testing.T) {
|
||||||
f(`* | stats count() "foo.bar:baz", uniq(a) bar`, `* | stats count() as "foo.bar:baz", uniq(a) as bar`)
|
f(`* | stats count() "foo.bar:baz", uniq(a) bar`, `* | stats count() as "foo.bar:baz", uniq(a) as bar`)
|
||||||
f(`* | stats by (x, y) count(*) foo, uniq(a,b) bar`, `* | stats by (x, y) count(*) as foo, uniq(a, b) as bar`)
|
f(`* | stats by (x, y) count(*) foo, uniq(a,b) bar`, `* | stats by (x, y) count(*) as foo, uniq(a, b) as bar`)
|
||||||
|
|
||||||
|
// stats pipe with grouping buckets
|
||||||
|
f(`* | stats by(_time:1d, response_size:1_000KiB, request_duration:5s, foo) count() as foo`, `* | stats by (_time:1d, response_size:1_000KiB, request_duration:5s, foo) count() as foo`)
|
||||||
|
f(`*|stats by(client_ip:/24, server_ip:/16) count() foo`, `* | stats by (client_ip:/24, server_ip:/16) count() as foo`)
|
||||||
|
|
||||||
// multiple different pipes
|
// multiple different pipes
|
||||||
f(`* | fields foo, bar | head 100 | stats by(foo,bar) count(baz) as qwert`, `* | fields foo, bar | head 100 | stats by (foo, bar) count(baz) as qwert`)
|
f(`* | fields foo, bar | head 100 | stats by(foo,bar) count(baz) as qwert`, `* | fields foo, bar | head 100 | stats by (foo, bar) count(baz) as qwert`)
|
||||||
f(`* | skip 100 | head 20 | skip 10`, `* | skip 100 | head 20 | skip 10`)
|
f(`* | skip 100 | head 20 | skip 10`, `* | skip 100 | head 20 | skip 10`)
|
||||||
|
@ -1130,6 +1134,10 @@ func TestParseQueryFailure(t *testing.T) {
|
||||||
f(`foo | stats uniq`)
|
f(`foo | stats uniq`)
|
||||||
f(`foo | stats uniq()`)
|
f(`foo | stats uniq()`)
|
||||||
|
|
||||||
|
// invalid grouping fields
|
||||||
|
f(`foo | stats by(foo:bar) count() baz`)
|
||||||
|
f(`foo | stats by(foo:/bar) count() baz`)
|
||||||
|
|
||||||
// invalid by clause
|
// invalid by clause
|
||||||
f(`foo | stats by`)
|
f(`foo | stats by`)
|
||||||
f(`foo | stats by bar`)
|
f(`foo | stats by bar`)
|
||||||
|
|
|
@ -16,8 +16,8 @@ import (
|
||||||
//
|
//
|
||||||
// See https://docs.victoriametrics.com/victorialogs/logsql/#stats
|
// See https://docs.victoriametrics.com/victorialogs/logsql/#stats
|
||||||
type pipeStats struct {
|
type pipeStats struct {
|
||||||
// byFields contains field names from 'by(...)' clause.
|
// byFields contains field names with optional buckets from 'by(...)' clause.
|
||||||
byFields []string
|
byFields []*byField
|
||||||
|
|
||||||
// resultNames contains names of output results generated by funcs.
|
// resultNames contains names of output results generated by funcs.
|
||||||
resultNames []string
|
resultNames []string
|
||||||
|
@ -64,7 +64,11 @@ type statsProcessor interface {
|
||||||
func (ps *pipeStats) String() string {
|
func (ps *pipeStats) String() string {
|
||||||
s := "stats "
|
s := "stats "
|
||||||
if len(ps.byFields) > 0 {
|
if len(ps.byFields) > 0 {
|
||||||
s += "by (" + fieldNamesString(ps.byFields) + ") "
|
a := make([]string, len(ps.byFields))
|
||||||
|
for i := range ps.byFields {
|
||||||
|
a[i] = ps.byFields[i].String()
|
||||||
|
}
|
||||||
|
s += "by (" + strings.Join(a, ", ") + ") "
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(ps.funcs) == 0 {
|
if len(ps.funcs) == 0 {
|
||||||
|
@ -185,18 +189,29 @@ func (psp *pipeStatsProcessor) writeBlock(workerID uint, br *blockResult) {
|
||||||
}
|
}
|
||||||
if len(byFields) == 1 {
|
if len(byFields) == 1 {
|
||||||
// Special case for grouping by a single column.
|
// Special case for grouping by a single column.
|
||||||
c := br.getColumnByName(byFields[0])
|
bf := byFields[0]
|
||||||
|
c := br.getColumnByName(bf.name)
|
||||||
if c.isConst {
|
if c.isConst {
|
||||||
// Fast path for column with constant value.
|
// Fast path for column with constant value.
|
||||||
shard.keyBuf = encoding.MarshalBytes(shard.keyBuf[:0], bytesutil.ToUnsafeBytes(c.encodedValues[0]))
|
v := br.getBucketedValue(c.encodedValues[0], bf.bucketSize)
|
||||||
|
shard.keyBuf = encoding.MarshalBytes(shard.keyBuf[:0], bytesutil.ToUnsafeBytes(v))
|
||||||
for _, sfp := range shard.getStatsProcessors(shard.keyBuf) {
|
for _, sfp := range shard.getStatsProcessors(shard.keyBuf) {
|
||||||
shard.stateSizeBudget -= sfp.updateStatsForAllRows(br)
|
shard.stateSizeBudget -= sfp.updateStatsForAllRows(br)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Slower path for column with different values.
|
values := c.getBucketedValues(br, bf.bucketSize)
|
||||||
values := c.getValues(br)
|
if areConstValues(values) {
|
||||||
|
// Fast path for column with constant values.
|
||||||
|
shard.keyBuf = encoding.MarshalBytes(shard.keyBuf[:0], bytesutil.ToUnsafeBytes(values[0]))
|
||||||
|
for _, sfp := range shard.getStatsProcessors(shard.keyBuf) {
|
||||||
|
shard.stateSizeBudget -= sfp.updateStatsForAllRows(br)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slower generic path for a column with different values.
|
||||||
var sfps []statsProcessor
|
var sfps []statsProcessor
|
||||||
keyBuf := shard.keyBuf[:0]
|
keyBuf := shard.keyBuf[:0]
|
||||||
for i := range br.timestamps {
|
for i := range br.timestamps {
|
||||||
|
@ -212,34 +227,39 @@ func (psp *pipeStatsProcessor) writeBlock(workerID uint, br *blockResult) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Obtain columns for byFields
|
||||||
|
columnValues := shard.columnValues[:0]
|
||||||
|
for _, bf := range byFields {
|
||||||
|
c := br.getColumnByName(bf.name)
|
||||||
|
values := c.getBucketedValues(br, bf.bucketSize)
|
||||||
|
columnValues = append(columnValues, values)
|
||||||
|
}
|
||||||
|
shard.columnValues = columnValues
|
||||||
|
|
||||||
// Verify whether all the 'by (...)' columns are constant.
|
// Verify whether all the 'by (...)' columns are constant.
|
||||||
areAllConstColumns := true
|
areAllConstColumns := true
|
||||||
keyBuf := shard.keyBuf[:0]
|
for _, values := range columnValues {
|
||||||
for _, f := range byFields {
|
if !areConstValues(values) {
|
||||||
c := br.getColumnByName(f)
|
|
||||||
if !c.isConst {
|
|
||||||
areAllConstColumns = false
|
areAllConstColumns = false
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
keyBuf = encoding.MarshalBytes(keyBuf, bytesutil.ToUnsafeBytes(c.encodedValues[0]))
|
|
||||||
}
|
}
|
||||||
shard.keyBuf = keyBuf
|
|
||||||
|
|
||||||
if areAllConstColumns {
|
if areAllConstColumns {
|
||||||
// Fast path for constant 'by (...)' columns.
|
// Fast path for constant 'by (...)' columns.
|
||||||
|
keyBuf := shard.keyBuf[:0]
|
||||||
|
for _, values := range columnValues {
|
||||||
|
keyBuf = encoding.MarshalBytes(keyBuf, bytesutil.ToUnsafeBytes(values[0]))
|
||||||
|
}
|
||||||
for _, sfp := range shard.getStatsProcessors(keyBuf) {
|
for _, sfp := range shard.getStatsProcessors(keyBuf) {
|
||||||
shard.stateSizeBudget -= sfp.updateStatsForAllRows(br)
|
shard.stateSizeBudget -= sfp.updateStatsForAllRows(br)
|
||||||
}
|
}
|
||||||
|
shard.keyBuf = keyBuf
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// The slowest path - group by multiple columns with different values across rows.
|
// The slowest path - group by multiple columns with different values across rows.
|
||||||
|
|
||||||
// Pre-calculate column values for byFields in order to speed up building group key in the loop below.
|
|
||||||
shard.columnValues = br.appendColumnValues(shard.columnValues[:0], byFields)
|
|
||||||
columnValues := shard.columnValues
|
|
||||||
|
|
||||||
var sfps []statsProcessor
|
var sfps []statsProcessor
|
||||||
|
keyBuf := shard.keyBuf[:0]
|
||||||
for i := range br.timestamps {
|
for i := range br.timestamps {
|
||||||
// Verify whether the key for 'by (...)' fields equals the previous key
|
// Verify whether the key for 'by (...)' fields equals the previous key
|
||||||
sameValue := sfps != nil
|
sameValue := sfps != nil
|
||||||
|
@ -305,8 +325,8 @@ func (psp *pipeStatsProcessor) flush() error {
|
||||||
|
|
||||||
var values []string
|
var values []string
|
||||||
var br blockResult
|
var br blockResult
|
||||||
for _, f := range byFields {
|
for _, bf := range byFields {
|
||||||
br.addEmptyStringColumn(f)
|
br.addEmptyStringColumn(bf.name)
|
||||||
}
|
}
|
||||||
for _, resultName := range psp.ps.resultNames {
|
for _, resultName := range psp.ps.resultNames {
|
||||||
br.addEmptyStringColumn(resultName)
|
br.addEmptyStringColumn(resultName)
|
||||||
|
@ -358,20 +378,22 @@ func (psp *pipeStatsProcessor) flush() error {
|
||||||
func (ps *pipeStats) neededFields() []string {
|
func (ps *pipeStats) neededFields() []string {
|
||||||
var neededFields []string
|
var neededFields []string
|
||||||
m := make(map[string]struct{})
|
m := make(map[string]struct{})
|
||||||
updateNeededFields := func(fields []string) {
|
|
||||||
for _, field := range fields {
|
for _, bf := range ps.byFields {
|
||||||
if _, ok := m[field]; !ok {
|
name := bf.name
|
||||||
m[field] = struct{}{}
|
if _, ok := m[name]; !ok {
|
||||||
neededFields = append(neededFields, field)
|
m[name] = struct{}{}
|
||||||
}
|
neededFields = append(neededFields, name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateNeededFields(ps.byFields)
|
|
||||||
|
|
||||||
for _, f := range ps.funcs {
|
for _, f := range ps.funcs {
|
||||||
fields := f.neededFields()
|
for _, fieldName := range f.neededFields() {
|
||||||
updateNeededFields(fields)
|
if _, ok := m[fieldName]; !ok {
|
||||||
|
m[fieldName] = struct{}{}
|
||||||
|
neededFields = append(neededFields, fieldName)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return neededFields
|
return neededFields
|
||||||
|
@ -385,11 +407,11 @@ func parsePipeStats(lex *lexer) (*pipeStats, error) {
|
||||||
var ps pipeStats
|
var ps pipeStats
|
||||||
if lex.isKeyword("by") {
|
if lex.isKeyword("by") {
|
||||||
lex.nextToken()
|
lex.nextToken()
|
||||||
fields, err := parseFieldNamesInParens(lex)
|
bfs, err := parseByFields(lex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot parse 'by': %w", err)
|
return nil, fmt.Errorf("cannot parse 'by' clause: %w", err)
|
||||||
}
|
}
|
||||||
ps.byFields = fields
|
ps.byFields = bfs
|
||||||
}
|
}
|
||||||
|
|
||||||
var resultNames []string
|
var resultNames []string
|
||||||
|
@ -476,6 +498,124 @@ func parseResultName(lex *lexer) (string, error) {
|
||||||
return resultName, nil
|
return resultName, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// byField represents by(...) field.
|
||||||
|
//
|
||||||
|
// It can have either `name` representation of `name:bucket` representation,
|
||||||
|
// where `bucket` can contain duration, size or numeric value for creating different buckets
|
||||||
|
// for 'value/bucket'.
|
||||||
|
type byField struct {
|
||||||
|
name string
|
||||||
|
|
||||||
|
// bucketSizeStr is string representation of the bucket size
|
||||||
|
bucketSizeStr string
|
||||||
|
|
||||||
|
// bucketSize is the bucket for grouping the given field values with value/bucketSize calculations.
|
||||||
|
bucketSize float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bf *byField) String() string {
|
||||||
|
s := quoteTokenIfNeeded(bf.name)
|
||||||
|
if bf.bucketSizeStr != "" {
|
||||||
|
s += ":" + bf.bucketSizeStr
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseByFields(lex *lexer) ([]*byField, error) {
|
||||||
|
if !lex.isKeyword("(") {
|
||||||
|
return nil, fmt.Errorf("missing `(`")
|
||||||
|
}
|
||||||
|
var bfs []*byField
|
||||||
|
for {
|
||||||
|
if !lex.mustNextToken() {
|
||||||
|
return nil, fmt.Errorf("missing field name or ')'")
|
||||||
|
}
|
||||||
|
if lex.isKeyword(")") {
|
||||||
|
lex.nextToken()
|
||||||
|
return bfs, nil
|
||||||
|
}
|
||||||
|
if lex.isKeyword(",") {
|
||||||
|
return nil, fmt.Errorf("unexpected `,`")
|
||||||
|
}
|
||||||
|
fieldName, err := parseFieldName(lex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot parse field name: %w", err)
|
||||||
|
}
|
||||||
|
bf := &byField{
|
||||||
|
name: fieldName,
|
||||||
|
}
|
||||||
|
if lex.isKeyword(":") {
|
||||||
|
lex.nextToken()
|
||||||
|
bucketSizeStr := lex.token
|
||||||
|
lex.nextToken()
|
||||||
|
if bucketSizeStr == "/" {
|
||||||
|
bucketSizeStr += lex.token
|
||||||
|
lex.nextToken()
|
||||||
|
}
|
||||||
|
bucketSize, ok := tryParseBucketSize(bucketSizeStr)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("cannot parse bucket size for field %q: %q", fieldName, bucketSizeStr)
|
||||||
|
}
|
||||||
|
if bucketSize < 0 {
|
||||||
|
return nil, fmt.Errorf("bucketSize for the field %q cannot be negative; got %q", fieldName, bucketSizeStr)
|
||||||
|
}
|
||||||
|
bf.bucketSizeStr = bucketSizeStr
|
||||||
|
bf.bucketSize = bucketSize
|
||||||
|
}
|
||||||
|
bfs = append(bfs, bf)
|
||||||
|
switch {
|
||||||
|
case lex.isKeyword(")"):
|
||||||
|
lex.nextToken()
|
||||||
|
return bfs, nil
|
||||||
|
case lex.isKeyword(","):
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unexpected token: %q; expecting ',' or ')'", lex.token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// tryParseBucketSize tries parsing bucket size, which can have the following formats:
|
||||||
|
//
|
||||||
|
// - integer number: 12345
|
||||||
|
// - floating-point number: 1.2345
|
||||||
|
// - duration: 1.5s - it is converted to the number of nanoseconds
|
||||||
|
// - bytes: 1.5KiB
|
||||||
|
// - ipv4 mask: /24
|
||||||
|
func tryParseBucketSize(s string) (float64, bool) {
|
||||||
|
// Try parsing s as floating point number
|
||||||
|
if f, ok := tryParseFloat64(s); ok {
|
||||||
|
return f, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try parsing s as duration (1s, 5m, etc.)
|
||||||
|
if nsecs, ok := tryParseDuration(s); ok {
|
||||||
|
return float64(nsecs), true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try parsing s as bytes (KiB, MB, etc.)
|
||||||
|
if n, ok := tryParseBytes(s); ok {
|
||||||
|
return float64(n), true
|
||||||
|
}
|
||||||
|
|
||||||
|
if n, ok := tryParseIPv4Mask(s); ok {
|
||||||
|
return float64(n), true
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseFieldNamesForFunc(lex *lexer, funcName string) ([]string, error) {
|
||||||
|
if !lex.isKeyword(funcName) {
|
||||||
|
return nil, fmt.Errorf("unexpected func; got %q; want %q", lex.token, funcName)
|
||||||
|
}
|
||||||
|
lex.nextToken()
|
||||||
|
fields, err := parseFieldNamesInParens(lex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot parse %q args: %w", funcName, err)
|
||||||
|
}
|
||||||
|
return fields, nil
|
||||||
|
}
|
||||||
|
|
||||||
func parseFieldNamesInParens(lex *lexer) ([]string, error) {
|
func parseFieldNamesInParens(lex *lexer) ([]string, error) {
|
||||||
if !lex.isKeyword("(") {
|
if !lex.isKeyword("(") {
|
||||||
return nil, fmt.Errorf("missing `(`")
|
return nil, fmt.Errorf("missing `(`")
|
||||||
|
@ -509,10 +649,10 @@ func parseFieldNamesInParens(lex *lexer) ([]string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseFieldName(lex *lexer) (string, error) {
|
func parseFieldName(lex *lexer) (string, error) {
|
||||||
if lex.isKeyword(",", "(", ")", "[", "]", "|", "") {
|
if lex.isKeyword(",", "(", ")", "[", "]", "|", ":", "") {
|
||||||
return "", fmt.Errorf("unexpected token: %q", lex.token)
|
return "", fmt.Errorf("unexpected token: %q", lex.token)
|
||||||
}
|
}
|
||||||
token := getCompoundPhrase(lex, true)
|
token := getCompoundPhrase(lex, false)
|
||||||
return token, nil
|
return token, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -526,3 +666,16 @@ func fieldNamesString(fields []string) string {
|
||||||
}
|
}
|
||||||
return strings.Join(a, ", ")
|
return strings.Join(a, ", ")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func areConstValues(values []string) bool {
|
||||||
|
if len(values) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
v := values[0]
|
||||||
|
for i := 1; i < len(values); i++ {
|
||||||
|
if v != values[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
61
lib/logstorage/pipe_stats_test.go
Normal file
61
lib/logstorage/pipe_stats_test.go
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
package logstorage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTryParseBucketSize_Success(t *testing.T) {
|
||||||
|
f := func(s string, resultExpected float64) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
result, ok := tryParseBucketSize(s)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("cannot parse %q", s)
|
||||||
|
}
|
||||||
|
if result != resultExpected {
|
||||||
|
t.Fatalf("unexpected result; got %f; want %f", result, resultExpected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// integers
|
||||||
|
f("0", 0)
|
||||||
|
f("123", 123)
|
||||||
|
f("1_234_678", 1234678)
|
||||||
|
f("-1_234_678", -1234678)
|
||||||
|
|
||||||
|
// floating-point numbers
|
||||||
|
f("0.0", 0)
|
||||||
|
f("123.435", 123.435)
|
||||||
|
f("1_000.433_344", 1000.433344)
|
||||||
|
f("-1_000.433_344", -1000.433344)
|
||||||
|
|
||||||
|
// durations
|
||||||
|
f("5m", 5*nsecsPerMinute)
|
||||||
|
f("1h5m3.5s", nsecsPerHour+5*nsecsPerMinute+3.5*nsecsPerSecond)
|
||||||
|
f("-1h5m3.5s", -(nsecsPerHour + 5*nsecsPerMinute + 3.5*nsecsPerSecond))
|
||||||
|
|
||||||
|
// bytes
|
||||||
|
f("1b", 1)
|
||||||
|
f("1k", 1_000)
|
||||||
|
f("1Kb", 1_000)
|
||||||
|
f("5.5KiB", 5.5*(1<<10))
|
||||||
|
f("10MB500KB10B", 10*1_000_000+500*1_000+10)
|
||||||
|
f("10m0k", 10*1_000_000)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTryParseBucketSize_Failure(t *testing.T) {
|
||||||
|
f := func(s string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
_, ok := tryParseBucketSize(s)
|
||||||
|
if ok {
|
||||||
|
t.Fatalf("expecting error when parsing %q", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
f("")
|
||||||
|
f("foo")
|
||||||
|
|
||||||
|
// negative bytes are forbidden
|
||||||
|
f("-10MB")
|
||||||
|
}
|
|
@ -93,10 +93,9 @@ func (sap *statsAvgProcessor) finalizeStats() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseStatsAvg(lex *lexer) (*statsAvg, error) {
|
func parseStatsAvg(lex *lexer) (*statsAvg, error) {
|
||||||
lex.nextToken()
|
fields, err := parseFieldNamesForFunc(lex, "avg")
|
||||||
fields, err := parseFieldNamesInParens(lex)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot parse 'avg' args: %w", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(fields) == 0 {
|
if len(fields) == 0 {
|
||||||
return nil, fmt.Errorf("'avg' must contain at least one arg")
|
return nil, fmt.Errorf("'avg' must contain at least one arg")
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package logstorage
|
package logstorage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
@ -195,10 +194,9 @@ func (scp *statsCountProcessor) finalizeStats() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseStatsCount(lex *lexer) (*statsCount, error) {
|
func parseStatsCount(lex *lexer) (*statsCount, error) {
|
||||||
lex.nextToken()
|
fields, err := parseFieldNamesForFunc(lex, "count")
|
||||||
fields, err := parseFieldNamesInParens(lex)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot parse 'count' args: %w", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
sc := &statsCount{
|
sc := &statsCount{
|
||||||
fields: fields,
|
fields: fields,
|
||||||
|
|
|
@ -93,10 +93,9 @@ func (smp *statsMaxProcessor) finalizeStats() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseStatsMax(lex *lexer) (*statsMax, error) {
|
func parseStatsMax(lex *lexer) (*statsMax, error) {
|
||||||
lex.nextToken()
|
fields, err := parseFieldNamesForFunc(lex, "max")
|
||||||
fields, err := parseFieldNamesInParens(lex)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot parse 'max' args: %w", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(fields) == 0 {
|
if len(fields) == 0 {
|
||||||
return nil, fmt.Errorf("'max' must contain at least one arg")
|
return nil, fmt.Errorf("'max' must contain at least one arg")
|
||||||
|
|
|
@ -93,10 +93,9 @@ func (smp *statsMinProcessor) finalizeStats() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseStatsMin(lex *lexer) (*statsMin, error) {
|
func parseStatsMin(lex *lexer) (*statsMin, error) {
|
||||||
lex.nextToken()
|
fields, err := parseFieldNamesForFunc(lex, "min")
|
||||||
fields, err := parseFieldNamesInParens(lex)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot parse 'min' args: %w", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(fields) == 0 {
|
if len(fields) == 0 {
|
||||||
return nil, fmt.Errorf("'min' must contain at least one arg")
|
return nil, fmt.Errorf("'min' must contain at least one arg")
|
||||||
|
|
|
@ -107,10 +107,9 @@ func (ssp *statsSumProcessor) finalizeStats() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseStatsSum(lex *lexer) (*statsSum, error) {
|
func parseStatsSum(lex *lexer) (*statsSum, error) {
|
||||||
lex.nextToken()
|
fields, err := parseFieldNamesForFunc(lex, "sum")
|
||||||
fields, err := parseFieldNamesInParens(lex)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot parse 'sum' args: %w", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(fields) == 0 {
|
if len(fields) == 0 {
|
||||||
return nil, fmt.Errorf("'sum' must contain at least one arg")
|
return nil, fmt.Errorf("'sum' must contain at least one arg")
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package logstorage
|
package logstorage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
@ -175,8 +174,13 @@ func (sup *statsUniqProcessor) updateStatsForAllRows(br *blockResult) int {
|
||||||
// Slow path for multiple columns.
|
// Slow path for multiple columns.
|
||||||
|
|
||||||
// Pre-calculate column values for byFields in order to speed up building group key in the loop below.
|
// Pre-calculate column values for byFields in order to speed up building group key in the loop below.
|
||||||
sup.columnValues = br.appendColumnValues(sup.columnValues[:0], fields)
|
columnValues := sup.columnValues[:0]
|
||||||
columnValues := sup.columnValues
|
for _, f := range fields {
|
||||||
|
c := br.getColumnByName(f)
|
||||||
|
values := c.getValues(br)
|
||||||
|
columnValues = append(columnValues, values)
|
||||||
|
}
|
||||||
|
sup.columnValues = columnValues
|
||||||
|
|
||||||
keyBuf := sup.keyBuf[:0]
|
keyBuf := sup.keyBuf[:0]
|
||||||
for i := range br.timestamps {
|
for i := range br.timestamps {
|
||||||
|
@ -352,10 +356,9 @@ func (sup *statsUniqProcessor) finalizeStats() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseStatsUniq(lex *lexer) (*statsUniq, error) {
|
func parseStatsUniq(lex *lexer) (*statsUniq, error) {
|
||||||
lex.nextToken()
|
fields, err := parseFieldNamesForFunc(lex, "uniq")
|
||||||
fields, err := parseFieldNamesInParens(lex)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot parse 'uniq' args: %w", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
su := &statsUniq{
|
su := &statsUniq{
|
||||||
fields: fields,
|
fields: fields,
|
||||||
|
|
|
@ -156,7 +156,7 @@ func (vd *valuesDecoder) decodeInplace(values []string, vt valueType, dict *valu
|
||||||
}
|
}
|
||||||
n := uint64(v[0])
|
n := uint64(v[0])
|
||||||
dstLen := len(dstBuf)
|
dstLen := len(dstBuf)
|
||||||
dstBuf = strconv.AppendUint(dstBuf, n, 10)
|
dstBuf = marshalUint64(dstBuf, n)
|
||||||
values[i] = bytesutil.ToUnsafeString(dstBuf[dstLen:])
|
values[i] = bytesutil.ToUnsafeString(dstBuf[dstLen:])
|
||||||
}
|
}
|
||||||
case valueTypeUint16:
|
case valueTypeUint16:
|
||||||
|
@ -167,7 +167,7 @@ func (vd *valuesDecoder) decodeInplace(values []string, vt valueType, dict *valu
|
||||||
b := bytesutil.ToUnsafeBytes(v)
|
b := bytesutil.ToUnsafeBytes(v)
|
||||||
n := uint64(encoding.UnmarshalUint16(b))
|
n := uint64(encoding.UnmarshalUint16(b))
|
||||||
dstLen := len(dstBuf)
|
dstLen := len(dstBuf)
|
||||||
dstBuf = strconv.AppendUint(dstBuf, n, 10)
|
dstBuf = marshalUint64(dstBuf, n)
|
||||||
values[i] = bytesutil.ToUnsafeString(dstBuf[dstLen:])
|
values[i] = bytesutil.ToUnsafeString(dstBuf[dstLen:])
|
||||||
}
|
}
|
||||||
case valueTypeUint32:
|
case valueTypeUint32:
|
||||||
|
@ -178,7 +178,7 @@ func (vd *valuesDecoder) decodeInplace(values []string, vt valueType, dict *valu
|
||||||
b := bytesutil.ToUnsafeBytes(v)
|
b := bytesutil.ToUnsafeBytes(v)
|
||||||
n := uint64(encoding.UnmarshalUint32(b))
|
n := uint64(encoding.UnmarshalUint32(b))
|
||||||
dstLen := len(dstBuf)
|
dstLen := len(dstBuf)
|
||||||
dstBuf = strconv.AppendUint(dstBuf, n, 10)
|
dstBuf = marshalUint64(dstBuf, n)
|
||||||
values[i] = bytesutil.ToUnsafeString(dstBuf[dstLen:])
|
values[i] = bytesutil.ToUnsafeString(dstBuf[dstLen:])
|
||||||
}
|
}
|
||||||
case valueTypeUint64:
|
case valueTypeUint64:
|
||||||
|
@ -189,7 +189,7 @@ func (vd *valuesDecoder) decodeInplace(values []string, vt valueType, dict *valu
|
||||||
b := bytesutil.ToUnsafeBytes(v)
|
b := bytesutil.ToUnsafeBytes(v)
|
||||||
n := encoding.UnmarshalUint64(b)
|
n := encoding.UnmarshalUint64(b)
|
||||||
dstLen := len(dstBuf)
|
dstLen := len(dstBuf)
|
||||||
dstBuf = strconv.AppendUint(dstBuf, n, 10)
|
dstBuf = marshalUint64(dstBuf, n)
|
||||||
values[i] = bytesutil.ToUnsafeString(dstBuf[dstLen:])
|
values[i] = bytesutil.ToUnsafeString(dstBuf[dstLen:])
|
||||||
}
|
}
|
||||||
case valueTypeDict:
|
case valueTypeDict:
|
||||||
|
@ -239,19 +239,18 @@ func (vd *valuesDecoder) decodeInplace(values []string, vt valueType, dict *valu
|
||||||
func toTimestampISO8601String(dst []byte, v string) []byte {
|
func toTimestampISO8601String(dst []byte, v string) []byte {
|
||||||
b := bytesutil.ToUnsafeBytes(v)
|
b := bytesutil.ToUnsafeBytes(v)
|
||||||
n := encoding.UnmarshalUint64(b)
|
n := encoding.UnmarshalUint64(b)
|
||||||
t := time.Unix(0, int64(n)).UTC()
|
dst = marshalTimestampISO8601(dst, int64(n))
|
||||||
dst = t.AppendFormat(dst, iso8601Timestamp)
|
|
||||||
return dst
|
return dst
|
||||||
}
|
}
|
||||||
|
|
||||||
func toIPv4String(dst []byte, v string) []byte {
|
func toIPv4String(dst []byte, v string) []byte {
|
||||||
dst = strconv.AppendUint(dst, uint64(v[0]), 10)
|
dst = marshalUint64(dst, uint64(v[0]))
|
||||||
dst = append(dst, '.')
|
dst = append(dst, '.')
|
||||||
dst = strconv.AppendUint(dst, uint64(v[1]), 10)
|
dst = marshalUint64(dst, uint64(v[1]))
|
||||||
dst = append(dst, '.')
|
dst = append(dst, '.')
|
||||||
dst = strconv.AppendUint(dst, uint64(v[2]), 10)
|
dst = marshalUint64(dst, uint64(v[2]))
|
||||||
dst = append(dst, '.')
|
dst = append(dst, '.')
|
||||||
dst = strconv.AppendUint(dst, uint64(v[3]), 10)
|
dst = marshalUint64(dst, uint64(v[3]))
|
||||||
return dst
|
return dst
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -259,7 +258,7 @@ func toFloat64String(dst []byte, v string) []byte {
|
||||||
b := bytesutil.ToUnsafeBytes(v)
|
b := bytesutil.ToUnsafeBytes(v)
|
||||||
n := encoding.UnmarshalUint64(b)
|
n := encoding.UnmarshalUint64(b)
|
||||||
f := math.Float64frombits(n)
|
f := math.Float64frombits(n)
|
||||||
dst = strconv.AppendFloat(dst, f, 'g', -1, 64)
|
dst = marshalFloat64(dst, f)
|
||||||
return dst
|
return dst
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -279,10 +278,10 @@ func putValuesDecoder(vd *valuesDecoder) {
|
||||||
var valuesDecoderPool sync.Pool
|
var valuesDecoderPool sync.Pool
|
||||||
|
|
||||||
func tryTimestampISO8601Encoding(dstBuf []byte, dstValues, srcValues []string) ([]byte, []string, valueType, uint64, uint64) {
|
func tryTimestampISO8601Encoding(dstBuf []byte, dstValues, srcValues []string) ([]byte, []string, valueType, uint64, uint64) {
|
||||||
u64s := encoding.GetUint64s(len(srcValues))
|
u64s := encoding.GetInt64s(len(srcValues))
|
||||||
defer encoding.PutUint64s(u64s)
|
defer encoding.PutInt64s(u64s)
|
||||||
a := u64s.A
|
a := u64s.A
|
||||||
var minValue, maxValue uint64
|
var minValue, maxValue int64
|
||||||
for i, v := range srcValues {
|
for i, v := range srcValues {
|
||||||
n, ok := tryParseTimestampISO8601(v)
|
n, ok := tryParseTimestampISO8601(v)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -298,14 +297,69 @@ func tryTimestampISO8601Encoding(dstBuf []byte, dstValues, srcValues []string) (
|
||||||
}
|
}
|
||||||
for _, n := range a {
|
for _, n := range a {
|
||||||
dstLen := len(dstBuf)
|
dstLen := len(dstBuf)
|
||||||
dstBuf = encoding.MarshalUint64(dstBuf, n)
|
dstBuf = encoding.MarshalUint64(dstBuf, uint64(n))
|
||||||
v := bytesutil.ToUnsafeString(dstBuf[dstLen:])
|
v := bytesutil.ToUnsafeString(dstBuf[dstLen:])
|
||||||
dstValues = append(dstValues, v)
|
dstValues = append(dstValues, v)
|
||||||
}
|
}
|
||||||
return dstBuf, dstValues, valueTypeTimestampISO8601, minValue, maxValue
|
return dstBuf, dstValues, valueTypeTimestampISO8601, uint64(minValue), uint64(maxValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
func tryParseTimestampISO8601(s string) (uint64, bool) {
|
// tryParseTimestampRFC3339Nano parses 'YYYY-MM-DDThh:mm:ss' with optional nanoseconds part and 'Z' tail and returns unix timestamp in nanoseconds.
|
||||||
|
//
|
||||||
|
// The returned timestamp can be negative if s is smaller than 1970 year.
|
||||||
|
func tryParseTimestampRFC3339Nano(s string) (int64, bool) {
|
||||||
|
// Do not parse timestamps with timezone other than Z, since they cannot be converted back
|
||||||
|
// to the same string representation in general case.
|
||||||
|
// This may break search.
|
||||||
|
|
||||||
|
if len(s) < len("2006-01-02T15:04:05Z") {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
secs, ok, tail := tryParseTimestampSecs(s)
|
||||||
|
if !ok {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
s = tail
|
||||||
|
nsecs := secs * 1e9
|
||||||
|
|
||||||
|
// Parse optional fractional part of seconds.
|
||||||
|
n := strings.IndexByte(s, 'Z')
|
||||||
|
if n < 0 || n != len(s)-1 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
s = s[:n]
|
||||||
|
if len(s) == 0 {
|
||||||
|
return nsecs, true
|
||||||
|
}
|
||||||
|
if s[0] == '.' {
|
||||||
|
s = s[1:]
|
||||||
|
}
|
||||||
|
digits := len(s)
|
||||||
|
if digits > 9 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
n64, ok := tryParseUint64(s)
|
||||||
|
if !ok {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
if digits < 9 {
|
||||||
|
n64 *= uint64(math.Pow10(9 - digits))
|
||||||
|
}
|
||||||
|
nsecs += int64(n64)
|
||||||
|
return nsecs, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// marshalTimestampRFC3339Nano appends RFC3339Nano-formatted nsecs to dst and returns the result.
|
||||||
|
func marshalTimestampRFC3339Nano(dst []byte, nsecs int64) []byte {
|
||||||
|
return time.Unix(0, nsecs).UTC().AppendFormat(dst, time.RFC3339Nano)
|
||||||
|
}
|
||||||
|
|
||||||
|
// tryParseTimestampISO8601 parses 'YYYY-MM-DDThh:mm:ss.mssZ' and returns unix timestamp in nanoseconds.
|
||||||
|
//
|
||||||
|
// The returned timestamp can be negative if s is smaller than 1970 year.
|
||||||
|
func tryParseTimestampISO8601(s string) (int64, bool) {
|
||||||
// Do not parse timestamps with timezone, since they cannot be converted back
|
// Do not parse timestamps with timezone, since they cannot be converted back
|
||||||
// to the same string representation in general case.
|
// to the same string representation in general case.
|
||||||
// This may break search.
|
// This may break search.
|
||||||
|
@ -313,117 +367,155 @@ func tryParseTimestampISO8601(s string) (uint64, bool) {
|
||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
secs, ok, tail := tryParseTimestampSecs(s)
|
||||||
|
if !ok {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
s = tail
|
||||||
|
nsecs := secs * 1e9
|
||||||
|
|
||||||
|
if s[0] != '.' {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
s = s[1:]
|
||||||
|
|
||||||
|
// Parse milliseconds
|
||||||
|
tzDelimiter := s[len("000")]
|
||||||
|
if tzDelimiter != 'Z' {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
millisecondStr := s[:len("000")]
|
||||||
|
msecs, ok := tryParseUint64(millisecondStr)
|
||||||
|
if !ok {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
s = s[len("000")+1:]
|
||||||
|
|
||||||
|
if len(s) != 0 {
|
||||||
|
logger.Panicf("BUG: unexpected tail in timestamp: %q", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
nsecs += int64(msecs) * 1e6
|
||||||
|
return nsecs, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// marshalTimestampISO8601 appends ISO8601-formatted nsecs to dst and returns the result.
|
||||||
|
func marshalTimestampISO8601(dst []byte, nsecs int64) []byte {
|
||||||
|
return time.Unix(0, nsecs).UTC().AppendFormat(dst, iso8601Timestamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
const iso8601Timestamp = "2006-01-02T15:04:05.000Z"
|
||||||
|
|
||||||
|
// tryParseTimestampSecs parses YYYY-MM-DDTHH:mm:ss into unix timestamp in seconds.
|
||||||
|
func tryParseTimestampSecs(s string) (int64, bool, string) {
|
||||||
// Parse year
|
// Parse year
|
||||||
if s[len("YYYY")] != '-' {
|
if s[len("YYYY")] != '-' {
|
||||||
return 0, false
|
return 0, false, s
|
||||||
}
|
}
|
||||||
yearStr := s[:len("YYYY")]
|
yearStr := s[:len("YYYY")]
|
||||||
n, ok := tryParseUint64(yearStr)
|
n, ok := tryParseUint64(yearStr)
|
||||||
if !ok || n > 3000 {
|
if !ok || n < 1677 || n > 2262 {
|
||||||
return 0, false
|
return 0, false, s
|
||||||
}
|
}
|
||||||
year := int(n)
|
year := int(n)
|
||||||
s = s[len("YYYY")+1:]
|
s = s[len("YYYY")+1:]
|
||||||
|
|
||||||
// Parse month
|
// Parse month
|
||||||
if s[len("MM")] != '-' {
|
if s[len("MM")] != '-' {
|
||||||
return 0, false
|
return 0, false, s
|
||||||
}
|
}
|
||||||
monthStr := s[:len("MM")]
|
monthStr := s[:len("MM")]
|
||||||
n, ok = tryParseUint64(monthStr)
|
n, ok = tryParseUint64(monthStr)
|
||||||
if !ok || n < 1 || n > 12 {
|
if !ok {
|
||||||
return 0, false
|
return 0, false, s
|
||||||
}
|
}
|
||||||
month := time.Month(n)
|
month := time.Month(n)
|
||||||
s = s[len("MM")+1:]
|
s = s[len("MM")+1:]
|
||||||
|
|
||||||
// Parse day
|
// Parse day
|
||||||
if s[len("DD")] != 'T' {
|
if s[len("DD")] != 'T' {
|
||||||
return 0, false
|
return 0, false, s
|
||||||
}
|
}
|
||||||
dayStr := s[:len("DD")]
|
dayStr := s[:len("DD")]
|
||||||
n, ok = tryParseUint64(dayStr)
|
n, ok = tryParseUint64(dayStr)
|
||||||
if !ok || n < 1 || n > 31 {
|
if !ok {
|
||||||
return 0, false
|
return 0, false, s
|
||||||
}
|
}
|
||||||
day := int(n)
|
day := int(n)
|
||||||
s = s[len("DD")+1:]
|
s = s[len("DD")+1:]
|
||||||
|
|
||||||
// Parse hour
|
// Parse hour
|
||||||
if s[len("HH")] != ':' {
|
if s[len("HH")] != ':' {
|
||||||
return 0, false
|
return 0, false, s
|
||||||
}
|
}
|
||||||
hourStr := s[:len("HH")]
|
hourStr := s[:len("HH")]
|
||||||
n, ok = tryParseUint64(hourStr)
|
n, ok = tryParseUint64(hourStr)
|
||||||
if !ok || n > 23 {
|
if !ok {
|
||||||
return 0, false
|
return 0, false, s
|
||||||
}
|
}
|
||||||
hour := int(n)
|
hour := int(n)
|
||||||
s = s[len("HH")+1:]
|
s = s[len("HH")+1:]
|
||||||
|
|
||||||
// Parse minute
|
// Parse minute
|
||||||
if s[len("MM")] != ':' {
|
if s[len("MM")] != ':' {
|
||||||
return 0, false
|
return 0, false, s
|
||||||
}
|
}
|
||||||
minuteStr := s[:len("MM")]
|
minuteStr := s[:len("MM")]
|
||||||
n, ok = tryParseUint64(minuteStr)
|
n, ok = tryParseUint64(minuteStr)
|
||||||
if !ok || n > 59 {
|
if !ok {
|
||||||
return 0, false
|
return 0, false, s
|
||||||
}
|
}
|
||||||
minute := int(n)
|
minute := int(n)
|
||||||
s = s[len("MM")+1:]
|
s = s[len("MM")+1:]
|
||||||
|
|
||||||
// Parse second
|
// Parse second
|
||||||
if s[len("SS")] != '.' {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
secondStr := s[:len("SS")]
|
secondStr := s[:len("SS")]
|
||||||
n, ok = tryParseUint64(secondStr)
|
n, ok = tryParseUint64(secondStr)
|
||||||
if !ok || n > 59 {
|
if !ok {
|
||||||
return 0, false
|
return 0, false, s
|
||||||
}
|
}
|
||||||
second := int(n)
|
second := int(n)
|
||||||
s = s[len("SS")+1:]
|
s = s[len("SS"):]
|
||||||
|
|
||||||
// Parse millisecond
|
secs := time.Date(year, month, day, hour, minute, second, 0, time.UTC).Unix()
|
||||||
tzDelimiter := s[len("000")]
|
if secs < int64(-1<<63)/1e9 || secs >= int64((1<<63)-1)/1e9 {
|
||||||
if tzDelimiter != 'Z' {
|
// Too big or too small timestamp
|
||||||
return 0, false
|
return 0, false, s
|
||||||
}
|
}
|
||||||
millisecondStr := s[:len("000")]
|
return secs, true, s
|
||||||
n, ok = tryParseUint64(millisecondStr)
|
|
||||||
if !ok || n > 999 {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
millisecond := int(n)
|
|
||||||
s = s[len("000")+1:]
|
|
||||||
|
|
||||||
if len(s) != 0 {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
|
|
||||||
t := time.Date(year, month, day, hour, minute, second, millisecond*1e6, time.UTC)
|
|
||||||
ts := t.UnixNano()
|
|
||||||
return uint64(ts), true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tryParseUint64 parses s as uint64 value.
|
||||||
func tryParseUint64(s string) (uint64, bool) {
|
func tryParseUint64(s string) (uint64, bool) {
|
||||||
if len(s) == 0 || len(s) > 18 {
|
if len(s) == 0 || len(s) > len("18_446_744_073_709_551_615") {
|
||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
n := uint64(0)
|
n := uint64(0)
|
||||||
for i := 0; i < len(s); i++ {
|
for i := 0; i < len(s); i++ {
|
||||||
ch := s[i]
|
ch := s[i]
|
||||||
|
if ch == '_' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if ch < '0' || ch > '9' {
|
if ch < '0' || ch > '9' {
|
||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
|
if n > ((1<<64)-1)/10 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
n *= 10
|
n *= 10
|
||||||
n += uint64(ch - '0')
|
d := uint64(ch - '0')
|
||||||
|
if n > (1<<64)-1-d {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
n += d
|
||||||
}
|
}
|
||||||
return n, true
|
return n, true
|
||||||
}
|
}
|
||||||
|
|
||||||
const iso8601Timestamp = "2006-01-02T15:04:05.000Z"
|
// marshalUint64 appends string representation of n to dst and returns the result.
|
||||||
|
func marshalUint64(dst []byte, n uint64) []byte {
|
||||||
|
return strconv.AppendUint(dst, n, 10)
|
||||||
|
}
|
||||||
|
|
||||||
func tryIPv4Encoding(dstBuf []byte, dstValues, srcValues []string) ([]byte, []string, valueType, uint64, uint64) {
|
func tryIPv4Encoding(dstBuf []byte, dstValues, srcValues []string) ([]byte, []string, valueType, uint64, uint64) {
|
||||||
u32s := encoding.GetUint32s(len(srcValues))
|
u32s := encoding.GetUint32s(len(srcValues))
|
||||||
|
@ -452,6 +544,7 @@ func tryIPv4Encoding(dstBuf []byte, dstValues, srcValues []string) ([]byte, []st
|
||||||
return dstBuf, dstValues, valueTypeIPv4, uint64(minValue), uint64(maxValue)
|
return dstBuf, dstValues, valueTypeIPv4, uint64(minValue), uint64(maxValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tryParseIPv4 tries parsing ipv4 from s.
|
||||||
func tryParseIPv4(s string) (uint32, bool) {
|
func tryParseIPv4(s string) (uint32, bool) {
|
||||||
if len(s) < len("1.1.1.1") || len(s) > len("255.255.255.255") || strings.Count(s, ".") != 3 {
|
if len(s) < len("1.1.1.1") || len(s) > len("255.255.255.255") || strings.Count(s, ".") != 3 {
|
||||||
// Fast path - the entry isn't IPv4
|
// Fast path - the entry isn't IPv4
|
||||||
|
@ -509,6 +602,18 @@ func tryParseIPv4(s string) (uint32, bool) {
|
||||||
return ipv4, true
|
return ipv4, true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// marshalIPv4 appends string representation of IPv4 address in n to dst and returns the result.
|
||||||
|
func marshalIPv4(dst []byte, n uint32) []byte {
|
||||||
|
dst = marshalUint64(dst, uint64(n>>24))
|
||||||
|
dst = append(dst, '.')
|
||||||
|
dst = marshalUint64(dst, uint64((n>>16)&0xff))
|
||||||
|
dst = append(dst, '.')
|
||||||
|
dst = marshalUint64(dst, uint64((n>>8)&0xff))
|
||||||
|
dst = append(dst, '.')
|
||||||
|
dst = marshalUint64(dst, uint64(n&0xff))
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
func tryFloat64Encoding(dstBuf []byte, dstValues, srcValues []string) ([]byte, []string, valueType, uint64, uint64) {
|
func tryFloat64Encoding(dstBuf []byte, dstValues, srcValues []string) ([]byte, []string, valueType, uint64, uint64) {
|
||||||
u64s := encoding.GetUint64s(len(srcValues))
|
u64s := encoding.GetUint64s(len(srcValues))
|
||||||
defer encoding.PutUint64s(u64s)
|
defer encoding.PutUint64s(u64s)
|
||||||
|
@ -538,6 +643,20 @@ func tryFloat64Encoding(dstBuf []byte, dstValues, srcValues []string) ([]byte, [
|
||||||
return dstBuf, dstValues, valueTypeFloat64, minValueU64, maxValueU64
|
return dstBuf, dstValues, valueTypeFloat64, minValueU64, maxValueU64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tryParseFloat64Prefix tries parsing float64 number at the beginning of s and returns the remaining tail.
|
||||||
|
func tryParseFloat64Prefix(s string) (float64, bool, string) {
|
||||||
|
i := 0
|
||||||
|
for i < len(s) && (s[i] >= '0' && s[i] <= '9' || s[i] == '.' || s[i] == '_') {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if i == 0 {
|
||||||
|
return 0, false, s
|
||||||
|
}
|
||||||
|
f, ok := tryParseFloat64(s[:i])
|
||||||
|
return f, ok, s[i:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// tryParseFloat64 tries parsing s as float64.
|
||||||
func tryParseFloat64(s string) (float64, bool) {
|
func tryParseFloat64(s string) (float64, bool) {
|
||||||
if len(s) == 0 || len(s) > 20 {
|
if len(s) == 0 || len(s) > 20 {
|
||||||
return 0, false
|
return 0, false
|
||||||
|
@ -578,13 +697,292 @@ func tryParseFloat64(s string) (float64, bool) {
|
||||||
if !ok {
|
if !ok {
|
||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
f := math.FMA(float64(nFrac), math.Pow10(-len(sFrac)), float64(nInt))
|
p10 := math.Pow10(strings.Count(sFrac, "_") - len(sFrac))
|
||||||
|
f := math.FMA(float64(nFrac), p10, float64(nInt))
|
||||||
if minus {
|
if minus {
|
||||||
f = -f
|
f = -f
|
||||||
}
|
}
|
||||||
return f, true
|
return f, true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// marshalFloat64 appends formatted f to dst and returns the result.
|
||||||
|
func marshalFloat64(dst []byte, f float64) []byte {
|
||||||
|
return strconv.AppendFloat(dst, f, 'f', -1, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
// tryParseBytes parses user-readable bytes representation in s.
|
||||||
|
//
|
||||||
|
// Supported suffixes:
|
||||||
|
//
|
||||||
|
// K, KB - for 1000
|
||||||
|
func tryParseBytes(s string) (int64, bool) {
|
||||||
|
if len(s) == 0 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
n := int64(0)
|
||||||
|
for len(s) > 0 {
|
||||||
|
f, ok, tail := tryParseFloat64Prefix(s)
|
||||||
|
if !ok {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
s = tail
|
||||||
|
if len(s) == 0 {
|
||||||
|
n += int64(f)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(s) >= 3 {
|
||||||
|
prefix := s[:3]
|
||||||
|
switch {
|
||||||
|
case strings.EqualFold(prefix, "kib"):
|
||||||
|
n += int64(f * (1 << 10))
|
||||||
|
s = s[3:]
|
||||||
|
continue
|
||||||
|
case strings.EqualFold(prefix, "mib"):
|
||||||
|
n += int64(f * (1 << 20))
|
||||||
|
s = s[3:]
|
||||||
|
continue
|
||||||
|
case strings.EqualFold(prefix, "gib"):
|
||||||
|
n += int64(f * (1 << 30))
|
||||||
|
s = s[3:]
|
||||||
|
continue
|
||||||
|
case strings.EqualFold(prefix, "tib"):
|
||||||
|
n += int64(f * (1 << 40))
|
||||||
|
s = s[3:]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(s) >= 2 {
|
||||||
|
prefix := s[:2]
|
||||||
|
switch {
|
||||||
|
case strings.EqualFold(prefix, "ki"):
|
||||||
|
n += int64(f * (1 << 10))
|
||||||
|
s = s[2:]
|
||||||
|
continue
|
||||||
|
case strings.EqualFold(prefix, "mi"):
|
||||||
|
n += int64(f * (1 << 20))
|
||||||
|
s = s[2:]
|
||||||
|
continue
|
||||||
|
case strings.EqualFold(prefix, "gi"):
|
||||||
|
n += int64(f * (1 << 30))
|
||||||
|
s = s[2:]
|
||||||
|
continue
|
||||||
|
case strings.EqualFold(prefix, "ti"):
|
||||||
|
n += int64(f * (1 << 40))
|
||||||
|
s = s[2:]
|
||||||
|
continue
|
||||||
|
case strings.EqualFold(prefix, "kb"):
|
||||||
|
n += int64(f * 1_000)
|
||||||
|
s = s[2:]
|
||||||
|
continue
|
||||||
|
case strings.EqualFold(prefix, "mb"):
|
||||||
|
n += int64(f * 1_000_000)
|
||||||
|
s = s[2:]
|
||||||
|
continue
|
||||||
|
case strings.EqualFold(prefix, "gb"):
|
||||||
|
n += int64(f * 1_000_000_000)
|
||||||
|
s = s[2:]
|
||||||
|
continue
|
||||||
|
case strings.EqualFold(prefix, "tb"):
|
||||||
|
n += int64(f * 1_000_000_000_000)
|
||||||
|
s = s[2:]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prefix := s[:1]
|
||||||
|
switch {
|
||||||
|
case strings.EqualFold(prefix, "b"):
|
||||||
|
n += int64(f)
|
||||||
|
s = s[1:]
|
||||||
|
continue
|
||||||
|
case strings.EqualFold(prefix, "k"):
|
||||||
|
n += int64(f * 1_000)
|
||||||
|
s = s[1:]
|
||||||
|
continue
|
||||||
|
case strings.EqualFold(prefix, "m"):
|
||||||
|
n += int64(f * 1_000_000)
|
||||||
|
s = s[1:]
|
||||||
|
continue
|
||||||
|
case strings.EqualFold(prefix, "g"):
|
||||||
|
n += int64(f * 1_000_000_000)
|
||||||
|
s = s[1:]
|
||||||
|
continue
|
||||||
|
case strings.EqualFold(prefix, "t"):
|
||||||
|
n += int64(f * 1_000_000_000_000)
|
||||||
|
s = s[1:]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// tryParseIPv4Mask parses '/num' ipv4 mask and returns (1<<(32-num))
|
||||||
|
func tryParseIPv4Mask(s string) (uint64, bool) {
|
||||||
|
if len(s) == 0 || s[0] != '/' {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
s = s[1:]
|
||||||
|
n, ok := tryParseUint64(s)
|
||||||
|
if !ok || n > 32 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return 1 << (32 - uint8(n)), true
|
||||||
|
}
|
||||||
|
|
||||||
|
// tryParseDuration parses the given duration in nanoseconds and returns the result.
|
||||||
|
func tryParseDuration(s string) (int64, bool) {
|
||||||
|
if len(s) == 0 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
isMinus := s[0] == '-'
|
||||||
|
if isMinus {
|
||||||
|
s = s[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
nsecs := int64(0)
|
||||||
|
for len(s) > 0 {
|
||||||
|
f, ok, tail := tryParseFloat64Prefix(s)
|
||||||
|
if !ok {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
s = tail
|
||||||
|
if len(s) == 0 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
if len(s) >= 3 {
|
||||||
|
prefix := s[:3]
|
||||||
|
if strings.EqualFold(prefix, "µs") {
|
||||||
|
nsecs += int64(f * nsecsPerMicrosecond)
|
||||||
|
s = s[3:]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(s) >= 2 {
|
||||||
|
prefix := s[:2]
|
||||||
|
switch {
|
||||||
|
case strings.EqualFold(prefix, "ms"):
|
||||||
|
nsecs += int64(f * nsecsPerMillisecond)
|
||||||
|
s = s[2:]
|
||||||
|
continue
|
||||||
|
case strings.EqualFold(prefix, "ns"):
|
||||||
|
nsecs += int64(f)
|
||||||
|
s = s[2:]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prefix := s[:1]
|
||||||
|
switch {
|
||||||
|
case strings.EqualFold(prefix, "y"):
|
||||||
|
nsecs += int64(f * nsecsPerYear)
|
||||||
|
s = s[1:]
|
||||||
|
case strings.EqualFold(prefix, "w"):
|
||||||
|
nsecs += int64(f * nsecsPerWeek)
|
||||||
|
s = s[1:]
|
||||||
|
continue
|
||||||
|
case strings.EqualFold(prefix, "d"):
|
||||||
|
nsecs += int64(f * nsecsPerDay)
|
||||||
|
s = s[1:]
|
||||||
|
continue
|
||||||
|
case strings.EqualFold(prefix, "h"):
|
||||||
|
nsecs += int64(f * nsecsPerHour)
|
||||||
|
s = s[1:]
|
||||||
|
continue
|
||||||
|
case strings.EqualFold(prefix, "m"):
|
||||||
|
nsecs += int64(f * nsecsPerMinute)
|
||||||
|
s = s[1:]
|
||||||
|
continue
|
||||||
|
case strings.EqualFold(prefix, "s"):
|
||||||
|
nsecs += int64(f * nsecsPerSecond)
|
||||||
|
s = s[1:]
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if isMinus {
|
||||||
|
nsecs = -nsecs
|
||||||
|
}
|
||||||
|
return nsecs, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// marshalDuration appends string representation of nsec duration to dst and returns the result.
|
||||||
|
func marshalDuration(dst []byte, nsecs int64) []byte {
|
||||||
|
if nsecs == 0 {
|
||||||
|
return append(dst, '0')
|
||||||
|
}
|
||||||
|
|
||||||
|
if nsecs < 0 {
|
||||||
|
dst = append(dst, '-')
|
||||||
|
nsecs = -nsecs
|
||||||
|
}
|
||||||
|
formatFloat64Seconds := nsecs >= nsecsPerSecond
|
||||||
|
|
||||||
|
if nsecs >= nsecsPerWeek {
|
||||||
|
weeks := nsecs / nsecsPerWeek
|
||||||
|
nsecs -= weeks * nsecsPerWeek
|
||||||
|
dst = marshalUint64(dst, uint64(weeks))
|
||||||
|
dst = append(dst, 'w')
|
||||||
|
}
|
||||||
|
if nsecs >= nsecsPerDay {
|
||||||
|
days := nsecs / nsecsPerDay
|
||||||
|
nsecs -= days * nsecsPerDay
|
||||||
|
dst = marshalUint64(dst, uint64(days))
|
||||||
|
dst = append(dst, 'd')
|
||||||
|
}
|
||||||
|
if nsecs >= nsecsPerHour {
|
||||||
|
hours := nsecs / nsecsPerHour
|
||||||
|
nsecs -= hours * nsecsPerHour
|
||||||
|
dst = marshalUint64(dst, uint64(hours))
|
||||||
|
dst = append(dst, 'h')
|
||||||
|
}
|
||||||
|
if nsecs >= nsecsPerMinute {
|
||||||
|
minutes := nsecs / nsecsPerMinute
|
||||||
|
nsecs -= minutes * nsecsPerMinute
|
||||||
|
dst = marshalUint64(dst, uint64(minutes))
|
||||||
|
dst = append(dst, 'm')
|
||||||
|
}
|
||||||
|
if nsecs >= nsecsPerSecond {
|
||||||
|
if formatFloat64Seconds {
|
||||||
|
seconds := float64(nsecs) / nsecsPerSecond
|
||||||
|
dst = marshalFloat64(dst, seconds)
|
||||||
|
dst = append(dst, 's')
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
seconds := nsecs / nsecsPerSecond
|
||||||
|
nsecs -= seconds * nsecsPerSecond
|
||||||
|
dst = marshalUint64(dst, uint64(seconds))
|
||||||
|
dst = append(dst, 's')
|
||||||
|
}
|
||||||
|
if nsecs >= nsecsPerMillisecond {
|
||||||
|
msecs := nsecs / nsecsPerMillisecond
|
||||||
|
nsecs -= msecs * nsecsPerMillisecond
|
||||||
|
dst = marshalUint64(dst, uint64(msecs))
|
||||||
|
dst = append(dst, "ms"...)
|
||||||
|
}
|
||||||
|
if nsecs >= nsecsPerMicrosecond {
|
||||||
|
usecs := nsecs / nsecsPerMicrosecond
|
||||||
|
nsecs -= usecs * nsecsPerMicrosecond
|
||||||
|
dst = marshalUint64(dst, uint64(usecs))
|
||||||
|
dst = append(dst, "µs"...)
|
||||||
|
}
|
||||||
|
if nsecs > 0 {
|
||||||
|
dst = marshalUint64(dst, uint64(nsecs))
|
||||||
|
dst = append(dst, "ns"...)
|
||||||
|
}
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
nsecsPerYear = 365 * 24 * 3600 * 1e9
|
||||||
|
nsecsPerWeek = 7 * 24 * 3600 * 1e9
|
||||||
|
nsecsPerDay = 24 * 3600 * 1e9
|
||||||
|
nsecsPerHour = 3600 * 1e9
|
||||||
|
nsecsPerMinute = 60 * 1e9
|
||||||
|
nsecsPerSecond = 1e9
|
||||||
|
nsecsPerMillisecond = 1e6
|
||||||
|
nsecsPerMicrosecond = 1e3
|
||||||
|
)
|
||||||
|
|
||||||
func tryUintEncoding(dstBuf []byte, dstValues, srcValues []string) ([]byte, []string, valueType, uint64, uint64) {
|
func tryUintEncoding(dstBuf []byte, dstValues, srcValues []string) ([]byte, []string, valueType, uint64, uint64) {
|
||||||
u64s := encoding.GetUint64s(len(srcValues))
|
u64s := encoding.GetUint64s(len(srcValues))
|
||||||
defer encoding.PutUint64s(u64s)
|
defer encoding.PutUint64s(u64s)
|
||||||
|
|
|
@ -96,133 +96,601 @@ func TestValuesEncoder(t *testing.T) {
|
||||||
f(values, valueTypeFloat64, 4607182418800017408, 4613937818241073152)
|
f(values, valueTypeFloat64, 4607182418800017408, 4613937818241073152)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTryParseIPv4(t *testing.T) {
|
func TestTryParseIPv4_Success(t *testing.T) {
|
||||||
f := func(s string, nExpected uint32, okExpected bool) {
|
f := func(s string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
n, ok := tryParseIPv4(s)
|
n, ok := tryParseIPv4(s)
|
||||||
if n != nExpected {
|
if !ok {
|
||||||
t.Fatalf("unexpected n; got %d; want %d", n, nExpected)
|
t.Fatalf("cannot parse %q", s)
|
||||||
}
|
}
|
||||||
if ok != okExpected {
|
data := marshalIPv4(nil, n)
|
||||||
t.Fatalf("unexpected ok; got %v; want %v", ok, okExpected)
|
if string(data) != s {
|
||||||
|
t.Fatalf("unexpected ip; got %q; want %q", data, s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
f("", 0, false)
|
f("0.0.0.0")
|
||||||
f("foo", 0, false)
|
f("1.2.3.4")
|
||||||
f("a.b.c.d", 0, false)
|
f("255.255.255.255")
|
||||||
f("1.2.3.4", 0x01020304, true)
|
f("127.0.0.1")
|
||||||
f("255.255.255.255", 0xffffffff, true)
|
|
||||||
f("0.0.0.0", 0, true)
|
|
||||||
f("127.0.0.1", 0x7f000001, true)
|
|
||||||
f("127.0.0.x", 0, false)
|
|
||||||
f("127.0.x.0", 0, false)
|
|
||||||
f("127.x.0.0", 0, false)
|
|
||||||
f("x.0.0.0", 0, false)
|
|
||||||
f("127.127.127.256", 0, false)
|
|
||||||
f("127.127.256.127", 0, false)
|
|
||||||
f("127.256.127.127", 0, false)
|
|
||||||
f("256.127.127.127", 0, false)
|
|
||||||
f("-1.127.127.127", 0, false)
|
|
||||||
f("127.-1.127.127", 0, false)
|
|
||||||
f("127.127.-1.127", 0, false)
|
|
||||||
f("127.127.127.-1", 0, false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTryParseTimestampISO8601(t *testing.T) {
|
func TestTryParseIPv4_Failure(t *testing.T) {
|
||||||
f := func(s string, timestampExpected uint64, okExpected bool) {
|
f := func(s string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
timestamp, ok := tryParseTimestampISO8601(s)
|
|
||||||
if timestamp != timestampExpected {
|
_, ok := tryParseIPv4(s)
|
||||||
t.Fatalf("unexpected timestamp; got %d; want %d", timestamp, timestampExpected)
|
if ok {
|
||||||
}
|
t.Fatalf("expecting error when parsing %q", s)
|
||||||
if ok != okExpected {
|
|
||||||
t.Fatalf("unexpected ok; got %v; want %v", ok, okExpected)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
f("2023-01-15T23:45:51.123Z", 1673826351123000000, true)
|
f("")
|
||||||
|
f("foo")
|
||||||
|
f("a.b.c.d")
|
||||||
|
f("127.0.0.x")
|
||||||
|
f("127.0.x.0")
|
||||||
|
f("127.x.0.0")
|
||||||
|
f("x.0.0.0")
|
||||||
|
|
||||||
// Invalid milliseconds
|
// Too big octets
|
||||||
f("2023-01-15T22:15:51.12345Z", 0, false)
|
f("127.127.127.256")
|
||||||
f("2023-01-15T22:15:51.12Z", 0, false)
|
f("127.127.256.127")
|
||||||
f("2023-01-15T22:15:51Z", 0, false)
|
f("127.256.127.127")
|
||||||
|
f("256.127.127.127")
|
||||||
|
|
||||||
// Missing Z
|
// Negative octets
|
||||||
f("2023-01-15T23:45:51.123", 0, false)
|
f("-1.127.127.127")
|
||||||
|
f("127.-1.127.127")
|
||||||
|
f("127.127.-1.127")
|
||||||
|
f("127.127.127.-1")
|
||||||
|
}
|
||||||
|
|
||||||
// Invalid timestamp
|
func TestTryParseTimestampRFC3339Nano_Success(t *testing.T) {
|
||||||
f("foo", 0, false)
|
f := func(s string) {
|
||||||
f("2023-01-15T23:45:51.123Zxyabcd", 0, false)
|
t.Helper()
|
||||||
f("2023-01-15T23:45:51.123Z01:00", 0, false)
|
nsecs, ok := tryParseTimestampRFC3339Nano(s)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("cannot parse timestamp %q", s)
|
||||||
|
}
|
||||||
|
data := marshalTimestampRFC3339Nano(nil, nsecs)
|
||||||
|
if string(data) != s {
|
||||||
|
t.Fatalf("unexpected timestamp; got %q; want %q", data, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No fractional seconds
|
||||||
|
f("2023-01-15T23:45:51Z")
|
||||||
|
|
||||||
|
// Different number of fractional seconds
|
||||||
|
f("2023-01-15T23:45:51.1Z")
|
||||||
|
f("2023-01-15T23:45:51.12Z")
|
||||||
|
f("2023-01-15T23:45:51.123Z")
|
||||||
|
f("2023-01-15T23:45:51.1234Z")
|
||||||
|
f("2023-01-15T23:45:51.12345Z")
|
||||||
|
f("2023-01-15T23:45:51.123456Z")
|
||||||
|
f("2023-01-15T23:45:51.1234567Z")
|
||||||
|
f("2023-01-15T23:45:51.12345678Z")
|
||||||
|
f("2023-01-15T23:45:51.123456789Z")
|
||||||
|
|
||||||
|
// The minimum possible timestamp
|
||||||
|
f("1677-09-21T00:12:44Z")
|
||||||
|
|
||||||
|
// The maximum possible timestamp
|
||||||
|
f("2262-04-11T23:47:15.999999999Z")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTryParseTimestampRFC3339Nano_Failure(t *testing.T) {
|
||||||
|
f := func(s string) {
|
||||||
|
t.Helper()
|
||||||
|
_, ok := tryParseTimestampRFC3339Nano(s)
|
||||||
|
if ok {
|
||||||
|
t.Fatalf("expecting faulure when parsing %q", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// invalid length
|
||||||
|
f("")
|
||||||
|
f("foobar")
|
||||||
|
|
||||||
|
// Missing Z at the end
|
||||||
|
f("2023-01-15T22:15:51")
|
||||||
|
f("2023-01-15T22:15:51.123")
|
||||||
|
|
||||||
|
// missing fractional part after dot
|
||||||
|
f("2023-01-15T22:15:51.Z")
|
||||||
|
|
||||||
// timestamp with timezone
|
// timestamp with timezone
|
||||||
f("2023-01-16T00:45:51.123+01:00", 0, false)
|
f("2023-01-16T00:45:51+01:00")
|
||||||
|
f("2023-01-16T00:45:51.123+01:00")
|
||||||
|
|
||||||
|
// too small year
|
||||||
|
f("1676-09-21T00:12:43Z")
|
||||||
|
|
||||||
|
// too big year
|
||||||
|
f("2263-04-11T23:47:17Z")
|
||||||
|
|
||||||
|
// too small timestamp
|
||||||
|
f("1677-09-21T00:12:43.999999999Z")
|
||||||
|
|
||||||
|
// too big timestamp
|
||||||
|
f("2262-04-11T23:47:16Z")
|
||||||
|
|
||||||
|
// invalid year
|
||||||
|
f("YYYY-04-11T23:47:17Z")
|
||||||
|
|
||||||
|
// invalid moth
|
||||||
|
f("2023-MM-11T23:47:17Z")
|
||||||
|
|
||||||
|
// invalid day
|
||||||
|
f("2023-01-DDT23:47:17Z")
|
||||||
|
|
||||||
|
// invalid hour
|
||||||
|
f("2023-01-23Thh:47:17Z")
|
||||||
|
|
||||||
|
// invalid minute
|
||||||
|
f("2023-01-23T23:mm:17Z")
|
||||||
|
|
||||||
|
// invalid second
|
||||||
|
f("2023-01-23T23:33:ssZ")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTryParseFloat64(t *testing.T) {
|
func TestTryParseTimestampISO8601_Success(t *testing.T) {
|
||||||
f := func(s string, valueExpected float64, okExpected bool) {
|
f := func(s string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
nsecs, ok := tryParseTimestampISO8601(s)
|
||||||
value, ok := tryParseFloat64(s)
|
if !ok {
|
||||||
if value != valueExpected {
|
t.Fatalf("cannot parse timestamp %q", s)
|
||||||
t.Fatalf("unexpected value; got %v; want %v", value, valueExpected)
|
|
||||||
}
|
}
|
||||||
if ok != okExpected {
|
data := marshalTimestampISO8601(nil, nsecs)
|
||||||
t.Fatalf("unexpected ok; got %v; want %v", ok, okExpected)
|
if string(data) != s {
|
||||||
|
t.Fatalf("unexpected timestamp; got %q; want %q", data, s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
f("0", 0, true)
|
// regular timestamp
|
||||||
f("1234567890", 1234567890, true)
|
f("2023-01-15T23:45:51.123Z")
|
||||||
f("-1.234567", -1.234567, true)
|
|
||||||
|
// The minimum possible timestamp
|
||||||
|
f("1677-09-21T00:12:44.000Z")
|
||||||
|
|
||||||
|
// The maximum possible timestamp
|
||||||
|
f("2262-04-11T23:47:15.999Z")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTryParseTimestampISO8601_Failure(t *testing.T) {
|
||||||
|
f := func(s string) {
|
||||||
|
t.Helper()
|
||||||
|
_, ok := tryParseTimestampISO8601(s)
|
||||||
|
if ok {
|
||||||
|
t.Fatalf("expecting faulure when parsing %q", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// invalid length
|
||||||
|
f("")
|
||||||
|
f("foobar")
|
||||||
|
|
||||||
|
// Missing Z at the end
|
||||||
|
f("2023-01-15T22:15:51.123")
|
||||||
|
f("2023-01-15T22:15:51.1234")
|
||||||
|
|
||||||
|
// timestamp with timezone
|
||||||
|
f("2023-01-16T00:45:51.123+01:00")
|
||||||
|
|
||||||
|
// too small year
|
||||||
|
f("1676-09-21T00:12:43.434Z")
|
||||||
|
|
||||||
|
// too big year
|
||||||
|
f("2263-04-11T23:47:17.434Z")
|
||||||
|
|
||||||
|
// too small timestamp
|
||||||
|
f("1677-09-21T00:12:43.999Z")
|
||||||
|
|
||||||
|
// too big timestamp
|
||||||
|
f("2262-04-11T23:47:16.000Z")
|
||||||
|
|
||||||
|
// invalid year
|
||||||
|
f("YYYY-04-11T23:47:17.123Z")
|
||||||
|
|
||||||
|
// invalid moth
|
||||||
|
f("2023-MM-11T23:47:17.123Z")
|
||||||
|
|
||||||
|
// invalid day
|
||||||
|
f("2023-01-DDT23:47:17.123Z")
|
||||||
|
|
||||||
|
// invalid hour
|
||||||
|
f("2023-01-23Thh:47:17.123Z")
|
||||||
|
|
||||||
|
// invalid minute
|
||||||
|
f("2023-01-23T23:mm:17.123Z")
|
||||||
|
|
||||||
|
// invalid second
|
||||||
|
f("2023-01-23T23:33:ss.123Z")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTryParseDuration_Success(t *testing.T) {
|
||||||
|
f := func(s string, nsecsExpected int64) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
nsecs, ok := tryParseDuration(s)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("cannot parse %q", s)
|
||||||
|
}
|
||||||
|
if nsecs != nsecsExpected {
|
||||||
|
t.Fatalf("unexpected value; got %d; want %d", nsecs, nsecsExpected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// zero duration
|
||||||
|
f("0s", 0)
|
||||||
|
f("0S", 0)
|
||||||
|
f("0.0w0d0h0s0.0ms", 0)
|
||||||
|
f("-0w", 0)
|
||||||
|
|
||||||
|
// positive duration
|
||||||
|
f("1s", nsecsPerSecond)
|
||||||
|
f("1.5ms", 1.5*nsecsPerMillisecond)
|
||||||
|
f("1µs", nsecsPerMicrosecond)
|
||||||
|
f("1ns", 1)
|
||||||
|
f("1NS", 1)
|
||||||
|
f("1nS", 1)
|
||||||
|
f("1Ns", 1)
|
||||||
|
f("1h", nsecsPerHour)
|
||||||
|
f("1H", nsecsPerHour)
|
||||||
|
f("1.5d", 1.5*nsecsPerDay)
|
||||||
|
f("1.5D", 1.5*nsecsPerDay)
|
||||||
|
f("1.5w", 1.5*nsecsPerWeek)
|
||||||
|
f("1.5W", 1.5*nsecsPerWeek)
|
||||||
|
f("2.5y", 2.5*nsecsPerYear)
|
||||||
|
f("1m5.123456789s", nsecsPerMinute+5.123456789*nsecsPerSecond)
|
||||||
|
|
||||||
|
// composite duration
|
||||||
|
f("1h5m", nsecsPerHour+5*nsecsPerMinute)
|
||||||
|
f("1.1h5m2.5s3_456ns", 1.1*nsecsPerHour+5*nsecsPerMinute+2.5*nsecsPerSecond+3456)
|
||||||
|
|
||||||
|
// nedgative duration
|
||||||
|
f("-1h5m3s", -(nsecsPerHour + 5*nsecsPerMinute + 3*nsecsPerSecond))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTryParseDuration_Failure(t *testing.T) {
|
||||||
|
f := func(s string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
_, ok := tryParseDuration(s)
|
||||||
|
if ok {
|
||||||
|
t.Fatalf("expecting error for parsing %q", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// empty string
|
||||||
|
f("")
|
||||||
|
|
||||||
|
// missing suffix
|
||||||
|
f("2")
|
||||||
|
f("2.5")
|
||||||
|
|
||||||
|
// invalid string
|
||||||
|
f("foobar")
|
||||||
|
f("1foo")
|
||||||
|
f("1soo")
|
||||||
|
f("3.43e")
|
||||||
|
f("3.43es")
|
||||||
|
|
||||||
|
// superflouous space
|
||||||
|
f(" 2s")
|
||||||
|
f("2s ")
|
||||||
|
f("2s 3ms")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshalDuration(t *testing.T) {
|
||||||
|
f := func(nsecs int64, resultExpected string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
result := marshalDuration(nil, nsecs)
|
||||||
|
if string(result) != resultExpected {
|
||||||
|
t.Fatalf("unexpected result; got %q; want %q", result, resultExpected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
f(0, "0")
|
||||||
|
f(1, "1ns")
|
||||||
|
f(-1, "-1ns")
|
||||||
|
f(12345, "12µs345ns")
|
||||||
|
f(123456789, "123ms456µs789ns")
|
||||||
|
f(12345678901, "12.345678901s")
|
||||||
|
f(1234567890143, "20m34.567890143s")
|
||||||
|
f(1234567890123457, "2w6h56m7.890123457s")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTryParseBytes_Success(t *testing.T) {
|
||||||
|
f := func(s string, resultExpected int64) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
result, ok := tryParseBytes(s)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("cannot parse %q", s)
|
||||||
|
}
|
||||||
|
if result != resultExpected {
|
||||||
|
t.Fatalf("unexpected result; got %d; want %d", result, resultExpected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
f("123.456", 123)
|
||||||
|
f("1_500", 1_500)
|
||||||
|
|
||||||
|
f("2.5b", 2)
|
||||||
|
f("2.5B", 2)
|
||||||
|
|
||||||
|
f("1.5k", 1_500)
|
||||||
|
f("1.5m", 1_500_000)
|
||||||
|
f("1.5g", 1_500_000_000)
|
||||||
|
f("1.5t", 1_500_000_000_000)
|
||||||
|
|
||||||
|
f("1.5K", 1_500)
|
||||||
|
f("1.5M", 1_500_000)
|
||||||
|
f("1.5G", 1_500_000_000)
|
||||||
|
f("1.5T", 1_500_000_000_000)
|
||||||
|
|
||||||
|
f("1.5kb", 1_500)
|
||||||
|
f("1.5mb", 1_500_000)
|
||||||
|
f("1.5gb", 1_500_000_000)
|
||||||
|
f("1.5tb", 1_500_000_000_000)
|
||||||
|
|
||||||
|
f("1.5Kb", 1_500)
|
||||||
|
f("1.5Mb", 1_500_000)
|
||||||
|
f("1.5Gb", 1_500_000_000)
|
||||||
|
f("1.5Tb", 1_500_000_000_000)
|
||||||
|
|
||||||
|
f("1.5KB", 1_500)
|
||||||
|
f("1.5MB", 1_500_000)
|
||||||
|
f("1.5GB", 1_500_000_000)
|
||||||
|
f("1.5TB", 1_500_000_000_000)
|
||||||
|
|
||||||
|
f("1.5ki", 1.5*(1<<10))
|
||||||
|
f("1.5mi", 1.5*(1<<20))
|
||||||
|
f("1.5gi", 1.5*(1<<30))
|
||||||
|
f("1.5ti", 1.5*(1<<40))
|
||||||
|
|
||||||
|
f("1.5Ki", 1.5*(1<<10))
|
||||||
|
f("1.5Mi", 1.5*(1<<20))
|
||||||
|
f("1.5Gi", 1.5*(1<<30))
|
||||||
|
f("1.5Ti", 1.5*(1<<40))
|
||||||
|
|
||||||
|
f("1.5KI", 1.5*(1<<10))
|
||||||
|
f("1.5MI", 1.5*(1<<20))
|
||||||
|
f("1.5GI", 1.5*(1<<30))
|
||||||
|
f("1.5TI", 1.5*(1<<40))
|
||||||
|
|
||||||
|
f("1.5kib", 1.5*(1<<10))
|
||||||
|
f("1.5mib", 1.5*(1<<20))
|
||||||
|
f("1.5gib", 1.5*(1<<30))
|
||||||
|
f("1.5tib", 1.5*(1<<40))
|
||||||
|
|
||||||
|
f("1.5kiB", 1.5*(1<<10))
|
||||||
|
f("1.5miB", 1.5*(1<<20))
|
||||||
|
f("1.5giB", 1.5*(1<<30))
|
||||||
|
f("1.5tiB", 1.5*(1<<40))
|
||||||
|
|
||||||
|
f("1.5KiB", 1.5*(1<<10))
|
||||||
|
f("1.5MiB", 1.5*(1<<20))
|
||||||
|
f("1.5GiB", 1.5*(1<<30))
|
||||||
|
f("1.5TiB", 1.5*(1<<40))
|
||||||
|
|
||||||
|
f("1MiB500KiB200B", (1<<20)+500*(1<<10)+200)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTryParseBytes_Failure(t *testing.T) {
|
||||||
|
f := func(s string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
_, ok := tryParseBytes(s)
|
||||||
|
if ok {
|
||||||
|
t.Fatalf("expecting error when parsing %q", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// empty string
|
||||||
|
f("")
|
||||||
|
|
||||||
|
// invalid number
|
||||||
|
f("foobar")
|
||||||
|
|
||||||
|
// invalid suffix
|
||||||
|
f("123q")
|
||||||
|
f("123qs")
|
||||||
|
f("123qsb")
|
||||||
|
f("123sqsb")
|
||||||
|
f("123s5qsb")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTryParseFloat64_Success(t *testing.T) {
|
||||||
|
f := func(s string, resultExpected float64) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
result, ok := tryParseFloat64(s)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("cannot parse %q", s)
|
||||||
|
}
|
||||||
|
if !float64Equal(result, resultExpected) {
|
||||||
|
t.Fatalf("unexpected value; got %f; want %f", result, resultExpected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
f("0", 0)
|
||||||
|
f("1", 1)
|
||||||
|
f("-1", -1)
|
||||||
|
f("1234567890", 1234567890)
|
||||||
|
f("1_234_567_890", 1234567890)
|
||||||
|
f("-1.234_567", -1.234567)
|
||||||
|
|
||||||
|
f("0.345", 0.345)
|
||||||
|
f("-0.345", -0.345)
|
||||||
|
}
|
||||||
|
|
||||||
|
func float64Equal(a, b float64) bool {
|
||||||
|
return math.Abs(a-b)*math.Abs(max(a, b)) < 1e-15
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTryParseFloat64_Failure(t *testing.T) {
|
||||||
|
f := func(s string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
_, ok := tryParseFloat64(s)
|
||||||
|
if ok {
|
||||||
|
t.Fatalf("expecting error when parsing %q", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Empty value
|
// Empty value
|
||||||
f("", 0, false)
|
f("")
|
||||||
|
|
||||||
// Plus in the value isn't allowed, since it cannot be convered back to the same string representation
|
// Plus in the value isn't allowed, since it cannot be convered back to the same string representation
|
||||||
f("+123", 0, false)
|
f("+123")
|
||||||
|
|
||||||
// Dot at the beginning and the end of value isn't allowed, since it cannot converted back to the same string representation
|
// Dot at the beginning and the end of value isn't allowed, since it cannot converted back to the same string representation
|
||||||
f(".123", 0, false)
|
f(".123")
|
||||||
f("123.", 0, false)
|
f("123.")
|
||||||
|
|
||||||
// Multiple dots aren't allowed
|
// Multiple dots aren't allowed
|
||||||
f("123.434.55", 0, false)
|
f("123.434.55")
|
||||||
|
|
||||||
// Invalid dots
|
// Invalid dots
|
||||||
f("-.123", 0, false)
|
f("-.123")
|
||||||
f(".", 0, false)
|
f(".")
|
||||||
|
|
||||||
// Scientific notation isn't allowed, since it cannot be converted back to the same string representation
|
// Scientific notation isn't allowed, since it cannot be converted back to the same string representation
|
||||||
f("12e5", 0, false)
|
f("12e5")
|
||||||
|
|
||||||
// Minus in the middle of string isn't allowed
|
// Minus in the middle of string isn't allowed
|
||||||
f("12-5", 0, false)
|
f("12-5")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTryParseUint64(t *testing.T) {
|
func TestMarshalFloat64(t *testing.T) {
|
||||||
f := func(s string, valueExpected uint64, okExpected bool) {
|
f := func(f float64, resultExpected string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
value, ok := tryParseUint64(s)
|
result := marshalFloat64(nil, f)
|
||||||
if value != valueExpected {
|
if string(result) != resultExpected {
|
||||||
t.Fatalf("unexpected value; got %d; want %d", value, valueExpected)
|
t.Fatalf("unexpected result; got %q; want %q", result, resultExpected)
|
||||||
}
|
|
||||||
if ok != okExpected {
|
|
||||||
t.Fatalf("unexpected ok; got %v; want %v", ok, okExpected)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
f("0", 0, true)
|
f(0, "0")
|
||||||
f("123456789012345678", 123456789012345678, true)
|
f(1234, "1234")
|
||||||
|
f(-12345678, "-12345678")
|
||||||
|
f(1.234, "1.234")
|
||||||
|
f(-1.234567, "-1.234567")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTryParseUint64_Success(t *testing.T) {
|
||||||
|
f := func(s string, resultExpected uint64) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
result, ok := tryParseUint64(s)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("cannot parse %q", s)
|
||||||
|
}
|
||||||
|
if result != resultExpected {
|
||||||
|
t.Fatalf("unexpected value; got %d; want %d", result, resultExpected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
f("0", 0)
|
||||||
|
f("123", 123)
|
||||||
|
f("123456", 123456)
|
||||||
|
f("123456789", 123456789)
|
||||||
|
f("123456789012", 123456789012)
|
||||||
|
f("123456789012345", 123456789012345)
|
||||||
|
f("123456789012345678", 123456789012345678)
|
||||||
|
f("12345678901234567890", 12345678901234567890)
|
||||||
|
f("12_345_678_901_234_567_890", 12345678901234567890)
|
||||||
|
|
||||||
|
// the maximum possible value
|
||||||
|
f("18446744073709551615", 18446744073709551615)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTryParseUint64_Failure(t *testing.T) {
|
||||||
|
f := func(s string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
_, ok := tryParseUint64(s)
|
||||||
|
if ok {
|
||||||
|
t.Fatalf("expecting error when parsing %q", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// empty value
|
// empty value
|
||||||
f("", 0, false)
|
f("")
|
||||||
|
|
||||||
// too big value
|
// too big value
|
||||||
f("1234567890123456789", 0, false)
|
f("18446744073709551616")
|
||||||
|
|
||||||
// invalid value
|
// invalid value
|
||||||
f("foo", 0, false)
|
f("foo")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshalUint64(t *testing.T) {
|
||||||
|
f := func(n uint64, resultExpected string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
result := marshalUint64(nil, n)
|
||||||
|
if string(result) != resultExpected {
|
||||||
|
t.Fatalf("unexpected result; got %q; want %q", result, resultExpected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
f(0, "0")
|
||||||
|
f(123456, "123456")
|
||||||
|
|
||||||
|
// the maximum possible value
|
||||||
|
f(18446744073709551615, "18446744073709551615")
|
||||||
|
f(18_446_744_073_709_551_615, "18446744073709551615")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTryParseIPv4Mask_Success(t *testing.T) {
|
||||||
|
f := func(s string, resultExpected uint64) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
result, ok := tryParseIPv4Mask(s)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("cannot parse %q", s)
|
||||||
|
}
|
||||||
|
if result != resultExpected {
|
||||||
|
t.Fatalf("unexpected result; got %d; want %d", result, resultExpected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
f("/0", 1<<32)
|
||||||
|
f("/1", 1<<31)
|
||||||
|
f("/8", 1<<24)
|
||||||
|
f("/24", 1<<8)
|
||||||
|
f("/32", 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTryParseIPv4Mask_Failure(t *testing.T) {
|
||||||
|
f := func(s string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
_, ok := tryParseIPv4Mask(s)
|
||||||
|
if ok {
|
||||||
|
t.Fatalf("expecting error when parsing %q", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty mask
|
||||||
|
f("")
|
||||||
|
|
||||||
|
// Invalid prefix
|
||||||
|
f("foo")
|
||||||
|
|
||||||
|
// Non-numeric mask
|
||||||
|
f("/foo")
|
||||||
|
|
||||||
|
// Too big mask
|
||||||
|
f("/33")
|
||||||
|
|
||||||
|
// Negative mask
|
||||||
|
f("/-1")
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,12 +5,35 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func BenchmarkTryParseTimestampRFC3339Nano(b *testing.B) {
|
||||||
|
a := []string{
|
||||||
|
"2023-01-15T23:45:51Z",
|
||||||
|
"2023-02-15T23:45:51.123Z",
|
||||||
|
"2024-02-15T23:45:51.123456Z",
|
||||||
|
"2025-02-15T22:45:51.123456789Z",
|
||||||
|
"2023-02-15T22:45:51.000000000Z",
|
||||||
|
}
|
||||||
|
|
||||||
|
b.SetBytes(int64(len(a)))
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
for _, s := range a {
|
||||||
|
_, ok := tryParseTimestampRFC3339Nano(s)
|
||||||
|
if !ok {
|
||||||
|
panic(fmt.Errorf("cannot parse timestamp %q", s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkTryParseTimestampISO8601(b *testing.B) {
|
func BenchmarkTryParseTimestampISO8601(b *testing.B) {
|
||||||
a := []string{
|
a := []string{
|
||||||
"2023-01-15T23:45:51.123Z",
|
"2023-01-15T23:45:51.123Z",
|
||||||
"2023-02-15T23:45:51.123Z",
|
"2023-02-15T23:45:51.123Z",
|
||||||
"2023-02-15T23:45:51.123+01:00",
|
"2024-02-15T23:45:51.123Z",
|
||||||
"2023-02-15T22:45:51.123-10:30",
|
"2025-02-15T22:45:51.123Z",
|
||||||
"2023-02-15T22:45:51.000Z",
|
"2023-02-15T22:45:51.000Z",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue