Skip to content

Commit

Permalink
less blindly pre-populate charts data
Browse files Browse the repository at this point in the history
This makes the pre-population of charts data smarter by avoiding duplicate
or simultaneous queries, and by avoiding it during explorerUI construction.1

Do not pre-populate in explorer.New. Wait for initial Store.
Give chartDataCounter a Height and Update function.
Height returns -1 when data has never been assigned.
In prePopulateChartsData, return without update if cache is at same
height as the explorerUI (as determined by .pageData.BlockInfo.Height
via (*explorerUI).Height).
In (*explorerUI).Store, only run prePopulateChartsData AFTER storing
the new BlockInfo in pageData.

Avoid launching two identical queries by holding the cache locked when
when performing both the height check and update in
prePopulateChartsData.
Add thread-unsafe height and update functions for chartDataCounter
so that the cache can be locked for both operations manually.
  • Loading branch information
chappjc authored Nov 15, 2018
1 parent dd7c592 commit e766982
Showing 1 changed file with 64 additions and 33 deletions.
97 changes: 64 additions & 33 deletions explorer/explorer.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,17 +96,50 @@ type explorerDataSource interface {
// chartDataCounter is a data cache for the historical charts.
type chartDataCounter struct {
sync.RWMutex
Data map[string]*dbtypes.ChartsData
updateHeight int64
Data map[string]*dbtypes.ChartsData
}

// cacheChartsData holds the prepopulated data that is used to draw the charts
// cacheChartsData holds the prepopulated data that is used to draw the charts.
var cacheChartsData chartDataCounter

// Height returns the last update height of the charts data cache.
func (c *chartDataCounter) Height() int64 {
c.RLock()
defer c.RUnlock()
return c.height()
}

// Update sets new data for the given height in the the charts data cache.
func (c *chartDataCounter) Update(height int64, newData map[string]*dbtypes.ChartsData) {
c.Lock()
defer c.Unlock()
c.update(height, newData)
}

// height returns the last update height of the charts data cache. Use Height
// instead for thread-safe access.
func (c *chartDataCounter) height() int64 {
if c.Data == nil {
return -1
}
return c.updateHeight
}

// update sets new data for the given height in the the charts data cache. Use
// Update instead for thread-safe access.
func (c *chartDataCounter) update(height int64, newData map[string]*dbtypes.ChartsData) {
c.updateHeight = height
c.Data = newData
}

// ChartTypeData is a thread-safe way to access chart data of the given type.
func ChartTypeData(chartType string) (data *dbtypes.ChartsData, ok bool) {
cacheChartsData.RLock()
defer cacheChartsData.RUnlock()

// Data updates replace the entire map rather than modifying the data to
// which the pointers refer, so the pointer can safely be returned here.
data, ok = cacheChartsData.Data[chartType]
return
}
Expand Down Expand Up @@ -165,7 +198,6 @@ type explorerUI struct {
// displaySyncStatusPage indicates if the sync status page is the only web
// page that should be accessible during DB synchronization.
displaySyncStatusPage atomic.Value
genesisBlock *BlockInfo
}

func (exp *explorerUI) reloadTemplates() error {
Expand Down Expand Up @@ -322,13 +354,6 @@ func New(dataSource explorerDataSourceLite, primaryDataSource explorerDataSource
}
}

// Do not fetch charts updates when on liteMode or when blockchain syncing
// is running in the background.
isSyncRunning := exp.DisplaySyncStatusPage() || SyncExplorerUpdateStatus()
if !exp.liteMode && !isSyncRunning {
exp.prePopulateChartsData()
}

exp.addRoutes()

exp.wsHub = NewWebsocketHub()
Expand All @@ -350,14 +375,11 @@ func (exp *explorerUI) PrepareCharts() {
func (exp *explorerUI) StartSyncingStatusMonitor() {
go func() {
timer := time.NewTicker(syncStatusInterval)
for {
select {
case <-timer.C:
if !exp.DisplaySyncStatusPage() {
timer.Stop()
}
exp.wsHub.HubRelay <- sigSyncStatus
for range timer.C {
if !exp.DisplaySyncStatusPage() {
timer.Stop()
}
exp.wsHub.HubRelay <- sigSyncStatus
}
}()
}
Expand All @@ -370,7 +392,7 @@ func (exp *explorerUI) DisplaySyncStatusPage() bool {

// SetDisplaySyncStatusPage is a thread-safe way to update the displaySyncStatusPage.
func (exp *explorerUI) SetDisplaySyncStatusPage(displayStatus bool) {
if displayStatus == false {
if !displayStatus {
// Send the one last signal so that the websocket can send the final
// confirmation that syncing is done and home page auto reload should happen.
exp.wsHub.HubRelay <- sigSyncStatus
Expand All @@ -392,9 +414,20 @@ func (exp *explorerUI) prePopulateChartsData() {
log.Warnf("Charts are not supported in lite mode!")
return
}

// Hold charts cache during check and update.
cacheChartsData.Lock()
defer cacheChartsData.Unlock()

// Avoid needlessly updating charts data.
expHeight := exp.Height()
if expHeight == cacheChartsData.height() {
log.Debugf("Not updating charts data again for height %d.", expHeight)
return
}

log.Info("Pre-populating the charts data. This may take a minute...")
var err error

pgData, err := exp.explorerSource.GetPgChartsData()
if err != nil {
log.Errorf("Invalid PG data found: %v", err)
Expand All @@ -411,24 +444,12 @@ func (exp *explorerUI) prePopulateChartsData() {
pgData[k] = v
}

cacheChartsData.Lock()
cacheChartsData.Data = pgData
cacheChartsData.Unlock()
cacheChartsData.update(expHeight, pgData)

log.Info("Done Pre-populating the charts data")
log.Info("Done pre-populating the charts data.")
}

func (exp *explorerUI) Store(blockData *blockdata.BlockData, msgBlock *wire.MsgBlock) error {
bData := blockData.ToBlockExplorerSummary()

isSyncRunning := exp.DisplaySyncStatusPage() || SyncExplorerUpdateStatus()

// Update the charts data after every five blocks or if no charts data
// exists yet. Do not update the charts data if blockchain sync is running.
if !isSyncRunning && bData.Height%5 == 0 || (len(cacheChartsData.Data) == 0 && !exp.liteMode) {
go exp.prePopulateChartsData()
}

// Retrieve block data for the passed block hash.
newBlockData := exp.blockData.GetExplorerBlock(msgBlock.BlockHash().String())

Expand Down Expand Up @@ -515,6 +536,16 @@ func (exp *explorerUI) Store(blockData *blockdata.BlockData, msgBlock *wire.MsgB
go exp.updateDevFundBalance()
}

// Update the charts data after every five blocks or if no charts data
// exists yet. Do not update the charts data if blockchain sync is running.
isSyncRunning := exp.DisplaySyncStatusPage() || SyncExplorerUpdateStatus()
if !isSyncRunning && (newBlockData.Height%5 == 0 || cacheChartsData.Height() == -1) {
// This must be done after storing BlockInfo since that provides the
// explorer's best block height, which is used by prePopulateChartsData
// to decide if an update is needed.
go exp.prePopulateChartsData()
}

// Signal to the websocket hub that a new block was received, but do not
// block Store(), and do not hang forever in a goroutine waiting to send.
go func() {
Expand Down

0 comments on commit e766982

Please sign in to comment.