|
|
- package monitor
-
- import (
- "sync"
- "time"
-
- metrics "github.com/rcrowley/go-metrics"
- tmtypes "github.com/tendermint/tendermint/types"
- )
-
- // UptimeData stores data for how long network has been running.
- type UptimeData struct {
- StartTime time.Time `json:"start_time"`
- Uptime float64 `json:"uptime" amino:"unsafe"` // percentage of time we've been healthy, ever
-
- totalDownTime time.Duration // total downtime (only updated when we come back online)
- wentDown time.Time
- }
-
- // Health describes the health of the network. Note that this applies only to
- // the observed nodes, and not to the entire cluster, which may consist of
- // thousands of machines. It may change in the future.
- type Health int
-
- const (
- // FullHealth means all nodes online, synced, validators making blocks
- FullHealth = Health(0)
- // ModerateHealth means we're making blocks
- ModerateHealth = Health(1)
- // Dead means we're not making blocks due to all validators freezing or crashing
- Dead = Health(2)
- )
-
- // Common statistics for network of nodes
- type Network struct {
- Height int64 `json:"height"`
-
- AvgBlockTime float64 `json:"avg_block_time" amino:"unsafe"` // ms (avg over last minute)
- blockTimeMeter metrics.Meter
- AvgTxThroughput float64 `json:"avg_tx_throughput" amino:"unsafe"` // tx/s (avg over last minute)
- txThroughputMeter metrics.Meter
- AvgBlockLatency float64 `json:"avg_block_latency" amino:"unsafe"` // ms (avg over last minute)
- blockLatencyMeter metrics.Meter
-
- NumValidators int `json:"num_validators"`
- NumNodesMonitored int `json:"num_nodes_monitored"`
- NumNodesMonitoredOnline int `json:"num_nodes_monitored_online"`
-
- Health Health `json:"health"`
-
- UptimeData *UptimeData `json:"uptime_data"`
-
- nodeStatusMap map[string]bool
-
- mu sync.Mutex
- }
-
- func NewNetwork() *Network {
- return &Network{
- blockTimeMeter: metrics.NewMeter(),
- txThroughputMeter: metrics.NewMeter(),
- blockLatencyMeter: metrics.NewMeter(),
- Health: FullHealth,
- UptimeData: &UptimeData{
- StartTime: time.Now(),
- Uptime: 100.0,
- },
- nodeStatusMap: make(map[string]bool),
- }
- }
-
- func (n *Network) NewBlock(b tmtypes.Header) {
- n.mu.Lock()
- defer n.mu.Unlock()
-
- if n.Height >= b.Height {
- return
- }
-
- n.Height = b.Height
-
- n.blockTimeMeter.Mark(1)
- if n.blockTimeMeter.Rate1() > 0.0 {
- n.AvgBlockTime = (1.0 / n.blockTimeMeter.Rate1()) * 1000 // 1/s to ms
- } else {
- n.AvgBlockTime = 0.0
- }
- n.txThroughputMeter.Mark(int64(b.NumTxs))
- n.AvgTxThroughput = n.txThroughputMeter.Rate1()
- }
-
- func (n *Network) NewBlockLatency(l float64) {
- n.mu.Lock()
- defer n.mu.Unlock()
-
- n.blockLatencyMeter.Mark(int64(l))
- n.AvgBlockLatency = n.blockLatencyMeter.Rate1() / 1000000.0 // ns to ms
- }
-
- // RecalculateUptime calculates uptime on demand.
- func (n *Network) RecalculateUptime() {
- n.mu.Lock()
- defer n.mu.Unlock()
-
- since := time.Since(n.UptimeData.StartTime)
- uptime := since - n.UptimeData.totalDownTime
- if n.Health != FullHealth {
- uptime -= time.Since(n.UptimeData.wentDown)
- }
- n.UptimeData.Uptime = (float64(uptime) / float64(since)) * 100.0
- }
-
- // NodeIsDown is called when the node disconnects for whatever reason.
- // Must be safe to call multiple times.
- func (n *Network) NodeIsDown(name string) {
- n.mu.Lock()
- defer n.mu.Unlock()
-
- if online, ok := n.nodeStatusMap[name]; !ok || online {
- n.nodeStatusMap[name] = false
- n.NumNodesMonitoredOnline--
- n.UptimeData.wentDown = time.Now()
- n.updateHealth()
- }
- }
-
- // NodeIsOnline is called when connection to the node is restored.
- // Must be safe to call multiple times.
- func (n *Network) NodeIsOnline(name string) {
- n.mu.Lock()
- defer n.mu.Unlock()
-
- if online, ok := n.nodeStatusMap[name]; ok && !online {
- n.nodeStatusMap[name] = true
- n.NumNodesMonitoredOnline++
- n.UptimeData.totalDownTime += time.Since(n.UptimeData.wentDown)
- n.updateHealth()
- }
- }
-
- // NewNode is called when the new node is added to the monitor.
- func (n *Network) NewNode(name string) {
- n.NumNodesMonitored++
- n.NumNodesMonitoredOnline++
- }
-
- // NodeDeleted is called when the node is deleted from under the monitor.
- func (n *Network) NodeDeleted(name string) {
- n.NumNodesMonitored--
- n.NumNodesMonitoredOnline--
- }
-
- func (n *Network) updateHealth() {
- // if we are connected to all validators, we're at full health
- // TODO: make sure they're all at the same height (within a block)
- // and all proposing (and possibly validating ) Alternatively, just
- // check there hasn't been a new round in numValidators rounds
- if n.NumValidators != 0 && n.NumNodesMonitoredOnline == n.NumValidators {
- n.Health = FullHealth
- } else if n.NumNodesMonitoredOnline > 0 && n.NumNodesMonitoredOnline <= n.NumNodesMonitored {
- n.Health = ModerateHealth
- } else {
- n.Health = Dead
- }
- }
-
- func (n *Network) UpdateNumValidatorsForHeight(num int, height int64) {
- n.mu.Lock()
- defer n.mu.Unlock()
-
- if n.Height <= height {
- n.NumValidators = num
- }
- }
-
- func (n *Network) GetHealthString() string {
- switch n.Health {
- case FullHealth:
- return "full"
- case ModerateHealth:
- return "moderate"
- case Dead:
- return "dead"
- default:
- return "undefined"
- }
- }
-
- // Uptime returns network's uptime in percentages.
- func (n *Network) Uptime() float64 {
- n.mu.Lock()
- defer n.mu.Unlock()
- return n.UptimeData.Uptime
- }
-
- // StartTime returns time we started monitoring.
- func (n *Network) StartTime() time.Time {
- return n.UptimeData.StartTime
- }
|