Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
078bae74a5 |
No files matched your search
@@ -49,9 +49,6 @@ P2: security: per-origin rate limiting
|
|||||||
- `Variant` and `Source` each open the file and hand its access time to the
|
- `Variant` and `Source` each open the file and hand its access time to the
|
||||||
background writer. A cache hit still makes no database call.
|
background writer. A cache hit still makes no database call.
|
||||||
- `imgcache.go` and `module.go` are gone.
|
- `imgcache.go` and `module.go` are gone.
|
||||||
- `NewProxy` makes the counters of upstream fetches and transforms, and
|
|
||||||
registers them on the registry it is given. The cache keeps only the hit
|
|
||||||
and miss counters.
|
|
||||||
- A cached source whose row cannot be read is logged as "failed to load
|
- A cached source whose row cannot be read is logged as "failed to load
|
||||||
cached source, fetching". It was logged as "source lookup failed".
|
cached source, fetching". It was logged as "source lookup failed".
|
||||||
- The proxy's tests moved to `internal/imageproxy`. They reach the cache
|
- The proxy's tests moved to `internal/imageproxy`. They reach the cache
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ func newCountingDatabaseServer(
|
|||||||
}),
|
}),
|
||||||
MaxResponseSize: 50 << 20,
|
MaxResponseSize: 50 << 20,
|
||||||
Logger: slog.New(slog.DiscardHandler),
|
Logger: slog.New(slog.DiscardHandler),
|
||||||
}, prometheus.NewRegistry())
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tb.Fatalf("imageproxy.NewProxy() error = %v", err)
|
tb.Fatalf("imageproxy.NewProxy() error = %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ func newRoutesFetchingThrough(
|
|||||||
}),
|
}),
|
||||||
MaxResponseSize: 50 << 20,
|
MaxResponseSize: 50 << 20,
|
||||||
Logger: log,
|
Logger: log,
|
||||||
}, prometheus.NewRegistry())
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("imageproxy.NewProxy() error = %v", err)
|
t.Fatalf("imageproxy.NewProxy() error = %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ func setupTestHandler(t *testing.T) *testFixtures {
|
|||||||
MaxConcurrentProcessing: 2,
|
MaxConcurrentProcessing: 2,
|
||||||
}),
|
}),
|
||||||
MaxResponseSize: 50 << 20,
|
MaxResponseSize: 50 << 20,
|
||||||
}, prometheus.NewRegistry())
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to create proxy: %v", err)
|
t.Fatalf("failed to create proxy: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ func newSignedHostServer(
|
|||||||
}),
|
}),
|
||||||
MaxResponseSize: 50 << 20,
|
MaxResponseSize: 50 << 20,
|
||||||
Logger: log,
|
Logger: log,
|
||||||
}, prometheus.NewRegistry())
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("imageproxy.NewProxy() error = %v", err)
|
t.Fatalf("imageproxy.NewProxy() error = %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ func newImageRoute(t *testing.T, fetcher httpfetcher.Fetcher) http.Handler {
|
|||||||
MaxConcurrentProcessing: 2,
|
MaxConcurrentProcessing: 2,
|
||||||
}),
|
}),
|
||||||
MaxResponseSize: 50 << 20,
|
MaxResponseSize: 50 << 20,
|
||||||
}, prometheus.NewRegistry())
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to create proxy: %v", err)
|
t.Fatalf("failed to create proxy: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ func newRefererRoutes(t *testing.T) (http.Handler, *Handlers, *countingFetcher)
|
|||||||
MaxConcurrentProcessing: 2,
|
MaxConcurrentProcessing: 2,
|
||||||
}),
|
}),
|
||||||
MaxResponseSize: 50 << 20,
|
MaxResponseSize: 50 << 20,
|
||||||
}, prometheus.NewRegistry())
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("imageproxy.NewProxy() error = %v", err)
|
t.Fatalf("imageproxy.NewProxy() error = %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,78 +0,0 @@
|
|||||||
package imageproxy
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
|
||||||
"github.com/prometheus/client_golang/prometheus/testutil"
|
|
||||||
"sneak.berlin/go/pixa/internal/imgcache"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestNewProxy_RegistersItsCountersOnItsRegistry checks that NewProxy
|
|
||||||
// registers the proxy's counters on the registry it is given, under the
|
|
||||||
// names README.md gives them. The cache registers its own counters on
|
|
||||||
// another registry.
|
|
||||||
func TestNewProxy_RegistersItsCountersOnItsRegistry(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
cache, err := imgcache.NewCache(setupTestDB(t),
|
|
||||||
imgcache.Config{StateDir: t.TempDir(), MaxBytes: 1 << 30},
|
|
||||||
prometheus.NewRegistry())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("imgcache.NewCache() error = %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
registry := prometheus.NewRegistry()
|
|
||||||
|
|
||||||
_, err = NewProxy(&Config{Cache: cache}, registry)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("NewProxy() error = %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
names := []string{
|
|
||||||
"pixa_upstream_fetches_total",
|
|
||||||
"pixa_upstream_fetch_bytes_total",
|
|
||||||
"pixa_transforms_total",
|
|
||||||
}
|
|
||||||
|
|
||||||
count, err := testutil.GatherAndCount(registry, names...)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("testutil.GatherAndCount() error = %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if count != len(names) {
|
|
||||||
t.Errorf("%d of the counters %v are on the registry, want %d",
|
|
||||||
count, names, len(names))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestNewProxy_FailsWhenItsCountersCannotBeRegistered makes a second proxy
|
|
||||||
// on the registry of a first. Its counters have the same names, so the
|
|
||||||
// registry refuses them, and NewProxy must return that error.
|
|
||||||
func TestNewProxy_FailsWhenItsCountersCannotBeRegistered(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
cache, err := imgcache.NewCache(setupTestDB(t),
|
|
||||||
imgcache.Config{StateDir: t.TempDir(), MaxBytes: 1 << 30},
|
|
||||||
prometheus.NewRegistry())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("imgcache.NewCache() error = %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
registry := prometheus.NewRegistry()
|
|
||||||
|
|
||||||
_, err = NewProxy(&Config{Cache: cache}, registry)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("NewProxy() of the first proxy error = %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = NewProxy(&Config{Cache: cache}, registry)
|
|
||||||
t.Logf("NewProxy() of the second proxy error = %v", err)
|
|
||||||
|
|
||||||
var alreadyRegistered prometheus.AlreadyRegisteredError
|
|
||||||
if !errors.As(err, &alreadyRegistered) {
|
|
||||||
t.Errorf("NewProxy() of the second proxy error = %v, want %T",
|
|
||||||
err, alreadyRegistered)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -16,7 +16,6 @@ import (
|
|||||||
"github.com/dustin/go-humanize"
|
"github.com/dustin/go-humanize"
|
||||||
"github.com/getsentry/sentry-go"
|
"github.com/getsentry/sentry-go"
|
||||||
"github.com/go-chi/chi/v5/middleware"
|
"github.com/go-chi/chi/v5/middleware"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
|
||||||
"golang.org/x/sync/singleflight"
|
"golang.org/x/sync/singleflight"
|
||||||
"sneak.berlin/go/pixa/internal/httpfetcher"
|
"sneak.berlin/go/pixa/internal/httpfetcher"
|
||||||
"sneak.berlin/go/pixa/internal/imageprocessor"
|
"sneak.berlin/go/pixa/internal/imageprocessor"
|
||||||
@@ -35,12 +34,6 @@ type Proxy struct {
|
|||||||
// variantsInProgress lets the requests that miss the same variant at the
|
// variantsInProgress lets the requests that miss the same variant at the
|
||||||
// same time share one fetch and one transcode.
|
// same time share one fetch and one transcode.
|
||||||
variantsInProgress singleflight.Group
|
variantsInProgress singleflight.Group
|
||||||
|
|
||||||
// The counts of upstream fetches and transforms, made and registered by
|
|
||||||
// NewProxy. They are kept in memory, so adding to one waits for nothing.
|
|
||||||
upstreamFetches prometheus.Counter
|
|
||||||
upstreamFetchBytes prometheus.Counter
|
|
||||||
transforms prometheus.Counter
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config holds the proxy's configuration.
|
// Config holds the proxy's configuration.
|
||||||
@@ -61,13 +54,8 @@ type Config struct {
|
|||||||
// errCacheRequired is returned when the proxy is built without a cache.
|
// errCacheRequired is returned when the proxy is built without a cache.
|
||||||
var errCacheRequired = errors.New("cache is required")
|
var errCacheRequired = errors.New("cache is required")
|
||||||
|
|
||||||
// NewProxy creates a new image proxy and registers its counters on
|
// NewProxy creates a new image proxy.
|
||||||
// metricsRegistry. pixad passes Prometheus' default registry, the one
|
func NewProxy(cfg *Config) (*Proxy, error) {
|
||||||
// /metrics serves. It fails when metricsRegistry already holds counters of
|
|
||||||
// the same names.
|
|
||||||
func NewProxy(
|
|
||||||
cfg *Config, metricsRegistry prometheus.Registerer,
|
|
||||||
) (*Proxy, error) {
|
|
||||||
if cfg.Cache == nil {
|
if cfg.Cache == nil {
|
||||||
return nil, errCacheRequired
|
return nil, errCacheRequired
|
||||||
}
|
}
|
||||||
@@ -84,36 +72,13 @@ func NewProxy(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
p := &Proxy{
|
return &Proxy{
|
||||||
cache: cfg.Cache,
|
cache: cfg.Cache,
|
||||||
fetcher: cfg.Fetcher,
|
fetcher: cfg.Fetcher,
|
||||||
processor: cfg.Processor,
|
processor: cfg.Processor,
|
||||||
log: log,
|
log: log,
|
||||||
maxResponseSize: cfg.MaxResponseSize,
|
maxResponseSize: cfg.MaxResponseSize,
|
||||||
upstreamFetches: prometheus.NewCounter(prometheus.CounterOpts{
|
}, nil
|
||||||
Name: "pixa_upstream_fetches_total",
|
|
||||||
Help: "Fetches of a source image from upstream that read any bytes.",
|
|
||||||
}),
|
|
||||||
upstreamFetchBytes: prometheus.NewCounter(prometheus.CounterOpts{
|
|
||||||
Name: "pixa_upstream_fetch_bytes_total",
|
|
||||||
Help: "Bytes read from upstream by fetches of source images.",
|
|
||||||
}),
|
|
||||||
transforms: prometheus.NewCounter(prometheus.CounterOpts{
|
|
||||||
Name: "pixa_transforms_total",
|
|
||||||
Help: "Images the image processor transformed.",
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, counter := range []prometheus.Counter{
|
|
||||||
p.upstreamFetches, p.upstreamFetchBytes, p.transforms,
|
|
||||||
} {
|
|
||||||
err = metricsRegistry.Register(counter)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to register the proxy's counters: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return p, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrNegativeCached is returned when a URL is in the negative cache (recently failed).
|
// ErrNegativeCached is returned when a URL is in the negative cache (recently failed).
|
||||||
@@ -167,7 +132,7 @@ func (p *Proxy) Get(
|
|||||||
|
|
||||||
reader, size, contentType, err := p.cache.Variant(cacheKey)
|
reader, size, contentType, err := p.cache.Variant(cacheKey)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
p.cache.IncrementStats(true)
|
p.cache.IncrementStats(true, 0)
|
||||||
|
|
||||||
return &ImageResponse{
|
return &ImageResponse{
|
||||||
Content: reader,
|
Content: reader,
|
||||||
@@ -188,7 +153,7 @@ func (p *Proxy) Get(
|
|||||||
// failed or the request context has ended meanwhile
|
// failed or the request context has ended meanwhile
|
||||||
response, err := p.processOrWait(ctx, req)
|
response, err := p.processOrWait(ctx, req)
|
||||||
|
|
||||||
p.cache.IncrementStats(false)
|
p.cache.IncrementStats(false, 0)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -413,7 +378,7 @@ func (p *Proxy) fetchAndProcess(
|
|||||||
fetchBytes := int64(len(sourceData))
|
fetchBytes := int64(len(sourceData))
|
||||||
|
|
||||||
// Counted also when the request context has ended meanwhile
|
// Counted also when the request context has ended meanwhile
|
||||||
p.incrementUpstreamFetch(fetchBytes)
|
p.cache.IncrementUpstreamFetch(fetchBytes)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to read upstream response: %w", err)
|
return nil, fmt.Errorf("failed to read upstream response: %w", err)
|
||||||
@@ -467,17 +432,6 @@ func (p *Proxy) fetchAndProcess(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// incrementUpstreamFetch counts one upstream fetch that read fetchBytes bytes.
|
|
||||||
// A fetch that read no bytes is not counted.
|
|
||||||
func (p *Proxy) incrementUpstreamFetch(fetchBytes int64) {
|
|
||||||
if fetchBytes <= 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
p.upstreamFetches.Inc()
|
|
||||||
p.upstreamFetchBytes.Add(float64(fetchBytes))
|
|
||||||
}
|
|
||||||
|
|
||||||
// storeNegative stores a negative cache entry for req's source, whose fetch
|
// storeNegative stores a negative cache entry for req's source, whose fetch
|
||||||
// failed, so the source is not fetched again until the entry expires. A
|
// failed, so the source is not fetched again until the entry expires. A
|
||||||
// failure to store the entry is only logged.
|
// failure to store the entry is only logged.
|
||||||
@@ -523,7 +477,7 @@ func (p *Proxy) processAndStore(
|
|||||||
processDuration := time.Since(processStart)
|
processDuration := time.Since(processStart)
|
||||||
|
|
||||||
// Counted also when the request context has ended meanwhile
|
// Counted also when the request context has ended meanwhile
|
||||||
p.transforms.Inc()
|
p.cache.IncrementTransformCount()
|
||||||
|
|
||||||
// Read processed content
|
// Read processed content
|
||||||
processedData, err := io.ReadAll(processResult.Content)
|
processedData, err := io.ReadAll(processResult.Content)
|
||||||
|
|||||||
@@ -14,8 +14,7 @@ import (
|
|||||||
|
|
||||||
// TestProxy_Get_CountsStats walks Get through a miss that fetches the
|
// TestProxy_Get_CountsStats walks Get through a miss that fetches the
|
||||||
// source, a hit, a miss that reuses the cached source, and two misses whose
|
// source, a hit, a miss that reuses the cached source, and two misses whose
|
||||||
// source cannot be used, checking every counter of the proxy and its cache
|
// source cannot be used, checking every counter of the cache after each.
|
||||||
// after each.
|
|
||||||
func TestProxy_Get_CountsStats(t *testing.T) {
|
func TestProxy_Get_CountsStats(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@@ -115,10 +114,9 @@ func (u *fakeUpstream) Read(p []byte) (int, error) {
|
|||||||
return 0, u.err
|
return 0, u.err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestProxy_Get_CountsInterruptedMisses checks every counter of the proxy
|
// TestProxy_Get_CountsInterruptedMisses checks every counter of the cache
|
||||||
// and its cache after a miss whose request context ends during or after the
|
// after a miss whose request context ends during or after the upstream
|
||||||
// upstream fetch, and after a miss whose upstream body is over the size
|
// fetch, and after a miss whose upstream body is over the size limit.
|
||||||
// limit.
|
|
||||||
func TestProxy_Get_CountsInterruptedMisses(t *testing.T) {
|
func TestProxy_Get_CountsInterruptedMisses(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@@ -193,8 +191,8 @@ func TestProxy_Get_CountsInterruptedMisses(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TestProxy_Get_CountsHitAfterRequestEnds checks every counter of the
|
// TestProxy_Get_CountsHitAfterRequestEnds checks every counter of the
|
||||||
// proxy and its cache after a hit served with a request context that has
|
// cache after a hit served with a request context that has already ended:
|
||||||
// already ended: only the hit count moves.
|
// only the hit count moves.
|
||||||
func TestProxy_Get_CountsHitAfterRequestEnds(t *testing.T) {
|
func TestProxy_Get_CountsHitAfterRequestEnds(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|||||||
@@ -165,9 +165,9 @@ func generateTestGIF(t *testing.T, width, height int, c color.Color) []byte {
|
|||||||
return buf.Bytes()
|
return buf.Bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
// testProxy is a Proxy with the database and the state directory of its
|
// testProxy is a Proxy with the database, the state directory and the
|
||||||
// cache, through which a test reads and changes the cache behind the proxy.
|
// metrics registry of its cache, through which a test reads and changes the
|
||||||
// registry is the metrics registry the proxy and its cache share.
|
// cache behind the proxy.
|
||||||
type testProxy struct {
|
type testProxy struct {
|
||||||
*Proxy
|
*Proxy
|
||||||
|
|
||||||
@@ -207,7 +207,7 @@ func setupTestProxy(t *testing.T) (*testProxy, *TestFixtures) {
|
|||||||
MaxConcurrentProcessing: 2,
|
MaxConcurrentProcessing: 2,
|
||||||
}),
|
}),
|
||||||
MaxResponseSize: 50 << 20,
|
MaxResponseSize: 50 << 20,
|
||||||
}, registry)
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to create proxy: %v", err)
|
t.Fatalf("failed to create proxy: %v", err)
|
||||||
}
|
}
|
||||||
@@ -220,7 +220,7 @@ func setupTestProxy(t *testing.T) (*testProxy, *TestFixtures) {
|
|||||||
}, fixtures
|
}, fixtures
|
||||||
}
|
}
|
||||||
|
|
||||||
// counterValues holds the values of the counters of a proxy and its cache.
|
// counterValues holds the values of a cache's counters.
|
||||||
type counterValues struct {
|
type counterValues struct {
|
||||||
hitCount int64
|
hitCount int64
|
||||||
missCount int64
|
missCount int64
|
||||||
@@ -229,14 +229,13 @@ type counterValues struct {
|
|||||||
transformCount int64
|
transformCount int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// readCounters reads the counters of svc and its cache from their metrics
|
// readCounters reads the counters of svc's cache from its metrics registry.
|
||||||
// registry.
|
|
||||||
func readCounters(t *testing.T, svc *testProxy) counterValues {
|
func readCounters(t *testing.T, svc *testProxy) counterValues {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
families, err := svc.registry.Gather()
|
families, err := svc.registry.Gather()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to gather the counters: %v", err)
|
t.Fatalf("failed to gather the cache's counters: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
values := make(map[string]int64, len(families))
|
values := make(map[string]int64, len(families))
|
||||||
|
|||||||
@@ -84,10 +84,13 @@ type Cache struct {
|
|||||||
pendingAccessTimesDone chan struct{}
|
pendingAccessTimesDone chan struct{}
|
||||||
pendingAccessTimesCancel context.CancelFunc
|
pendingAccessTimesCancel context.CancelFunc
|
||||||
|
|
||||||
// The counts of cache hits and misses, made and registered by
|
// The counts of what the cache does, made and registered by
|
||||||
// registerCounters (see counters.go)
|
// registerCounters (see counters.go)
|
||||||
cacheHits prometheus.Counter
|
cacheHits prometheus.Counter
|
||||||
cacheMisses prometheus.Counter
|
cacheMisses prometheus.Counter
|
||||||
|
upstreamFetches prometheus.Counter
|
||||||
|
upstreamFetchBytes prometheus.Counter
|
||||||
|
transforms prometheus.Counter
|
||||||
|
|
||||||
// reconciliationPageSize is the most rows one read of a content table
|
// reconciliationPageSize is the most rows one read of a content table
|
||||||
// returns in the reconciliation pass. NewCache sets it to
|
// returns in the reconciliation pass. NewCache sets it to
|
||||||
@@ -364,15 +367,34 @@ func (c *Cache) CleanExpired(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// IncrementStats counts a cache hit or miss. It only adds to the cache's
|
// IncrementStats counts a cache hit or miss, and an upstream fetch that read
|
||||||
// Prometheus counters in memory (see counters.go), so it never waits for the
|
// fetchBytes bytes, as IncrementUpstreamFetch does. Like the other Increment
|
||||||
// database or fails.
|
// methods, it only adds to the cache's Prometheus counters in memory (see
|
||||||
func (c *Cache) IncrementStats(hit bool) {
|
// counters.go), so it never waits for the database or fails.
|
||||||
|
func (c *Cache) IncrementStats(hit bool, fetchBytes int64) {
|
||||||
if hit {
|
if hit {
|
||||||
c.cacheHits.Inc()
|
c.cacheHits.Inc()
|
||||||
} else {
|
} else {
|
||||||
c.cacheMisses.Inc()
|
c.cacheMisses.Inc()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.IncrementUpstreamFetch(fetchBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IncrementUpstreamFetch counts one upstream fetch that read fetchBytes bytes.
|
||||||
|
// A fetch that read no bytes is not counted.
|
||||||
|
func (c *Cache) IncrementUpstreamFetch(fetchBytes int64) {
|
||||||
|
if fetchBytes <= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.upstreamFetches.Inc()
|
||||||
|
c.upstreamFetchBytes.Add(float64(fetchBytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IncrementTransformCount counts one image transcoded by the image processor.
|
||||||
|
func (c *Cache) IncrementTransformCount() {
|
||||||
|
c.transforms.Inc()
|
||||||
}
|
}
|
||||||
|
|
||||||
// touchVariant hands the access time of a variant served from the cache to
|
// touchVariant hands the access time of a variant served from the cache to
|
||||||
|
|||||||
@@ -19,8 +19,22 @@ func (c *Cache) registerCounters(metricsRegistry prometheus.Registerer) error {
|
|||||||
Name: "pixa_cache_misses_total",
|
Name: "pixa_cache_misses_total",
|
||||||
Help: "Image requests not served from the cache.",
|
Help: "Image requests not served from the cache.",
|
||||||
})
|
})
|
||||||
|
c.upstreamFetches = prometheus.NewCounter(prometheus.CounterOpts{
|
||||||
|
Name: "pixa_upstream_fetches_total",
|
||||||
|
Help: "Fetches of a source image from upstream that read any bytes.",
|
||||||
|
})
|
||||||
|
c.upstreamFetchBytes = prometheus.NewCounter(prometheus.CounterOpts{
|
||||||
|
Name: "pixa_upstream_fetch_bytes_total",
|
||||||
|
Help: "Bytes read from upstream by fetches of source images.",
|
||||||
|
})
|
||||||
|
c.transforms = prometheus.NewCounter(prometheus.CounterOpts{
|
||||||
|
Name: "pixa_transforms_total",
|
||||||
|
Help: "Images the image processor transformed.",
|
||||||
|
})
|
||||||
|
|
||||||
for _, counter := range []prometheus.Counter{c.cacheHits, c.cacheMisses} {
|
for _, counter := range []prometheus.Counter{
|
||||||
|
c.cacheHits, c.cacheMisses, c.upstreamFetches, c.upstreamFetchBytes, c.transforms,
|
||||||
|
} {
|
||||||
err := metricsRegistry.Register(counter)
|
err := metricsRegistry.Register(counter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to register the cache's counters: %w", err)
|
return fmt.Errorf("failed to register the cache's counters: %w", err)
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ func TestNewCache_RegistersItsCountersOnItsRegistry(t *testing.T) {
|
|||||||
names := []string{
|
names := []string{
|
||||||
"pixa_cache_hits_total",
|
"pixa_cache_hits_total",
|
||||||
"pixa_cache_misses_total",
|
"pixa_cache_misses_total",
|
||||||
|
"pixa_upstream_fetches_total",
|
||||||
|
"pixa_upstream_fetch_bytes_total",
|
||||||
|
"pixa_transforms_total",
|
||||||
}
|
}
|
||||||
|
|
||||||
count, err := testutil.GatherAndCount(registry, names...)
|
count, err := testutil.GatherAndCount(registry, names...)
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ import (
|
|||||||
|
|
||||||
// Constructors returns the constructors of the parts this package builds,
|
// Constructors returns the constructors of the parts this package builds,
|
||||||
// for fx.Provide. The fx graph must also provide the prometheus.Registerer
|
// for fx.Provide. The fx graph must also provide the prometheus.Registerer
|
||||||
// the cache and the image service register their counters on.
|
// the cache registers its counters on.
|
||||||
func Constructors() []any {
|
func Constructors() []any {
|
||||||
return []any{
|
return []any{
|
||||||
newKeys,
|
newKeys,
|
||||||
@@ -214,12 +214,11 @@ func newProcessor(cfg *config.Config) *imageprocessor.ImageProcessor {
|
|||||||
type imageServiceParams struct {
|
type imageServiceParams struct {
|
||||||
fx.In
|
fx.In
|
||||||
|
|
||||||
Config *config.Config
|
Config *config.Config
|
||||||
Cache *imgcache.Cache
|
Cache *imgcache.Cache
|
||||||
Fetcher *httpfetcher.HTTPFetcher
|
Fetcher *httpfetcher.HTTPFetcher
|
||||||
Processor *imageprocessor.ImageProcessor
|
Processor *imageprocessor.ImageProcessor
|
||||||
Logger *logger.Logger
|
Logger *logger.Logger
|
||||||
MetricsRegistry prometheus.Registerer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// newImageService builds the image service from the cache, the fetcher
|
// newImageService builds the image service from the cache, the fetcher
|
||||||
@@ -233,7 +232,7 @@ func newImageService(
|
|||||||
Processor: params.Processor,
|
Processor: params.Processor,
|
||||||
MaxResponseSize: params.Config.UpstreamMaxResponseSize,
|
MaxResponseSize: params.Config.UpstreamMaxResponseSize,
|
||||||
Logger: params.Logger.Get(),
|
Logger: params.Logger.Get(),
|
||||||
}, params.MetricsRegistry)
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// sessionManagerParams defines dependencies for newSessionManager.
|
// sessionManagerParams defines dependencies for newSessionManager.
|
||||||
|
|||||||
Reference in new issue
Block a user