blob: 3a473db9dc3b6bb8e803f2364f42628088554ad1 [file] [log] [blame]
package cache
import (
"context"
"sort"
"strconv"
"time"
lru "github.com/hashicorp/golang-lru"
"go.skia.org/infra/go/metrics2"
"go.skia.org/infra/perf/go/anomalies"
"go.skia.org/infra/go/skerr"
"go.skia.org/infra/go/sklog"
)
// cacheSize is the size of the lru cache.
const cacheSize = 25000
// How often to cleanup the lru cache.
const cacheCleanupPeriod = time.Minute
// TTL of the lru cache items.
const cacheItemTTL = 10 * time.Minute
// store implements anomalies.Store.
type store struct {
cache *lru.Cache
// metrics
numEntriesInCache metrics2.Int64Metric
ChromePerf anomalies.Store
}
type commitAnomalyMapCacheEntry struct {
addTime time.Time // When this entry was added.
commitAnomalyMap anomalies.CommitNumberAnomalyMap
}
// New returns a new anomalies.Store instance with LRU cache.
func New(chromePerf anomalies.Store) (*store, error) {
cache, err := lru.New(cacheSize)
if err != nil {
return nil, skerr.Wrapf(err, "Failed to create anomaly store cache.")
}
// cleanup the lru cache periodically.
go func() {
for range time.Tick(cacheCleanupPeriod) {
cleanupCache(cache)
}
}()
ret := &store{
cache: cache,
numEntriesInCache: metrics2.GetInt64Metric("anomaly_store_num_entries_in_cache"),
ChromePerf: chromePerf,
}
return ret, nil
}
// cleanupCache cleans up the oldest cache items based on the cache item TTL.
func cleanupCache(cache *lru.Cache) {
for {
if cache.Len() == 0 {
return
}
_, iOldestCommitAnomalyMapCacheEntry, ok := cache.GetOldest()
if !ok {
sklog.Warningf("Failed to get the oldest cache item.")
return
}
oldestCommitAnomalyMapCacheEntry, ok := iOldestCommitAnomalyMapCacheEntry.(commitAnomalyMapCacheEntry)
if oldestCommitAnomalyMapCacheEntry.addTime.Before(time.Now().Add(-cacheItemTTL)) {
cache.RemoveOldest()
sklog.Info("Cache item %s was removed from anomaly store cache.", oldestCommitAnomalyMapCacheEntry)
} else {
return
}
}
}
// GetAnomalies implements anomalies.Store
// It fetches anomalies from cache at first, then calls chrome perf API to fetch the anomlies missing from cache.
func (as *store) GetAnomalies(ctx context.Context, traceNames []string, startCommitPosition int, endCommitPosition int) (anomalies.AnomalyMap, error) {
// Get anomalies from cache
traceNamesMissingFromCache := make([]string, 0)
result := anomalies.AnomalyMap{}
numEntriesInCache := 0
for _, traceName := range traceNames {
iCommitAnomalyMapCacheEntry, ok := as.cache.Get(getAnomalyCacheKey(traceName, startCommitPosition, endCommitPosition))
if !ok {
traceNamesMissingFromCache = append(traceNamesMissingFromCache, traceName)
continue
}
commitAnomalyMapCacheEntry, ok := iCommitAnomalyMapCacheEntry.(commitAnomalyMapCacheEntry)
result[traceName] = commitAnomalyMapCacheEntry.commitAnomalyMap
numEntriesInCache++
}
as.numEntriesInCache.Update(int64(numEntriesInCache))
if len(traceNamesMissingFromCache) == 0 {
return result, nil
}
// Get anomalies from Chrome Perf
sort.Strings(traceNamesMissingFromCache)
chromePerfAnomalies, err := as.ChromePerf.GetAnomalies(ctx, traceNamesMissingFromCache, startCommitPosition, endCommitPosition)
if err != nil {
sklog.Errorf("Failed to get chrome perf anomalies: %s", err)
} else {
for traceName, commitNumberAnomalyMap := range chromePerfAnomalies {
result[traceName] = commitNumberAnomalyMap
// Add anomalies to cache
as.cache.Add(getAnomalyCacheKey(traceName, startCommitPosition, endCommitPosition), commitAnomalyMapCacheEntry{
addTime: time.Now(),
commitAnomalyMap: commitNumberAnomalyMap,
})
}
}
return result, nil
}
// Anomaly cache key is "traceName:startCommitPosition:endCommitPosition"
func getAnomalyCacheKey(traceName string, startCommitPosition int, endCommitPosition int) string {
return traceName + ":" + strconv.Itoa(startCommitPosition) + ":" + strconv.Itoa(endCommitPosition)
}