blob: e1fa47e8218916b7d0709615a2e4587316c9c149 [file] [log] [blame]
package db
import (
"context"
"fmt"
"sync"
"time"
"go.skia.org/infra/go/eventbus"
"go.skia.org/infra/go/git/gitinfo"
"go.skia.org/infra/go/metrics2"
"go.skia.org/infra/go/sklog"
"go.skia.org/infra/go/tiling"
"go.skia.org/infra/golden/go/serialize"
)
const (
TILE_REFRESH_DURATION = 5 * time.Minute
NEW_TILE_AVAILABLE_EVENT = "new-tile-available-event"
)
// MasterTileBuilder continously loads Tiles from a trace/db.DB.
type MasterTileBuilder interface {
// GetTile returns the most recently loaded Tile.
GetTile() *tiling.Tile
}
// Impelementation of MasterTilebuilder
type masterTileBuilder struct {
// db is used to construct tiles.
db DB
// tile is the last successfully loaded Tile.
tile *tiling.Tile
// mutex protects access to tile.
mutex sync.Mutex
// tileSize is the number of commits that should be in the default Tile.
tileSize int
// git is the Git repo the commits come from.
git *gitinfo.GitInfo
// evt is the eventbus where we announce the availability of new tiles.
evt eventbus.EventBus
// cachePath is the path to the file where tiles are cahed for quick restarts.
cachePath string
}
// NewBuilder creates a new Builder given the gitinfo, and loads Tiles from the
// traceserver running at the given address. The tiles contain the last
// 'tileSize' commits and are built from Traces of the type that traceBuilder
// returns.
func NewMasterTileBuilder(ctx context.Context, db DB, git *gitinfo.GitInfo, tileSize int, evt eventbus.EventBus, cachePath string) (MasterTileBuilder, error) {
ret := &masterTileBuilder{
tileSize: tileSize,
tile: nil,
db: db,
git: git,
evt: evt,
cachePath: cachePath,
}
var err error
if cachePath != "" {
// Load tile from disk cache. No mutex needed since there is no concurrent
// access at this point.
if ret.tile, err = serialize.LoadCachedTile(cachePath); err != nil {
return nil, fmt.Errorf("Error loading tile from cache: %s", err)
}
}
// Load the tile if it was not in the cache.
initialTileLoaded := false
if ret.tile == nil {
initialTileLoaded = true
if err := ret.LoadTile(ctx); err != nil {
return nil, fmt.Errorf("NewTraceStore: Failed to load initial Tile: %s", err)
}
}
evt.Publish(NEW_TILE_AVAILABLE_EVENT, ret.GetTile(), false)
go func() {
if !initialTileLoaded {
// Load the initial tile from disk if it came from the disk cache.
if err := ret.LoadTile(ctx); err != nil {
sklog.Errorf("Failed to refresh tile: %s", err)
}
}
liveness := metrics2.NewLiveness("tile_refresh", map[string]string{"module": "tracedb"})
for range time.Tick(TILE_REFRESH_DURATION) {
if err := ret.LoadTile(ctx); err != nil {
sklog.Errorf("Failed to refresh tile: %s", err)
} else {
liveness.Reset()
evt.Publish(NEW_TILE_AVAILABLE_EVENT, ret.GetTile(), false)
}
}
}()
return ret, nil
}
// LoadTile loads a Tile from the db.
//
// Users of Builder should not normally need to call this func, as it is called
// periodically by the Builder to keep the tile fresh.
func (t *masterTileBuilder) LoadTile(ctx context.Context) error {
// Build CommitIDs for the last INITIAL_TILE_SIZE commits to the repo.
if err := t.git.Update(ctx, true, false); err != nil {
sklog.Errorf("Failed to update Git repo: %s", err)
}
hashes := t.git.LastN(ctx, t.tileSize)
commitIDs := make([]*CommitID, 0, len(hashes))
for _, h := range hashes {
commitIDs = append(commitIDs, &CommitID{
ID: h,
Source: "master",
Timestamp: t.git.Timestamp(h).Unix(),
})
}
// Build a Tile from those CommitIDs.
tile, _, err := t.db.TileFromCommits(commitIDs)
if err != nil {
return fmt.Errorf("Failed to load tile: %s", err)
}
// Now populate the author for each commit.
for _, c := range tile.Commits {
details, err := t.git.Details(ctx, c.Hash, true)
if err != nil {
return fmt.Errorf("Couldn't fill in author info in tile for commit %s: %s", c.Hash, err)
}
c.Author = details.Author
}
if err != nil {
return fmt.Errorf("Failed to load initial tile: %s", err)
}
// Cache the file to disk if a path was set.
if t.cachePath != "" {
go func() {
if err := serialize.CacheTile(tile, t.cachePath); err != nil {
sklog.Errorf("Error writing tile to cache: %s", err)
}
}()
}
t.mutex.Lock()
defer t.mutex.Unlock()
t.tile = tile
return nil
}
// See the MasterTileBuilder interface.
func (t *masterTileBuilder) GetTile() *tiling.Tile {
t.mutex.Lock()
defer t.mutex.Unlock()
return t.tile
}