chain/stat.go
package chain
import (
"encoding/json"
"sync"
"time"
"github.com/aergoio/aergo/v2/types"
)
//go:generate stringer -type=statIndex
type statIndex int
const (
// Warning: Each statIndex constant has a String method, which is
// automatically generated by 'stringer' with 'go generate' command. For the
// detail, check https://blog.golang.org/generate
// ReorgStat is a constant representing a stat about reorganization.
ReorgStat statIndex = iota
// MaxStat is a constant representing a value less than which all the
// constants corresponding chain stats must be.
MaxStat
)
var (
// To add a new one to chain stats, implements statItem interface and add
// its constructor here. Additionally, you need to add a constant
// corresponding to its index like statReorg above.
statItemCtors = map[statIndex]func() statItem{
ReorgStat: newStReorg,
}
)
type stats []*stat
func newStats() stats {
s := make(stats, MaxStat)
for i := statIndex(0); i < MaxStat; i++ {
s[i] = newStat(statItemCtors[i]())
}
return s
}
func (s stats) JSON() string {
r := make(map[string]json.RawMessage)
for i := statIndex(0); i < MaxStat; i++ {
if b, err := json.Marshal(s.clone(i)); err == nil {
r[i.String()] = json.RawMessage(b)
}
}
if m, err := json.Marshal(r); err == nil {
return string(m)
}
return ""
}
func (s stats) get(idx statIndex) *stat {
return []*stat(s)[idx]
}
func (s stats) clone(idx statIndex) interface{} {
i := s.get(idx)
i.RLock()
defer i.RUnlock()
return i.clone()
}
func (s stats) updateEvent(idx statIndex, args ...interface{}) {
i := s.get(idx)
i.Lock()
defer i.Unlock()
i.updateEvent(args...)
}
type stat struct {
sync.RWMutex
statItem
}
func newStat(i statItem) *stat {
return &stat{statItem: i}
}
type statItem interface {
updateEvent(args ...interface{})
clone() interface{}
}
type stReorg struct {
totalElapsed time.Duration
Count int64
AverageElapsed float64 `json:"Average Elapsed Time,omitempty"`
Latest *evReorg `json:",omitempty"`
}
func newStReorg() statItem {
return &stReorg{}
}
type evReorg struct {
OldBest *blockInfo `json:"Old Best,omitempty"`
Fork *blockInfo `json:"Fork At,omitempty"`
NewBest *blockInfo `json:"New Best,omitempty"`
Time time.Time
}
type blockInfo struct {
Hash string
Height types.BlockNo
}
func (sr *stReorg) getCount() int64 {
return sr.Count
}
func (sr *stReorg) getLatestEvent() interface{} {
return sr.Latest
}
func (sr *stReorg) updateEvent(args ...interface{}) {
if len(args) != 4 {
logger.Info().Int("len", len(args)).Msg("invalid # of arguments for the reorg stat update")
return
}
et := args[0].(time.Duration)
bi := make([]*blockInfo, len(args))
for i, a := range args[1:] {
var block *types.Block
ok := false
if block, ok = a.(*types.Block); !ok {
logger.Info().Int("arg idx", i).Msg("invalid type of argument")
return
}
bi[i] = &blockInfo{Hash: block.ID(), Height: block.BlockNo()}
}
sr.Latest = &evReorg{
OldBest: bi[0],
NewBest: bi[1],
Fork: bi[2],
Time: time.Now(),
}
sr.totalElapsed += et
sr.Count++
sr.AverageElapsed = (sr.totalElapsed / time.Duration(sr.Count)).Seconds()
}
func (sr *stReorg) clone() interface{} {
c := *sr
if sr.Latest != nil {
l := *sr.Latest
c.Latest = &l
}
return &c
}