Metrics: do not persist a partial first slot (#29977)

This commit is contained in:
andig 2026-05-17 15:37:05 +02:00 • committed by GitHub
parent 177cb19d5d
commit 1200fbc2f9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 55 additions and 5 deletions

View file

@ -56,19 +56,32 @@ func (c *Collector) process(fun func()) error {
fun()
if slotStart := now.Truncate(tariff.SlotDuration); slotStart.After(c.started) {
// skip incomplete first slot
if !c.started.IsZero() {
slotStart := now.Truncate(tariff.SlotDuration)
switch {
case c.started.IsZero():
// keep started un-truncated so a mid-slot start stays distinguishable
c.started = now
case slotStart.After(c.started):
// persist the completed slot only if started is the immediately
// preceding slot boundary - false for the mid-slot first slot and
// for a slot reached after a data gap
if c.started.Equal(slotStart.Add(-tariff.SlotDuration)) {
if err := c.persist(); err != nil {
return err
}
}
c.started = slotStart
c.accu.Energy = 0
c.accu.ReturnEnergy = 0
default:
return nil
}
c.accu.Energy = 0
c.accu.ReturnEnergy = 0
return nil
}

View file

@ -154,3 +154,40 @@ func TestCollectorSetImportAndExportMeterTotal(t *testing.T) {
require.InDelta(t, 0.3, col.accu.Imported(), 1e-10)
require.InDelta(t, 0.7, col.accu.Exported(), 1e-10)
}
// TestCollectorSkipsPartialFirstSlot verifies that the first slot, joined
// mid-way after (re)start, is not persisted as a full 15min slot.
func TestCollectorSkipsPartialFirstSlot(t *testing.T) {
clk := clock.NewMock() // 1970-01-01 00:00:00 UTC
require.NoError(t, db.NewInstance("sqlite", ":memory:"))
require.NoError(t, SetupSchema())
col, err := NewCollector("partial", "partial", WithClock(clk))
require.NoError(t, err)
// first update mid-slot (00:05) - slot 00:00 is only partially covered
clk.Add(5 * time.Minute)
require.NoError(t, col.AddEnergy(nil, nil, 1e3))
clk.Add(5 * time.Minute) // 00:10
require.NoError(t, col.AddEnergy(nil, nil, 1e3))
// cross into slot 00:15 - the partial slot 00:00 must not be persisted
clk.Add(5 * time.Minute) // 00:15
require.NoError(t, col.AddEnergy(nil, nil, 1e3))
var count int64
require.NoError(t, db.Instance.Model(new(meter)).Count(&count).Error)
require.Zero(t, count, "partial first slot must not be persisted")
// cross into slot 00:30 - the fully covered slot 00:15 must be persisted
clk.Add(15 * time.Minute) // 00:30
require.NoError(t, col.AddEnergy(nil, nil, 1e3))
require.NoError(t, db.Instance.Model(new(meter)).Count(&count).Error)
require.EqualValues(t, 1, count, "first full slot must be persisted")
var m meter
require.NoError(t, db.Instance.First(&m).Error)
require.Equal(t, int64(15*60), m.Timestamp, "persisted slot should start at 00:15")
}