[gold] refactor diffstore to split out subpackages
These small helpers can/should be written to not use boltdb,
since that might not scale well.
Bug: skia:9080
Change-Id: Id0e0dfa9a9c38298623c04b28d77c95ecb9a2809
Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/219776
Reviewed-by: Joe Gregorio <jcgregorio@google.com>
Commit-Queue: Kevin Lubick <kjlubick@google.com>
diff --git a/golden/cmd/sampler/main.go b/golden/cmd/sampler/main.go
index 11e9293..d8d35f2 100644
--- a/golden/cmd/sampler/main.go
+++ b/golden/cmd/sampler/main.go
@@ -179,6 +179,9 @@
evt := eventbus.New()
ts, err := auth.NewDefaultTokenSource(true)
+ if err != nil {
+ sklog.Fatalf("Cannot get token source: %s", err)
+ }
fsClient, err := firestore.NewClient(ctx, *fsProjectID, "gold", *fsNamespace, ts)
if err != nil {
diff --git a/golden/cmd/skia_diff_server/main.go b/golden/cmd/skia_diff_server/main.go
index 4c16892..ec9f087 100644
--- a/golden/cmd/skia_diff_server/main.go
+++ b/golden/cmd/skia_diff_server/main.go
@@ -15,6 +15,7 @@
"go.skia.org/infra/go/sklog"
"go.skia.org/infra/golden/go/diff"
"go.skia.org/infra/golden/go/diffstore"
+ "go.skia.org/infra/golden/go/diffstore/mapper/disk_mapper"
gstorage "google.golang.org/api/storage/v1"
"google.golang.org/grpc"
)
@@ -22,7 +23,6 @@
// Command line flags.
var (
cacheSize = flag.Int("cache_size", 1, "Approximate cachesize used to cache images and diff metrics in GiB. This is just a way to limit caching. 0 means no caching at all. Use default for testing.")
- convertLegacy = flag.Bool("convert_legacy", false, "Converts the legacy cache to the new format.")
gsBucketNames = flag.String("gs_buckets", "", "[required] Comma-separated list of google storage bucket that hold uploaded images.")
gsBaseDir = flag.String("gs_basedir", diffstore.DEFAULT_GCS_IMG_DIR_NAME, "String that represents the google storage directory/directories following the GS bucket")
imageDir = flag.String("image_dir", "/tmp/imagedir", "What directory to store test and diff images in.")
@@ -69,16 +69,12 @@
client := httputils.DefaultClientConfig().WithTokenSource(ts).With2xxOnly().Client()
// Get the DiffStore that does the work loading and diffing images.
- mapper := diffstore.NewGoldDiffStoreMapper(&diff.DiffMetrics{})
+ mapper := disk_mapper.New(&diff.DiffMetrics{})
memDiffStore, err := diffstore.NewMemDiffStore(client, *imageDir, strings.Split(*gsBucketNames, ","), *gsBaseDir, *cacheSize, mapper)
if err != nil {
sklog.Fatalf("Allocating DiffStore failed: %s", err)
}
- if *convertLegacy {
- memDiffStore.(*diffstore.MemDiffStore).ConvertLegacy()
- }
-
// Create the server side instance of the DiffService.
codec := diffstore.MetricMapCodec{}
serverImpl := diffstore.NewDiffServiceServer(memDiffStore, codec)
diff --git a/golden/cmd/skiacorrectness/main.go b/golden/cmd/skiacorrectness/main.go
index 75332e3..d8d8c81 100644
--- a/golden/cmd/skiacorrectness/main.go
+++ b/golden/cmd/skiacorrectness/main.go
@@ -44,6 +44,7 @@
"go.skia.org/infra/golden/go/baseline/gcs_baseliner"
"go.skia.org/infra/golden/go/diff"
"go.skia.org/infra/golden/go/diffstore"
+ "go.skia.org/infra/golden/go/diffstore/mapper/disk_mapper"
"go.skia.org/infra/golden/go/expstorage/fs_expstore"
"go.skia.org/infra/golden/go/ignore"
"go.skia.org/infra/golden/go/indexer"
@@ -240,7 +241,7 @@
if *gsBucketNames == "" {
sklog.Fatalf("Must specify --gs_buckets or (--diff_server_http and --diff_server_grpc)")
}
- mapper := diffstore.NewGoldDiffStoreMapper(&diff.DiffMetrics{})
+ mapper := disk_mapper.New(&diff.DiffMetrics{})
diffStore, err = diffstore.NewMemDiffStore(client, *imageDir, strings.Split(*gsBucketNames, ","), diffstore.DEFAULT_GCS_IMG_DIR_NAME, *cacheSize, mapper)
if err != nil {
sklog.Fatalf("Allocating local DiffStore failed: %s", err)
diff --git a/golden/go/baseline/gcs_baseliner/baseliner.go b/golden/go/baseline/gcs_baseliner/baseliner.go
index 624c7aa..f181c9b 100644
--- a/golden/go/baseline/gcs_baseliner/baseliner.go
+++ b/golden/go/baseline/gcs_baseliner/baseliner.go
@@ -263,7 +263,7 @@
} else {
// Clone the retrieved baseline before we inject the issue information.
masterBaseline = masterBaseline.Copy()
- masterBaseline.Expectations.Update(issueBaseline.Expectations)
+ masterBaseline.Expectations.MergeExpectations(issueBaseline.Expectations)
masterBaseline.Issue = issueID
}
}
diff --git a/golden/go/diff/diff.go b/golden/go/diff/diff.go
index 643fbe1..3289451 100644
--- a/golden/go/diff/diff.go
+++ b/golden/go/diff/diff.go
@@ -11,6 +11,7 @@
"os"
"unsafe"
+ "go.skia.org/infra/go/metrics2"
"go.skia.org/infra/go/sklog"
"go.skia.org/infra/go/util"
"go.skia.org/infra/golden/go/types"
@@ -277,7 +278,7 @@
// PixelDiff is a utility function that calculates the DiffMetrics and the image of the
// difference for the provided images.
func PixelDiff(img1, img2 image.Image) (*DiffMetrics, *image.NRGBA) {
-
+ defer metrics2.FuncTimer().Stop()
img1Bounds := img1.Bounds()
img2Bounds := img2.Bounds()
@@ -302,7 +303,7 @@
p1 := GetNRGBA(img1).Pix
p2 := GetNRGBA(img2).Pix
// Compare the bounds, if they are the same then use this fast path.
- // We pun to uint64 to compare 2 pixels at a time, so we also require
+ // We cast to uint64 to compare 2 pixels at a time, so we also require
// an even number of pixels here. If that's a big deal, we can easily
// fix that up, handling the straggler pixel separately at the end.
if img1Bounds.Eq(img2Bounds) && len(p1)%8 == 0 {
diff --git a/golden/go/diff/diff_test.go b/golden/go/diff/diff_test.go
index eaab1c1..be2d09f 100644
--- a/golden/go/diff/diff_test.go
+++ b/golden/go/diff/diff_test.go
@@ -3,6 +3,7 @@
import (
"bytes"
"image"
+ "math"
"path/filepath"
"reflect"
"strings"
@@ -342,3 +343,22 @@
PixelDiff(img1, img2)
}
}
+
+func TestCombinedDiffMetric(t *testing.T) {
+ unittest.SmallTest(t)
+ dm := &DiffMetrics{
+ MaxRGBADiffs: []int{},
+ PixelDiffPercent: 0.0,
+ }
+ assert.InDelta(t, 1.0, CombinedDiffMetric(dm, nil, nil), 0.000001)
+ dm = &DiffMetrics{
+ MaxRGBADiffs: []int{255, 255, 255, 255},
+ PixelDiffPercent: 1.0,
+ }
+ assert.InDelta(t, 1.0, CombinedDiffMetric(dm, nil, nil), 0.000001)
+ dm = &DiffMetrics{
+ MaxRGBADiffs: []int{255, 255, 255, 255},
+ PixelDiffPercent: 0.5,
+ }
+ assert.InDelta(t, math.Sqrt(0.5), CombinedDiffMetric(dm, nil, nil), 0.000001)
+}
diff --git a/golden/go/diff/metrics.go b/golden/go/diff/metrics.go
index 4de92e6..9d083ba 100644
--- a/golden/go/diff/metrics.go
+++ b/golden/go/diff/metrics.go
@@ -3,6 +3,8 @@
import (
"image"
"math"
+
+ "go.skia.org/infra/go/metrics2"
)
const (
@@ -16,7 +18,7 @@
// metrics contains the custom diff metrics.
var metrics = map[string]MetricFn{
- METRIC_COMBINED: combinedDiffMetric,
+ METRIC_COMBINED: CombinedDiffMetric,
METRIC_PERCENT: percentDiffMetric,
METRIC_PIXEL: pixelDiffMetric,
}
@@ -40,6 +42,7 @@
// DefaultDiffFn implements the DiffFn function type. Calculates the basic
// image difference along with custom diff metrics.
func DefaultDiffFn(leftImg *image.NRGBA, rightImg *image.NRGBA) (interface{}, *image.NRGBA) {
+ defer metrics2.FuncTimer().Stop()
ret, diffImg := PixelDiff(leftImg, rightImg)
// Calculate the metrics.
@@ -54,23 +57,22 @@
// combinedDiffMetric returns a value in [0, 1] that represents how large
// the diff is between two images. Implements the MetricFn signature.
-func combinedDiffMetric(basic *DiffMetrics, one *image.NRGBA, two *image.NRGBA) float32 {
- //
+func CombinedDiffMetric(dm *DiffMetrics, _ *image.NRGBA, _ *image.NRGBA) float32 {
// pixelDiffPercent float32, maxRGBA []int) float32 {
- if len(basic.MaxRGBADiffs) == 0 {
+ if len(dm.MaxRGBADiffs) == 0 {
return 1.0
}
// Turn maxRGBA into a percent by taking the root mean square difference from
// [0, 0, 0, 0].
sum := 0.0
- for _, c := range basic.MaxRGBADiffs {
+ for _, c := range dm.MaxRGBADiffs {
sum += float64(c) * float64(c)
}
- normalizedRGBA := math.Sqrt(sum/float64(len(basic.MaxRGBADiffs))) / 255.0
+ normalizedRGBA := math.Sqrt(sum/float64(len(dm.MaxRGBADiffs))) / 255.0
// We take the sqrt of (pixelDiffPercent * normalizedRGBA) to straigten out
// the curve, i.e. think about what a plot of x^2 would look like in the
// range [0, 1].
- return float32(math.Sqrt(float64(basic.PixelDiffPercent) * normalizedRGBA))
+ return float32(math.Sqrt(float64(dm.PixelDiffPercent) * normalizedRGBA))
}
// percentDiffMetric returns pixel percent as the metric. Implements the MetricFn signature.
diff --git a/golden/go/diffstore/bench_test.go b/golden/go/diffstore/bench_test.go
index d694371..13fd6cd 100644
--- a/golden/go/diffstore/bench_test.go
+++ b/golden/go/diffstore/bench_test.go
@@ -8,6 +8,8 @@
assert "github.com/stretchr/testify/require"
"go.skia.org/infra/go/testutils"
"go.skia.org/infra/golden/go/diff"
+ "go.skia.org/infra/golden/go/diffstore/mapper/disk_mapper"
+ d_utils "go.skia.org/infra/golden/go/diffstore/testutils"
"go.skia.org/infra/golden/go/ignore"
"go.skia.org/infra/golden/go/mocks"
"go.skia.org/infra/golden/go/serialize"
@@ -22,7 +24,7 @@
func BenchmarkMemDiffStore(b *testing.B) {
sample := loadSample(b)
- baseDir := TEST_DATA_BASE_DIR + "-bench-diffstore"
+ baseDir := d_utils.TEST_DATA_BASE_DIR + "-bench-diffstore"
client := mocks.GetHTTPClient(b)
defer testutils.RemoveAll(b, baseDir)
@@ -47,8 +49,8 @@
}
}
- mapper := NewGoldDiffStoreMapper(&diff.DiffMetrics{})
- diffStore, err := NewMemDiffStore(client, baseDir, []string{TEST_GCS_BUCKET_NAME}, TEST_GCS_IMAGE_DIR, 10, mapper)
+ mapper := disk_mapper.New(&diff.DiffMetrics{})
+ diffStore, err := NewMemDiffStore(client, baseDir, []string{d_utils.TEST_GCS_BUCKET_NAME}, d_utils.TEST_GCS_IMAGE_DIR, 10, mapper)
assert.NoError(b, err)
allDigests := make([]types.DigestSlice, 0, PROCESS_N_TESTS)
processed := 0
diff --git a/golden/go/diffstore/common.go b/golden/go/diffstore/common.go
deleted file mode 100644
index 13861cb..0000000
--- a/golden/go/diffstore/common.go
+++ /dev/null
@@ -1,116 +0,0 @@
-package diffstore
-
-import (
- "encoding/json"
- "fmt"
- "image"
- "image/png"
- "io"
- "os"
- "path"
-
- "github.com/boltdb/bolt"
- "go.skia.org/infra/go/fileutil"
- "go.skia.org/infra/go/util"
- "go.skia.org/infra/golden/go/diff"
- "go.skia.org/infra/golden/go/types"
-)
-
-const (
- // IMG_EXTENSION is the default extension of images.
- IMG_EXTENSION = "png"
-)
-
-// saveFile writes the given file to disk.
-func saveFile(targetPath string, r io.Reader) error {
- f, err := os.Create(targetPath)
- if err != nil {
- return err
- }
- defer util.Close(f)
-
- _, err = io.Copy(f, r)
- return err
-}
-
-// loadImg loads an image from disk.
-func loadImg(sourcePath string) (*image.NRGBA, error) {
- f, err := os.Open(sourcePath)
- if err != nil {
- return nil, err
- }
- defer util.Close(f)
- return decodeImg(f)
-}
-
-// encodeImg encodes the given image as a PNG and writes the result to the
-// given writer.
-func encodeImg(w io.Writer, img *image.NRGBA) error {
- encoder := png.Encoder{CompressionLevel: png.BestSpeed}
- if err := encoder.Encode(w, img); err != nil {
- return err
- }
- return nil
-}
-
-// decodeImg decodes an image from the given reader and returns it as a NRGBA image.
-func decodeImg(reader io.Reader) (*image.NRGBA, error) {
- im, err := png.Decode(reader)
- if err != nil {
- return nil, err
- }
- return diff.GetNRGBA(im), nil
-}
-
-// getDigestImageFileName returns the image name based on the digest.
-func getDigestImageFileName(digest types.Digest) string {
- return fmt.Sprintf("%s.%s", digest, IMG_EXTENSION)
-}
-
-// openBoltDB opens a boltDB in the given given directory with the given name.
-func openBoltDB(baseDir, name string) (*bolt.DB, error) {
- baseDir, err := fileutil.EnsureDirExists(baseDir)
- if err != nil {
- return nil, err
- }
-
- return bolt.Open(path.Join(baseDir, name), 0600, nil)
-}
-
-// saveFilePath saves the given buffer in path.
-func saveFilePath(path string, r io.Reader) error {
- err := fileutil.EnsureDirPathExists(path)
- if err != nil {
- return fmt.Errorf("Unable to create path for %s: %s", path, err)
- }
-
- if err := saveFile(path, r); err != nil {
- return fmt.Errorf("Unable to save file %s. Got error: %s", path, err)
- }
- return nil
-}
-
-// MetricMapCodec implements the util.LRUCodec interface by serializing and
-// deserializing generic diff result structs, instances of map[string]interface{}
-type MetricMapCodec struct{}
-
-// See util.LRUCodec interface
-func (m MetricMapCodec) Encode(data interface{}) ([]byte, error) {
- return json.Marshal(data)
-}
-
-// See util.LRUCodec interface
-func (m MetricMapCodec) Decode(byteData []byte) (interface{}, error) {
- dm := map[types.Digest]*diff.DiffMetrics{}
- err := json.Unmarshal(byteData, &dm)
- if err != nil {
- return nil, err
- }
-
- // Must make result of deserialization generic in order to propagate
- ret := make(map[types.Digest]interface{}, len(dm))
- for k, metric := range dm {
- ret[k] = metric
- }
- return ret, nil
-}
diff --git a/golden/go/diffstore/common/common.go b/golden/go/diffstore/common/common.go
new file mode 100644
index 0000000..3bce304
--- /dev/null
+++ b/golden/go/diffstore/common/common.go
@@ -0,0 +1,98 @@
+package common
+
+import (
+ "fmt"
+ "image"
+ "image/png"
+ "io"
+ "os"
+ "path"
+
+ "github.com/boltdb/bolt"
+ "go.skia.org/infra/go/fileutil"
+ "go.skia.org/infra/go/util"
+ "go.skia.org/infra/golden/go/diff"
+ "go.skia.org/infra/golden/go/types"
+)
+
+const (
+ // IMG_EXTENSION is the default extension of images.
+ IMG_EXTENSION = "png"
+)
+
+// SaveFile writes the given file to disk.
+func SaveFile(targetPath string, r io.Reader) error {
+ f, err := os.Create(targetPath)
+ if err != nil {
+ return err
+ }
+ defer util.Close(f)
+
+ _, err = io.Copy(f, r)
+ return err
+}
+
+// LoadImg loads an image from disk.
+func LoadImg(sourcePath string) (*image.NRGBA, error) {
+ f, err := os.Open(sourcePath)
+ if err != nil {
+ return nil, err
+ }
+ defer util.Close(f)
+ return DecodeImg(f)
+}
+
+// EncodeImg encodes the given image as a PNG and writes the result to the
+// given writer.
+func EncodeImg(w io.Writer, img *image.NRGBA) error {
+ encoder := png.Encoder{CompressionLevel: png.BestSpeed}
+ if err := encoder.Encode(w, img); err != nil {
+ return err
+ }
+ return nil
+}
+
+// DecodeImg decodes an image from the given reader and returns it as a NRGBA image.
+func DecodeImg(reader io.Reader) (*image.NRGBA, error) {
+ im, err := png.Decode(reader)
+ if err != nil {
+ return nil, err
+ }
+ return diff.GetNRGBA(im), nil
+}
+
+// GetDigestImageFileName returns the image name based on the digest.
+func GetDigestImageFileName(digest types.Digest) string {
+ return fmt.Sprintf("%s.%s", digest, IMG_EXTENSION)
+}
+
+// OpenBoltDB opens a boltDB in the given given directory with the given name.
+func OpenBoltDB(baseDir, name string) (*bolt.DB, error) {
+ baseDir, err := fileutil.EnsureDirExists(baseDir)
+ if err != nil {
+ return nil, err
+ }
+
+ return bolt.Open(path.Join(baseDir, name), 0600, nil)
+}
+
+// SaveFilePath saves the given buffer in path.
+func SaveFilePath(path string, r io.Reader) error {
+ err := fileutil.EnsureDirPathExists(path)
+ if err != nil {
+ return fmt.Errorf("Unable to create path for %s: %s", path, err)
+ }
+
+ if err := SaveFile(path, r); err != nil {
+ return fmt.Errorf("Unable to save file %s. Got error: %s", path, err)
+ }
+ return nil
+}
+
+func AsStrings(xd types.DigestSlice) []string {
+ s := make([]string, 0, len(xd))
+ for _, d := range xd {
+ s = append(s, string(d))
+ }
+ return s
+}
diff --git a/golden/go/diffstore/diffservice_impl.go b/golden/go/diffstore/diffservice_impl.go
index ac5c919..a3b4c35 100644
--- a/golden/go/diffstore/diffservice_impl.go
+++ b/golden/go/diffstore/diffservice_impl.go
@@ -40,14 +40,6 @@
return d
}
-func asStrings(xd types.DigestSlice) []string {
- s := make([]string, 0, len(xd))
- for _, d := range xd {
- s = append(s, string(d))
- }
- return s
-}
-
// GetDiffs wraps around the Get method of the underlying DiffStore.
func (d *DiffServiceImpl) GetDiffs(ctx context.Context, req *GetDiffsRequest) (*GetDiffsResponse, error) {
diffs, err := d.diffStore.Get(req.Priority, types.Digest(req.MainDigest), asDigests(req.RightDigests))
diff --git a/golden/go/diffstore/failure_store_test.go b/golden/go/diffstore/failure_store_test.go
deleted file mode 100644
index 815e411..0000000
--- a/golden/go/diffstore/failure_store_test.go
+++ /dev/null
@@ -1,53 +0,0 @@
-package diffstore
-
-import (
- "path"
- "testing"
-
- assert "github.com/stretchr/testify/require"
- "go.skia.org/infra/go/testutils"
- "go.skia.org/infra/go/testutils/unittest"
- "go.skia.org/infra/golden/go/diff"
- "go.skia.org/infra/golden/go/types"
-)
-
-func TestFailureHandling(t *testing.T) {
- unittest.MediumTest(t)
-
- // Get a small tile and get them cached.
- w, cleanup := testutils.TempDir(t)
- defer cleanup()
- baseDir := path.Join(w, TEST_DATA_BASE_DIR+"-diffstore-failure")
- client, tile := getSetupAndTile(t, baseDir)
-
- mapper := NewGoldDiffStoreMapper(&diff.DiffMetrics{})
- diffStore, err := NewMemDiffStore(client, baseDir, []string{TEST_GCS_BUCKET_NAME}, TEST_GCS_IMAGE_DIR, 10, mapper)
- assert.NoError(t, err)
-
- validDigestSet := types.DigestSet{}
- for _, trace := range tile.Traces {
- gTrace := trace.(*types.GoldenTrace)
- validDigestSet.AddLists(gTrace.Digests)
- }
- delete(validDigestSet, types.MISSING_DIGEST)
-
- invalidDigest_1 := types.Digest("invaliddigest1")
- invalidDigest_2 := types.Digest("invaliddigest2")
-
- validDigests := validDigestSet.Keys()
- mainDigest := validDigests[0]
- diffDigests := append(validDigests[1:6], invalidDigest_1, invalidDigest_2)
-
- diffs, err := diffStore.Get(diff.PRIORITY_NOW, mainDigest, diffDigests)
- assert.NoError(t, err)
- assert.Equal(t, len(diffDigests)-2, len(diffs))
-
- unavailableDigests := diffStore.UnavailableDigests()
- assert.Equal(t, 2, len(unavailableDigests))
- assert.NotNil(t, unavailableDigests[invalidDigest_1])
- assert.NotNil(t, unavailableDigests[invalidDigest_2])
-
- assert.NoError(t, diffStore.PurgeDigests(types.DigestSlice{invalidDigest_1, invalidDigest_2}, true))
- unavailableDigests = diffStore.UnavailableDigests()
- assert.Equal(t, 0, len(unavailableDigests))
-}
diff --git a/golden/go/diffstore/failure_store.go b/golden/go/diffstore/failurestore/bolt_failurestore/bolt_failurestore.go
similarity index 69%
rename from golden/go/diffstore/failure_store.go
rename to golden/go/diffstore/failurestore/bolt_failurestore/bolt_failurestore.go
index bbe5e2e..aaa731d 100644
--- a/golden/go/diffstore/failure_store.go
+++ b/golden/go/diffstore/failurestore/bolt_failurestore/bolt_failurestore.go
@@ -1,12 +1,15 @@
-package diffstore
+package bolt_failurestore
import (
+ "path/filepath"
"sync"
"github.com/boltdb/bolt"
"go.skia.org/infra/go/boltutil"
"go.skia.org/infra/go/util"
"go.skia.org/infra/golden/go/diff"
+ "go.skia.org/infra/golden/go/diffstore/common"
+ "go.skia.org/infra/golden/go/diffstore/failurestore"
"go.skia.org/infra/golden/go/types"
)
@@ -30,10 +33,10 @@
return nil
}
-// failureStore persists DigestFailures in boltDB database. It assumes that the
+// BoltImpl persists DigestFailures in boltDB database. It assumes that the
// number of unavailable digests is small and it keeps a copy of the entire list
// in memory at all time.
-type failureStore struct {
+type BoltImpl struct {
// store stores the digests that have failed to load.
store *boltutil.IndexedBucket
@@ -47,9 +50,10 @@
cacheMutex sync.RWMutex
}
-// newFailureStore returns a new instance of failureStore that opens a database in the given directory.
-func newFailureStore(baseDir string) (*failureStore, error) {
- db, err := openBoltDB(baseDir, FAILUREDB_NAME+".db")
+// New returns a new instance of BoltImpl that opens a database in the given directory.
+func New(baseDir string) (*BoltImpl, error) {
+ baseDir = filepath.Join(baseDir, FAILUREDB_NAME)
+ db, err := common.OpenBoltDB(baseDir, FAILUREDB_NAME+".db")
if err != nil {
return nil, err
}
@@ -65,7 +69,7 @@
return nil, err
}
- ret := &failureStore{
+ ret := &BoltImpl{
store: store,
}
@@ -76,26 +80,26 @@
return ret, nil
}
-// unavailableDigests returns the current list of unavailable digests for fast lookup.
-func (f *failureStore) unavailableDigests() map[types.Digest]*diff.DigestFailure {
+// UnavailableDigests returns the current list of unavailable digests for fast lookup.
+func (f *BoltImpl) UnavailableDigests() map[types.Digest]*diff.DigestFailure {
f.cacheMutex.RLock()
defer f.cacheMutex.RUnlock()
return f.cachedFailures
}
-// addDigestFailureIfNew adds a digest failure to the database only if the
+// AddDigestFailureIfNew adds a digest failure to the database only if the
// there is no failure recorded for the given digest.
-func (f *failureStore) addDigestFailureIfNew(failure *diff.DigestFailure) error {
- unavailable := f.unavailableDigests()
+func (f *BoltImpl) AddDigestFailureIfNew(failure *diff.DigestFailure) error {
+ unavailable := f.UnavailableDigests()
if _, ok := unavailable[failure.Digest]; !ok {
- return f.addDigestFailure(failure)
+ return f.AddDigestFailure(failure)
}
return nil
}
-// addDigestFailure adds a digest failure to the database or updates an
+// AddDigestFailure adds a digest failure to the database or updates an
// existing failure.
-func (f *failureStore) addDigestFailure(failure *diff.DigestFailure) error {
+func (f *BoltImpl) AddDigestFailure(failure *diff.DigestFailure) error {
f.dbMutex.Lock()
defer f.dbMutex.Unlock()
@@ -119,13 +123,13 @@
return f.loadDigestFailures()
}
-// purgeDigestFailures removes the failures identified by digests from the database.
-func (f *failureStore) purgeDigestFailures(digests types.DigestSlice) error {
+// PurgeDigestFailures removes the failures identified by digests from the database.
+func (f *BoltImpl) PurgeDigestFailures(digests types.DigestSlice) error {
f.dbMutex.Lock()
defer f.dbMutex.Unlock()
targets := make([]string, 0, len(digests))
- unavailable := f.unavailableDigests()
+ unavailable := f.UnavailableDigests()
for _, d := range digests {
if _, ok := unavailable[d]; ok {
targets = append(targets, string(d))
@@ -144,7 +148,7 @@
}
// loadDigestFailures loads all digest failures into memory and updates the current cache.
-func (f *failureStore) loadDigestFailures() error {
+func (f *BoltImpl) loadDigestFailures() error {
allFailures, _, err := f.store.List(0, -1)
if err != nil {
return err
@@ -161,3 +165,6 @@
f.cachedFailures = ret
return nil
}
+
+// Make sure BoltImpl fulfills the FailureStore interface
+var _ failurestore.FailureStore = (*BoltImpl)(nil)
diff --git a/golden/go/diffstore/failurestore/bolt_failurestore/bolt_failurestore_test.go b/golden/go/diffstore/failurestore/bolt_failurestore/bolt_failurestore_test.go
new file mode 100644
index 0000000..2a9494e
--- /dev/null
+++ b/golden/go/diffstore/failurestore/bolt_failurestore/bolt_failurestore_test.go
@@ -0,0 +1,104 @@
+package bolt_failurestore
+
+import (
+ "testing"
+ "time"
+
+ assert "github.com/stretchr/testify/require"
+ "go.skia.org/infra/go/testutils"
+ "go.skia.org/infra/go/testutils/unittest"
+ "go.skia.org/infra/golden/go/diff"
+ "go.skia.org/infra/golden/go/types"
+)
+
+func TestAddGet(t *testing.T) {
+ unittest.MediumTest(t)
+
+ w, cleanup := testutils.TempDir(t)
+ defer cleanup()
+
+ fs, err := New(w)
+ assert.NoError(t, err)
+
+ assert.Empty(t, fs.UnavailableDigests())
+
+ err = fs.AddDigestFailure(&failureOne)
+ assert.NoError(t, err)
+ err = fs.AddDigestFailure(&failureTwo)
+ assert.NoError(t, err)
+ err = fs.AddDigestFailure(&failureThree)
+ assert.NoError(t, err)
+
+ assert.Equal(t, map[types.Digest]*diff.DigestFailure{
+ digestOne: &failureThree,
+ digestTwo: &failureTwo,
+ }, fs.UnavailableDigests())
+}
+
+func TestAddIfNew(t *testing.T) {
+ unittest.MediumTest(t)
+
+ w, cleanup := testutils.TempDir(t)
+ defer cleanup()
+
+ fs, err := New(w)
+ assert.NoError(t, err)
+
+ err = fs.AddDigestFailureIfNew(&failureOne)
+ assert.NoError(t, err)
+ err = fs.AddDigestFailureIfNew(&failureTwo)
+ assert.NoError(t, err)
+ err = fs.AddDigestFailureIfNew(&failureThree)
+ assert.NoError(t, err)
+
+ assert.Equal(t, map[types.Digest]*diff.DigestFailure{
+ digestOne: &failureOne,
+ digestTwo: &failureTwo,
+ }, fs.UnavailableDigests())
+}
+
+func TestPurge(t *testing.T) {
+ unittest.MediumTest(t)
+
+ w, cleanup := testutils.TempDir(t)
+ defer cleanup()
+
+ fs, err := New(w)
+ assert.NoError(t, err)
+
+ err = fs.AddDigestFailure(&failureOne)
+ assert.NoError(t, err)
+ err = fs.AddDigestFailure(&failureTwo)
+ assert.NoError(t, err)
+ err = fs.PurgeDigestFailures(types.DigestSlice{digestOne})
+ assert.NoError(t, err)
+
+ assert.Equal(t, map[types.Digest]*diff.DigestFailure{
+ digestTwo: &failureTwo,
+ }, fs.UnavailableDigests())
+}
+
+const (
+ digestOne = types.Digest("ab592bfb76536d833e16028bf9525508")
+ digestTwo = types.Digest("9a58d5801670ef194eba7451b08621ac")
+)
+
+var (
+ now = time.Date(2019, time.June, 3, 4, 5, 16, 0, time.UTC)
+
+ failureOne = diff.DigestFailure{
+ Digest: digestOne,
+ Reason: "404",
+ TS: now.Unix() * 1000,
+ }
+ failureTwo = diff.DigestFailure{
+ Digest: digestTwo,
+ Reason: "417",
+ TS: now.Unix()*1000 + 2345,
+ }
+ failureThree = diff.DigestFailure{
+ Digest: digestOne,
+ Reason: "500",
+ TS: now.Unix()*1000 + 6789,
+ }
+)
diff --git a/golden/go/diffstore/failurestore/failurestore.go b/golden/go/diffstore/failurestore/failurestore.go
new file mode 100644
index 0000000..135afc8
--- /dev/null
+++ b/golden/go/diffstore/failurestore/failurestore.go
@@ -0,0 +1,23 @@
+package failurestore
+
+import (
+ "go.skia.org/infra/golden/go/diff"
+ "go.skia.org/infra/golden/go/types"
+)
+
+// FailureStore keeps track of any Digests that were unable to be fetched.
+type FailureStore interface {
+ // UnavailableDigests returns the current list of unavailable digests for fast lookup.
+ UnavailableDigests() map[types.Digest]*diff.DigestFailure
+
+ // AddDigestFailureIfNew adds a digest failure to the database only if the
+ // there is no failure recorded for the given digest.
+ AddDigestFailureIfNew(failure *diff.DigestFailure) error
+
+ // AddDigestFailure adds a digest failure to the database or updates an
+ // existing failure.
+ AddDigestFailure(failure *diff.DigestFailure) error
+
+ // PurgeDigestFailures removes the failures identified by digests from the database.
+ PurgeDigestFailures(digests types.DigestSlice) error
+}
diff --git a/golden/go/diffstore/imgloader.go b/golden/go/diffstore/imgloader.go
index 6aa6804..dca03f1 100644
--- a/golden/go/diffstore/imgloader.go
+++ b/golden/go/diffstore/imgloader.go
@@ -21,6 +21,10 @@
"go.skia.org/infra/go/sklog"
"go.skia.org/infra/go/util"
"go.skia.org/infra/golden/go/diff"
+ "go.skia.org/infra/golden/go/diffstore/common"
+ "go.skia.org/infra/golden/go/diffstore/failurestore"
+ "go.skia.org/infra/golden/go/diffstore/failurestore/bolt_failurestore"
+ "go.skia.org/infra/golden/go/diffstore/mapper"
"go.skia.org/infra/golden/go/types"
"google.golang.org/api/option"
)
@@ -51,24 +55,23 @@
imageCache rtcache.ReadThroughCache
// failureStore persists failures in retrieving images.
- failureStore *failureStore
+ failureStore failurestore.FailureStore
// mapper contains various functions for creating image IDs and paths.
- mapper DiffStoreMapper
+ mapper mapper.Mapper
}
// Creates a new instance of ImageLoader.
-func NewImgLoader(client *http.Client, baseDir, imgDir string, gsBucketNames []string, gsImageBaseDir string, maxCacheSize int, mapper DiffStoreMapper) (*ImageLoader, error) {
+func NewImgLoader(client *http.Client, baseDir, imgDir string, gsBucketNames []string, gsImageBaseDir string, maxCacheSize int, m mapper.Mapper) (*ImageLoader, error) {
storageClient, err := storage.NewClient(context.Background(), option.WithHTTPClient(client))
if err != nil {
return nil, err
}
- fsPath := filepath.Join(baseDir, FAILUREDB_NAME)
- fStore, err := newFailureStore(fsPath)
+ fStore, err := bolt_failurestore.New(baseDir)
if err != nil {
return nil, err
}
- sklog.Infof("failure store created at %s", fsPath)
+ sklog.Infof("failure store created at %s", baseDir)
ret := &ImageLoader{
storageClient: storageClient,
@@ -76,7 +79,7 @@
gsBucketNames: gsBucketNames,
gsImageBaseDir: gsImageBaseDir,
failureStore: fStore,
- mapper: mapper,
+ mapper: m,
}
// Set up the work queues that balance the load.
@@ -162,7 +165,7 @@
_, _ = msg.WriteString("\n")
// This captures the edge case when the error is cached in the image loader.
- util.LogErr(il.failureStore.addDigestFailureIfNew(diff.NewDigestFailure(errRet.id, diff.OTHER)))
+ util.LogErr(il.failureStore.AddDigestFailureIfNew(diff.NewDigestFailure(errRet.id, diff.OTHER)))
}
return nil, nil, errors.New(msg.String())
}
@@ -172,14 +175,14 @@
// IsOnDisk returns true if the image that corresponds to the given imageID is in the disk cache.
func (il *ImageLoader) IsOnDisk(imageID types.Digest) bool {
- localRelPath, _, _ := il.mapper.ImagePaths(imageID)
+ localRelPath, _ := il.mapper.ImagePaths(imageID)
return fileutil.FileExists(filepath.Join(il.localImgDir, localRelPath))
}
// PurgeImages removes the images that correspond to the given images.
func (il *ImageLoader) PurgeImages(images types.DigestSlice, purgeGCS bool) error {
for _, id := range images {
- localRelPath, bucket, gsRelPath := il.mapper.ImagePaths(id)
+ localRelPath, gsRelPath := il.mapper.ImagePaths(id)
localPath := filepath.Join(il.localImgDir, localRelPath)
if fileutil.FileExists(localPath) {
if err := os.Remove(localPath); err != nil {
@@ -188,7 +191,7 @@
}
if purgeGCS {
- il.removeImg(bucket, gsRelPath)
+ il.removeImg(gsRelPath)
}
}
return nil
@@ -198,32 +201,31 @@
// It loads an image file either from disk or from Google storage.
func (il *ImageLoader) imageLoadWorker(priority int64, imageID types.Digest) (interface{}, error) {
// Check if the image is in the disk cache.
- localRelPath, bucket, gsRelPath := il.mapper.ImagePaths(imageID)
+ localRelPath, gsRelPath := il.mapper.ImagePaths(imageID)
localPath := filepath.Join(il.localImgDir, localRelPath)
sklog.Debugf("Looking for image with id %s", imageID)
if fileutil.FileExists(localPath) {
- img, err := loadImg(localPath)
+ img, err := common.LoadImg(localPath)
if err != nil {
- util.LogErr(il.failureStore.addDigestFailure(diff.NewDigestFailure(imageID, diff.CORRUPTED)))
+ util.LogErr(il.failureStore.AddDigestFailure(diff.NewDigestFailure(imageID, diff.CORRUPTED)))
return nil, skerr.Fmt("Could not load %s from disk: %s", localPath, err)
}
sklog.Debugf("Found it on disk at %s", localPath)
- util.LogErr(il.failureStore.purgeDigestFailures(types.DigestSlice{imageID}))
+ util.LogErr(il.failureStore.PurgeDigestFailures(types.DigestSlice{imageID}))
return &imgRet{img: img}, nil
}
- sklog.Debugf("%s is not on disk yet, going to download from gs://%s", imageID, bucket)
// Download the image from GCS
- imgBytes, err := il.downloadImg(bucket, gsRelPath)
+ imgBytes, err := il.downloadImg(gsRelPath)
if err != nil {
- util.LogErr(il.failureStore.addDigestFailure(diff.NewDigestFailure(imageID, diff.HTTP)))
+ util.LogErr(il.failureStore.AddDigestFailure(diff.NewDigestFailure(imageID, diff.HTTP)))
return nil, err
}
// Decode it and return it.
- img, err := decodeImg(bytes.NewBuffer(imgBytes))
+ img, err := common.DecodeImg(bytes.NewBuffer(imgBytes))
if err != nil {
- util.LogErr(il.failureStore.addDigestFailure(diff.NewDigestFailure(imageID, diff.CORRUPTED)))
+ util.LogErr(il.failureStore.AddDigestFailure(diff.NewDigestFailure(imageID, diff.CORRUPTED)))
return nil, err
}
@@ -236,9 +238,9 @@
writeDoneCh := make(chan bool)
go func() {
defer close(writeDoneCh)
- localRelPath, _, _ := il.mapper.ImagePaths(imageID)
+ localRelPath, _ := il.mapper.ImagePaths(imageID)
p := filepath.Join(il.localImgDir, localRelPath)
- if err := saveFilePath(p, bytes.NewBuffer(imgBytes)); err != nil {
+ if err := common.SaveFilePath(p, bytes.NewBuffer(imgBytes)); err != nil {
sklog.Errorf("Could not write file to disk at %s: %s", p, err)
}
}()
@@ -248,16 +250,9 @@
// downloadImg retrieves the given image from Google storage. If bucket is not empty
// the gsPath is considered absolute within the bucket, otherwise it is relative to gsImageBaseDir.
// We will look in every bucket we are configured for.
-func (il *ImageLoader) downloadImg(bucket, gsPath string) ([]byte, error) {
- var bucketNames []string
- var objLocation string
- if bucket != "" {
- objLocation = gsPath
- bucketNames = []string{bucket}
- } else {
- objLocation = filepath.Join(il.gsImageBaseDir, gsPath)
- bucketNames = il.gsBucketNames
- }
+func (il *ImageLoader) downloadImg(gsPath string) ([]byte, error) {
+ bucketNames := il.gsBucketNames
+ objLocation := filepath.Join(il.gsImageBaseDir, gsPath)
var err error
var imgData []byte
@@ -329,13 +324,10 @@
}
// removeImg removes the image that corresponds to the given relative path from GCS.
-func (il *ImageLoader) removeImg(bucket, gsRelPath string) {
+func (il *ImageLoader) removeImg(gsRelPath string) {
// If the bucket is not empty then look there otherwise use the default buckets.
objLocation := filepath.Join(il.gsImageBaseDir, gsRelPath)
bucketNames := il.gsBucketNames
- if bucket != "" {
- bucketNames = []string{bucket}
- }
ctx := context.Background()
for _, bucketName := range bucketNames {
diff --git a/golden/go/diffstore/imgloader_test.go b/golden/go/diffstore/imgloader_test.go
index 8dcee32..d425745 100644
--- a/golden/go/diffstore/imgloader_test.go
+++ b/golden/go/diffstore/imgloader_test.go
@@ -1,7 +1,6 @@
package diffstore
import (
- "fmt"
"os"
"path"
"path/filepath"
@@ -13,6 +12,10 @@
"go.skia.org/infra/go/testutils"
"go.skia.org/infra/go/testutils/unittest"
"go.skia.org/infra/go/tiling"
+ "go.skia.org/infra/golden/go/diffstore/common"
+ "go.skia.org/infra/golden/go/diffstore/mapper"
+ "go.skia.org/infra/golden/go/diffstore/mapper/disk_mapper"
+ d_utils "go.skia.org/infra/golden/go/diffstore/testutils"
"go.skia.org/infra/golden/go/types"
)
@@ -24,8 +27,8 @@
func TestImageLoader(t *testing.T) {
unittest.LargeTest(t)
- mapper := GoldDiffStoreMapper{}
- workingDir, tile, imageLoader, cleanup := getImageLoaderAndTile(t, mapper)
+ m := &disk_mapper.DiskMapper{}
+ workingDir, tile, imageLoader, cleanup := getImageLoaderAndTile(t, m)
defer cleanup()
// Iterate over the tile and get all the digests
@@ -45,7 +48,8 @@
// Make sure they are all on disk.
for _, digest := range digests {
- assert.True(t, fileutil.FileExists(fileutil.TwoLevelRadixPath(workingDir, getDigestImageFileName(digest))))
+ fn := common.GetDigestImageFileName(digest)
+ assert.True(t, fileutil.FileExists(fileutil.TwoLevelRadixPath(workingDir, fn)))
}
// Fetch images from the secondary bucket.
@@ -55,23 +59,17 @@
assert.Error(t, err)
}
-// Calls TwoLevelRadixPath to create the local image file path.
-func DefaultImagePath(baseDir, imageID string) string {
- imagePath := fmt.Sprintf("%s.%s", imageID, IMG_EXTENSION)
- return fileutil.TwoLevelRadixPath(baseDir, imagePath)
-}
-
-func getImageLoaderAndTile(t sktest.TestingT, mapper DiffStoreMapper) (string, *tiling.Tile, *ImageLoader, func()) {
+func getImageLoaderAndTile(t sktest.TestingT, m mapper.Mapper) (string, *tiling.Tile, *ImageLoader, func()) {
w, cleanup := testutils.TempDir(t)
- baseDir := path.Join(w, TEST_DATA_BASE_DIR+"-imgloader")
- client, tile := getSetupAndTile(t, baseDir)
+ baseDir := path.Join(w, d_utils.TEST_DATA_BASE_DIR+"-imgloader")
+ client, tile := d_utils.GetSetupAndTile(t, baseDir)
workingDir := filepath.Join(baseDir, "images")
assert.Nil(t, os.Mkdir(workingDir, 0777))
imgCacheCount, _ := getCacheCounts(10)
- gsBuckets := []string{TEST_GCS_BUCKET_NAME, TEST_GCS_SECONDARY_BUCKET}
- imgLoader, err := NewImgLoader(client, baseDir, workingDir, gsBuckets, TEST_GCS_IMAGE_DIR, imgCacheCount, mapper)
+ gsBuckets := []string{d_utils.TEST_GCS_BUCKET_NAME, d_utils.TEST_GCS_SECONDARY_BUCKET}
+ imgLoader, err := NewImgLoader(client, baseDir, workingDir, gsBuckets, d_utils.TEST_GCS_IMAGE_DIR, imgCacheCount, m)
assert.NoError(t, err)
return workingDir, tile, imgLoader, cleanup
}
diff --git a/golden/go/diffstore/mapper.go b/golden/go/diffstore/mapper.go
deleted file mode 100644
index b893230..0000000
--- a/golden/go/diffstore/mapper.go
+++ /dev/null
@@ -1,94 +0,0 @@
-package diffstore
-
-import (
- "fmt"
- "image"
- "strings"
-
- "go.skia.org/infra/go/fileutil"
- "go.skia.org/infra/go/util"
- "go.skia.org/infra/golden/go/diff"
- "go.skia.org/infra/golden/go/types"
- "go.skia.org/infra/golden/go/validation"
-)
-
-const (
- // DIFF_IMG_SEPARATOR is the character that separates two image ids in the
- // resulting diff image.
- DIFF_IMG_SEPARATOR = "-"
-)
-
-// GoldIDPathMapper implements the DiffStoreMapper interface. It translates
-// between digests (image ids) and storage paths. It uses diff.DiffMetrics
-// as the Gold diff metric.
-type GoldDiffStoreMapper struct {
- util.LRUCodec
-}
-
-// NewGoldDiffStoreMapper returns a new instance of GoldDiffStoreMapper that uses
-// a JSON coded to serialize/deserialize instances of diff.DiffMetrics.
-func NewGoldDiffStoreMapper(diffInstance interface{}) DiffStoreMapper {
- return GoldDiffStoreMapper{LRUCodec: util.JSONCodec(diffInstance)}
-}
-
-// DiffFn implements the DiffStoreMapper interface.
-func (g GoldDiffStoreMapper) DiffFn(leftImg *image.NRGBA, rightImg *image.NRGBA) (interface{}, *image.NRGBA) {
- return diff.DefaultDiffFn(leftImg, rightImg)
-}
-
-// DiffID implements the DiffStoreMapper interface.
-func (g GoldDiffStoreMapper) DiffID(leftImgID, rightImgID types.Digest) string {
- _, _, diffID := g.getOrderedDiffID(leftImgID, rightImgID)
- return diffID
-}
-
-// SplitDiffID implements the DiffStoreMapper interface.
-func (g GoldDiffStoreMapper) SplitDiffID(diffID string) (types.Digest, types.Digest) {
- imageIDs := strings.Split(diffID, DIFF_IMG_SEPARATOR)
-
- // TODO(stephana): Remove this legacy handling code as soon as it has converted the
- // database in production.
- if strings.Contains(diffID, ":") {
- imageIDs = strings.Split(diffID, ":")
- }
-
- return types.Digest(imageIDs[0]), types.Digest(imageIDs[1])
-}
-
-// SplitDiffID implements the DiffStoreMapper interface.
-func (g GoldDiffStoreMapper) DiffPath(leftImgDigest, rightImgDigest types.Digest) string {
- // Get the diff ID and the left imageID.
- _, _, diffID := g.getOrderedDiffID(leftImgDigest, rightImgDigest)
- imagePath := fmt.Sprintf("%s.%s", diffID, IMG_EXTENSION)
-
- return fileutil.TwoLevelRadixPath(imagePath)
-}
-
-// ImagePaths implements the DiffStoreMapper interface.
-func (g GoldDiffStoreMapper) ImagePaths(imageID types.Digest) (string, string, string) {
- gsPath := fmt.Sprintf("%s.%s", imageID, IMG_EXTENSION)
- localPath := fileutil.TwoLevelRadixPath(gsPath)
- return localPath, "", gsPath
-}
-
-// IsValidDiffImgIDimplements the DiffStoreMapper interface.
-func (g GoldDiffStoreMapper) IsValidDiffImgID(diffImgID string) bool {
- imageIDs := strings.Split(diffImgID, DIFF_IMG_SEPARATOR)
- if len(imageIDs) != 2 {
- return false
- }
- return g.IsValidImgID(imageIDs[0]) && g.IsValidImgID(imageIDs[1])
-}
-
-// IsValidImgIDimplements the DiffStoreMapper interface.
-func (g GoldDiffStoreMapper) IsValidImgID(imgID string) bool {
- return validation.IsValidDigest(imgID)
-}
-
-func (g GoldDiffStoreMapper) getOrderedDiffID(leftImgID, rightImgID types.Digest) (types.Digest, types.Digest, string) {
- if rightImgID < leftImgID {
- // Make sure the smaller digest is left imageID.
- leftImgID, rightImgID = rightImgID, leftImgID
- }
- return leftImgID, rightImgID, string(leftImgID) + DIFF_IMG_SEPARATOR + string(rightImgID)
-}
diff --git a/golden/go/diffstore/mapper/disk_mapper/disk_mapper.go b/golden/go/diffstore/mapper/disk_mapper/disk_mapper.go
new file mode 100644
index 0000000..d846ede
--- /dev/null
+++ b/golden/go/diffstore/mapper/disk_mapper/disk_mapper.go
@@ -0,0 +1,57 @@
+package disk_mapper
+
+import (
+ "fmt"
+ "image"
+
+ "go.skia.org/infra/go/fileutil"
+ "go.skia.org/infra/go/util"
+ "go.skia.org/infra/golden/go/diff"
+ "go.skia.org/infra/golden/go/diffstore/mapper"
+ "go.skia.org/infra/golden/go/types"
+)
+
+const (
+ imgExtension = "png"
+)
+
+// DiskMapper implements the Mapper interface.
+// It uses diff.DiffMetrics as the Gold diff metric.
+// It stores the images and diffs on disk using a two
+// level radix prefix (i.e. for digest "abcdefg.png", the
+// image will be in ab/cd/abcdefg.png). The use of the radix
+// allows us to work around limits in Linux of how many files
+// can be in a folder.
+type DiskMapper struct {
+ util.LRUCodec
+}
+
+// New returns a new instance of DiskMapper that uses
+// a JSON coded to serialize/deserialize instances of diff.DiffMetrics.
+func New(diffInstance interface{}) *DiskMapper {
+ return &DiskMapper{LRUCodec: util.JSONCodec(diffInstance)}
+}
+
+// DiffFn implements the DiffStoreMapper interface.
+func (g *DiskMapper) DiffFn(left *image.NRGBA, right *image.NRGBA) (interface{}, *image.NRGBA) {
+ return diff.DefaultDiffFn(left, right)
+}
+
+// SplitDiffID implements the DiffStoreMapper interface.
+func (g *DiskMapper) DiffPath(left, right types.Digest) string {
+ // Get the diff ID and the left imageID.
+ diffID := mapper.DiffID(left, right)
+ imagePath := fmt.Sprintf("%s.%s", diffID, imgExtension)
+
+ return fileutil.TwoLevelRadixPath(imagePath)
+}
+
+// ImagePaths implements the DiffStoreMapper interface.
+func (g *DiskMapper) ImagePaths(imageID types.Digest) (string, string) {
+ gsPath := fmt.Sprintf("%s.%s", imageID, imgExtension)
+ localPath := fileutil.TwoLevelRadixPath(gsPath)
+ return localPath, gsPath
+}
+
+// Make sure DiskMapper fulfills the Mapper interface
+var _ mapper.Mapper = (*DiskMapper)(nil)
diff --git a/golden/go/diffstore/mapper/disk_mapper/disk_mapper_test.go b/golden/go/diffstore/mapper/disk_mapper/disk_mapper_test.go
new file mode 100644
index 0000000..8ca5caa
--- /dev/null
+++ b/golden/go/diffstore/mapper/disk_mapper/disk_mapper_test.go
@@ -0,0 +1,43 @@
+package disk_mapper
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "go.skia.org/infra/go/testutils/unittest"
+ "go.skia.org/infra/golden/go/diffstore/testutils"
+ "go.skia.org/infra/golden/go/types"
+)
+
+const (
+ // Arbitrary MD5 digests
+ imgOne = types.Digest("098f6bcd4621d373cade4e832627b4f6")
+ imgTwo = types.Digest("1660f0783f4076284bc18c5f4bdc9608")
+
+ exampleDiffId = "098f6bcd4621d373cade4e832627b4f6-1660f0783f4076284bc18c5f4bdc9608"
+
+ // PNG extension.
+ png = ".png"
+)
+
+func TestDiffPath(t *testing.T) {
+ unittest.SmallTest(t)
+
+ dm := New(&testutils.DummyDiffMetrics{})
+
+ actualDiffPath := dm.DiffPath(imgOne, imgTwo)
+ expectedPath := "09/8f/" + exampleDiffId + png
+ assert.Equal(t, expectedPath, actualDiffPath)
+}
+
+func TestImagePaths(t *testing.T) {
+ unittest.SmallTest(t)
+
+ dm := New(&testutils.DummyDiffMetrics{})
+
+ expectedLocalPath := "09/8f/" + string(imgOne) + png
+ expectedGSPath := string(imgOne + png)
+ localPath, gsPath := dm.ImagePaths(imgOne)
+ assert.Equal(t, expectedLocalPath, localPath)
+ assert.Equal(t, expectedGSPath, gsPath)
+}
diff --git a/golden/go/diffstore/mapper/mapper.go b/golden/go/diffstore/mapper/mapper.go
new file mode 100644
index 0000000..757707b
--- /dev/null
+++ b/golden/go/diffstore/mapper/mapper.go
@@ -0,0 +1,79 @@
+package mapper
+
+import (
+ "image"
+ "strings"
+
+ "go.skia.org/infra/go/util"
+ "go.skia.org/infra/golden/go/types"
+ "go.skia.org/infra/golden/go/validation"
+)
+
+// Mapper is the interface to define how the diff metric between two images
+// is calculated and defines how those diffs are stored on disk.
+// TODO(kjlubick): It might be nice to have Mapper just focus on the
+// diff metric and have a different interface for the disk storing.
+// Or make the interface just serve the bytes (if possible).
+type Mapper interface {
+ // LRUCodec defines the Encode and Decode functions to serialize/deserialize
+ // instances of the diff metrics returned by the DiffFn function below.
+ util.LRUCodec
+
+ // DiffFn calculates the different between two given images and returns a
+ // difference image. The type underlying interface{} is the input and output
+ // of the LRUCodec above. It is also what is returned by the Get(...) function
+ // of the DiffStore interface.
+ DiffFn(*image.NRGBA, *image.NRGBA) (interface{}, *image.NRGBA)
+
+ // DiffPath returns the local file path for the diff image of two images.
+ // This path is used to store the diff image on disk and serve it over HTTP.
+ DiffPath(left, right types.Digest) string
+
+ // ImagePaths returns the storage paths for a given image ID. The first return
+ // value is the local file path used to store the image on disk and serve it
+ // over HTTP. The second return value is the GCS path (not including the bucket).
+ ImagePaths(id types.Digest) (string, string)
+}
+
+const (
+ // DiffImageSeparator is the character that separates two image ids in the
+ // resulting diff image.
+ DiffImageSeparator = "-"
+)
+
+// Takes two image IDs and returns a unique diff ID.
+// Note: DiffID(a,b) == DiffID(b, a) holds.
+func DiffID(left, right types.Digest) string {
+ _, _, diffID := getOrderedDiffID(left, right)
+ return diffID
+}
+
+// Inverse function of DiffID.
+// SplitDiffID(DiffID(a,b)) deterministically returns (a,b) or (b,a).
+func SplitDiffID(diffID string) (types.Digest, types.Digest) {
+ imageIDs := strings.Split(diffID, DiffImageSeparator)
+
+ return types.Digest(imageIDs[0]), types.Digest(imageIDs[1])
+}
+
+// IsValidDiffImgID returns true if the given diffImgID is in the correct format.
+func IsValidDiffImgID(diffID string) bool {
+ imageIDs := strings.Split(diffID, DiffImageSeparator)
+ if len(imageIDs) != 2 {
+ return false
+ }
+ return IsValidImgID(imageIDs[0]) && IsValidImgID(imageIDs[1])
+}
+
+// IsValidImgID returns true if the given imgID is in the correct format.
+func IsValidImgID(imgID string) bool {
+ return validation.IsValidDigest(imgID)
+}
+
+func getOrderedDiffID(left, right types.Digest) (types.Digest, types.Digest, string) {
+ if right < left {
+ // Make sure the smaller digest is left imageID.
+ left, right = right, left
+ }
+ return left, right, string(left) + DiffImageSeparator + string(right)
+}
diff --git a/golden/go/diffstore/mapper/mapper_test.go b/golden/go/diffstore/mapper/mapper_test.go
new file mode 100644
index 0000000..b85c621
--- /dev/null
+++ b/golden/go/diffstore/mapper/mapper_test.go
@@ -0,0 +1,48 @@
+package mapper
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "go.skia.org/infra/go/testutils/unittest"
+ "go.skia.org/infra/golden/go/types"
+)
+
+const (
+ // Arbitrary MD5 digests
+ imgOne = types.Digest("098f6bcd4621d373cade4e832627b4f6")
+ imgTwo = types.Digest("1660f0783f4076284bc18c5f4bdc9608")
+
+ exampleDiffID = "098f6bcd4621d373cade4e832627b4f6-1660f0783f4076284bc18c5f4bdc9608"
+)
+
+func TestDiffID(t *testing.T) {
+ unittest.SmallTest(t)
+
+ diOne := DiffID(imgOne, imgTwo)
+ diTwo := DiffID(imgTwo, imgOne)
+ assert.Equal(t, diOne, diTwo)
+ assert.Equal(t, exampleDiffID, diOne)
+
+ assert.True(t, IsValidDiffImgID(diOne))
+ assert.False(t, IsValidDiffImgID("nope"))
+ assert.False(t, IsValidDiffImgID(string(imgOne)))
+}
+
+func TestSplitDiffID(t *testing.T) {
+ unittest.SmallTest(t)
+
+ actualDiffID := DiffID(imgOne, imgTwo)
+ actualLeft, actualRight := SplitDiffID(exampleDiffID)
+ assert.Equal(t, exampleDiffID, actualDiffID)
+ assert.Equal(t, imgOne, actualLeft)
+ assert.Equal(t, imgTwo, actualRight)
+}
+
+func TestIsValidImgID(t *testing.T) {
+ unittest.SmallTest(t)
+
+ assert.True(t, IsValidImgID(string(imgOne)))
+ assert.True(t, IsValidImgID(string(imgTwo)))
+ assert.False(t, IsValidImgID("nope"))
+}
diff --git a/golden/go/diffstore/mapper_test.go b/golden/go/diffstore/mapper_test.go
deleted file mode 100644
index 56e1ea3..0000000
--- a/golden/go/diffstore/mapper_test.go
+++ /dev/null
@@ -1,87 +0,0 @@
-package diffstore
-
-import (
- "path"
- "testing"
-
- assert "github.com/stretchr/testify/require"
- "go.skia.org/infra/go/testutils"
- "go.skia.org/infra/go/testutils/unittest"
- "go.skia.org/infra/golden/go/types"
-)
-
-const (
- // Test digests for GoldDiffStoreMapper.
- TEST_GOLD_LEFT = types.Digest("098f6bcd4621d373cade4e832627b4f6")
- TEST_GOLD_RIGHT = types.Digest("1660f0783f4076284bc18c5f4bdc9608")
-
- // PNG extension.
- DOT_EXT = ".png"
-)
-
-func TestGoldDiffStoreMapper(t *testing.T) {
- unittest.SmallTest(t)
-
- mapper := GoldDiffStoreMapper{}
-
- // Test DiffID and SplitDiffID
- expectedDiffID := string(TEST_GOLD_LEFT + DIFF_IMG_SEPARATOR + TEST_GOLD_RIGHT)
- actualDiffID := mapper.DiffID(TEST_GOLD_LEFT, TEST_GOLD_RIGHT)
- actualLeft, actualRight := mapper.SplitDiffID(expectedDiffID)
- assert.Equal(t, expectedDiffID, actualDiffID)
- assert.Equal(t, TEST_GOLD_LEFT, actualLeft)
- assert.Equal(t, TEST_GOLD_RIGHT, actualRight)
-
- // Test DiffPath
- twoLevelRadix := TEST_GOLD_LEFT[0:2] + "/" + TEST_GOLD_LEFT[2:4] + "/"
- expectedDiffPath := string(twoLevelRadix + TEST_GOLD_LEFT + "-" +
- TEST_GOLD_RIGHT + DOT_EXT)
- actualDiffPath := mapper.DiffPath(TEST_GOLD_LEFT, TEST_GOLD_RIGHT)
- assert.Equal(t, expectedDiffPath, actualDiffPath)
-
- // Test ImagePaths
- expectedLocalPath := string(twoLevelRadix + TEST_GOLD_LEFT + DOT_EXT)
- expectedGSPath := string(TEST_GOLD_LEFT + DOT_EXT)
- localPath, bucket, gsPath := mapper.ImagePaths(TEST_GOLD_LEFT)
- assert.Equal(t, expectedLocalPath, localPath)
- assert.Equal(t, "", bucket)
- assert.Equal(t, expectedGSPath, gsPath)
-
- // Test IsValidDiffImgID
- // Trim the two level radix path and image extension first
- expectedDiffImgID := expectedDiffPath[len(twoLevelRadix) : len(expectedDiffPath)-len(DOT_EXT)]
- assert.True(t, mapper.IsValidDiffImgID(expectedDiffImgID))
-
- // Test IsValidImgID
- assert.True(t, mapper.IsValidImgID(string(TEST_GOLD_LEFT)))
- assert.True(t, mapper.IsValidImgID(string(TEST_GOLD_RIGHT)))
-}
-
-func TestCodec(t *testing.T) {
- unittest.MediumTest(t)
-
- w, cleanup := testutils.TempDir(t)
- defer cleanup()
- baseDir := path.Join(w, TEST_DATA_BASE_DIR+"-codec")
- client, _ := getSetupAndTile(t, baseDir)
-
- // Instantiate a new MemDiffStore with a codec for the test struct defined above.
- mapper := NewGoldDiffStoreMapper(&DummyDiffMetrics{})
- diffStore, err := NewMemDiffStore(client, baseDir, []string{TEST_GCS_BUCKET_NAME}, TEST_GCS_IMAGE_DIR, 10, mapper)
- assert.NoError(t, err)
- memDiffStore := diffStore.(*MemDiffStore)
-
- diffID := mapper.DiffID(TEST_GOLD_LEFT, TEST_GOLD_RIGHT)
- diffMetrics := &DummyDiffMetrics{
- NumDiffPixels: 100,
- PercentDiffPixels: 0.5,
- }
- err = memDiffStore.metricsStore.saveDiffMetrics(diffID, diffMetrics)
- assert.NoError(t, err)
-
- // Verify the returned diff metrics object has the same type and same contents
- // as the object that was saved to the metricsStore.
- metrics, err := memDiffStore.metricsStore.loadDiffMetrics(diffID)
- assert.NoError(t, err)
- assert.Equal(t, diffMetrics, metrics)
-}
diff --git a/golden/go/diffstore/mem_diffstore.go b/golden/go/diffstore/mem_diffstore.go
index 2e51fdb..9452542 100644
--- a/golden/go/diffstore/mem_diffstore.go
+++ b/golden/go/diffstore/mem_diffstore.go
@@ -11,11 +11,16 @@
"sync"
"go.skia.org/infra/go/fileutil"
+ "go.skia.org/infra/go/metrics2"
"go.skia.org/infra/go/rtcache"
"go.skia.org/infra/go/skerr"
"go.skia.org/infra/go/sklog"
"go.skia.org/infra/go/util"
"go.skia.org/infra/golden/go/diff"
+ "go.skia.org/infra/golden/go/diffstore/common"
+ "go.skia.org/infra/golden/go/diffstore/mapper"
+ "go.skia.org/infra/golden/go/diffstore/metricsstore"
+ "go.skia.org/infra/golden/go/diffstore/metricsstore/bolt_metricsstore"
"go.skia.org/infra/golden/go/types"
)
@@ -60,13 +65,13 @@
imgLoader *ImageLoader
// metricsStore persists diff metrics.
- metricsStore *metricsStore
+ metricsStore metricsstore.MetricsStore
// wg is used to synchronize background operations like saving files. Used for testing.
wg sync.WaitGroup
// mapper contains various functions for creating image IDs, paths and diff metrics.
- mapper DiffStoreMapper
+ mapper mapper.Mapper
// maxGoRoutinesCh is a buffered channel that is used to limit the number of goroutines for diffing.
maxGoRoutinesCh chan bool
@@ -79,7 +84,7 @@
// If diffFn is not specified, the diff.DefaultDiffFn will be used. If codec is
// not specified, a JSON codec for the diff.DiffMetrics struct will be used.
// If mapper is not specified, GoldIDPathMapper will be used.
-func NewMemDiffStore(client *http.Client, baseDir string, gsBucketNames []string, gsImageBaseDir string, gigs int, mapper DiffStoreMapper) (diff.DiffStore, error) {
+func NewMemDiffStore(client *http.Client, baseDir string, gsBucketNames []string, gsImageBaseDir string, gigs int, m mapper.Mapper) (diff.DiffStore, error) {
imageCacheCount, diffCacheCount := getCacheCounts(gigs)
imgPath := filepath.Join(baseDir, DEFAULT_IMG_DIR_NAME)
@@ -95,12 +100,12 @@
}
// Set up image retrieval, caching and serving.
- imgLoader, err := NewImgLoader(client, baseDir, imgDir, gsBucketNames, gsImageBaseDir, imageCacheCount, mapper)
+ imgLoader, err := NewImgLoader(client, baseDir, imgDir, gsBucketNames, gsImageBaseDir, imageCacheCount, m)
if err != nil {
return nil, skerr.Fmt("Could not create img loader %s", err)
}
- mStore, err := newMetricsStore(baseDir, mapper, mapper)
+ mStore, err := bolt_metricsstore.New(baseDir, m)
if err != nil {
return nil, skerr.Fmt("Could not create metrics store %s", err)
}
@@ -110,7 +115,7 @@
localDiffDir: difDir,
imgLoader: imgLoader,
metricsStore: mStore,
- mapper: mapper,
+ mapper: m,
maxGoRoutinesCh: make(chan bool, maxGoRoutines),
}
@@ -120,13 +125,6 @@
return ret, nil
}
-// TODO(stephana): Remove the conversion below once all caches are fixed.
-
-// ConvertLegacy converts the legacy metrics cache to the new format in the background.
-func (d *MemDiffStore) ConvertLegacy() {
- d.metricsStore.convertDatabaseFromLegacy()
-}
-
// WarmDigests fetches images based on the given list of digests. It does
// not cache the images but makes sure they are downloaded from GCS.
func (d *MemDiffStore) WarmDigests(priority int64, digests types.DigestSlice, sync bool) {
@@ -146,7 +144,7 @@
// with varying priority (ignored vs "regular") we can call this multiple times.
func (d *MemDiffStore) WarmDiffs(priority int64, leftDigests types.DigestSlice, rightDigests types.DigestSlice) {
priority = rtcache.PriorityTimeCombined(priority)
- diffIDs := d.getDiffIds(leftDigests, rightDigests)
+ diffIDs := getDiffIds(leftDigests, rightDigests)
sklog.Infof("Warming %d diffs", len(diffIDs))
d.wg.Add(len(diffIDs))
for _, id := range diffIDs {
@@ -189,7 +187,7 @@
wg.Done()
<-d.maxGoRoutinesCh
}()
- id := d.mapper.DiffID(mainDigest, right)
+ id := mapper.DiffID(mainDigest, right)
ret, err := d.diffMetricsCache.Get(priority, id)
if err != nil {
sklog.Errorf("Unable to calculate diff for %s. Got error: %s", id, err)
@@ -207,7 +205,7 @@
// UnavailableDigests implements the DiffStore interface.
func (m *MemDiffStore) UnavailableDigests() map[types.Digest]*diff.DigestFailure {
- return m.imgLoader.failureStore.unavailableDigests()
+ return m.imgLoader.failureStore.UnavailableDigests()
}
// PurgeDigests implements the DiffStore interface.
@@ -231,18 +229,18 @@
}
removeKeys := make([]string, 0, len(digests))
for _, key := range m.diffMetricsCache.Keys() {
- d1, d2 := m.mapper.SplitDiffID(key)
+ d1, d2 := mapper.SplitDiffID(key)
if digestSet[d1] || digestSet[d2] {
removeKeys = append(removeKeys, key)
}
}
m.diffMetricsCache.Remove(removeKeys)
- if err := m.metricsStore.purgeDiffMetrics(digests); err != nil {
+ if err := m.metricsStore.PurgeDiffMetrics(digests); err != nil {
return err
}
- return m.imgLoader.failureStore.purgeDigestFailures(digests)
+ return m.imgLoader.failureStore.PurgeDigestFailures(digests)
}
// ImageHandler implements the DiffStore interface.
@@ -252,7 +250,7 @@
return nil, fmt.Errorf("Unable to get abs path of %s. Got error: %s", m.baseDir, err)
}
- dotExt := "." + IMG_EXTENSION
+ dotExt := "." + common.IMG_EXTENSION
// Setup the file server and define the handler function.
fileServer := http.FileServer(http.Dir(absPath))
@@ -284,7 +282,7 @@
var localRelPath string
if dir == DEFAULT_IMG_DIR_NAME {
// Validate the requested image ID.
- if !m.mapper.IsValidImgID(imgID) {
+ if !mapper.IsValidImgID(imgID) {
noCacheNotFound(w, r)
return
}
@@ -302,15 +300,15 @@
// Wait until the images are written to disk.
pendingWrites.Wait()
}
- localRelPath, _, _ = m.mapper.ImagePaths(imgDigest)
+ localRelPath, _ = m.mapper.ImagePaths(imgDigest)
} else {
// Validate the requested diff image ID.
- if !m.mapper.IsValidDiffImgID(imgID) {
+ if !mapper.IsValidDiffImgID(imgID) {
noCacheNotFound(w, r)
return
}
- localRelPath = m.mapper.DiffPath(m.mapper.SplitDiffID(imgID))
+ localRelPath = m.mapper.DiffPath(mapper.SplitDiffID(imgID))
}
// Rewrite the path to include the mapper's custom local path construction
@@ -336,10 +334,11 @@
// diffMetricsWorker calculates the diff if it's not in the cache.
func (d *MemDiffStore) diffMetricsWorker(priority int64, id string) (interface{}, error) {
- leftDigest, rightDigest := d.mapper.SplitDiffID(id)
+ defer metrics2.FuncTimer().Stop()
+ leftDigest, rightDigest := mapper.SplitDiffID(id)
// Load it from disk cache if necessary.
- if dm, err := d.metricsStore.loadDiffMetrics(id); err != nil {
+ if dm, err := d.metricsStore.LoadDiffMetrics(id); err != nil {
sklog.Errorf("Error trying to load diff metric: %s", err)
} else if dm != nil {
return dm, nil
@@ -358,7 +357,7 @@
// Encode the result image and save it to disk. If encoding causes an error
// we return an error.
var buf bytes.Buffer
- if err = encodeImg(&buf, diffImg); err != nil {
+ if err = common.EncodeImg(&buf, diffImg); err != nil {
return nil, err
}
@@ -376,7 +375,7 @@
d.wg.Done()
<-d.maxGoRoutinesCh
}()
- if err := d.metricsStore.saveDiffMetrics(diffID, diffMetrics); err != nil {
+ if err := d.metricsStore.SaveDiffMetrics(diffID, diffMetrics); err != nil {
sklog.Errorf("Error saving diff metric: %s", err)
}
}()
@@ -390,7 +389,7 @@
// Get the local file path using the mapper and save the diff image there.
localDiffPath := d.mapper.DiffPath(leftDigest, rightDigest)
- if err := saveFilePath(filepath.Join(d.localDiffDir, localDiffPath), bytes.NewBuffer(imgBytes)); err != nil {
+ if err := common.SaveFilePath(filepath.Join(d.localDiffDir, localDiffPath), bytes.NewBuffer(imgBytes)); err != nil {
sklog.Error(err)
}
}()
@@ -398,13 +397,13 @@
// Returns all combinations of leftDigests and rightDigests using the given
// DiffID function of the DiffStore's mapper.
-func (d *MemDiffStore) getDiffIds(leftDigests, rightDigests types.DigestSlice) []string {
+func getDiffIds(leftDigests, rightDigests types.DigestSlice) []string {
// reminder: diffIds are "digest1:digest2"
diffIDsSet := make(util.StringSet, len(leftDigests)*len(rightDigests))
for _, left := range leftDigests {
for _, right := range rightDigests {
if left != right {
- diffIDsSet[d.mapper.DiffID(left, right)] = true
+ diffIDsSet[mapper.DiffID(left, right)] = true
}
}
}
diff --git a/golden/go/diffstore/diffstore_test.go b/golden/go/diffstore/mem_diffstore_test.go
similarity index 60%
rename from golden/go/diffstore/diffstore_test.go
rename to golden/go/diffstore/mem_diffstore_test.go
index 69711de..bf17dce 100644
--- a/golden/go/diffstore/diffstore_test.go
+++ b/golden/go/diffstore/mem_diffstore_test.go
@@ -2,9 +2,6 @@
import (
"fmt"
- "image"
- "net"
- "net/http/httptest"
"path"
"path/filepath"
"sort"
@@ -17,8 +14,10 @@
"go.skia.org/infra/go/tiling"
"go.skia.org/infra/go/timer"
"go.skia.org/infra/golden/go/diff"
+ "go.skia.org/infra/golden/go/diffstore/mapper"
+ "go.skia.org/infra/golden/go/diffstore/mapper/disk_mapper"
+ d_utils "go.skia.org/infra/golden/go/diffstore/testutils"
"go.skia.org/infra/golden/go/types"
- "google.golang.org/grpc"
)
const (
@@ -34,96 +33,17 @@
// Get a small tile and get them cached.
w, cleanup := testutils.TempDir(t)
defer cleanup()
- baseDir := path.Join(w, TEST_DATA_BASE_DIR+"-diffstore")
- client, tile := getSetupAndTile(t, baseDir)
+ baseDir := path.Join(w, d_utils.TEST_DATA_BASE_DIR+"-diffstore")
+ client, tile := d_utils.GetSetupAndTile(t, baseDir)
- mapper := NewGoldDiffStoreMapper(&diff.DiffMetrics{})
- diffStore, err := NewMemDiffStore(client, baseDir, []string{TEST_GCS_BUCKET_NAME}, TEST_GCS_IMAGE_DIR, 10, mapper)
+ m := disk_mapper.New(&diff.DiffMetrics{})
+ diffStore, err := NewMemDiffStore(client, baseDir, []string{d_utils.TEST_GCS_BUCKET_NAME}, d_utils.TEST_GCS_IMAGE_DIR, 10, m)
assert.NoError(t, err)
memDiffStore := diffStore.(*MemDiffStore)
testDiffStore(t, tile, baseDir, diffStore, memDiffStore)
}
-type DummyDiffStoreMapper struct {
- GoldDiffStoreMapper
-}
-
-func (d DummyDiffStoreMapper) DiffFn(leftImg *image.NRGBA, rightImg *image.NRGBA) (interface{}, *image.NRGBA) {
- return 42, nil
-}
-
-func TestDiffFn(t *testing.T) {
- unittest.LargeTest(t)
-
- w, cleanup := testutils.TempDir(t)
- defer cleanup()
- baseDir := path.Join(w, TEST_DATA_BASE_DIR+"-difffn")
- client, _ := getSetupAndTile(t, baseDir)
-
- // Instantiate a new MemDiffStore with the DummyDiffFn.
- mapper := DummyDiffStoreMapper{GoldDiffStoreMapper: NewGoldDiffStoreMapper(&diff.DiffMetrics{}).(GoldDiffStoreMapper)}
- diffStore, err := NewMemDiffStore(client, baseDir, []string{TEST_GCS_BUCKET_NAME}, TEST_GCS_IMAGE_DIR, 10, mapper)
-
- assert.NoError(t, err)
- memDiffStore := diffStore.(*MemDiffStore)
- img1 := image.NewNRGBA(image.Rect(1, 2, 3, 4))
- img2 := image.NewNRGBA(image.Rect(9, 8, 7, 6))
-
- // Check that proper values are returned by the diff function.
- diffMetrics, diffImg := memDiffStore.mapper.DiffFn(img1, img2)
- assert.Equal(t, 42, diffMetrics)
- assert.Nil(t, diffImg)
-}
-
-func TestNetDiffStore(t *testing.T) {
- unittest.LargeTest(t)
-
- w, cleanup := testutils.TempDir(t)
- defer cleanup()
- baseDir := path.Join(w, TEST_DATA_BASE_DIR+"-netdiffstore")
- client, tile := getSetupAndTile(t, baseDir)
-
- mapper := NewGoldDiffStoreMapper(&diff.DiffMetrics{})
- memDiffStore, err := NewMemDiffStore(client, baseDir, []string{TEST_GCS_BUCKET_NAME}, TEST_GCS_IMAGE_DIR, 10, mapper)
- assert.NoError(t, err)
-
- // Start the server that wraps around the MemDiffStore.
- codec := MetricMapCodec{}
- serverImpl := NewDiffServiceServer(memDiffStore, codec)
- lis, err := net.Listen("tcp", "localhost:0")
- assert.NoError(t, err)
-
- // Start the grpc server.
- server := grpc.NewServer()
- RegisterDiffServiceServer(server, serverImpl)
- go func() {
- _ = server.Serve(lis)
- }()
- defer server.Stop()
-
- // Start the http server.
- imgHandler, err := memDiffStore.ImageHandler(IMAGE_URL_PREFIX)
- assert.NoError(t, err)
-
- httpServer := httptest.NewServer(imgHandler)
- defer func() { httpServer.Close() }()
-
- // Create the NetDiffStore.
- addr := lis.Addr().String()
- conn, err := grpc.Dial(addr, grpc.WithInsecure())
- assert.NoError(t, err)
- defer func() {
- assert.NoError(t, conn.Close())
- }()
-
- netDiffStore, err := NewNetDiffStore(conn, httpServer.Listener.Addr().String(), codec)
- assert.NoError(t, err)
-
- // run tests against it.
- testDiffStore(t, tile, baseDir, netDiffStore, memDiffStore.(*MemDiffStore))
-}
-
func testDiffStore(t *testing.T, tile *tiling.Tile, baseDir string, diffStore diff.DiffStore, memDiffStore *MemDiffStore) {
// Pick the test with highest number of digests.
byName := map[types.TestName]types.DigestSet{}
@@ -159,7 +79,7 @@
for _, d1 := range digests {
for _, d2 := range digests {
if d1 != d2 {
- id := memDiffStore.mapper.DiffID(d1, d2)
+ id := mapper.DiffID(d1, d2)
diffIDs = append(diffIDs, id)
assert.True(t, memDiffStore.diffMetricsCache.Contains(id))
}
@@ -176,8 +96,8 @@
// Load the diff from disk and compare.
for twoDigest, dr := range found {
- id := memDiffStore.mapper.DiffID(oneDigest, twoDigest)
- loadedDr, err := memDiffStore.metricsStore.loadDiffMetrics(id)
+ id := mapper.DiffID(oneDigest, twoDigest)
+ loadedDr, err := memDiffStore.metricsStore.LoadDiffMetrics(id)
assert.NoError(t, err)
assert.Equal(t, dr, loadedDr, "Comparing: %s", id)
}
@@ -212,9 +132,74 @@
}
}
-type DummyDiffMetrics struct {
- NumDiffPixels int
- PercentDiffPixels float32
+func TestFailureHandling(t *testing.T) {
+ unittest.MediumTest(t)
+
+ // Get a small tile and get them cached.
+ w, cleanup := testutils.TempDir(t)
+ defer cleanup()
+ baseDir := path.Join(w, d_utils.TEST_DATA_BASE_DIR+"-diffstore-failure")
+ client, tile := d_utils.GetSetupAndTile(t, baseDir)
+
+ m := disk_mapper.New(&diff.DiffMetrics{})
+ diffStore, err := NewMemDiffStore(client, baseDir, []string{d_utils.TEST_GCS_BUCKET_NAME}, d_utils.TEST_GCS_IMAGE_DIR, 10, m)
+ assert.NoError(t, err)
+
+ validDigestSet := types.DigestSet{}
+ for _, trace := range tile.Traces {
+ gTrace := trace.(*types.GoldenTrace)
+ validDigestSet.AddLists(gTrace.Digests)
+ }
+ delete(validDigestSet, types.MISSING_DIGEST)
+
+ invalidDigest_1 := types.Digest("invaliddigest1")
+ invalidDigest_2 := types.Digest("invaliddigest2")
+
+ validDigests := validDigestSet.Keys()
+ mainDigest := validDigests[0]
+ diffDigests := append(validDigests[1:6], invalidDigest_1, invalidDigest_2)
+
+ diffs, err := diffStore.Get(diff.PRIORITY_NOW, mainDigest, diffDigests)
+ assert.NoError(t, err)
+ assert.Equal(t, len(diffDigests)-2, len(diffs))
+
+ unavailableDigests := diffStore.UnavailableDigests()
+ assert.Equal(t, 2, len(unavailableDigests))
+ assert.NotNil(t, unavailableDigests[invalidDigest_1])
+ assert.NotNil(t, unavailableDigests[invalidDigest_2])
+
+ assert.NoError(t, diffStore.PurgeDigests(types.DigestSlice{invalidDigest_1, invalidDigest_2}, true))
+ unavailableDigests = diffStore.UnavailableDigests()
+ assert.Equal(t, 0, len(unavailableDigests))
+}
+
+func TestCodec(t *testing.T) {
+ unittest.MediumTest(t)
+
+ w, cleanup := testutils.TempDir(t)
+ defer cleanup()
+ baseDir := path.Join(w, d_utils.TEST_DATA_BASE_DIR+"-codec")
+ client, _ := d_utils.GetSetupAndTile(t, baseDir)
+
+ // Instantiate a new MemDiffStore with a codec for the test struct defined above.
+ m := disk_mapper.New(&d_utils.DummyDiffMetrics{})
+ diffStore, err := NewMemDiffStore(client, baseDir, []string{d_utils.TEST_GCS_BUCKET_NAME}, d_utils.TEST_GCS_IMAGE_DIR, 10, m)
+ assert.NoError(t, err)
+ memDiffStore := diffStore.(*MemDiffStore)
+
+ diffID := mapper.DiffID(types.Digest("abc"), types.Digest("def"))
+ diffMetrics := &d_utils.DummyDiffMetrics{
+ NumDiffPixels: 100,
+ PercentDiffPixels: 0.5,
+ }
+ err = memDiffStore.metricsStore.SaveDiffMetrics(diffID, diffMetrics)
+ assert.NoError(t, err)
+
+ // Verify the returned diff metrics object has the same type and same contents
+ // as the object that was saved to the metricsStore.
+ metrics, err := memDiffStore.metricsStore.LoadDiffMetrics(diffID)
+ assert.NoError(t, err)
+ assert.Equal(t, diffMetrics, metrics)
}
// Allows for sorting slices of digests by the length (longer slices first)
diff --git a/golden/go/diffstore/metrics_store.go b/golden/go/diffstore/metricsstore/bolt_metricsstore/bolt_metricsstore.go
similarity index 69%
rename from golden/go/diffstore/metrics_store.go
rename to golden/go/diffstore/metricsstore/bolt_metricsstore/bolt_metricsstore.go
index 8c7edb7..331cb08 100644
--- a/golden/go/diffstore/metrics_store.go
+++ b/golden/go/diffstore/metricsstore/bolt_metricsstore/bolt_metricsstore.go
@@ -1,4 +1,4 @@
-package diffstore
+package bolt_metricsstore
import (
"encoding/json"
@@ -10,6 +10,8 @@
"go.skia.org/infra/go/sklog"
"go.skia.org/infra/go/util"
"go.skia.org/infra/golden/go/diff"
+ "go.skia.org/infra/golden/go/diffstore/common"
+ "go.skia.org/infra/golden/go/diffstore/mapper"
"go.skia.org/infra/golden/go/types"
)
@@ -26,18 +28,14 @@
metricsRecIndices = []string{METRICS_DIGEST_INDEX}
)
-// metricsStore stores diff metrics on disk.
-type metricsStore struct {
+// BoltImpl stores diff metrics on disk.
+type BoltImpl struct {
// store stores the diff metrics in a boltdb database.
store *boltutil.IndexedBucket
// codec is used to encode/decode the DiffMetrics field of a metricsRec struct
codec util.LRUCodec
- // TODO(stephana): Remove mapper field once we don't need the legacy code anymore.
- // mapper is an instance of DiffStoreMapper.
- mapper DiffStoreMapper
-
// factory acts as the codec for metrics and is used to create instances of metricsRec.
factory *metricsRecFactory
}
@@ -89,9 +87,9 @@
return ret, nil
}
-// newMetricsStore returns a new instance of metricsStore.
-func newMetricsStore(baseDir string, mapper DiffStoreMapper, codec util.LRUCodec) (*metricsStore, error) {
- db, err := openBoltDB(baseDir, METRICSDB_NAME+".db")
+// New returns a new instance of BoltImpl.
+func New(baseDir string, codec util.LRUCodec) (*BoltImpl, error) {
+ db, err := common.OpenBoltDB(baseDir, METRICSDB_NAME+".db")
if err != nil {
return nil, err
}
@@ -117,16 +115,15 @@
return nil, err
}
- return &metricsStore{
+ return &BoltImpl{
store: store,
codec: codec,
- mapper: mapper,
factory: factoryCodec,
}, nil
}
-// loadDiffMetrics loads diff metrics from disk.
-func (m *metricsStore) loadDiffMetrics(id string) (interface{}, error) {
+// LoadDiffMetrics loads diff metrics from disk.
+func (m *BoltImpl) LoadDiffMetrics(id string) (interface{}, error) {
recs, err := m.store.Read([]string{id})
if err != nil {
return nil, err
@@ -153,8 +150,8 @@
return diffMetrics, nil
}
-// saveDiffMetrics stores diff metrics to disk.
-func (m *metricsStore) saveDiffMetrics(id string, diffMetrics interface{}) error {
+// SaveDiffMetrics stores diff metrics to disk.
+func (m *BoltImpl) SaveDiffMetrics(id string, diffMetrics interface{}) error {
// Serialize the diffMetrics.
bytes, err := m.codec.Encode(diffMetrics)
if err != nil {
@@ -169,10 +166,10 @@
return m.store.Insert([]boltutil.Record{rec})
}
-// purgeDiffMetrics removes all diff metrics based on specific digests.
-func (m *metricsStore) purgeDiffMetrics(digests types.DigestSlice) error {
+// PurgeDiffMetrics removes all diff metrics based on specific digests.
+func (m *BoltImpl) PurgeDiffMetrics(digests types.DigestSlice) error {
updateFn := func(tx *bolt.Tx) error {
- metricIDMap, err := m.store.ReadIndexTx(tx, METRICS_DIGEST_INDEX, asStrings(digests))
+ metricIDMap, err := m.store.ReadIndexTx(tx, METRICS_DIGEST_INDEX, common.AsStrings(digests))
if err != nil {
return err
}
@@ -195,7 +192,7 @@
*diff.DiffMetrics
}
-func (m *metricsStore) fixLegacyRecord(id string, recBytes []byte) *diff.DiffMetrics {
+func (m *BoltImpl) fixLegacyRecord(id string, recBytes []byte) *diff.DiffMetrics {
var newRec *diff.DiffMetrics = nil
// If we have data bytes then we just have to deserialize.
if len(recBytes) > 0 {
@@ -231,7 +228,7 @@
newRec = legRec.DiffMetrics
}
// Regenerate the diffID to filter out the old format.
- newID := m.mapper.DiffID(m.mapper.SplitDiffID(id))
+ newID := mapper.DiffID(mapper.SplitDiffID(id))
// Write the new record to the database in the background.
go func() {
@@ -241,7 +238,7 @@
}
}()
- if err := m.saveDiffMetrics(newID, newRec); err != nil {
+ if err := m.SaveDiffMetrics(newID, newRec); err != nil {
sklog.Errorf("Error writing legacy record to DB: %s", err)
}
@@ -257,58 +254,3 @@
return newRec
}
-
-// convertDatabaseFromLegacy iterates over the entire database in the background
-// and loads every entry, implicitly forcing a conversion to the new serialization format.
-func (m *metricsStore) convertDatabaseFromLegacy() {
- go func() {
- defer func() {
- if r := recover(); r != nil {
- sklog.Errorf("Recovered panic: %s", r)
- }
- }()
-
- ids, err := m.listIDs()
- if err != nil {
- sklog.Errorf("Unable to get the database ids. Got error: %s", err)
- }
-
- sklog.Infof("Processing %d diffmetric records.", len(ids))
-
- for _, id := range ids {
- // The call to loadDiffMetrics will also convert a legacy record to the new record if necessary.
- if _, err := m.loadDiffMetrics(id); err != nil {
- sklog.Errorf("Error trying to convert legacy record: %s", err)
- }
- }
- sklog.Infof("Legacy conversion: Loaded %d records.", len(ids))
- if err := m.store.ReIndex(); err != nil {
- sklog.Errorf("Error re-indexing data store: %s", err)
- }
- sklog.Infof("Legacy conversion completed.")
- }()
-}
-
-// listIDs returns a slice with all the ids in the database.
-func (m *metricsStore) listIDs() ([]string, error) {
- ret := []string{}
- viewFn := func(tx *bolt.Tx) error {
- b := tx.Bucket([]byte(METRICSDB_NAME))
- if b == nil {
- return nil
- }
-
- ret = make([]string, 0, b.Stats().KeyN)
- c := b.Cursor()
-
- for k, _ := c.First(); k != nil; k, _ = c.Next() {
- ret = append(ret, string(append([]byte(nil), k...)))
- }
- return nil
- }
-
- if err := m.store.DB.View(viewFn); err != nil {
- return nil, err
- }
- return ret, nil
-}
diff --git a/golden/go/diffstore/metricsstore/bolt_metricsstore/bolt_metricsstore_test.go b/golden/go/diffstore/metricsstore/bolt_metricsstore/bolt_metricsstore_test.go
new file mode 100644
index 0000000..31d1e64
--- /dev/null
+++ b/golden/go/diffstore/metricsstore/bolt_metricsstore/bolt_metricsstore_test.go
@@ -0,0 +1,38 @@
+package bolt_metricsstore
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "go.skia.org/infra/go/testutils"
+ "go.skia.org/infra/go/testutils/unittest"
+ "go.skia.org/infra/go/util"
+ d_utils "go.skia.org/infra/golden/go/diffstore/testutils"
+)
+
+func TestAddGet(t *testing.T) {
+ unittest.MediumTest(t)
+
+ w, cleanup := testutils.TempDir(t)
+ defer cleanup()
+
+ ms, err := New(w, util.JSONCodec(d_utils.DummyDiffMetrics{}))
+ assert.NoError(t, err)
+
+ id := "abc-def"
+
+ dm, err := ms.LoadDiffMetrics(id)
+ assert.NoError(t, err)
+ assert.Nil(t, dm)
+
+ expected := &d_utils.DummyDiffMetrics{
+ NumDiffPixels: 3,
+ PercentDiffPixels: 0.3,
+ }
+
+ assert.NoError(t, ms.SaveDiffMetrics(id, expected))
+
+ dm, err = ms.LoadDiffMetrics(id)
+ assert.NoError(t, err)
+ assert.Equal(t, expected, dm)
+}
diff --git a/golden/go/diffstore/metricsstore/metricsstore.go b/golden/go/diffstore/metricsstore/metricsstore.go
new file mode 100644
index 0000000..8405832
--- /dev/null
+++ b/golden/go/diffstore/metricsstore/metricsstore.go
@@ -0,0 +1,14 @@
+package metricsstore
+
+import "go.skia.org/infra/golden/go/types"
+
+type MetricsStore interface {
+ // PurgeDiffMetrics removes all diff metrics based on specific digests.
+ PurgeDiffMetrics(digests types.DigestSlice) error
+
+ // SaveDiffMetrics stores diff metrics.
+ SaveDiffMetrics(id string, diffMetrics interface{}) error
+
+ // LoadDiffMetrics loads diff metrics from disk.
+ LoadDiffMetrics(id string) (interface{}, error)
+}
diff --git a/golden/go/diffstore/net_diffstore.go b/golden/go/diffstore/net_diffstore.go
index 6c5d230..e0fa562 100644
--- a/golden/go/diffstore/net_diffstore.go
+++ b/golden/go/diffstore/net_diffstore.go
@@ -10,6 +10,7 @@
"go.skia.org/infra/go/sklog"
"go.skia.org/infra/go/util"
"go.skia.org/infra/golden/go/diff"
+ "go.skia.org/infra/golden/go/diffstore/common"
"go.skia.org/infra/golden/go/types"
"google.golang.org/grpc"
)
@@ -44,7 +45,7 @@
// Get, see the diff.DiffStore interface.
func (n *NetDiffStore) Get(priority int64, mainDigest types.Digest, rightDigests types.DigestSlice) (map[types.Digest]interface{}, error) {
- req := &GetDiffsRequest{Priority: priority, MainDigest: string(mainDigest), RightDigests: asStrings(rightDigests)}
+ req := &GetDiffsRequest{Priority: priority, MainDigest: string(mainDigest), RightDigests: common.AsStrings(rightDigests)}
resp, err := n.serviceClient.GetDiffs(context.Background(), req)
if err != nil {
return nil, err
@@ -74,7 +75,7 @@
// WarmDigests, see the diff.DiffStore interface.
func (n *NetDiffStore) WarmDigests(priority int64, digests types.DigestSlice, sync bool) {
- req := &WarmDigestsRequest{Priority: priority, Digests: asStrings(digests), Sync: sync}
+ req := &WarmDigestsRequest{Priority: priority, Digests: common.AsStrings(digests), Sync: sync}
_, err := n.serviceClient.WarmDigests(context.Background(), req)
if err != nil {
sklog.Errorf("Error warming digests: %s", err)
@@ -83,7 +84,7 @@
// WarmDiffs, see the diff.DiffStore interface.
func (n *NetDiffStore) WarmDiffs(priority int64, leftDigests types.DigestSlice, rightDigests types.DigestSlice) {
- req := &WarmDiffsRequest{Priority: priority, LeftDigests: asStrings(leftDigests), RightDigests: asStrings(rightDigests)}
+ req := &WarmDiffsRequest{Priority: priority, LeftDigests: common.AsStrings(leftDigests), RightDigests: common.AsStrings(rightDigests)}
_, err := n.serviceClient.WarmDiffs(context.Background(), req)
if err != nil {
sklog.Errorf("Error warming diffs: %s", err)
@@ -110,7 +111,7 @@
// PurgeDigests, see the diff.DiffStore interface.
func (n *NetDiffStore) PurgeDigests(digests types.DigestSlice, purgeGCS bool) error {
- req := &PurgeDigestsRequest{Digests: asStrings(digests), PurgeGCS: purgeGCS}
+ req := &PurgeDigestsRequest{Digests: common.AsStrings(digests), PurgeGCS: purgeGCS}
_, err := n.serviceClient.PurgeDigests(context.Background(), req)
if err != nil {
return fmt.Errorf("Error purging digests: %s", err)
diff --git a/golden/go/diffstore/net_diffstore_test.go b/golden/go/diffstore/net_diffstore_test.go
new file mode 100644
index 0000000..4fac853
--- /dev/null
+++ b/golden/go/diffstore/net_diffstore_test.go
@@ -0,0 +1,64 @@
+package diffstore
+
+import (
+ "net"
+ "net/http/httptest"
+ "path"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "go.skia.org/infra/go/testutils"
+ "go.skia.org/infra/go/testutils/unittest"
+ "go.skia.org/infra/golden/go/diff"
+ "go.skia.org/infra/golden/go/diffstore/mapper/disk_mapper"
+ d_utils "go.skia.org/infra/golden/go/diffstore/testutils"
+ "google.golang.org/grpc"
+)
+
+func TestNetDiffStore(t *testing.T) {
+ unittest.LargeTest(t)
+
+ w, cleanup := testutils.TempDir(t)
+ defer cleanup()
+ baseDir := path.Join(w, d_utils.TEST_DATA_BASE_DIR+"-netdiffstore")
+ client, tile := d_utils.GetSetupAndTile(t, baseDir)
+
+ m := disk_mapper.New(&diff.DiffMetrics{})
+ memDiffStore, err := NewMemDiffStore(client, baseDir, []string{d_utils.TEST_GCS_BUCKET_NAME}, d_utils.TEST_GCS_IMAGE_DIR, 10, m)
+ assert.NoError(t, err)
+
+ // Start the server that wraps around the MemDiffStore.
+ codec := MetricMapCodec{}
+ serverImpl := NewDiffServiceServer(memDiffStore, codec)
+ lis, err := net.Listen("tcp", "localhost:0")
+ assert.NoError(t, err)
+
+ // Start the grpc server.
+ server := grpc.NewServer()
+ RegisterDiffServiceServer(server, serverImpl)
+ go func() {
+ _ = server.Serve(lis)
+ }()
+ defer server.Stop()
+
+ // Start the http server.
+ imgHandler, err := memDiffStore.ImageHandler(IMAGE_URL_PREFIX)
+ assert.NoError(t, err)
+
+ httpServer := httptest.NewServer(imgHandler)
+ defer func() { httpServer.Close() }()
+
+ // Create the NetDiffStore.
+ addr := lis.Addr().String()
+ conn, err := grpc.Dial(addr, grpc.WithInsecure())
+ assert.NoError(t, err)
+ defer func() {
+ assert.NoError(t, conn.Close())
+ }()
+
+ netDiffStore, err := NewNetDiffStore(conn, httpServer.Listener.Addr().String(), codec)
+ assert.NoError(t, err)
+
+ // run tests against it.
+ testDiffStore(t, tile, baseDir, netDiffStore, memDiffStore.(*MemDiffStore))
+}
diff --git a/golden/go/diffstore/shared_test.go b/golden/go/diffstore/testutils/testutils_diffstore.go
similarity index 87%
rename from golden/go/diffstore/shared_test.go
rename to golden/go/diffstore/testutils/testutils_diffstore.go
index 18d96d1..8c7e949 100644
--- a/golden/go/diffstore/shared_test.go
+++ b/golden/go/diffstore/testutils/testutils_diffstore.go
@@ -1,4 +1,4 @@
-package diffstore
+package testutils
import (
"net/http"
@@ -34,7 +34,12 @@
TEST_PATH_IMG_1 = "gold-testdata/filediffstore-testdata/10552995703607727960.png"
)
-func getSetupAndTile(t assert.TestingT, baseDir string) (*http.Client, *tiling.Tile) {
+type DummyDiffMetrics struct {
+ NumDiffPixels int
+ PercentDiffPixels float32
+}
+
+func GetSetupAndTile(t assert.TestingT, baseDir string) (*http.Client, *tiling.Tile) {
testDataPath := filepath.Join(baseDir, TEST_DATA_FILE_NAME)
assert.NoError(t, gcs_testutils.DownloadTestDataFile(t, TEST_DATA_STORAGE_BUCKET, TEST_DATA_STORAGE_PATH, testDataPath))
diff --git a/golden/go/diffstore/types.go b/golden/go/diffstore/types.go
index 037ff91..dd51e32 100644
--- a/golden/go/diffstore/types.go
+++ b/golden/go/diffstore/types.go
@@ -1,47 +1,33 @@
package diffstore
import (
- "image"
+ "encoding/json"
- "go.skia.org/infra/go/util"
+ "go.skia.org/infra/golden/go/diff"
"go.skia.org/infra/golden/go/types"
)
-// DiffStoreMapper is the interface to customize the specific behavior of MemDiffStore.
-// It defines what diff metric is calculated and how to translate image ids and diff
-// ids into paths on the file system and in GCS.
-type DiffStoreMapper interface {
- // LRUCodec defines the Encode and Decode functions to serialize/deserialize
- // instances of the diff metrics returned by the DiffFn function below.
- util.LRUCodec
+// MetricMapCodec implements the util.LRUCodec interface by serializing and
+// deserializing generic diff result structs, instances of map[string]interface{}
+type MetricMapCodec struct{}
- // DiffFn calculates the different between two given images and returns a
- // difference image. The type underlying interface{} is the input and output
- // of the LRUCodec above. It is also what is returned by the Get(...) function
- // of the DiffStore interface.
- DiffFn(*image.NRGBA, *image.NRGBA) (interface{}, *image.NRGBA)
+// See util.LRUCodec interface
+func (m MetricMapCodec) Encode(data interface{}) ([]byte, error) {
+ return json.Marshal(data)
+}
- // Takes two image IDs and returns a unique diff ID.
- // Note: DiffID(a,b) == DiffID(b, a) should hold.
- DiffID(leftImgID, rightImgID types.Digest) string
+// See util.LRUCodec interface
+func (m MetricMapCodec) Decode(byteData []byte) (interface{}, error) {
+ dm := map[types.Digest]*diff.DiffMetrics{}
+ err := json.Unmarshal(byteData, &dm)
+ if err != nil {
+ return nil, err
+ }
- // Inverse function of DiffID.
- // SplitDiffID(DiffID(a,b)) should return (a,b) or (b,a).
- SplitDiffID(diffID string) (types.Digest, types.Digest)
-
- // DiffPath returns the local file path for the diff image of two images.
- // This path is used to store the diff image on disk and serve it over HTTP.
- DiffPath(leftImgID, rightImgID types.Digest) string
-
- // ImagePaths returns the storage paths for a given image ID. The first return
- // value is the local file path used to store the image on disk and serve it
- // over HTTP. The second return value is the storage bucket and the third the
- // path within that bucket.
- ImagePaths(imageID types.Digest) (string, string, string)
-
- // IsValidDiffImgID returns true if the given diffImgID is in the correct format.
- IsValidDiffImgID(diffImgID string) bool
-
- // IsValidImgID returns true if the given imgID is in the correct format.
- IsValidImgID(imgID string) bool
+ // Must make result of deserialization generic in order to propagate
+ ret := make(map[types.Digest]interface{}, len(dm))
+ for k, metric := range dm {
+ ret[k] = metric
+ }
+ return ret, nil
}
diff --git a/golden/go/diffstore/common_test.go b/golden/go/diffstore/types_test.go
similarity index 100%
rename from golden/go/diffstore/common_test.go
rename to golden/go/diffstore/types_test.go
diff --git a/golden/go/digesttools/digesttools.go b/golden/go/digesttools/digesttools.go
index 95bbcce..7410f7a 100644
--- a/golden/go/digesttools/digesttools.go
+++ b/golden/go/digesttools/digesttools.go
@@ -98,7 +98,7 @@
} else {
for digest, diffs := range diffMetrics {
dm := diffs.(*diff.DiffMetrics)
- if delta := combinedDiffMetric(dm.PixelDiffPercent, dm.MaxRGBADiffs); delta < ret.Diff {
+ if delta := diff.CombinedDiffMetric(dm, nil, nil); delta < ret.Diff {
ret.Digest = digest
ret.Diff = delta
ret.DiffPixels = dm.PixelDiffPercent
@@ -111,32 +111,13 @@
// ClosestFromDiffMetrics returns an instance of Closest with the values of the
// given diff.DiffMetrics. The Digest field will be left empty.
-func ClosestFromDiffMetrics(diff *diff.DiffMetrics) *Closest {
+func ClosestFromDiffMetrics(dm *diff.DiffMetrics) *Closest {
return &Closest{
- Diff: combinedDiffMetric(diff.PixelDiffPercent, diff.MaxRGBADiffs),
- DiffPixels: diff.PixelDiffPercent,
- MaxRGBA: diff.MaxRGBADiffs,
+ Diff: diff.CombinedDiffMetric(dm, nil, nil),
+ DiffPixels: dm.PixelDiffPercent,
+ MaxRGBA: dm.MaxRGBADiffs,
}
}
-// combinedDiffMetric returns a value in [0, 1] that represents how large
-// the diff is between two images.
-func combinedDiffMetric(pixelDiffPercent float32, maxRGBA []int) float32 {
- if len(maxRGBA) == 0 {
- return 1.0
- }
- // Turn maxRGBA into a percent by taking the root mean square difference from
- // [0, 0, 0, 0].
- sum := 0.0
- for _, c := range maxRGBA {
- sum += float64(c) * float64(c)
- }
- normalizedRGBA := math.Sqrt(sum/float64(len(maxRGBA))) / 255.0
- // We take the sqrt of (pixelDiffPercent * normalizedRGBA) to straigten out
- // the curve, i.e. think about what a plot of x^2 would look like in the
- // range [0, 1].
- return float32(math.Sqrt(float64(pixelDiffPercent) * normalizedRGBA))
-}
-
// Make sure Impl fulfills the ClosestDiffFinder interface
var _ ClosestDiffFinder = (*Impl)(nil)
diff --git a/golden/go/digesttools/internals_test.go b/golden/go/digesttools/internals_test.go
deleted file mode 100644
index 740d67a..0000000
--- a/golden/go/digesttools/internals_test.go
+++ /dev/null
@@ -1,16 +0,0 @@
-package digesttools
-
-import (
- "math"
- "testing"
-
- assert "github.com/stretchr/testify/require"
- "go.skia.org/infra/go/testutils/unittest"
-)
-
-func TestCombinedDiffMetric(t *testing.T) {
- unittest.SmallTest(t)
- assert.InDelta(t, 1.0, combinedDiffMetric(0.0, []int{}), 0.000001)
- assert.InDelta(t, 1.0, combinedDiffMetric(1.0, []int{255, 255, 255, 255}), 0.000001)
- assert.InDelta(t, math.Sqrt(0.5), combinedDiffMetric(0.5, []int{255, 255, 255, 255}), 0.000001)
-}
diff --git a/golden/go/dsutil/dsutil.go b/golden/go/dsutil/dsutil.go
index 3082900..b895246 100644
--- a/golden/go/dsutil/dsutil.go
+++ b/golden/go/dsutil/dsutil.go
@@ -121,8 +121,7 @@
// The Added and Deleted keys are mutually exclusive, so we can only have to
// check the queried keys for recently added and deleted keys.
- var ret []*datastore.Key
- ret = make([]*datastore.Key, 0, len(r.Added)+len(queried))
+ ret := make([]*datastore.Key, 0, len(r.Added)+len(queried))
ret = append(ret, r.Added...)
for _, k := range queried {
if addMap[k.ID] == nil && delMap[k.ID] == nil {
diff --git a/golden/go/types/expectations.go b/golden/go/types/expectations.go
index 01f13f5..a4c1c97 100644
--- a/golden/go/types/expectations.go
+++ b/golden/go/types/expectations.go
@@ -39,19 +39,6 @@
// the ones provided by the passed in parameter overwrite any existing data.
func (e Expectations) MergeExpectations(other Expectations) {
for testName, digests := range other {
- if _, ok := e[testName]; !ok {
- e[testName] = map[Digest]Label{}
- }
- for digest, label := range digests {
- e[testName][digest] = label
- }
- }
-}
-
-// Update updates the current expectations with the expectations in 'right'. Any existing
-// test_name/digest pair will be overwritten.
-func (e Expectations) Update(right Expectations) {
- for testName, digests := range right {
e.AddDigests(testName, digests)
}
}
@@ -59,9 +46,7 @@
// DeepCopy makes a deep copy of the current expectations/baseline.
func (e Expectations) DeepCopy() Expectations {
ret := make(Expectations, len(e))
- for testName, digests := range e {
- ret.AddDigests(testName, digests)
- }
+ ret.MergeExpectations(e)
return ret
}