grafana/k6 · error

aggregation period is not allowed to have sub-second precisi

Error message

aggregation period is not allowed to have sub-second precision

What it means

Returned by newCollector (output/cloud/expv2/collect.go:69-71) when the aggregation period is not a whole number of seconds (aggrPeriod != aggrPeriod.Truncate(time.Second)). Bucket boundaries are computed in whole seconds — bucketID is UnixNano/period and the flushed MetricSet reports aggregation_period in seconds as a uint32 (flush.go:183) — so sub-second precision (e.g. 500ms) cannot be represented faithfully and is rejected.

Source

Thrown at output/cloud/expv2/collect.go:70

	nowFunc func() time.Time

	aggregationPeriod time.Duration
	waitPeriod        time.Duration

	// we should no longer have to handle metrics that have times long in the past. So instead of a
	// map, we can probably use a simple slice (or even an array!) as a ring buffer to store the
	// aggregation buckets. This should save us a some time, since it would make the lookups and WaitPeriod
	// checks basically O(1). And even if for some reason there are occasional metrics with past times that
	// don't fit in the chosen ring buffer size, we could just send them along to the buffer unaggregated
	timeBuckets map[int64]map[metrics.TimeSeries]metricValue
}

func newCollector(aggrPeriod, waitPeriod time.Duration) (*collector, error) {
	if aggrPeriod == 0 {
		return nil, errors.New("aggregation period is not allowed to be zero")
	}
	if aggrPeriod != aggrPeriod.Truncate(time.Second) {
		return nil, errors.New("aggregation period is not allowed to have sub-second precision")
	}
	if waitPeriod == 0 {
		// TODO: we could simplify the expiring logic
		// just having an internal static logic.
		// Like skip only not closed buckets bucketEnd > now.
		return nil, errors.New("aggregation wait period is not allowed to be zero")
	}
	if waitPeriod != waitPeriod.Truncate(time.Second) {
		return nil, errors.New("aggregation wait period is not allowed to have sub-second precision")
	}
	return &collector{
		bq:                bucketQ{},
		nowFunc:           time.Now,
		timeBuckets:       make(map[int64]map[metrics.TimeSeries]metricValue),
		aggregationPeriod: aggrPeriod,
		waitPeriod:        waitPeriod,
	}, nil
}

View on GitHub (pinned to 93accf6570)

Solutions

  1. Round the period to whole seconds; the minimum valid value is 1s
  2. For near-real-time dashboards use 1s and accept the Cloud ingestion cadence, rather than fractional seconds

Example fix

# before
K6_CLOUD_AGGREGATION_PERIOD=500ms k6 run -o cloud script.js

# after
K6_CLOUD_AGGREGATION_PERIOD=1s k6 run -o cloud script.js
Defensive patterns

Strategy: validation

Validate before calling

# shell: require whole seconds
P="${K6_CLOUD_AGGREGATION_PERIOD:-10s}"
case "$P" in *ms | *us | *ns | *.*) echo "aggregation period must be whole seconds"; exit 1;; esac
export K6_CLOUD_AGGREGATION_PERIOD="$P"

Prevention

When it happens

Trigger: K6_CLOUD_AGGREGATION_PERIOD=500ms (or 1500ms, 1.5s), or constructing the expv2 output config with AggregationPeriod: types.NullDurationFrom(500*time.Millisecond). Output.Start fails with 'failed to initialize the samples collector: aggregation period is not allowed to have sub-second precision'.

Common situations: Tuning aggregation latency down below 1s to make the Cloud dashboard update faster; values written as milliseconds by config tooling.

Related errors


AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15). Data as JSON: /api/errors/35c21e32936ba922. Report an issue: GitHub.