Count hits, misses, fetches and transforms as Prometheus counters (closes #311)
check / check (push) Canceled after 0s

The five counts the cache_stats table held are now Prometheus counters in
memory, in internal/imgcache/counters.go, on the default registry that
/metrics serves. The Increment methods add to them, so counting makes no
database call.

cache_stats, Cache.Stats, Service.Stats, CacheStats and the pending counts
are gone, with their tests. The background goroutine now writes only access
times, and its names say so: StartPendingAccessTimeWrites,
StopPendingAccessTimeWrites and pendingaccesstimes.go.

The counters count for the whole process, so the tests that check how much
they rose do not call t.Parallel. Two stop tests whose write under way was a
hit count now use an access time.

Model: opus-5-5
This commit is contained in:
2026-10-26 08:50:41 +00:00
parent 6b19e4c96c
commit c3a6799763
22 changed files with 672 additions and 1194 deletions
@@ -112,11 +112,13 @@ func assertWarningNames(t *testing.T, logs, path string) {
// pass, which must remove it with a warning naming it. It then asks for two
// new sizes of the source: the first must fetch it again and cache it whole,
// so that the second is made from the cache without a third fetch.
//
//nolint:paralleltest // it reads counters that other tests add to
func TestService_Get_FetchesAgainForEmptyCachedSource(t *testing.T) {
t.Parallel()
svc, fixtures := SetupTestService(t)
before := readCounters()
getImage(t, svc, photoVariant(fixtures, 85, imagerequest.FitCover))
path := cachedSourcePath(t, svc, photoVariant(fixtures, 85, imagerequest.FitCover))
@@ -128,9 +130,7 @@ func TestService_Get_FetchesAgainForEmptyCachedSource(t *testing.T) {
getImage(t, svc, photoVariant(fixtures, 40, imagerequest.FitCover))
getImage(t, svc, photoVariant(fixtures, 30, imagerequest.FitCover))
waitForPendingCountsWritten(t, svc.cache)
fetches := readCacheStatsCounters(t, svc.cache).upstreamFetchCount
fetches := readCounters().minus(before).upstreamFetchCount
t.Logf("upstream fetches = %d", fetches)
if fetches != 2 {
@@ -150,11 +150,13 @@ func TestService_Get_FetchesAgainForEmptyCachedSource(t *testing.T) {
// It then asks for two new sizes of the source: the first must fetch it
// again rather than fail to decode the partial file, and the second is
// made from the cache.
//
//nolint:paralleltest // it reads counters that other tests add to
func TestService_Get_FetchesAgainForPartialCachedSource(t *testing.T) {
t.Parallel()
svc, fixtures := SetupTestService(t)
before := readCounters()
getImage(t, svc, photoVariant(fixtures, 85, imagerequest.FitCover))
path := cachedSourcePath(t, svc, photoVariant(fixtures, 85, imagerequest.FitCover))
@@ -166,9 +168,7 @@ func TestService_Get_FetchesAgainForPartialCachedSource(t *testing.T) {
getImage(t, svc, photoVariant(fixtures, 40, imagerequest.FitCover))
getImage(t, svc, photoVariant(fixtures, 30, imagerequest.FitCover))
waitForPendingCountsWritten(t, svc.cache)
fetches := readCacheStatsCounters(t, svc.cache).upstreamFetchCount
fetches := readCounters().minus(before).upstreamFetchCount
t.Logf("upstream fetches = %d", fetches)
if fetches != 2 {