Compare commits

...

3 Commits

Author SHA1 Message Date
Hill Patel
e425aebbc2 lib/timeutil: accept scientific-notation timestamps with sub-second precision (#11278)
Fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11268

`/api/v1/query` and `/api/v1/query_range` reject `start`/`end` values written in scientific notation whose mantissa carries more fractional digits than the exponent shifts — e.g. `start=1.784144612388E9` returns
`HTTP 422`, while the mathematically equal `start=1784144612.388` returns `200`. Prometheus accepts both (it parses the timestamp with `strconv.ParseFloat`), so this is a Prometheus-compatibility gap.

**Root cause:** in `lib/timeutil/time.go`,
`tryParseScientificNumberForUnixTimestamp` had a guard `if decimalExp <
len(fracStr) { return 0, false }`. For `1.784144612388E9` the mantissa
has 12 fractional digits and the exponent is 9, so `9 < 12` rejected it
— even though the value (`1784144612.388`) is a valid sub-second
timestamp.

**Fix:** when the exponent leaves sub-second fractional digits (`0 <=
decimalExp < len(fracStr)`), keep the mantissa (already
decimal-point-removed) and pad it up to the nearest milli/micro/nano
boundary so `getUnixTimestampNanoseconds` classifies its unit correctly
— exactly how the equivalent plain fractional timestamp is already
parsed by `TryParseUnixTimestamp`.

Kept deliberately in scope, per the discussion on the issue:
- **Negative exponents** on a fractional mantissa (e.g.
`17841446121e-1`) remain unsupported, as @JayiceZ and @valyala decided.
- Sub-second scientific values too small to be a millisecond-scale
timestamp (e.g. `1.23e1`) remain rejected as before, so **no
previously-rejected input changes behaviour** — the change is purely
additive for the reported class of large sub-second timestamps.

## Test plan

- Added success cases to `TestTryParseUnixTimestamp_Success` covering
`1.784144612388E9`, its lowercase and negative forms, and
`1.5000000005e9`, asserting they equal the corresponding
plain-fractional result.
- Verified `TestTryParseUnixTimestamp_Failure` still passes unchanged
(`1.23e1`, `1.234e0`, `1E-1`, negative-exponent forms all still
rejected).
- `go test ./lib/timeutil/`, `go vet ./lib/timeutil/`, and `gofmt` all
clean.

## Disclosure

This change was prepared with AI assistance; I have reviewed the diff,
verified the root cause against the source, and am responsible for the
change and able to explain it.

---------

Co-authored-by: Max Kotliar <mkotlyar@victoriametrics.com>
2026-07-30 21:02:33 +03:00
beyond-infra
f52771ceaf lib/streamaggr: fix sum_samples_total non-monotonic output with enable_windows (#11262)
Fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11261

`sum_samples_total` produces non-monotonic and undercounted output when`enable_windows: true` is set.


When windows are enabled, blue and green window values are initialized via:
```go
nv.blue[idx]  = ac.getValue(nil)
nv.green[idx] = ac.getValue(nv.blue[idx].state())
```

`sum_samples_total`'s `state()` returned `nil` and `getValue()` ignored its argument, so each window maintained an **independent** cumulative sum. They alternated flushing under the same metric name, producing
repeated, undercounted, and non-monotonic values — e.g. `1, 1, 2, 2` instead of `1, 2, 3, 4`.

## Fix

Introduced `sumSamplesAggrValueShared`, mirroring the pattern already used in `total.go`:
- The shared struct holds the cross-window cumulative `total`
- Both windows receive a pointer to the **same** shared instance via
`state()` / `getValue(s)`
- Each flush adds the window-local `delta` into `shared.total` and outputs that value

`sum_samples` (`resetTotalOnFlush=true`) is unchanged — it has no
cumulative state and uses a plain per-window value as before.

## Test

Added a regression test in `streamaggr_synctest_test.go` that sends 4
batches of `delta=1` with `enable_windows: true` and asserts the output
is monotonically `1, 2, 3, 4`. Before the fix the test fails with `1, 1,
2, 2`.

PR https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11262

---------

Co-authored-by: Hui Wang <haley@victoriametrics.com>
Co-authored-by: Max Kotliar <mkotlyar@victoriametrics.com>
2026-07-30 20:41:12 +03:00
Max Kotliar
eeef07836e docs/changelog: chore changelog 2026-07-30 20:32:29 +03:00
5 changed files with 92 additions and 25 deletions

View File

@@ -36,10 +36,9 @@ See also [LTS releases](https://docs.victoriametrics.com/victoriametrics/lts-rel
* FEATURE: [vmagent](https://docs.victoriametrics.com/victoriametrics/vmagent/): add `name` label identifying the corresponding `-remoteWrite.url` target to the `vm_persistentqueue_*` metrics exposed by persistent queue. See [#7944](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7944). Thanks to @tIGO for contribution.
* FEATURE: [vmagent](https://docs.victoriametrics.com/victoriametrics/vmagent/): add `-remoteWrite.obfuscateLabels` flag for hashing values of the specified labels before sending metrics to the corresponding `-remoteWrite.url`. This allows sharing metrics with external systems while keeping sensitive label values hidden. See [#10599](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/10599).
* FEATURE: [vmalert](https://docs.victoriametrics.com/victoriametrics/vmalert/): add `-replay.continueWithExecutionErr` flag to allow continuing to replay other rules when a rule execution fails with a 422 response code, which can happen due to an expression syntax error or a resource limit being hit. See [11313](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11313).
* FEATURE: [vmalert](https://docs.victoriametrics.com/victoriametrics/vmalert/): add template variable `$interval` to expose the alerting rule group's evaluation interval. This allows generating dashboard links with a lookback window relative to the rule's interval, for example `&from={{ ($activeAt.Add (parseDurationTime (printf "-%s" .Interval))).UnixMilli }}&to={{ $activeAt.UnixMilli }}`. See this issue [#11232](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11232) for more details. Thanks to @1solomonwakhungu for contribution.
* FEATURE: [vmbackupmanager](https://docs.victoriametrics.com/victoriametrics/vmbackupmanager/) and [alerts](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/deployment/docker/rules): introduce `vm_backup_last_success_at` metric to track the last successful backup by type. Add [alerting rules](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/deployment/docker/rules/alerts-vmbackupmanager.yml) `NoLatestBackupWithinLastDay`, `NoHourlyBackupWithinLastDay`,
`NoDailyBackupWithinLast3Days`, `NoWeeklyBackupWithinLast14Days` and `NoMonthlyBackupWithinLast62Days` to remind users about the missing backups. See [#11217](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11217).
* FEATURE: [vmctl](https://docs.victoriametrics.com/victoriametrics/vmctl/): support [Prometheus native histograms](https://prometheus.io/docs/specs/native_histograms/) migration in [remote read mode](https://docs.victoriametrics.com/victoriametrics/vmctl/remoteread/). Native histograms are converted into `_count`, `_sum` and `_bucket` series with `vmrange` labels in the same way as VictoriaMetrics converts native histograms received via Prometheus remote write protocol, except that for native histograms with custom buckets the original bucket bounds are preserved instead of being estimated with the exponential formula. Previously native histograms were silently ignored in `SAMPLES` mode, while in stream mode the migration failed with `EOF` error. See [#11292](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11292). Thanks to @liuxu623 for contribution.
* FEATURE: [vmalert](https://docs.victoriametrics.com/victoriametrics/vmalert/): add template variable `$interval` to expose the alerting rule group's evaluation interval. This allows generating dashboard links with a lookback window relative to the rule's interval, for example `&from={{ ($activeAt.Add (parseDurationTime (printf "-%s" .Interval))).UnixMilli }}&to={{ $activeAt.UnixMilli }}`. See [#11232](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11232). Thanks to @1solomonwakhungu for contribution.
* FEATURE: [vmbackupmanager](https://docs.victoriametrics.com/victoriametrics/vmbackupmanager/) and [alerts](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/deployment/docker/rules): introduce `vm_backup_last_success_at` metric to track the last successful backup by type. Add [alerting rules](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/deployment/docker/rules/alerts-vmbackupmanager.yml) `NoLatestBackupWithinLastDay`, `NoHourlyBackupWithinLastDay`, `NoDailyBackupWithinLast3Days`, `NoWeeklyBackupWithinLast14Days` and `NoMonthlyBackupWithinLast62Days` to remind users about the missing backups. See [#11217](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11217).
* FEATURE: [vmctl](https://docs.victoriametrics.com/victoriametrics/vmctl/): support [Prometheus native histograms](https://prometheus.io/docs/specs/native_histograms/) migration in [remote read mode](https://docs.victoriametrics.com/victoriametrics/vmctl/remoteread/). Native histograms are converted into `_count`, `_sum` and `_bucket` series with `vmrange` labels in the same way as VictoriaMetrics [converts native histograms received via Prometheus remote write protocol](https://docs.victoriametrics.com/victoriametrics/integrations/prometheus/#native-histograms), except that for native histograms with custom buckets the original bucket bounds are preserved instead of being estimated with the exponential formula. Previously native histograms were silently ignored in `SAMPLES` mode, while in stream mode the migration failed with `EOF` error. See [#11292](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11292). Thanks to @liuxu623 for contribution.
* FEATURE: `vminsert` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/): enable [slowness-based re-routing](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/#slowness-based-re-routing) by default. Previously, `-disableRerouting` defaulted to `true`, which limited ingestion throughput to the slowest `vmstorage` node. Now `-disableRerouting` defaults to `false`, so `vminsert` automatically routes data away from the slowest `vmstorage` node, improving overall ingestion performance. Slowness re-routing is automatically disabled when `-replicationFactor` is greater than 1. See [#11287](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11287).
* FEATURE: [vmui](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#vmui): persist the selected auto-refresh interval in the URL. See [VictoriaLogs#1310](https://github.com/VictoriaMetrics/VictoriaLogs/issues/1310).
@@ -47,6 +46,8 @@ See also [LTS releases](https://docs.victoriametrics.com/victoriametrics/lts-rel
* BUGFIX: [vmagent](https://docs.victoriametrics.com/victoriametrics/vmagent/) and [vmsingle](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/): ignore HTTP proxy environment variables when scraping targets over Unix domain sockets. See [#11318](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11318). Thanks to @lwmacct for contribution.
* BUGFIX: [vmalert](https://docs.victoriametrics.com/victoriametrics/vmalert/): fixed the display of rule state badges on the `Groups` page in the web UI. See [#11160](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11160).
* 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.
## [v1.148.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.148.0)

View File

@@ -935,4 +935,25 @@ foo:1m_sum_samples{baz="qwe"} 10
dedup_interval: 30s
outputs: [sum_samples]
`, "11111111")
// Reproduce issue #11261: sum_samples_total must be monotonic with enable_windows: true
// See https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11262
f([]string{`
test_delta 1
`, `
test_delta 1
`, `
test_delta 1
`, `
test_delta 1
`}, time.Minute, `test_delta 1
test_delta 2
test_delta 3
test_delta 4
`, `
- interval: 1m
keep_metric_names: true
outputs: [sum_samples_total]
enable_windows: true
`, "1111")
}

View File

@@ -4,30 +4,39 @@ import (
"math"
)
type sumSamplesAggrValueShared struct {
total float64
}
type sumSamplesAggrValue struct {
sum float64
delta float64
shared *sumSamplesAggrValueShared
}
func (av *sumSamplesAggrValue) pushSample(_ aggrConfig, sample *pushSample, _ string, _ int64) {
if math.Abs(av.sum) >= (1 << 53) {
// It is time to reset the entry, since it starts losing float64 precision
av.sum = 0
}
av.sum += sample.value
av.delta += sample.value
}
func (av *sumSamplesAggrValue) flush(c aggrConfig, ctx *flushCtx, key string, _ bool) {
ac := c.(*sumSamplesAggrConfig)
if ac.resetTotalOnFlush {
ctx.appendSeries(key, "sum_samples", av.sum)
av.sum = 0
ctx.appendSeries(key, "sum_samples", av.delta)
av.delta = 0
return
}
ctx.appendSeries(key, "sum_samples_total", av.sum)
total := av.shared.total + av.delta
av.delta = 0
if math.Abs(total) >= (1 << 53) {
// It is time to reset the entry, since it starts losing float64 precision
av.shared.total = 0
} else {
av.shared.total = total
}
ctx.appendSeries(key, "sum_samples_total", total)
}
func (*sumSamplesAggrValue) state() any {
return nil
func (av *sumSamplesAggrValue) state() any {
return av.shared
}
func newSumSamplesAggrConfig(resetTotalOnFlush bool) aggrConfig {
@@ -40,6 +49,14 @@ type sumSamplesAggrConfig struct {
resetTotalOnFlush bool
}
func (*sumSamplesAggrConfig) getValue(_ any) aggrValue {
return &sumSamplesAggrValue{}
func (*sumSamplesAggrConfig) getValue(s any) aggrValue {
var shared *sumSamplesAggrValueShared
if s == nil {
shared = &sumSamplesAggrValueShared{}
} else {
shared = s.(*sumSamplesAggrValueShared)
}
return &sumSamplesAggrValue{
shared: shared,
}
}

View File

@@ -206,17 +206,36 @@ func tryParseScientificNumberForUnixTimestamp(s string, decimalExp int64) (int64
return multiplyByDecimalExp(n, decimalExp)
}
intStr := s[:dotIdx]
fracStr := s[dotIdx+1:]
if decimalExp < int64(len(fracStr)) {
if decimalExp < 0 {
// Negative exponents on a fractional mantissa are intentionally not
// supported. See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11268
return 0, false
}
intStr := s[:dotIdx]
fracStr := s[dotIdx+1:]
n, ok := tryParseFractionalNumberForUnixTimestamp(intStr, fracStr)
if !ok {
return 0, false
}
decimalExp -= int64(len(fracStr))
return multiplyByDecimalExp(n, decimalExp)
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).
decimalExp -= int64(len(fracStr))
return multiplyByDecimalExp(n, decimalExp)
}
// 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++
}
return n, true
}
func tryParseFractionalNumberForUnixTimestamp(intStr, fracStr string) (int64, bool) {

View File

@@ -69,6 +69,17 @@ func TestTryParseUnixTimestamp_Success(t *testing.T) {
f("1.23e2", 123000000000)
f("1.2e1", 12000000000)
f("1123.456789123456789E15", 1123456789123456789)
// 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
}
func TestTryParseUnixTimestamp_Failure(t *testing.T) {
@@ -115,9 +126,7 @@ func TestTryParseUnixTimestamp_Failure(t *testing.T) {
f("1e19")
f("1.3e123456789090123")
// too small decimal exponent
f("1.23e1")
f("1.234e0")
// negative decimal exponent
f("1E-1")
f("1.3e-123456789090123")
}