blob: 8ead151c66bcf146143436e6ef6fe54118aadf1a [file]
package history
import (
"time"
"github.com/skia-dev/glog"
"go.skia.org/infra/go/metrics"
"go.skia.org/infra/go/timer"
"go.skia.org/infra/go/util"
"go.skia.org/infra/golden/go/digeststore"
"go.skia.org/infra/golden/go/expstorage"
"go.skia.org/infra/golden/go/storage"
"go.skia.org/infra/golden/go/types"
ptypes "go.skia.org/infra/perf/go/types"
)
// Init initializes the history module and starts background processes to
// continuously update information about digests.
// If nTilesToBackfill is larger than zero, the given number of tiles will
// be traversed to backfill information about digests.
func Init(storages *storage.Storage, nTilesToBackfill int) error {
var err error
_, err = newHistorian(storages, nTilesToBackfill)
return err
}
// historian runs background processes to update information about digests,
// i.e. recording when we encounter a digests for the first and the last time.
type historian struct {
storages *storage.Storage
}
// Create a new instance of historian.
func newHistorian(storages *storage.Storage, nTilesToBackfill int) (*historian, error) {
defer timer.New("historian").Stop()
ret := &historian{
storages: storages,
}
// Start running the background process to gather digestinfo.
if err := ret.start(); err != nil {
return nil, err
}
// There is at least one tile to backfill digestinfo then start the process.
if nTilesToBackfill > 0 {
ret.backFillDigestInfo(nTilesToBackfill)
}
return ret, nil
}
func (h *historian) start() error {
expChanges := make(chan []string)
h.storages.EventBus.SubscribeAsync(expstorage.EV_EXPSTORAGE_CHANGED, func(e interface{}) {
expChanges <- e.([]string)
})
tileStream := h.storages.GetTileStreamNow(2*time.Minute, true)
lastTile := <-tileStream
if err := h.updateDigestInfo(lastTile); err != nil {
return err
}
liveness := metrics.NewLiveness("digest-history-monitoring")
// Keep processing tiles and feed them into the process channel.
go func() {
for {
select {
case tile := <-tileStream:
if err := h.updateDigestInfo(tile); err != nil {
glog.Errorf("Error calculating status: %s", err)
continue
} else {
lastTile = tile
}
case <-expChanges:
storage.DrainChangeChannel(expChanges)
if err := h.updateDigestInfo(lastTile); err != nil {
glog.Errorf("Error calculating tile after expectation udpate: %s", err)
continue
}
}
liveness.Update()
}
}()
return nil
}
func (h *historian) updateDigestInfo(tile *ptypes.Tile) error {
return h.processTile(tile)
}
func (h *historian) backFillDigestInfo(tilesToBackfill int) {
go func() {
// Get the first tile and determine the tile id of the first tile
var err error
lastTile, err := h.storages.TileStore.Get(0, -1)
if err != nil {
glog.Errorf("Unable to retrieve last tile. Quiting backfill. Error: %s", err)
return
}
var tile *ptypes.Tile
firstTileIndex := util.MaxInt(lastTile.TileIndex-tilesToBackfill+1, 0)
for idx := firstTileIndex; idx <= lastTile.TileIndex; idx++ {
if tile, err = h.storages.TileStore.Get(0, idx); err != nil {
glog.Errorf("Unable to read tile %d from tile store. Quiting backfill. Error: %s", idx, err)
return
}
// Process the tile, but giving higher priority to digests from the
// latest tile.
if err = h.processTile(tile); err != nil {
glog.Errorf("Error processing tile: %s", err)
}
// Read the last tile, just to make sure it has not changed.
if lastTile, err = h.storages.TileStore.Get(0, -1); err != nil {
glog.Errorf("Unable to retrieve last tile. Quiting backfill. Error: %s", err)
return
}
}
}()
}
func (h *historian) processTile(tile *ptypes.Tile) error {
dStore := h.storages.DigestStore
tileLen := tile.LastCommitIndex() + 1
var digestInfo *digeststore.DigestInfo
var ok bool
counter := 0
minMaxTimes := map[string]map[string]*digeststore.DigestInfo{}
for _, trace := range tile.Traces {
gTrace := trace.(*ptypes.GoldenTrace)
testName := trace.Params()[types.PRIMARY_KEY_FIELD]
for idx, digest := range gTrace.Values[:tileLen] {
if digest != ptypes.MISSING_DIGEST {
timeStamp := tile.Commits[idx].CommitTime
if digestInfo, ok = minMaxTimes[testName][digest]; !ok {
digestInfo = &digeststore.DigestInfo{
TestName: testName,
Digest: digest,
First: timeStamp,
Last: timeStamp,
}
if testVal, ok := minMaxTimes[testName]; !ok {
minMaxTimes[testName] = map[string]*digeststore.DigestInfo{digest: digestInfo}
} else {
testVal[digest] = digestInfo
}
counter++
} else {
digestInfo.First = util.MinInt64(digestInfo.First, timeStamp)
digestInfo.Last = util.MaxInt64(digestInfo.Last, timeStamp)
}
}
}
}
digestInfos := make([]*digeststore.DigestInfo, 0, counter)
for _, digests := range minMaxTimes {
for _, digestInfo := range digests {
digestInfos = append(digestInfos, digestInfo)
}
}
return dStore.Update(digestInfos)
}