mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2026-08-01 21:42:06 +03:00
Compare commits
4 Commits
master
...
issue-1132
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d3f1c38b87 | ||
|
|
127eaa6f87 | ||
|
|
f25ee774cd | ||
|
|
d89d5734cc |
@@ -48,6 +48,7 @@ See also [LTS releases](https://docs.victoriametrics.com/victoriametrics/lts-rel
|
||||
* BUGFIX: [vmbackupmanager](https://docs.victoriametrics.com/victoriametrics/vmbackupmanager/): previously, `vmbackupmanager` was crashing on startup when it failed to restore backup state from remote storage, causing a crash loop. Now it logs the error and continues running, retrying the state restore before each scheduled backup. Added `vm_backup_errors_total{type="restoreState"}` metric to track backup state restore failures. See [#11217](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11217).
|
||||
* BUGFIX: [stream aggregation](https://docs.victoriametrics.com/victoriametrics/stream-aggregation/): fix incorrect [sum_samples_total](https://docs.victoriametrics.com/victoriametrics/stream-aggregation/configuration/#sum_samples_total) results when `enable_windows: true` is set. See [#11261](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11261). Thanks to @beyond-infra for contribution.
|
||||
* BUGFIX: [vmsingle](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/), `vmselect` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/) and [vmctl](https://docs.victoriametrics.com/victoriametrics/vmctl/): accept scientific notation with sub-second precision (e.g. `1.784144612388E9`) for timestamp args such as `start` and `end` in `/api/v1/query_range` and `--vm-native-filter-time-start` and `--vm-native-filter-time-end` in `vmctl`. Previously, values with this pattern were rejected, which is incompatible with Prometheus. See [#11268](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11268). Thanks to @STiFLeR7 for contribution.
|
||||
* BUGFIX: [vmsingle](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/), `vmselect` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/) and [vmctl](https://docs.victoriametrics.com/victoriametrics/vmctl/): properly parse small fractional Unix timestamps in timestamp args such as `start` and `end` in `/api/v1/query_range` and `--vm-native-filter-time-start` and `--vm-native-filter-time-end` in `vmctl`. Previously, fractional Unix timestamps with the integer part below `9223372` will be interpreted with the wrong unit, for example `12.0` previously will be parsed as `12000` seconds instead of `12` seconds. See [#11324](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11324).
|
||||
|
||||
## [v1.148.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.148.0)
|
||||
|
||||
|
||||
@@ -148,11 +148,11 @@ func TryParseUnixTimestamp(s string) (int64, bool) {
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
n, ok := tryParseScientificNumberForUnixTimestamp(s[:expIdx], decimalExp)
|
||||
n, ok := tryParseScientificUnixTimestamp(s[:expIdx], decimalExp)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
return getUnixTimestampNanoseconds(n), true
|
||||
return n, true
|
||||
}
|
||||
|
||||
dotIdx := strings.IndexByte(s, '.')
|
||||
@@ -168,22 +168,11 @@ func TryParseUnixTimestamp(s string) (int64, bool) {
|
||||
// The timestamp is fractional.
|
||||
intStr := s[:dotIdx]
|
||||
fracStr := s[dotIdx+1:]
|
||||
n, ok := tryParseFractionalNumberForUnixTimestamp(intStr, fracStr)
|
||||
n, ok := tryParseFractionalUnixTimestamp(intStr, fracStr)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// Adjust the n to multiples of thousands, since this is expected by getUnixTimestampNanoseconds.
|
||||
decimalExp := len(fracStr)
|
||||
for decimalExp%3 != 0 {
|
||||
if n >= 0 && n > math.MaxInt64/10 || n < 0 && n < math.MinInt64/10 {
|
||||
return 0, false
|
||||
}
|
||||
n *= 10
|
||||
decimalExp++
|
||||
}
|
||||
|
||||
return getUnixTimestampNanoseconds(n), true
|
||||
return n, true
|
||||
}
|
||||
|
||||
func getExpIndex(s string) int {
|
||||
@@ -196,14 +185,18 @@ func getExpIndex(s string) int {
|
||||
return -1
|
||||
}
|
||||
|
||||
func tryParseScientificNumberForUnixTimestamp(s string, decimalExp int64) (int64, bool) {
|
||||
func tryParseScientificUnixTimestamp(s string, decimalExp int64) (int64, bool) {
|
||||
dotIdx := strings.IndexByte(s, '.')
|
||||
if dotIdx < 0 {
|
||||
n, ok := tryParseInt64(s)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
return multiplyByDecimalExp(n, decimalExp)
|
||||
n, ok = multiplyByDecimalExp(n, decimalExp)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
return getUnixTimestampNanoseconds(n), true
|
||||
}
|
||||
|
||||
if decimalExp < 0 {
|
||||
@@ -214,31 +207,32 @@ func tryParseScientificNumberForUnixTimestamp(s string, decimalExp int64) (int64
|
||||
|
||||
intStr := s[:dotIdx]
|
||||
fracStr := s[dotIdx+1:]
|
||||
n, ok := tryParseFractionalNumberForUnixTimestamp(intStr, fracStr)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
if decimalExp >= int64(len(fracStr)) {
|
||||
// The exponent shifts the decimal point past every fractional digit,
|
||||
// so the value is an integer number of seconds (or coarser).
|
||||
n, ok := tryParseDecimalMantissaAsInt(intStr, fracStr)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
decimalExp -= int64(len(fracStr))
|
||||
return multiplyByDecimalExp(n, decimalExp)
|
||||
n, ok = multiplyByDecimalExp(n, decimalExp)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
return getUnixTimestampNanoseconds(n), true
|
||||
}
|
||||
|
||||
// The exponent leaves fractional digits, e.g. 1.784144612388E9 == 1784144612.388
|
||||
// Pad n as plain fractional timestamps do.
|
||||
fracDigits := int64(len(fracStr)) - decimalExp
|
||||
for fracDigits%3 != 0 {
|
||||
if n >= 0 && n > math.MaxInt64/10 || n < 0 && n < math.MinInt64/10 {
|
||||
return 0, false
|
||||
}
|
||||
n *= 10
|
||||
fracDigits++
|
||||
if decimalExp >= int64(len(decimalMultipliers)) {
|
||||
return 0, false
|
||||
}
|
||||
return n, true
|
||||
decimalExpInt := int(decimalExp)
|
||||
intStr = s[:dotIdx] + fracStr[:decimalExpInt]
|
||||
fracStr = fracStr[decimalExpInt:]
|
||||
return tryParseFractionalUnixTimestamp(intStr, fracStr)
|
||||
}
|
||||
|
||||
func tryParseFractionalNumberForUnixTimestamp(intStr, fracStr string) (int64, bool) {
|
||||
func tryParseDecimalMantissaAsInt(intStr, fracStr string) (int64, bool) {
|
||||
n, ok := tryParseInt64(intStr)
|
||||
if !ok {
|
||||
return 0, false
|
||||
@@ -270,6 +264,49 @@ func tryParseFractionalNumberForUnixTimestamp(intStr, fracStr string) (int64, bo
|
||||
return num, true
|
||||
}
|
||||
|
||||
func tryParseFractionalUnixTimestamp(intStr, fracStr string) (int64, bool) {
|
||||
n, ok := tryParseInt64(intStr)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
frac, ok := tryParseInt64(fracStr)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
decimalExp := len(fracStr)
|
||||
if decimalExp == 0 || decimalExp >= len(decimalMultipliers) {
|
||||
return 0, false
|
||||
}
|
||||
multiplier := getUnixTimestampNanosecondsMultiplier(n)
|
||||
if multiplier == 1 && frac != 0 {
|
||||
return 0, false
|
||||
}
|
||||
n *= multiplier
|
||||
scale := decimalMultipliers[decimalExp]
|
||||
var fracNsec int64
|
||||
if scale >= multiplier {
|
||||
divisor := scale / multiplier
|
||||
// Reject fractional values that cannot be represented as a whole number of nanoseconds.
|
||||
// e.g. 1700000000000000000.1 ns
|
||||
if frac%divisor != 0 {
|
||||
return 0, false
|
||||
}
|
||||
fracNsec = frac / divisor
|
||||
} else {
|
||||
fracNsec = frac * (multiplier / scale)
|
||||
}
|
||||
if strings.HasPrefix(intStr, "-") {
|
||||
if n < math.MinInt64+fracNsec {
|
||||
return 0, false
|
||||
}
|
||||
return n - fracNsec, true
|
||||
}
|
||||
if n > math.MaxInt64-fracNsec {
|
||||
return 0, false
|
||||
}
|
||||
return n + fracNsec, true
|
||||
}
|
||||
|
||||
func multiplyByDecimalExp(n int64, decimalExp int64) (int64, bool) {
|
||||
if decimalExp < 0 {
|
||||
return 0, false
|
||||
@@ -302,20 +339,24 @@ const (
|
||||
)
|
||||
|
||||
func getUnixTimestampNanoseconds(n int64) int64 {
|
||||
return n * getUnixTimestampNanosecondsMultiplier(n)
|
||||
}
|
||||
|
||||
func getUnixTimestampNanosecondsMultiplier(n int64) int64 {
|
||||
if n <= maxValidSecond && n >= minValidSecond {
|
||||
// The timestamp is in seconds.
|
||||
return n * 1e9
|
||||
return 1e9
|
||||
}
|
||||
if n <= maxValidMilli && n >= minValidMilli {
|
||||
// The timestamp is in milliseconds.
|
||||
return n * 1e6
|
||||
return 1e6
|
||||
}
|
||||
if n <= maxValidMicro && n >= minValidMicro {
|
||||
// The timestamp is in microseconds.
|
||||
return n * 1e3
|
||||
return 1e3
|
||||
}
|
||||
// The timestamp is in nanoseconds
|
||||
return n
|
||||
return 1
|
||||
}
|
||||
|
||||
func tryParseInt64(s string) (int64, bool) {
|
||||
|
||||
@@ -24,62 +24,74 @@ func TestTryParseUnixTimestamp_Success(t *testing.T) {
|
||||
f("0", 0)
|
||||
|
||||
// nanoseconds
|
||||
f("-1234567890123456789", -1234567890123456789)
|
||||
f("1234567890123456789", 1234567890123456789)
|
||||
f("1234567890123456.789", 1234567890123456789)
|
||||
f("-1234567890123456789", -1234567890_123_456_789)
|
||||
f("1234567890123456789", 1234567890_123_456_789)
|
||||
f("1234567890123456.789", 1234567890_123_456_789)
|
||||
|
||||
// microseconds
|
||||
f("-1234567890123456", -1234567890123456000)
|
||||
f("1234567890123456", 1234567890123456000)
|
||||
f("1234567890123456.789", 1234567890123456789)
|
||||
f("-1234567890123456", -1234567890_123_456_000)
|
||||
f("1234567890123456", 1234567890_123_456_000)
|
||||
f("1234567890123456.789", 1234567890_123_456_789)
|
||||
f("12345678901234.5000", 12345678_901_234_500)
|
||||
|
||||
// milliseconds
|
||||
f("-1234567890123", -1234567890123000000)
|
||||
f("1234567890123", 1234567890123000000)
|
||||
f("1234567890123.456", 1234567890123456000)
|
||||
f("-1234567890123", -1234567890_123_000_000)
|
||||
f("1234567890123", 1234567890_123_000_000)
|
||||
f("1234567890123.456", 1234567890_123_456_000)
|
||||
|
||||
// seconds
|
||||
f("-1234567890", -1234567890000000000)
|
||||
f("1234567890", 1234567890000000000)
|
||||
f("1234567890.123456789", 1234567890123456789)
|
||||
f("1234567890.12345678", 1234567890123456780)
|
||||
f("1234567890.1234567", 1234567890123456700)
|
||||
f("-1234567890.123456", -1234567890123456000)
|
||||
f("-1234567890.12345", -1234567890123450000)
|
||||
f("-1234567890.1234", -1234567890123400000)
|
||||
f("-1234567890.123", -1234567890123000000)
|
||||
f("-1234567890.12", -1234567890120000000)
|
||||
f("-1234567890.1", -1234567890100000000)
|
||||
f("-1234567890", -1234567890_000_000_000)
|
||||
f("1234567890", 1234567890_000_000_000)
|
||||
f("1234567890.123456789", 1234567890_123_456_789)
|
||||
f("1234567890.12345678", 1234567890_123_456_780)
|
||||
f("1234567890.1234567", 1234567890_123_456_700)
|
||||
f("-1234567890.123456", -1234567890_123_456_000)
|
||||
f("-1234567890.12345", -1234567890_123_450_000)
|
||||
f("-1234567890.1234", -1234567890_123_400_000)
|
||||
f("-1234567890.123", -1234567890_123_000_000)
|
||||
f("-1234567890.12", -1234567890_120_000_000)
|
||||
f("-1234567890.1", -1234567890_100_000_000)
|
||||
f("12", 12_000_000_000)
|
||||
f("12.0", 12_000_000_000)
|
||||
f("12.34", 12_340_000_000)
|
||||
f("12.999999999000000000", 12_999_999_999)
|
||||
f("-12", -12_000_000_000)
|
||||
f("-12.34", -12_340_000_000)
|
||||
f("8223372", 8223372_000_000_000)
|
||||
f("8223372.0", 8223372_000_000_000)
|
||||
f("1700000000", 1700000000_000_000_000)
|
||||
f("1700000000.0", 1700000000_000_000_000)
|
||||
|
||||
// scientific notation
|
||||
f("1e9", 1000000000000000000)
|
||||
f("1.234e9", 1234000000000000000)
|
||||
f("-1.23456789e9", -1234567890000000000)
|
||||
f("1.234567890123456789e18", 1234567890123456789)
|
||||
f("-1.234567890123456789e18", -1234567890123456789)
|
||||
f("0.23456789e9", 234567890000000000)
|
||||
f("123.456789123e9", 123456789123000000)
|
||||
f("-1234.5678912e9", -1234567891200000000)
|
||||
f("123.678912e7", 1236789120000000000)
|
||||
f("1.23e7", 12300000000000000)
|
||||
f("1.23e6", 1230000000000000)
|
||||
f("1.23e5", 123000000000000)
|
||||
f("1.23e4", 12300000000000)
|
||||
f("1.23e3", 1230000000000)
|
||||
f("1.23e2", 123000000000)
|
||||
f("1.2e1", 12000000000)
|
||||
f("1123.456789123456789E15", 1123456789123456789)
|
||||
f("1e9", 1000000000_000_000_000)
|
||||
f("1.234e9", 1234000000_000_000_000)
|
||||
f("-1.23456789e9", -1234567890_000_000_000)
|
||||
f("1.234567890123456789e18", 1234567890_123_456_789)
|
||||
f("-1.234567890123456789e18", -1234567890_123_456_789)
|
||||
f("0.23456789e9", 234567890_000_000_000)
|
||||
f("123.456789123e9", 123456789_123_000_000)
|
||||
f("-1234.5678912e9", -1234567891_200_000_000)
|
||||
f("123.678912e7", 1236789120_000_000_000)
|
||||
f("1.23e7", 12300000_000_000_000)
|
||||
f("1.23e6", 1230000_000_000_000)
|
||||
f("1.23e5", 123000_000_000_000)
|
||||
f("1.23e4", 12300_000_000_000)
|
||||
f("1.23e3", 1230_000_000_000)
|
||||
f("1.23e2", 123_000_000_000)
|
||||
f("1.2e1", 12_000_000_000)
|
||||
f("1123.456789123456789E15", 1123456789_123_456_789)
|
||||
|
||||
// scientific notation with sub-second precision, i.e. more fractional digits
|
||||
// than the exponent shifts (https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11268).
|
||||
// These must match the equivalent plain fractional form.
|
||||
f("1.784144612388E9", 1784144612388000000) // == 1784144612.388
|
||||
f("1.784144612388e9", 1784144612388000000)
|
||||
f("-1.784144612388e9", -1784144612388000000)
|
||||
f("1.5000000005e9", 1500000000500000000) // == 1500000000.5
|
||||
f("1.23456789e9", 1234567890000000000) // exponent consumes all frac digits (integer result)
|
||||
f("1.23e1", 12300000000000) // == 12.3
|
||||
f("1.234e0", 1234000000000) // == 1.234
|
||||
f("1.784144612388E9", 1784144612_388_000_000) // == 1784144612.388
|
||||
f("1.784144612388e9", 1784144612_388_000_000)
|
||||
f("-1.784144612388e9", -1784144612_388_000_000)
|
||||
f("1.5000000005e9", 1500000000_500_000_000) // == 1500000000.5
|
||||
f("1.23456789e9", 1234567890_000_000_000) // exponent consumes all frac digits (integer result)
|
||||
f("1.23e1", 12_300_000_000) // == 12.3
|
||||
f("1.234e0", 1_234_000_000) // == 1.234
|
||||
f("1234567890123456789.0e0", 1234567890_123_456_789)
|
||||
}
|
||||
|
||||
func TestTryParseUnixTimestamp_Failure(t *testing.T) {
|
||||
@@ -129,6 +141,10 @@ func TestTryParseUnixTimestamp_Failure(t *testing.T) {
|
||||
// negative decimal exponent
|
||||
f("1E-1")
|
||||
f("1.3e-123456789090123")
|
||||
|
||||
// fractional part for nanoseconds
|
||||
f("1234567890123456789.1")
|
||||
f("1234567890123456789.1e0")
|
||||
}
|
||||
|
||||
func TestParseTimeAtSuccess(t *testing.T) {
|
||||
@@ -146,27 +162,27 @@ func TestParseTimeAtSuccess(t *testing.T) {
|
||||
now := time.Now().UnixNano()
|
||||
|
||||
// unix timestamp in seconds
|
||||
f("1562529662", now, 1562529662*1e9)
|
||||
f("1562529662.6", now, 1562529662600*1e6)
|
||||
f("1562529662.67", now, 1562529662670*1e6)
|
||||
f("1562529662.678", now, 1562529662678*1e6)
|
||||
f("1562529662.678123", now, 1562529662678123*1e3)
|
||||
f("1562529662.678123456", now, 1562529662678123456)
|
||||
f("1562529662", now, 1562529662_000_000_000)
|
||||
f("1562529662.6", now, 1562529662_600_000_000)
|
||||
f("1562529662.67", now, 1562529662_670_000_000)
|
||||
f("1562529662.678", now, 1562529662_678_000_000)
|
||||
f("1562529662.678123", now, 1562529662_678_123_000)
|
||||
f("1562529662.678123456", now, 1562529662_678_123_456)
|
||||
|
||||
// unix timestamp in milliseconds
|
||||
f("1562529662678", now, 1562529662678*1e6)
|
||||
f("1562529662678.9", now, 1562529662678900*1e3)
|
||||
f("1562529662678.901", now, 1562529662678901*1e3)
|
||||
f("1562529662678.901324", now, 1562529662678901324)
|
||||
f("1562529662678", now, 1562529662_678_000_000)
|
||||
f("1562529662678.9", now, 1562529662_678_900_000)
|
||||
f("1562529662678.901", now, 1562529662_678_901_000)
|
||||
f("1562529662678.901324", now, 1562529662_678_901_324)
|
||||
|
||||
// unix timestamp in microseconds
|
||||
f("1562529662678901", now, 1562529662678901*1e3)
|
||||
f("1562529662678901.3", now, 1562529662678901300)
|
||||
f("1562529662678901.32", now, 1562529662678901320)
|
||||
f("1562529662678901.321", now, 1562529662678901321)
|
||||
f("1562529662678901", now, 1562529662_678_901_000)
|
||||
f("1562529662678901.3", now, 1562529662_678_901_300)
|
||||
f("1562529662678901.32", now, 1562529662_678_901_320)
|
||||
f("1562529662678901.321", now, 1562529662_678_901_321)
|
||||
|
||||
// unix timestamp in nanoseconds
|
||||
f("1562529662678901234", now, 1562529662678901234)
|
||||
f("1562529662678901234", now, 1562529662_678_901_234)
|
||||
|
||||
// duration relative to the current time
|
||||
f("now", now, now)
|
||||
@@ -175,7 +191,7 @@ func TestParseTimeAtSuccess(t *testing.T) {
|
||||
// negative duration relative to the current time
|
||||
f("-5m", now, now-5*60*1e9)
|
||||
f("-123", now, now-123*1e9)
|
||||
f("-123.456", now, now-123456*1e6)
|
||||
f("-123.456", now, now-123_456*1e6)
|
||||
f("now-1h5m", now, now-(3600+5*60)*1e9)
|
||||
|
||||
// Year
|
||||
@@ -209,9 +225,9 @@ func TestParseTimeAtSuccess(t *testing.T) {
|
||||
f("2023-05-20T04:57:43-02:30", now, 1.684567663e+09*1e9)
|
||||
|
||||
// milliseconds
|
||||
f("2023-05-20T04:57:43.123Z", now, 1684558663123000000)
|
||||
f("2023-05-20T04:57:43.123456789+02:30", now, 1684549663123456789)
|
||||
f("2023-05-20T04:57:43.123456789-02:30", now, 1684567663123456789)
|
||||
f("2023-05-20T04:57:43.123Z", now, 1684558663_123_000_000)
|
||||
f("2023-05-20T04:57:43.123456789+02:30", now, 1684549663_123_456_789)
|
||||
f("2023-05-20T04:57:43.123456789-02:30", now, 1684567663_123_456_789)
|
||||
}
|
||||
|
||||
func TestParseTimeAtLimits(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user