Hold a chart's last bin back until all of its data arrived
The newest metric bucket is upserted every flush while its minute runs, and both rollup endpoints summed it in — so every curve on Home ended on a fall that was only the clock. The timeseries now stops at the last closed bin, and the flow rollups' window ends on the last closed minute, so all sixty slices are whole ones. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -124,6 +124,17 @@ def _since(hours: int) -> datetime:
|
||||
return datetime.now(UTC) - timedelta(hours=hours)
|
||||
|
||||
|
||||
def _closed_before(stride: int) -> datetime:
|
||||
"""The start of the bin `now` falls in — the newest one still filling.
|
||||
|
||||
A bucket is upserted every flush while its minute runs, so the newest bin
|
||||
always holds part of a minute. Drawn, it reads as a fall that never
|
||||
happened; excluded, the curve ends on the last bin that is all there.
|
||||
"""
|
||||
now = int(datetime.now(UTC).timestamp())
|
||||
return datetime.fromtimestamp(now // stride * stride, UTC)
|
||||
|
||||
|
||||
def _window_hours(hours: int) -> int:
|
||||
"""A window the rollups can answer for: an hour at least, retention at most.
|
||||
|
||||
@@ -235,7 +246,10 @@ def read_timeseries(
|
||||
func.max(col(MetricBucket.duration_max_ms)).label("max_ms"),
|
||||
func.sum(col(MetricBucket.lag_sum_ms)).label("lag_sum_ms"),
|
||||
func.sum(col(MetricBucket.items)).label("items"),
|
||||
).where(col(MetricBucket.bucket) >= _since(hours))
|
||||
).where(
|
||||
col(MetricBucket.bucket) >= _since(hours),
|
||||
col(MetricBucket.bucket) < _closed_before(stride),
|
||||
)
|
||||
if flow:
|
||||
statement = statement.where(col(MetricBucket.flow) == flow)
|
||||
if node:
|
||||
@@ -259,9 +273,14 @@ def read_timeseries(
|
||||
def read_flow_rollups(session: SessionDep, hours: int = 24) -> Any:
|
||||
"""One row per flow, with a coarse trend of how much it ran."""
|
||||
hours = _window_hours(hours)
|
||||
since = _since(hours)
|
||||
window = hours * 3600
|
||||
start = since.timestamp()
|
||||
# The window ends on the last closed minute rather than on `now`, so every
|
||||
# slice it hands back is a whole one. Ending at `now` cut the last slice
|
||||
# wherever the request happened to land, and the trend fell off a cliff
|
||||
# that was only the clock.
|
||||
end = int(datetime.now(UTC).timestamp()) // 60 * 60
|
||||
start = float(end - window)
|
||||
since = datetime.fromtimestamp(start, UTC)
|
||||
|
||||
# Binned to the sparkline slice rather than the minute, so a flow costs at
|
||||
# most SPARK_SLICES rows however long the window is. The slice is the
|
||||
@@ -282,7 +301,10 @@ def read_flow_rollups(session: SessionDep, hours: int = 24) -> Any:
|
||||
func.sum(col(MetricBucket.lag_sum_ms)).label("lag_sum_ms"),
|
||||
func.sum(col(MetricBucket.items)).label("items"),
|
||||
)
|
||||
.where(col(MetricBucket.bucket) >= since)
|
||||
.where(
|
||||
col(MetricBucket.bucket) >= since,
|
||||
col(MetricBucket.bucket) < datetime.fromtimestamp(float(end), UTC),
|
||||
)
|
||||
.group_by(col(MetricBucket.flow), slot)
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user