diff --git a/blockbook.go b/blockbook.go index 9841c042..e28f3f52 100644 --- a/blockbook.go +++ b/blockbook.go @@ -234,7 +234,7 @@ func main() { var httpServer *server.HTTPServer if *httpServerBinding != "" { - httpServer, err = server.NewHTTPServer(*httpServerBinding, *certFiles, index, chain, txCache) + httpServer, err = server.NewHTTPServer(*httpServerBinding, *certFiles, index, chain, txCache, internalState) if err != nil { glog.Error("https: ", err) return @@ -424,7 +424,7 @@ func storeInternalStateLoop() { lastCompute := time.Now() // randomize the duration between ComputeInternalStateColumnStats to avoid peaks after reboot of machine with multiple blockbooks computePeriod := 9*time.Hour + time.Duration(rand.Float64()*float64((2*time.Hour).Nanoseconds())) - glog.Info("storeInternalStateLoop starting with internal state compute period ", computePeriod) + glog.Info("storeInternalStateLoop starting with db stats recompute period ", computePeriod) tickAndDebounce(storeInternalStatePeriodMs*time.Millisecond, (storeInternalStatePeriodMs-1)*time.Millisecond, chanStoreInternalState, func() { if !computeRunning && lastCompute.Add(computePeriod).Before(time.Now()) { computeRunning = true diff --git a/common/internalstate.go b/common/internalstate.go index b29ac414..15b8f26a 100644 --- a/common/internalstate.go +++ b/common/internalstate.go @@ -15,11 +15,12 @@ const ( // InternalStateColumn contains the data of a db column type InternalStateColumn struct { - Name string `json:"name"` - Version uint32 `json:"version"` - Rows int64 `json:"rows"` - KeyBytes int64 `json:"keysSum"` - ValueBytes int64 `json:"valuesSum"` + Name string `json:"name"` + Version uint32 `json:"version"` + Rows int64 `json:"rows"` + KeyBytes int64 `json:"keyBytes"` + ValueBytes int64 `json:"valueBytes"` + Updated time.Time `json:"updated"` } // InternalState contains the data of the internal state @@ -91,29 +92,36 @@ func (is *InternalState) FinishedMempoolSync(mempoolSize int) { } // GetMempoolSyncState gets the state of mempool synchronization -func (is *InternalState) GetMempoolSyncState() (bool, time.Time) { +func (is *InternalState) GetMempoolSyncState() (bool, time.Time, int) { is.mux.Lock() defer is.mux.Unlock() - return is.IsMempoolSynchronized, is.LastMempoolSync + return is.IsMempoolSynchronized, is.LastMempoolSync, is.MempoolSize } +// AddDBColumnStats adds differences in column statistics to column stats func (is *InternalState) AddDBColumnStats(c int, rowsDiff int64, keyBytesDiff int64, valueBytesDiff int64) { is.mux.Lock() defer is.mux.Unlock() - is.DbColumns[c].Rows += rowsDiff - is.DbColumns[c].KeyBytes += keyBytesDiff - is.DbColumns[c].ValueBytes += valueBytesDiff + dc := &is.DbColumns[c] + dc.Rows += rowsDiff + dc.KeyBytes += keyBytesDiff + dc.ValueBytes += valueBytesDiff + dc.Updated = time.Now() } +// SetDBColumnStats sets new values of column stats func (is *InternalState) SetDBColumnStats(c int, rows int64, keyBytes int64, valueBytes int64) { is.mux.Lock() defer is.mux.Unlock() - is.DbColumns[c].Rows = rows - is.DbColumns[c].KeyBytes = keyBytes - is.DbColumns[c].ValueBytes = valueBytes + dc := &is.DbColumns[c] + dc.Rows = rows + dc.KeyBytes = keyBytes + dc.ValueBytes = valueBytes + dc.Updated = time.Now() } -func (is *InternalState) GetDBColumnStats(c int) (int64, int64, int64) { +// GetDBColumnStatValues gets stat values for given column +func (is *InternalState) GetDBColumnStatValues(c int) (int64, int64, int64) { is.mux.Lock() defer is.mux.Unlock() if c < len(is.DbColumns) { @@ -122,6 +130,16 @@ func (is *InternalState) GetDBColumnStats(c int) (int64, int64, int64) { return 0, 0, 0 } +// GetAllDBColumnStats returns stats for all columns +func (is *InternalState) GetAllDBColumnStats() []InternalStateColumn { + is.mux.Lock() + defer is.mux.Unlock() + rv := make([]InternalStateColumn, len(is.DbColumns)) + copy(rv, is.DbColumns) + return rv +} + +// DBSizeTotal sums the computed sizes of all columns func (is *InternalState) DBSizeTotal() int64 { is.mux.Lock() defer is.mux.Unlock() @@ -132,6 +150,7 @@ func (is *InternalState) DBSizeTotal() int64 { return total } +// Pack marshals internal state to json func (is *InternalState) Pack() ([]byte, error) { is.mux.Lock() defer is.mux.Unlock() @@ -139,6 +158,7 @@ func (is *InternalState) Pack() ([]byte, error) { return json.Marshal(is) } +// UnpackInternalState unmarshals internal state from json func UnpackInternalState(buf []byte) (*InternalState, error) { var is InternalState if err := json.Unmarshal(buf, &is); err != nil { diff --git a/db/rocksdb.go b/db/rocksdb.go index 9ed0645c..37b0c085 100644 --- a/db/rocksdb.go +++ b/db/rocksdb.go @@ -934,6 +934,7 @@ func (d *RocksDB) LoadInternalState(rpcCoin string) (*common.InternalState, erro nc[i].Rows = sc[j].Rows nc[i].KeyBytes = sc[j].KeyBytes nc[i].ValueBytes = sc[j].ValueBytes + nc[i].Updated = sc[j].Updated break } } @@ -950,7 +951,7 @@ func (d *RocksDB) SetInternalState(is *common.InternalState) { // StoreInternalState stores the internal state to db func (d *RocksDB) StoreInternalState(is *common.InternalState) error { for c := 0; c < len(cfNames); c++ { - rows, keyBytes, valueBytes := d.is.GetDBColumnStats(c) + rows, keyBytes, valueBytes := d.is.GetDBColumnStatValues(c) d.metrics.DbColumnRows.With(common.Labels{"column": cfNames[c]}).Set(float64(rows)) d.metrics.DbColumnSize.With(common.Labels{"column": cfNames[c]}).Set(float64(keyBytes + valueBytes)) } diff --git a/server/https.go b/server/https.go index 62232f68..0af9c3a1 100644 --- a/server/https.go +++ b/server/https.go @@ -2,6 +2,7 @@ package server import ( "blockbook/bchain" + "blockbook/common" "blockbook/db" "context" "encoding/json" @@ -9,6 +10,7 @@ import ( "fmt" "net/http" "strconv" + "time" "github.com/golang/glog" @@ -24,10 +26,26 @@ type HTTPServer struct { txCache *db.TxCache chain bchain.BlockChain chainParser bchain.BlockChainParser + is *common.InternalState +} + +type resAboutBlockbookInternal struct { + Coin string `json:"coin"` + Host string `json:"host"` + Version string `json:"version"` + GitCommit string `json:"gitcommit"` + BuildTime string `json:"buildtime"` + InSync bool `json:"inSync"` + BestHeight uint32 `json:"bestHeight"` + LastBlockTime time.Time `json:"lastBlockTime"` + InSyncMempool bool `json:"inSyncMempool"` + LastMempoolTime time.Time `json:"lastMempoolTime"` + MempoolSize int `json:"mempoolSize"` + DbColumns []common.InternalStateColumn `json:"dbColumns"` } // NewHTTPServer creates new REST interface to blockbook and returns its handle -func NewHTTPServer(httpServerBinding string, certFiles string, db *db.RocksDB, chain bchain.BlockChain, txCache *db.TxCache) (*HTTPServer, error) { +func NewHTTPServer(httpServerBinding string, certFiles string, db *db.RocksDB, chain bchain.BlockChain, txCache *db.TxCache, is *common.InternalState) (*HTTPServer, error) { r := mux.NewRouter() https := &http.Server{ Addr: httpServerBinding, @@ -40,9 +58,10 @@ func NewHTTPServer(httpServerBinding string, certFiles string, db *db.RocksDB, c txCache: txCache, chain: chain, chainParser: chain.GetChainParser(), + is: is, } - r.HandleFunc("/", s.info) + r.HandleFunc("/", s.index) r.HandleFunc("/bestBlockHash", s.bestBlockHash) r.HandleFunc("/blockHash/{height}", s.blockHash) r.HandleFunc("/transactions/{address}/{lower}/{higher}", s.transactions) @@ -89,23 +108,29 @@ func respondHashData(w http.ResponseWriter, hash string) { }) } -func (s *HTTPServer) info(w http.ResponseWriter, r *http.Request) { - type info struct { - Version string `json:"version"` - BestBlockHeight uint32 `json:"bestBlockHeight"` - BestBlockHash string `json:"bestBlockHash"` +func (s *HTTPServer) index(w http.ResponseWriter, r *http.Request) { + vi := common.GetVersionInfo() + ss, bh, st := s.is.GetSyncState() + ms, mt, msz := s.is.GetMempoolSyncState() + a := resAboutBlockbookInternal{ + Coin: s.is.Coin, + Host: s.is.Host, + Version: vi.Version, + GitCommit: vi.GitCommit, + BuildTime: vi.BuildTime, + InSync: ss, + BestHeight: bh, + LastBlockTime: st, + InSyncMempool: ms, + LastMempoolTime: mt, + MempoolSize: msz, + DbColumns: s.is.GetAllDBColumnStats(), } - - height, hash, err := s.db.GetBestBlock() + buf, err := json.MarshalIndent(a, "", " ") if err != nil { - glog.Errorf("https info: %v", err) + glog.Error(err) } - - json.NewEncoder(w).Encode(info{ - Version: "0.0.1", - BestBlockHeight: height, - BestBlockHash: hash, - }) + w.Write(buf) } func (s *HTTPServer) bestBlockHash(w http.ResponseWriter, r *http.Request) {