github.com/ebakus/go-ebakus@v1.0.5-0.20200520105415-dbccef9ec421/p2p/metrics.go (about)

     1  // Copyright 2019 The ebakus/go-ebakus Authors
     2  // This file is part of the ebakus/go-ebakus library.
     3  //
     4  // The ebakus/go-ebakus library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The ebakus/go-ebakus library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the ebakus/go-ebakus library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  // Contains the meters and timers used by the networking layer.
    18  
    19  package p2p
    20  
    21  import (
    22  	"net"
    23  	"sync"
    24  	"sync/atomic"
    25  	"time"
    26  
    27  	"github.com/ebakus/go-ebakus/event"
    28  	"github.com/ebakus/go-ebakus/log"
    29  	"github.com/ebakus/go-ebakus/metrics"
    30  )
    31  
    32  const (
    33  	MetricsInboundTraffic   = "p2p/ingress" // Name for the registered inbound traffic meter
    34  	MetricsOutboundTraffic  = "p2p/egress"  // Name for the registered outbound traffic meter
    35  	MetricsOutboundConnects = "p2p/dials"   // Name for the registered outbound connects meter
    36  	MetricsInboundConnects  = "p2p/serves"  // Name for the registered inbound connects meter
    37  
    38  	MeteredPeerLimit = 1024 // This amount of peers are individually metered
    39  )
    40  
    41  var (
    42  	ingressConnectMeter = metrics.NewRegisteredMeter(MetricsInboundConnects, nil)  // Meter counting the ingress connections
    43  	ingressTrafficMeter = metrics.NewRegisteredMeter(MetricsInboundTraffic, nil)   // Meter metering the cumulative ingress traffic
    44  	egressConnectMeter  = metrics.NewRegisteredMeter(MetricsOutboundConnects, nil) // Meter counting the egress connections
    45  	egressTrafficMeter  = metrics.NewRegisteredMeter(MetricsOutboundTraffic, nil)  // Meter metering the cumulative egress traffic
    46  	activePeerGauge     = metrics.NewRegisteredGauge("p2p/peers", nil)             // Gauge tracking the current peer count
    47  
    48  	PeerIngressRegistry = metrics.NewPrefixedChildRegistry(metrics.EphemeralRegistry, MetricsInboundTraffic+"/")  // Registry containing the peer ingress
    49  	PeerEgressRegistry  = metrics.NewPrefixedChildRegistry(metrics.EphemeralRegistry, MetricsOutboundTraffic+"/") // Registry containing the peer egress
    50  
    51  	meteredPeerFeed  event.Feed // Event feed for peer metrics
    52  	meteredPeerCount int32      // Actually stored peer connection count
    53  )
    54  
    55  // MeteredPeerEventType is the type of peer events emitted by a metered connection.
    56  type MeteredPeerEventType int
    57  
    58  const (
    59  	// PeerHandshakeSucceeded is the type of event
    60  	// emitted when a peer successfully makes the handshake.
    61  	PeerHandshakeSucceeded MeteredPeerEventType = iota
    62  
    63  	// PeerHandshakeFailed is the type of event emitted when a peer fails to
    64  	// make the handshake or disconnects before it.
    65  	PeerHandshakeFailed
    66  
    67  	// PeerDisconnected is the type of event emitted when a peer disconnects.
    68  	PeerDisconnected
    69  )
    70  
    71  // MeteredPeerEvent is an event emitted when peers connect or disconnect.
    72  type MeteredPeerEvent struct {
    73  	Type    MeteredPeerEventType // Type of peer event
    74  	Addr    string               // TCP address of the peer
    75  	Elapsed time.Duration        // Time elapsed between the connection and the handshake/disconnection
    76  	Peer    *Peer                // Connected remote node instance
    77  	Ingress uint64               // Ingress count at the moment of the event
    78  	Egress  uint64               // Egress count at the moment of the event
    79  }
    80  
    81  // SubscribeMeteredPeerEvent registers a subscription for peer life-cycle events
    82  // if metrics collection is enabled.
    83  func SubscribeMeteredPeerEvent(ch chan<- MeteredPeerEvent) event.Subscription {
    84  	return meteredPeerFeed.Subscribe(ch)
    85  }
    86  
    87  // meteredConn is a wrapper around a net.Conn that meters both the
    88  // inbound and outbound network traffic.
    89  type meteredConn struct {
    90  	net.Conn // Network connection to wrap with metering
    91  
    92  	connected time.Time    // Connection time of the peer
    93  	addr      *net.TCPAddr // TCP address of the peer
    94  	peer      *Peer        // Peer instance
    95  
    96  	// trafficMetered denotes if the peer is registered in the traffic registries.
    97  	// Its value is true if the metered peer count doesn't reach the limit in the
    98  	// moment of the peer's connection.
    99  	trafficMetered bool
   100  	ingressMeter   metrics.Meter // Meter for the read bytes of the peer
   101  	egressMeter    metrics.Meter // Meter for the written bytes of the peer
   102  
   103  	lock sync.RWMutex // Lock protecting the metered connection's internals
   104  }
   105  
   106  // newMeteredConn creates a new metered connection, bumps the ingress or egress
   107  // connection meter and also increases the metered peer count. If the metrics
   108  // system is disabled or the IP address is unspecified, this function returns
   109  // the original object.
   110  func newMeteredConn(conn net.Conn, ingress bool, addr *net.TCPAddr) net.Conn {
   111  	// Short circuit if metrics are disabled
   112  	if !metrics.Enabled {
   113  		return conn
   114  	}
   115  	if addr == nil || addr.IP.IsUnspecified() {
   116  		log.Warn("Peer address is unspecified")
   117  		return conn
   118  	}
   119  	// Bump the connection counters and wrap the connection
   120  	if ingress {
   121  		ingressConnectMeter.Mark(1)
   122  	} else {
   123  		egressConnectMeter.Mark(1)
   124  	}
   125  	activePeerGauge.Inc(1)
   126  
   127  	return &meteredConn{
   128  		Conn:      conn,
   129  		addr:      addr,
   130  		connected: time.Now(),
   131  	}
   132  }
   133  
   134  // Read delegates a network read to the underlying connection, bumping the common
   135  // and the peer ingress traffic meters along the way.
   136  func (c *meteredConn) Read(b []byte) (n int, err error) {
   137  	n, err = c.Conn.Read(b)
   138  	ingressTrafficMeter.Mark(int64(n))
   139  	c.lock.RLock()
   140  	if c.trafficMetered {
   141  		c.ingressMeter.Mark(int64(n))
   142  	}
   143  	c.lock.RUnlock()
   144  	return n, err
   145  }
   146  
   147  // Write delegates a network write to the underlying connection, bumping the common
   148  // and the peer egress traffic meters along the way.
   149  func (c *meteredConn) Write(b []byte) (n int, err error) {
   150  	n, err = c.Conn.Write(b)
   151  	egressTrafficMeter.Mark(int64(n))
   152  	c.lock.RLock()
   153  	if c.trafficMetered {
   154  		c.egressMeter.Mark(int64(n))
   155  	}
   156  	c.lock.RUnlock()
   157  	return n, err
   158  }
   159  
   160  // handshakeDone is called after the connection passes the handshake.
   161  func (c *meteredConn) handshakeDone(peer *Peer) {
   162  	if atomic.AddInt32(&meteredPeerCount, 1) >= MeteredPeerLimit {
   163  		// Don't register the peer in the traffic registries.
   164  		atomic.AddInt32(&meteredPeerCount, -1)
   165  		c.lock.Lock()
   166  		c.peer, c.trafficMetered = peer, false
   167  		c.lock.Unlock()
   168  		log.Warn("Metered peer count reached the limit")
   169  	} else {
   170  		enode := peer.Node().String()
   171  		c.lock.Lock()
   172  		c.peer, c.trafficMetered = peer, true
   173  		c.ingressMeter = metrics.NewRegisteredMeter(enode, PeerIngressRegistry)
   174  		c.egressMeter = metrics.NewRegisteredMeter(enode, PeerEgressRegistry)
   175  		c.lock.Unlock()
   176  	}
   177  	meteredPeerFeed.Send(MeteredPeerEvent{
   178  		Type:    PeerHandshakeSucceeded,
   179  		Addr:    c.addr.String(),
   180  		Peer:    peer,
   181  		Elapsed: time.Since(c.connected),
   182  	})
   183  }
   184  
   185  // Close delegates a close operation to the underlying connection, unregisters
   186  // the peer from the traffic registries and emits close event.
   187  func (c *meteredConn) Close() error {
   188  	err := c.Conn.Close()
   189  	c.lock.RLock()
   190  	if c.peer == nil {
   191  		// If the peer disconnects before/during the handshake.
   192  		c.lock.RUnlock()
   193  		meteredPeerFeed.Send(MeteredPeerEvent{
   194  			Type:    PeerHandshakeFailed,
   195  			Addr:    c.addr.String(),
   196  			Elapsed: time.Since(c.connected),
   197  		})
   198  		activePeerGauge.Dec(1)
   199  		return err
   200  	}
   201  	peer := c.peer
   202  	if !c.trafficMetered {
   203  		// If the peer isn't registered in the traffic registries.
   204  		c.lock.RUnlock()
   205  		meteredPeerFeed.Send(MeteredPeerEvent{
   206  			Type: PeerDisconnected,
   207  			Addr: c.addr.String(),
   208  			Peer: peer,
   209  		})
   210  		activePeerGauge.Dec(1)
   211  		return err
   212  	}
   213  	ingress, egress, enode := uint64(c.ingressMeter.Count()), uint64(c.egressMeter.Count()), c.peer.Node().String()
   214  	c.lock.RUnlock()
   215  
   216  	// Decrement the metered peer count
   217  	atomic.AddInt32(&meteredPeerCount, -1)
   218  
   219  	// Unregister the peer from the traffic registries
   220  	PeerIngressRegistry.Unregister(enode)
   221  	PeerEgressRegistry.Unregister(enode)
   222  
   223  	meteredPeerFeed.Send(MeteredPeerEvent{
   224  		Type:    PeerDisconnected,
   225  		Addr:    c.addr.String(),
   226  		Peer:    peer,
   227  		Ingress: ingress,
   228  		Egress:  egress,
   229  	})
   230  	activePeerGauge.Dec(1)
   231  	return err
   232  }