[perf] Add an interface for an LRU cache.

Also includes an implementation using golang-lru.

Change-Id: Iafc2e0eb11b486f3e4006f75b9135eae5f00385c
Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/305598
Reviewed-by: Kevin Lubick <kjlubick@google.com>
Commit-Queue: Joe Gregorio <jcgregorio@google.com>
diff --git a/perf/go/cache/cache.go b/perf/go/cache/cache.go
new file mode 100644
index 0000000..d7e71b8
--- /dev/null
+++ b/perf/go/cache/cache.go
@@ -0,0 +1,15 @@
+// Package cache defines an interface for an LRU cache.
+package cache
+
+// Cache in an interface for an LRU cache.
+type Cache interface {
+	// Add adds a value to the cache.
+	Add(key string, value interface{})
+
+	// Get looks up a key's value from the cache, returning the value and true
+	// if found, otherwise the returned bool is false.
+	Get(key string) (interface{}, bool)
+
+	// Exists returns true  if the key is found in the cache.
+	Exists(key string) bool
+}
diff --git a/perf/go/cache/local/local.go b/perf/go/cache/local/local.go
new file mode 100644
index 0000000..c44264d
--- /dev/null
+++ b/perf/go/cache/local/local.go
@@ -0,0 +1,42 @@
+// Package local implements cache.Cache with an in-memory cache.
+package local
+
+import (
+	lru "github.com/hashicorp/golang-lru"
+	"go.skia.org/infra/go/skerr"
+	"go.skia.org/infra/perf/go/cache"
+)
+
+// Cache implements the cache.Cache interface.
+type Cache struct {
+	cache *lru.Cache
+}
+
+// New returns a new in-memory cache of the given size.
+func New(size int) (*Cache, error) {
+	c, err := lru.New(size)
+	if err != nil {
+		return nil, skerr.Wrapf(err, "failed to create local cache of size: %d", size)
+	}
+	return &Cache{
+		cache: c,
+	}, nil
+}
+
+// Add implements the cache.Cache interface.
+func (c *Cache) Add(key string, value interface{}) {
+	_ = c.cache.Add(key, value)
+}
+
+// Get implements the cache.Cache interface.
+func (c *Cache) Get(key string) (interface{}, bool) {
+	return c.cache.Get(key)
+}
+
+// Exists implements the cache.Cache interface.
+func (c *Cache) Exists(key string) bool {
+	return c.cache.Contains(key)
+}
+
+// Confirm we implement the interface.
+var _ cache.Cache = (*Cache)(nil)
diff --git a/perf/go/cache/local/local_test.go b/perf/go/cache/local/local_test.go
new file mode 100644
index 0000000..39e8e8c
--- /dev/null
+++ b/perf/go/cache/local/local_test.go
@@ -0,0 +1,57 @@
+package local
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+	"go.skia.org/infra/go/testutils/unittest"
+)
+
+func TestCache_New_Failure(t *testing.T) {
+	unittest.SmallTest(t)
+	_, err := New(-12)
+	require.Error(t, err)
+}
+
+func TestCache_Get_Success(t *testing.T) {
+	unittest.SmallTest(t)
+	c, err := New(12)
+	require.NoError(t, err)
+
+	c.Add("foo", "bar")
+	got, ok := c.Get("foo")
+	assert.True(t, ok)
+	assert.Equal(t, "bar", got.(string))
+
+	_, ok = c.Get("quux")
+	assert.False(t, ok)
+}
+
+func TestCache_Get_FalseOnMiss(t *testing.T) {
+	unittest.SmallTest(t)
+	c, err := New(12)
+	require.NoError(t, err)
+
+	_, ok := c.Get("quux")
+	assert.False(t, ok)
+}
+
+func TestCache_Exists_Success(t *testing.T) {
+	unittest.SmallTest(t)
+	c, err := New(12)
+	require.NoError(t, err)
+
+	c.Add("foo", "bar")
+	ok := c.Exists("foo")
+	assert.True(t, ok)
+}
+
+func TestCache_Exists_FalseOnMiss(t *testing.T) {
+	unittest.SmallTest(t)
+	c, err := New(12)
+	require.NoError(t, err)
+
+	ok := c.Exists("foo")
+	assert.False(t, ok)
+}