aergoio/aergo

View on GitHub
chain/stat.go

Summary

Maintainability
A
0 mins
Test Coverage
B
83%
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
}