fix(stats): flush throttled daily mileage projections

This commit is contained in:
lingniu
2026-07-19 16:00:13 +08:00
parent 81532d91b7
commit fa60bbb2c8
6 changed files with 381 additions and 44 deletions

View File

@@ -105,20 +105,21 @@ func TestProcessStatMessageRecordsMetrics(t *testing.T) {
ProjectionsWritten: 1,
ProjectionsSkippedThrottled: 2,
}, cacheStats: stats.CacheStats{
LastTotalMileageEntries: 300,
LastSourceSeenEntries: 3,
LastProjectionEntries: 280,
BaselineEntries: 295,
MaxEntries: 1000000,
LastCleanupAt: time.Unix(1782918600, 0),
LastCleanupTotalMileage: 11,
LastCleanupSourceSeen: 1,
LastCleanupProjection: 9,
LastCleanupBaseline: 10,
TotalMileageEvictions: 4,
SourceSeenEvictions: 1,
ProjectionEvictions: 2,
BaselineEvictions: 3,
LastTotalMileageEntries: 300,
LastSourceSeenEntries: 3,
LastProjectionEntries: 280,
PendingProjectionEntries: 12,
BaselineEntries: 295,
MaxEntries: 1000000,
LastCleanupAt: time.Unix(1782918600, 0),
LastCleanupTotalMileage: 11,
LastCleanupSourceSeen: 1,
LastCleanupProjection: 9,
LastCleanupBaseline: 10,
TotalMileageEvictions: 4,
SourceSeenEvictions: 1,
ProjectionEvictions: 2,
BaselineEvictions: 3,
}},
&contextCheckingStatCommitter{},
kafka.Message{Topic: "vehicle.fields.go.jt808.v1", Partition: 4, Offset: 30, HighWaterMark: 41, Value: payload},
@@ -150,6 +151,7 @@ func TestProcessStatMessageRecordsMetrics(t *testing.T) {
`vehicle_stat_cache_entries{cache="last_total_mileage"} 300`,
`vehicle_stat_cache_entries{cache="source_seen"} 3`,
`vehicle_stat_cache_entries{cache="projection"} 280`,
`vehicle_stat_pending_projection_entries 12`,
`vehicle_stat_cache_entries{cache="baseline"} 295`,
`vehicle_stat_cache_max_entries{cache="last_total_mileage"} 1000000`,
`vehicle_stat_cache_max_entries{cache="source_seen"} 1000000`,
@@ -177,6 +179,52 @@ func TestProcessStatMessageRecordsMetrics(t *testing.T) {
}
}
func TestFlushPendingProjectionsRecordsEventualProjectionMetrics(t *testing.T) {
registry := metrics.NewRegistry()
flusher := &recordingPendingProjectionFlusher{
result: stats.ProjectionFlushResult{Attempted: 3, Written: 3},
}
readyBefore := time.Date(2026, 7, 19, 15, 4, 45, 0, time.Local)
flushPendingProjections(context.Background(), discardStatLogger{}, registry, flusher, readyBefore)
if flusher.calls != 1 || !flusher.readyBefore.Equal(readyBefore) {
t.Fatalf("flusher calls=%d readyBefore=%s, want one call at %s", flusher.calls, flusher.readyBefore, readyBefore)
}
text := registry.Render()
for _, want := range []string{
`vehicle_stat_pending_projections_total{status="attempted"} 3`,
`vehicle_stat_pending_projections_total{status="written"} 3`,
`vehicle_stat_last_pending_projection_unix_seconds{status="written"} `,
} {
if !strings.Contains(text, want) {
t.Fatalf("pending projection metrics missing %s:\n%s", want, text)
}
}
}
func TestFlushPendingProjectionsRetainsErrorMetricForRetry(t *testing.T) {
registry := metrics.NewRegistry()
flusher := &recordingPendingProjectionFlusher{
result: stats.ProjectionFlushResult{Attempted: 3, Written: 1, Failed: 2},
err: errors.New("temporary projection failure"),
}
flushPendingProjections(context.Background(), discardStatLogger{}, registry, flusher, time.Time{})
text := registry.Render()
for _, want := range []string{
`vehicle_stat_pending_projections_total{status="attempted"} 3`,
`vehicle_stat_pending_projections_total{status="written"} 1`,
`vehicle_stat_pending_projections_total{status="error"} 2`,
`vehicle_stat_last_pending_projection_unix_seconds{status="error"} `,
} {
if !strings.Contains(text, want) {
t.Fatalf("pending projection error metrics missing %s:\n%s", want, text)
}
}
}
func TestRecordStatWriteE2EDurationSkipsMissingReceivedAt(t *testing.T) {
registry := metrics.NewRegistry()
@@ -1232,6 +1280,19 @@ type discardStatLogger struct{}
func (discardStatLogger) Error(string, ...any) {}
func (discardStatLogger) Warn(string, ...any) {}
type recordingPendingProjectionFlusher struct {
result stats.ProjectionFlushResult
err error
calls int
readyBefore time.Time
}
func (f *recordingPendingProjectionFlusher) FlushPendingProjections(_ context.Context, readyBefore time.Time) (stats.ProjectionFlushResult, error) {
f.calls++
f.readyBefore = readyBefore
return f.result, f.err
}
var errTestStatAppend = errors.New("test stat append failed")
func committedOffsets(messages []kafka.Message) []int64 {