package imageproxy import ( "testing" "sneak.berlin/go/pixa/internal/imagerequest" ) // metricValues holds the metrics of a proxy and its cache that // TestProxy_Get_CountsSourceCacheFetchesAndTransforms checks. A histogram // is given by its count of observations. type metricValues struct { sourceCacheHits uint64 sourceCacheMisses uint64 fetchDurations uint64 fetchErrors uint64 transformDuration uint64 inProgress float64 } // readMetricValues reads the metrics of svc and its cache that // metricValues holds from their metrics registry. func readMetricValues(t *testing.T, svc *testProxy) metricValues { t.Helper() families, err := svc.registry.Gather() if err != nil { t.Fatalf("failed to gather the metrics: %v", err) } var values metricValues for _, family := range families { metric := family.GetMetric()[0] switch family.GetName() { case "pixa_source_cache_hits_total": values.sourceCacheHits = uint64(metric.GetCounter().GetValue()) case "pixa_source_cache_misses_total": values.sourceCacheMisses = uint64(metric.GetCounter().GetValue()) case "pixa_upstream_fetch_duration_seconds": values.fetchDurations = metric.GetHistogram().GetSampleCount() case "pixa_upstream_fetch_errors_total": values.fetchErrors = uint64(metric.GetCounter().GetValue()) case "pixa_transform_duration_seconds": values.transformDuration = metric.GetHistogram().GetSampleCount() case "pixa_transforms_in_progress": values.inProgress = metric.GetGauge().GetValue() } } return values } // TestProxy_Get_CountsSourceCacheFetchesAndTransforms walks Get through a // miss that fetches the source, a hit, a miss that uses the cached source // and a miss whose source is not found upstream, checking the source cache, // upstream fetch and transform metrics after each. func TestProxy_Get_CountsSourceCacheFetchesAndTransforms(t *testing.T) { t.Parallel() svc, fixtures := setupTestProxy(t) // want is source cache hits, source cache misses, fetch durations // observed, fetch errors, transform durations observed, transforms in // progress. steps := []struct { name string path string size int wantErr bool want metricValues }{ {"miss that fetches the source", testPathPhoto, 50, false, metricValues{0, 1, 1, 0, 1, 0}}, {"hit", testPathPhoto, 50, false, metricValues{0, 1, 1, 0, 1, 0}}, {"miss that uses the cached source", testPathPhoto, 25, false, metricValues{1, 1, 1, 0, 2, 0}}, {"miss whose source is not found", "/images/absent.jpg", 50, true, metricValues{1, 2, 1, 1, 2, 0}}, } for _, step := range steps { resp, err := svc.Get(t.Context(), &imagerequest.Request{ Source: imagerequest.Source{ Host: fixtures.GoodHost, Path: step.path, }, Transform: imagerequest.Transform{ Size: imagerequest.Size{Width: step.size, Height: step.size}, Format: imagerequest.FormatJPEG, Quality: 85, FitMode: imagerequest.FitCover, }, }) if (err != nil) != step.wantErr { t.Fatalf("%s: Get() error = %v, want error %t", step.name, err, step.wantErr) } if err == nil { _ = resp.Content.Close() } got := readMetricValues(t, svc) t.Logf("after the %s: metrics = %+v", step.name, got) if got != step.want { t.Fatalf("after the %s: metrics = %+v, want %+v", step.name, got, step.want) } } }