mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 10:22:23 +00:00
dashboard: peer metering using custom event stream
This commit is contained in:
parent
58df98a6ab
commit
746e97d87f
8 changed files with 28404 additions and 44333 deletions
70523
dashboard/assets.go
70523
dashboard/assets.go
File diff suppressed because one or more lines are too long
|
|
@ -92,10 +92,8 @@ const defaultContent: () => Content = () => ({
|
|||
network: {
|
||||
peers: {
|
||||
bundles: {},
|
||||
removedKnownIP: [],
|
||||
removedKnownID: [],
|
||||
removedUnknownIP: [],
|
||||
},
|
||||
diff: [],
|
||||
},
|
||||
system: {
|
||||
activeMemory: [],
|
||||
|
|
@ -127,9 +125,7 @@ const updaters = {
|
|||
home: null,
|
||||
chain: null,
|
||||
txpool: null,
|
||||
network: {
|
||||
peers: peerInserter,
|
||||
},
|
||||
network: peerInserter(200),
|
||||
system: {
|
||||
activeMemory: appender(200),
|
||||
virtualMemory: appender(200),
|
||||
|
|
|
|||
|
|
@ -23,59 +23,86 @@ import TableHead from '@material-ui/core/TableHead';
|
|||
import TableBody from '@material-ui/core/TableBody';
|
||||
import TableRow from '@material-ui/core/TableRow';
|
||||
import TableCell from '@material-ui/core/TableCell';
|
||||
import type {Network as NetworkType, Peers} from '../types/content';
|
||||
import type {Network as NetworkType, PeerEvent} from '../types/content';
|
||||
|
||||
// inserter is a state updater function for the main component, which handles the peers.
|
||||
export const inserter = (update: Peers, prev: Peers) => {
|
||||
console.log(update);
|
||||
return prev;
|
||||
Object.keys(update.bundles).forEach((ip) => {
|
||||
if (!update[ip]) {
|
||||
export const inserter = (sampleLimit: number) => (update: NetworkType, prev: NetworkType) => {
|
||||
if (update.peers && update.peers.bundles) {
|
||||
prev.peers = update.peers;
|
||||
}
|
||||
if (Array.isArray(update.diff)) {
|
||||
update.diff.forEach((event: PeerEvent) => {
|
||||
if (event.removeIP) {
|
||||
if (event.removeID && prev.peers.bundles[event.removeIP]) {
|
||||
delete prev.peers.bundles[event.removeIP].knownPeers[event.removeID];
|
||||
}
|
||||
delete prev.peers.bundles[event.removeIP];
|
||||
return;
|
||||
}
|
||||
if (!prev[ip]) {
|
||||
prev[ip] = update[ip];
|
||||
if (!event.ip) {
|
||||
console.error('Peer event without IP', event);
|
||||
return;
|
||||
}
|
||||
if (update[ip].location) {
|
||||
prev[ip].location = update[ip].location;
|
||||
if (!prev.peers.bundles[event.ip]) {
|
||||
prev.peers.bundles[event.ip] = {
|
||||
location: {},
|
||||
knownPeers: {},
|
||||
unknownPeers: [],
|
||||
};
|
||||
}
|
||||
if (!update[ip].peers) {
|
||||
return;
|
||||
const bundle = prev.peers.bundles[event.ip];
|
||||
if (event.location) {
|
||||
bundle.location = event.location;
|
||||
}
|
||||
Object.entries(update[ip].peers).forEach(([id, u]) => {
|
||||
if (!prev[ip].peers[id]) {
|
||||
prev[ip].peers[id] = u;
|
||||
return;
|
||||
}
|
||||
const p: Peer = prev[ip].peers[id];
|
||||
if (u.connected) {
|
||||
if (!Array.isArray(p.connected)) {
|
||||
p.connected = [];
|
||||
}
|
||||
p.connected = [...p.connected, ...u.connected];
|
||||
}
|
||||
if (u.disconnected) {
|
||||
if (!Array.isArray(p.disconnected)) {
|
||||
p.disconnected = [];
|
||||
}
|
||||
p.disconnected = [...p.disconnected, ...u.disconnected];
|
||||
}
|
||||
if (Array.isArray(u.ingress)) {
|
||||
if (!Array.isArray(p.ingress)) {
|
||||
p.ingress = [];
|
||||
}
|
||||
p.ingress = [...p.ingress, ...u.ingress].slice(-200);
|
||||
}
|
||||
if (Array.isArray(u.egress)) {
|
||||
if (!Array.isArray(p.egress)) {
|
||||
p.egress = [];
|
||||
}
|
||||
p.egress = [...p.egress, ...u.egress].slice(-200);
|
||||
}
|
||||
prev[ip].peers[id] = p;
|
||||
if (!event.id) {
|
||||
bundle.unknownPeers.push({
|
||||
connected: event.connected,
|
||||
disconnected: event.disconnected,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!bundle.knownPeers[event.id]) {
|
||||
bundle.knownPeers[event.id] = {
|
||||
connected: [],
|
||||
disconnected: [],
|
||||
ingress: [],
|
||||
egress: [],
|
||||
};
|
||||
}
|
||||
const peer = bundle.knownPeers[event.id];
|
||||
if (event.connected) {
|
||||
peer.connected.push(event.connected);
|
||||
}
|
||||
if (event.disconnected) {
|
||||
peer.disconnected.push(event.disconnected);
|
||||
}
|
||||
if (Array.isArray(event.ingress) && Array.isArray(event.egress)) {
|
||||
if (event.ingress.length !== event.egress.length) {
|
||||
console.error('Different traffic sample length', event);
|
||||
return;
|
||||
}
|
||||
if (peer.ingress.length > 0) {
|
||||
if (peer.ingress[peer.ingress.length - 1].value < event.ingress[0].value) {
|
||||
event.ingress[0].value -= peer.ingress[peer.ingress.length - 1].value;
|
||||
event.egress[0].value -= peer.egress[peer.egress.length - 1].value;
|
||||
}
|
||||
}
|
||||
for (let i = 1; i < event.ingress.length; i++) {
|
||||
event.ingress[i].value -= event.ingress[i - 1].value;
|
||||
event.egress[i].value -= event.egress[i - 1].value;
|
||||
}
|
||||
peer.ingress.splice(peer.ingress.length, 0, ...event.ingress);
|
||||
peer.egress.splice(peer.egress.length, 0, ...event.egress);
|
||||
if (peer.ingress.length > sampleLimit) {
|
||||
peer.ingress.splice(0, peer.ingress.length - sampleLimit);
|
||||
}
|
||||
if (peer.egress.length > sampleLimit) {
|
||||
peer.egress.splice(0, peer.egress.length - sampleLimit);
|
||||
}
|
||||
// console.log(event.ingress, prev.peers.bundles[event.ip].knownPeers[event.id].ingress);
|
||||
}
|
||||
});
|
||||
}
|
||||
return prev;
|
||||
};
|
||||
|
||||
|
|
@ -112,7 +139,8 @@ class Network extends Component<Props, State> {
|
|||
<TableRow>
|
||||
<TableCell>IP</TableCell>
|
||||
<TableCell>Location</TableCell>
|
||||
<TableCell>Peer ID</TableCell>
|
||||
<TableCell>Unknown</TableCell>
|
||||
<TableCell>Node ID</TableCell>
|
||||
<TableCell>Ingress</TableCell>
|
||||
<TableCell>Egress</TableCell>
|
||||
<TableCell>Connected</TableCell>
|
||||
|
|
@ -120,7 +148,7 @@ class Network extends Component<Props, State> {
|
|||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{Object.entries(this.props.content.peers).map(([ip, bundle]) => { console.log(ip, bundle); return (
|
||||
{Object.entries(this.props.content.peers.bundles).map(([ip, bundle]) => (
|
||||
<TableRow key={ip}>
|
||||
<TableCell>{ip}</TableCell>
|
||||
<TableCell>
|
||||
|
|
@ -130,22 +158,25 @@ class Network extends Component<Props, State> {
|
|||
})() : ''}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{bundle.peers && Object.keys(bundle.peers).map(id => id.substring(0, 10)).join(' ')}
|
||||
{bundle.unknownPeers && Object.values(bundle.unknownPeers).map(peer => peer.connected && peer.disconnected && `${this.formatTime(peer.connected)}~${this.formatTime(peer.disconnected)}`).join(', ')}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{bundle.peers && Object.values(bundle.peers).map(peer => peer.ingress && peer.ingress.map(sample => sample.value).join(' ')).join(', ')}
|
||||
{bundle.knownPeers && Object.keys(bundle.knownPeers).map(id => id.substring(0, 10)).join(' ')}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{bundle.peers && Object.values(bundle.peers).map(peer => peer.egress && peer.egress.map(sample => sample.value).join(' ')).join(', ')}
|
||||
{bundle.knownPeers && Object.values(bundle.knownPeers).map(peer => peer.ingress && peer.ingress.map(sample => sample.value).join(' ')).join(', ')}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{bundle.peers && Object.values(bundle.peers).map(peer => peer.connected && peer.connected.map(time => this.formatTime(time)).join(' ')).join(', ')}
|
||||
{bundle.knownPeers && Object.values(bundle.knownPeers).map(peer => peer.egress && peer.egress.map(sample => sample.value).join(' ')).join(', ')}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{bundle.peers && Object.values(bundle.peers).map(peer => peer.disconnected && peer.disconnected.map(time => this.formatTime(time)).join(' ')).join(', ')}
|
||||
{bundle.knownPeers && Object.values(bundle.knownPeers).map(peer => peer.connected && peer.connected.map(time => this.formatTime(time)).join(' ')).join(', ')}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{bundle.knownPeers && Object.values(bundle.knownPeers).map(peer => peer.disconnected && peer.disconnected.map(time => this.formatTime(time)).join(' ')).join(', ')}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)})}
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -52,13 +52,23 @@ export type TxPool = {
|
|||
|
||||
export type Network = {
|
||||
peers: Peers,
|
||||
diff: Array<PeerEvent>
|
||||
};
|
||||
|
||||
export type PeerEvent = {
|
||||
ip: string,
|
||||
id: string,
|
||||
removeIP: string,
|
||||
removeID: string,
|
||||
location: GeoLocation,
|
||||
connected: Date,
|
||||
disconnected: Date,
|
||||
ingress: ChartEntries,
|
||||
egress: ChartEntries,
|
||||
};
|
||||
|
||||
export type Peers = {
|
||||
bundles: {[string]: PeerBundle},
|
||||
removedKnownIP: Array<string>,
|
||||
removedKnownID: Array<string>,
|
||||
removedUnknownIP: Array<string>,
|
||||
};
|
||||
|
||||
export type PeerBundle = {
|
||||
|
|
@ -68,15 +78,10 @@ export type PeerBundle = {
|
|||
};
|
||||
|
||||
export type KnownPeer = {
|
||||
active: boolean,
|
||||
sessions: Array<PeerSession>
|
||||
};
|
||||
|
||||
export type PeerSession = {
|
||||
connected: Date,
|
||||
disconnected: Date,
|
||||
ingress: ChartEntries,
|
||||
egress: ChartEntries,
|
||||
connected: Array<Date>,
|
||||
disconnected: Array<Date>,
|
||||
ingress: Array<ChartEntries>,
|
||||
egress: Array<ChartEntries>,
|
||||
};
|
||||
|
||||
export type UnknownPeer = {
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
sampleLimit = 3 // Maximum number of data samples
|
||||
sampleLimit = 200 // Maximum number of data samples
|
||||
)
|
||||
|
||||
// Dashboard contains the dashboard internals.
|
||||
|
|
|
|||
|
|
@ -86,9 +86,9 @@ func (db *GeoDB) Lookup(ip net.IP) *GeoDBInfo {
|
|||
return result
|
||||
}
|
||||
|
||||
// Location retrieves the geographical location of the given IP address.
|
||||
func (db *GeoDB) Location (ip string) *GeoLocation {
|
||||
location := db.Lookup(net.ParseIP(ip))
|
||||
//location := new(GeoDBInfo)
|
||||
return &GeoLocation{
|
||||
Country: location.Country.Names.English,
|
||||
City: location.City.Names.English,
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ package dashboard
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
|
@ -56,210 +55,11 @@ type TxPoolMessage struct {
|
|||
/* TODO (kurkomisi) */
|
||||
}
|
||||
|
||||
// NetworkMessage contains information about the peers organized based on the IP address.
|
||||
// NetworkMessage contains information about the peers
|
||||
// organized based on their IP address and node ID.
|
||||
type NetworkMessage struct {
|
||||
Peers *PeersMessage `json:"peers,omitempty"`
|
||||
}
|
||||
|
||||
type PeersMessage struct {
|
||||
Bundles map[string]*PeerBundle `json:"bundles,omitempty"`
|
||||
RemovedKnownIP []string `json:"removedKnownIP,omitempty"`
|
||||
RemovedKnownID []string `json:"removedKnownID,omitempty"`
|
||||
RemovedUnknownIP []string `json:"removedUnknownIP,omitempty"`
|
||||
}
|
||||
|
||||
func NewPeersMessage() *PeersMessage {
|
||||
return &PeersMessage{
|
||||
Bundles: make(map[string]*PeerBundle),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *PeersMessage) hasBundle(ip string) bool {
|
||||
_, ok := m.Bundles[ip]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (m *PeersMessage) hasKnownPeer(ip, id string) bool {
|
||||
if m.hasBundle(ip) {
|
||||
return m.Bundles[ip].has(id)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *PeersMessage) initBundle(ip string) bool {
|
||||
if !m.hasBundle(ip) {
|
||||
m.Bundles[ip] = &PeerBundle{
|
||||
KnownPeers: make(map[string]*KnownPeer),
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *PeersMessage) initKnownPeer(ip, id string) (bundle, peer bool) {
|
||||
return m.initBundle(ip), m.Bundles[ip].initKnownPeer(id)
|
||||
}
|
||||
|
||||
func (m *PeersMessage) getOrInitBundle(ip string) *PeerBundle {
|
||||
m.initBundle(ip)
|
||||
return m.Bundles[ip]
|
||||
}
|
||||
|
||||
func (m *PeersMessage) getOrInitKnownPeer(ip, id string) *KnownPeer {
|
||||
return m.getOrInitBundle(ip).getOrInitKnownPeer(id)
|
||||
}
|
||||
|
||||
func (m *PeersMessage) removeKnownPeer(ip, id string) {
|
||||
if b, ok := m.Bundles[ip]; ok {
|
||||
b.removeKnownPeer(id)
|
||||
if len(b.KnownPeers) < 1 && len(b.UnknownPeers) < 1 {
|
||||
delete(m.Bundles, ip)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *PeersMessage) removeUnknownPeer(ip string) {
|
||||
if b, ok := m.Bundles[ip]; ok {
|
||||
if len(b.UnknownPeers) > 0 {
|
||||
b.UnknownPeers = b.UnknownPeers[1:]
|
||||
}
|
||||
if len(b.KnownPeers) < 1 && len(b.UnknownPeers) < 1 {
|
||||
delete(m.Bundles, ip)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *PeersMessage) clear() {
|
||||
for _, bundle := range m.Bundles {
|
||||
bundle.Location = nil
|
||||
for _, peer := range bundle.KnownPeers {
|
||||
peer.clear()
|
||||
}
|
||||
bundle.UnknownPeers = bundle.UnknownPeers[:0]
|
||||
}
|
||||
m.RemovedKnownIP = m.RemovedKnownIP[:0]
|
||||
m.RemovedKnownID = m.RemovedKnownID[:0]
|
||||
m.RemovedUnknownIP = m.RemovedUnknownIP[:0]
|
||||
}
|
||||
|
||||
type PeerBundle struct {
|
||||
Location *GeoLocation `json:"location,omitempty"` // Geographical location based on IP
|
||||
KnownPeers map[string]*KnownPeer `json:"knownPeers,omitempty"`
|
||||
UnknownPeers []*UnknownPeer `json:"unknownPeers,omitempty"`
|
||||
}
|
||||
|
||||
func (b *PeerBundle) has(id string) bool {
|
||||
_, ok := b.KnownPeers[id]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (b *PeerBundle) initKnownPeer(id string) bool {
|
||||
if !b.has(id) {
|
||||
b.KnownPeers[id] = new(KnownPeer)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (b *PeerBundle) getOrInitKnownPeer(id string) *KnownPeer {
|
||||
b.initKnownPeer(id)
|
||||
return b.KnownPeers[id]
|
||||
}
|
||||
|
||||
func (b *PeerBundle) removeKnownPeer(id string) bool {
|
||||
if b.has(id) {
|
||||
b.KnownPeers[id].clear()
|
||||
delete(b.KnownPeers, id)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type KnownPeer struct {
|
||||
Active bool `json:"active"`
|
||||
Sessions []*PeerSession `json:"sessions,omitempty"`
|
||||
sampleCount int
|
||||
}
|
||||
|
||||
func (peer *KnownPeer) append(session *PeerSession) {
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
ingress, egress := session.Ingress, session.Egress
|
||||
// Truncate the traffic arrays if they have more samples than the limit.
|
||||
if first := len(ingress) - sampleLimit; first > 0 {
|
||||
ingress = ingress[first:]
|
||||
}
|
||||
// If the length of the ingress and the egress arrays are different,
|
||||
// cut the first part of the longer one. i.e. make sure they have the
|
||||
// same length.
|
||||
if first := len(ingress) - len(egress); first > 0 {
|
||||
ingress = ingress[first:]
|
||||
} else if first < 0 {
|
||||
egress = egress[-first:]
|
||||
}
|
||||
if len(peer.Sessions) < 1 {
|
||||
// If this is the first session.
|
||||
peer.Sessions = append(peer.Sessions, session)
|
||||
peer.sampleCount = len(ingress)
|
||||
return
|
||||
}
|
||||
// Cut the old samples from the beginning if the
|
||||
// count with the new samples exceeds the limit.
|
||||
for l := sampleLimit + len(ingress) - peer.sampleCount; l > 0; l-- {
|
||||
for len(peer.Sessions) > 0 && len(peer.Sessions[0].Ingress) < 1 {
|
||||
peer.Sessions = peer.Sessions[1:]
|
||||
}
|
||||
if len(peer.Sessions) < 1 {
|
||||
// This can only happen, when the sample count is greater than the
|
||||
// sample limit. Theoretically impossible.
|
||||
log.Warn("Empty session array with sample count greater than 0")
|
||||
return
|
||||
}
|
||||
first := peer.Sessions[0]
|
||||
first.Ingress = first.Ingress[1:]
|
||||
first.Egress = first.Egress[1:]
|
||||
peer.sampleCount--
|
||||
}
|
||||
peer.sampleCount += len(ingress)
|
||||
if session.Connected != nil {
|
||||
peer.Sessions = append(peer.Sessions, session)
|
||||
return
|
||||
}
|
||||
last := peer.Sessions[len(peer.Sessions)-1]
|
||||
last.Disconnected = session.Disconnected
|
||||
last.Ingress = append(last.Ingress, ingress...)
|
||||
last.Egress = append(last.Egress, egress...)
|
||||
}
|
||||
|
||||
func (peer *KnownPeer) upgrade(p *KnownPeer) {
|
||||
peer.Active = p.Active
|
||||
for _, session := range p.Sessions {
|
||||
peer.append(session)
|
||||
}
|
||||
}
|
||||
|
||||
func (peer *KnownPeer) clear() {
|
||||
for _, s := range peer.Sessions {
|
||||
s.Connected = nil
|
||||
s.Disconnected = nil
|
||||
s.Ingress = nil
|
||||
s.Egress = nil
|
||||
}
|
||||
peer.Sessions = peer.Sessions[:0]
|
||||
peer.sampleCount = 0
|
||||
}
|
||||
|
||||
type PeerSession struct {
|
||||
Connected *time.Time `json:"connected,omitempty"`
|
||||
Disconnected *time.Time `json:"disconnected,omitempty"`
|
||||
Ingress ChartEntries `json:"ingress,omitempty"`
|
||||
Egress ChartEntries `json:"egress,omitempty"`
|
||||
}
|
||||
|
||||
type UnknownPeer struct {
|
||||
Connected time.Time `json:"connected"`
|
||||
Disconnected time.Time `json:"disconnected"`
|
||||
Peers *PeerContainer `json:"peers,omitempty"` // Peer tree.
|
||||
Diff []*PeerEvent `json:"diff,omitempty"` // Events that change the peer tree.
|
||||
}
|
||||
|
||||
// SystemMessage contains the metered system data samples.
|
||||
|
|
|
|||
|
|
@ -18,12 +18,8 @@ package dashboard
|
|||
|
||||
import (
|
||||
"container/list"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
"github.com/mohae/deepcopy"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
|
|
@ -31,146 +27,344 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
eventBufferLimit = 128
|
||||
knownPeerLimit = 3 //p2p.MeteredPeerLimit
|
||||
unknownPeerLimit = 3 //p2p.MeteredPeerLimit
|
||||
eventBufferLimit = 128 // Maximum number of events of buffered peer events.
|
||||
knownPeerLimit = p2p.MeteredPeerLimit // Maximum number of stored peers, which successfully made the handshake.
|
||||
unknownPeerLimit = p2p.MeteredPeerLimit // Maximum number of stored peers, which failed to make the handshake.
|
||||
|
||||
// eventLimit is the maximum number of the dashboard's custom peer events,
|
||||
// that are collected between two metering period and sent to the clients
|
||||
// as one message.
|
||||
// TODO (kurkomisi): Limit the number of events.
|
||||
eventLimit = knownPeerLimit << 2
|
||||
)
|
||||
|
||||
type knownPeerDiff struct {
|
||||
*KnownPeer
|
||||
activeListElement *list.Element
|
||||
listElement *list.Element // Pointer to the peer element in the list.
|
||||
ip, id string
|
||||
}
|
||||
// PeerContainer contains information about the node's peers. This data structure
|
||||
// maintains the metered peer data based on the different behaviours of the peers.
|
||||
//
|
||||
// Every peer has an IP address, and the peers that manage to make the handshake
|
||||
// (known peers) have node IDs too. There can appear more peers with the same IP,
|
||||
// therefore the peer maintainer data structure is a tree consisting of a map of
|
||||
// maps, where the first key groups the peers by IP, while the second one groups
|
||||
// them by the node ID. The peers failing before the handshake (unknown peers)
|
||||
// only have IP addresses, so their connection attempts are stored as part of the
|
||||
// value of the outer map.
|
||||
//
|
||||
// Another criteria is to limit the number of metered peers so that
|
||||
// they don't fill the memory. The selection order is based on the
|
||||
// peers activity: the peers that are inactive for the longest time
|
||||
// are thrown first. For the selection a fifo list is used which is
|
||||
// linked to the bottom of the peer tree in a way that every activity
|
||||
// of the peer pushes the peer to the end of the list, so the inactive
|
||||
// ones come to the front. When a peer has some activity, it is removed
|
||||
// from and reinserted into the list. When the length of the list reaches
|
||||
// the limit, the first element is removed from the list, as well as from
|
||||
// the tree.
|
||||
//
|
||||
// The active peers that are still connected have priority over the disconnected
|
||||
// ones, therefore the list is extended by a separator, which is a pointer to a
|
||||
// list element. The separator separates the active peers from the inactive ones,
|
||||
// and it is the entry for the list. If the peer that is to be inserted is active,
|
||||
// it goes after the separator, otherwise it goes before. This way the active peers
|
||||
// never move to the front before the inactive ones.
|
||||
//
|
||||
// The peers that don't manage to make handshake are not inserted into the list,
|
||||
// only their connection attempts are appended to the array belonging to their IP.
|
||||
// In order to keep the fifo principle, a super array contains the order of the
|
||||
// attempts, and when the overall count reaches the limit, the earliest attempt is
|
||||
// removed from the beginning of its array.
|
||||
//
|
||||
// This data structure makes it possible to marshal the peer
|
||||
// history simply by passing it to the JSON marshaler.
|
||||
type PeerContainer struct {
|
||||
// Bundles is the outer map using the peer's IP address as key.
|
||||
Bundles map[string]*PeerBundle `json:"bundles,omitempty"`
|
||||
|
||||
type peerDiff struct {
|
||||
*PeersMessage
|
||||
root *PeersMessage
|
||||
rootLock *sync.RWMutex
|
||||
// activeSeparator is a pointer to the last inactive peer element, splitting
|
||||
// the list into an inactive and an active part, and forming the entry for
|
||||
// the peer list.
|
||||
activeSeparator *list.Element
|
||||
|
||||
knownActivePeerList *list.List
|
||||
knownInactivePeerList *list.List
|
||||
// knownPeers contains the peers that managed to make handshake.
|
||||
knownPeers *list.List
|
||||
|
||||
// unknownPeers is the super array containing the IP addresses, from which
|
||||
// the peers attempted to connect then failed before/during the handshake.
|
||||
// Its values are appended in chronological order, which means that the
|
||||
// oldest attempt is at the beginning of the array. When the first element
|
||||
// is removed, the first element of the related bundle's attempt array is
|
||||
// removed too, ensuring that always the latest attempts are stored.
|
||||
unknownPeers []string
|
||||
|
||||
// geodb is the geoip database used to retrieve the peers' geographical location.
|
||||
geodb *GeoDB
|
||||
|
||||
// refresh is the refresh rate used to generate the
|
||||
// initial auxiliary traffic samples' time stamps.
|
||||
refresh time.Duration
|
||||
}
|
||||
|
||||
func newPeerDiff(root *PeersMessage, rootLock *sync.RWMutex, geodb *GeoDB, refresh time.Duration) *peerDiff {
|
||||
return &peerDiff{
|
||||
PeersMessage: NewPeersMessage(),
|
||||
root: root,
|
||||
rootLock: rootLock,
|
||||
knownActivePeerList: list.New(),
|
||||
knownInactivePeerList: list.New(),
|
||||
// NewPeerContainer returns a new instance of the peer container.
|
||||
func NewPeerContainer(geodb *GeoDB, refresh time.Duration) *PeerContainer {
|
||||
return &PeerContainer{
|
||||
Bundles: make(map[string]*PeerBundle),
|
||||
knownPeers: list.New(),
|
||||
unknownPeers: make([]string, 0, unknownPeerLimit),
|
||||
geodb: geodb,
|
||||
refresh: refresh,
|
||||
}
|
||||
}
|
||||
|
||||
func (diff *peerDiff) insert(ip, id string, session *PeerSession) {
|
||||
newIP, newID := diff.initKnownPeer(ip, id)
|
||||
bundle := diff.Bundles[ip]
|
||||
if newIP {
|
||||
bundle.Location = diff.geodb.Location(ip)
|
||||
// getOrInitBundle inserts a new peer bundle into the map, if the peer belonging
|
||||
// to the given IP wasn't metered so far. In this case retrieves the location of
|
||||
// the IP address from the database and creates a corresponding peer event.
|
||||
// Returns the bundle belonging to the given IP and the events occurring during
|
||||
// the initialization.
|
||||
func (pc *PeerContainer) getOrInitBundle(ip string) (*PeerBundle, []*PeerEvent) {
|
||||
var events []*PeerEvent
|
||||
if _, ok := pc.Bundles[ip]; !ok {
|
||||
location := pc.geodb.Location(ip)
|
||||
events = append(events, &PeerEvent{
|
||||
IP: ip,
|
||||
Location: location,
|
||||
})
|
||||
pc.Bundles[ip] = &PeerBundle{
|
||||
Location: location,
|
||||
KnownPeers: make(map[string]*KnownPeer),
|
||||
}
|
||||
peer := &knownPeerDiff{
|
||||
KnownPeer: bundle.KnownPeers[id],
|
||||
}
|
||||
return pc.Bundles[ip], events
|
||||
}
|
||||
|
||||
// extendKnown handles the events of the successfully connected peers.
|
||||
// Returns the events occurring during the extension.
|
||||
func (pc *PeerContainer) extendKnown(event *PeerEvent) (events []*PeerEvent) {
|
||||
bundle, bundleInitEvents := pc.getOrInitBundle(event.IP)
|
||||
events = append(events, bundleInitEvents...)
|
||||
peer, peerInitEvents := bundle.getOrInitKnownPeer(event.IP, event.ID, pc.refresh)
|
||||
events = append(events, peerInitEvents...)
|
||||
if peer.listElement != nil {
|
||||
// If the peer is already in the list, remove it and reinsert later.
|
||||
pc.knownPeers.Remove(peer.listElement)
|
||||
peer.listElement = nil
|
||||
}
|
||||
for pc.knownPeers.Len() >= knownPeerLimit {
|
||||
// While the length of the list is greater than or equal to the limit before
|
||||
// the insertion, remove the first element from the list and from the map.
|
||||
if removedPeer, ok := pc.knownPeers.Remove(pc.knownPeers.Front()).(*KnownPeer); ok {
|
||||
if event := pc.removeKnown(removedPeer.ip, removedPeer.id); event != nil {
|
||||
events = append(events, event)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Append the connect and the disconnect events to
|
||||
// the corresponding arrays keeping the limit.
|
||||
if event.Connected != nil {
|
||||
peer.Connected = append(peer.Connected, event.Connected)
|
||||
if first := len(peer.Connected) - sampleLimit; first > 0 {
|
||||
peer.Connected = peer.Connected[first:]
|
||||
}
|
||||
}
|
||||
if event.Disconnected != nil {
|
||||
peer.Disconnected = append(peer.Disconnected, event.Disconnected)
|
||||
if first := len(peer.Disconnected) - sampleLimit; first > 0 {
|
||||
peer.Disconnected = peer.Disconnected[first:]
|
||||
}
|
||||
}
|
||||
// Insert the peer into the list.
|
||||
if pc.activeSeparator == nil {
|
||||
// If there isn't active peer in the list
|
||||
peer.listElement = pc.knownPeers.PushBack(peer)
|
||||
pc.activeSeparator = peer.listElement
|
||||
} else {
|
||||
// Insert the peer after the last inactive peer, then increment the separator.
|
||||
peer.listElement = pc.knownPeers.InsertAfter(peer, pc.activeSeparator)
|
||||
pc.activeSeparator = pc.activeSeparator.Next()
|
||||
}
|
||||
return events
|
||||
}
|
||||
|
||||
// extendUnknown handles the events of the peers failing before/during the handshake.
|
||||
// Returns the events occurring during the extension.
|
||||
func (pc *PeerContainer) extendUnknown(event *PeerEvent) (events []*PeerEvent) {
|
||||
bundle, initEvents := pc.getOrInitBundle(event.IP)
|
||||
events = append(events, initEvents...)
|
||||
bundle.UnknownPeers = append(bundle.UnknownPeers, &UnknownPeer{
|
||||
Connected: *event.Connected,
|
||||
Disconnected: *event.Disconnected,
|
||||
})
|
||||
pc.unknownPeers = append(pc.unknownPeers, event.IP)
|
||||
for len(pc.unknownPeers) > unknownPeerLimit {
|
||||
// While the length of the connection attempt order array is greater
|
||||
// than the limit, remove the first element from the involved peer's
|
||||
// array and also from the super array.
|
||||
if r := pc.removeUnknown(pc.unknownPeers[0]); r != nil {
|
||||
events = append(events, r)
|
||||
}
|
||||
pc.unknownPeers = pc.unknownPeers[1:]
|
||||
}
|
||||
return events
|
||||
}
|
||||
|
||||
// setActive moves the peer denoted by the given IP address and node ID after
|
||||
// the list's active separator.
|
||||
func (pc *PeerContainer) setActive(ip, id string) {
|
||||
if bundle, ok := pc.Bundles[ip]; ok {
|
||||
if peer, ok := bundle.KnownPeers[id]; ok {
|
||||
if peer.listElement != nil {
|
||||
// If the peer is already in the list, remove it first.
|
||||
// Theoretically this should always happen, because all
|
||||
// the peers are inserted into the list.
|
||||
pc.knownPeers.Remove(peer.listElement)
|
||||
}
|
||||
if pc.activeSeparator == nil {
|
||||
// If there isn't active peer yet.
|
||||
peer.listElement = pc.knownPeers.PushBack(peer)
|
||||
pc.activeSeparator = peer.listElement
|
||||
} else {
|
||||
peer.listElement = pc.knownPeers.InsertAfter(peer, pc.activeSeparator)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// resetActiveSeparator resets the active separator, denoting
|
||||
// that active peers are not considered active anymore.
|
||||
func (pc *PeerContainer) resetActiveSeparator() {
|
||||
pc.activeSeparator = nil
|
||||
}
|
||||
|
||||
// PeerBundle contains the peers belonging to a given IP address.
|
||||
type PeerBundle struct {
|
||||
// Location contains the geographical location based on the bundle's IP address.
|
||||
Location *GeoLocation `json:"location,omitempty"`
|
||||
|
||||
// KnownPeers is the inner map of the metered peer
|
||||
// maintainer data structure using the node ID as key.
|
||||
KnownPeers map[string]*KnownPeer `json:"knownPeers,omitempty"`
|
||||
|
||||
// UnknownPeers contains the failed connection attempts of the
|
||||
// peers belonging to a given IP address in chronological order.
|
||||
UnknownPeers []*UnknownPeer `json:"unknownPeers,omitempty"`
|
||||
}
|
||||
|
||||
// removeKnown removes the known peer belonging to the
|
||||
// given IP address and node ID from the peer tree.
|
||||
func (pc *PeerContainer) removeKnown(ip, id string) (removed *PeerEvent) {
|
||||
if bundle, ok := pc.Bundles[ip]; ok {
|
||||
if _, ok := bundle.KnownPeers[id]; ok {
|
||||
removed = &PeerEvent{
|
||||
RemoveID: id,
|
||||
}
|
||||
delete(bundle.KnownPeers, id)
|
||||
}
|
||||
if len(bundle.KnownPeers) < 1 && len(bundle.UnknownPeers) < 1 {
|
||||
if removed == nil {
|
||||
removed = &PeerEvent{
|
||||
RemoveIP: ip,
|
||||
}
|
||||
} else {
|
||||
removed.RemoveIP = ip
|
||||
}
|
||||
delete(pc.Bundles, ip)
|
||||
}
|
||||
}
|
||||
return removed
|
||||
}
|
||||
|
||||
// removeUnknown removes the unknown peer belonging to the
|
||||
// given IP address and node ID from the peer tree.
|
||||
func (pc *PeerContainer) removeUnknown(ip string) (removed *PeerEvent) {
|
||||
if bundle, ok := pc.Bundles[ip]; ok {
|
||||
if len(bundle.UnknownPeers) > 0 {
|
||||
bundle.UnknownPeers = bundle.UnknownPeers[1:]
|
||||
}
|
||||
if len(bundle.KnownPeers) < 1 && len(bundle.UnknownPeers) < 1 {
|
||||
removed = &PeerEvent{RemoveIP: ip}
|
||||
delete(pc.Bundles, ip)
|
||||
}
|
||||
}
|
||||
return removed
|
||||
}
|
||||
|
||||
// getOrInitKnownPeer inserts a new peer into the map, if the peer belonging
|
||||
// to the given IP address and node ID wasn't metered so far. Returns the peer
|
||||
// belonging to the given IP and ID as well as the events occurring during the
|
||||
// initialization.
|
||||
func (bundle *PeerBundle) getOrInitKnownPeer(ip, id string, refresh time.Duration) (*KnownPeer, []*PeerEvent) {
|
||||
var events []*PeerEvent
|
||||
if _, ok := bundle.KnownPeers[id]; !ok {
|
||||
now := time.Now()
|
||||
ingress := emptyChartEntries(now, sampleLimit, refresh)
|
||||
egress := emptyChartEntries(now, sampleLimit, refresh)
|
||||
events = append(events, &PeerEvent{
|
||||
IP: ip,
|
||||
ID: id,
|
||||
Ingress: ingress,
|
||||
Egress: egress,
|
||||
})
|
||||
bundle.KnownPeers[id] = &KnownPeer{
|
||||
ip: ip,
|
||||
id: id,
|
||||
}
|
||||
if newID {
|
||||
now := time.Now()
|
||||
peer.append(&PeerSession{
|
||||
Ingress: emptyChartEntries(now, sampleLimit, diff.refresh),
|
||||
Egress: emptyChartEntries(now, sampleLimit, diff.refresh),
|
||||
})
|
||||
}
|
||||
peer.append(session)
|
||||
if peer.activeListElement != nil {
|
||||
diff.knownActivePeerList.Remove(peer.activeListElement)
|
||||
}
|
||||
if peer.listElement != nil {
|
||||
diff.knownInactivePeerList.Remove(peer.listElement)
|
||||
}
|
||||
// Set peer activity
|
||||
if len(peer.Sessions) > 0 {
|
||||
peer.Active = peer.Sessions[len(peer.Sessions)-1].Disconnected == nil
|
||||
} else {
|
||||
diff.rootLock.RLock()
|
||||
if diff.root.hasKnownPeer(ip, id) {
|
||||
rootSessions := diff.root.Bundles[ip].KnownPeers[id].Sessions
|
||||
peer.Active = len(rootSessions) > 0 && rootSessions[len(rootSessions)-1].Disconnected == nil
|
||||
} else {
|
||||
peer.Active = false
|
||||
}
|
||||
diff.rootLock.RUnlock()
|
||||
}
|
||||
if peer.Active {
|
||||
peer.activeListElement = diff.knownActivePeerList.PushBack(peer)
|
||||
} else {
|
||||
peer.listElement = diff.knownInactivePeerList.PushBack(peer)
|
||||
}
|
||||
for diff.knownActivePeerList.Len()+diff.knownInactivePeerList.Len() > knownPeerLimit {
|
||||
var removed interface{}
|
||||
if diff.knownInactivePeerList.Len() > 0 {
|
||||
removed = diff.knownInactivePeerList.Remove(diff.knownInactivePeerList.Front())
|
||||
} else {
|
||||
removed = diff.knownActivePeerList.Remove(diff.knownActivePeerList.Front())
|
||||
}
|
||||
if p, ok := removed.(*knownPeerDiff); ok {
|
||||
diff.removeKnownPeer(p.ip, p.id)
|
||||
diff.rootLock.RLock()
|
||||
if diff.root.hasKnownPeer(p.ip, p.id) {
|
||||
diff.RemovedKnownIP = append(diff.RemovedKnownIP, p.ip)
|
||||
diff.RemovedKnownID = append(diff.RemovedKnownID, p.id)
|
||||
}
|
||||
diff.rootLock.RUnlock()
|
||||
Ingress: ingress,
|
||||
Egress: egress,
|
||||
}
|
||||
}
|
||||
return bundle.KnownPeers[id], events
|
||||
}
|
||||
|
||||
func (diff *peerDiff) insertUnknown(ip string, peer *UnknownPeer) {
|
||||
newBundle := diff.initBundle(ip)
|
||||
bundle := diff.Bundles[ip]
|
||||
if newBundle {
|
||||
bundle.Location = diff.geodb.Location(ip)
|
||||
}
|
||||
diff.unknownPeers = append(diff.unknownPeers, ip)
|
||||
bundle.UnknownPeers = append(bundle.UnknownPeers, peer)
|
||||
for len(diff.unknownPeers) > unknownPeerLimit {
|
||||
rip := diff.unknownPeers[0]
|
||||
diff.RemovedUnknownIP = append(diff.RemovedUnknownIP, rip)
|
||||
diff.removeUnknownPeer(rip)
|
||||
diff.unknownPeers = diff.unknownPeers[1:]
|
||||
}
|
||||
// KnownPeer contains the metered data of a particular peer.
|
||||
type KnownPeer struct {
|
||||
// Connected contains the timestamps of the peer's connection events.
|
||||
Connected []*time.Time `json:"connected,omitempty"`
|
||||
|
||||
// Disconnected contains the timestamps of the peer's disconnection events.
|
||||
Disconnected []*time.Time `json:"disconnected,omitempty"`
|
||||
|
||||
// Ingress and Egress contain the peer's traffic samples, which are collected
|
||||
// periodically from the metrics registry.
|
||||
//
|
||||
// A peer can connect multiple times, and we want to visualize the time
|
||||
// passed between two connections, so after the first connection a 0 value
|
||||
// is appended to the traffic arrays even if the peer is inactive until the
|
||||
// peer is removed.
|
||||
Ingress ChartEntries `json:"ingress,omitempty"`
|
||||
Egress ChartEntries `json:"egress,omitempty"`
|
||||
|
||||
listElement *list.Element // Pointer to the peer element in the list.
|
||||
ip, id string // The IP and the ID by which the peer can be accessed in the tree.
|
||||
}
|
||||
|
||||
func (diff *peerDiff) dump() {
|
||||
diff.rootLock.Lock()
|
||||
for i := 0; i < len(diff.RemovedKnownIP); i++ {
|
||||
diff.root.removeKnownPeer(diff.RemovedKnownIP[i], diff.RemovedKnownID[i])
|
||||
// UnknownPeer contains a failed peer connection attempt's attributes.
|
||||
type UnknownPeer struct {
|
||||
// Connected contains the timestamp of the connection attempt's moment.
|
||||
Connected time.Time `json:"connected"`
|
||||
|
||||
// Disconnected contains the timestamp of the
|
||||
// moment when the connection attempt failed.
|
||||
Disconnected time.Time `json:"disconnected"`
|
||||
}
|
||||
|
||||
// PeerEvent contains the attributes of a peer event.
|
||||
type PeerEvent struct {
|
||||
IP string `json:"ip,omitempty"` // IP address of the peer.
|
||||
ID string `json:"id,omitempty"` // Node ID of the peer.
|
||||
RemoveIP string `json:"removeIP,omitempty"` // IP address of the peer that is to be removed.
|
||||
RemoveID string `json:"removeID,omitempty"` // Node ID of the peer that is to be removed.
|
||||
Location *GeoLocation `json:"location,omitempty"` // Geographical location of the peer.
|
||||
Connected *time.Time `json:"connected,omitempty"` // Timestamp of the connection moment.
|
||||
Disconnected *time.Time `json:"disconnected,omitempty"` // Timestamp of the disonnection moment.
|
||||
Ingress ChartEntries `json:"ingress,omitempty"` // Ingress samples.
|
||||
Egress ChartEntries `json:"egress,omitempty"` // Egress samples.
|
||||
}
|
||||
|
||||
// trafficMap
|
||||
type trafficMap map[string]map[string]float64
|
||||
|
||||
func (m *trafficMap) insert(ip, id string, val float64) {
|
||||
if _, ok := (*m)[ip]; !ok {
|
||||
(*m)[ip] = make(map[string]float64)
|
||||
}
|
||||
for _, rip := range diff.RemovedUnknownIP {
|
||||
diff.root.removeUnknownPeer(rip)
|
||||
}
|
||||
for e := diff.knownActivePeerList.Front(); e != nil; e = e.Next() {
|
||||
if peer, ok := e.Value.(*knownPeerDiff); ok {
|
||||
diff.root.getOrInitKnownPeer(peer.ip, peer.id).upgrade(peer.KnownPeer)
|
||||
} else {
|
||||
log.Warn("Invalid value in the active peer metrics list")
|
||||
}
|
||||
}
|
||||
for e := diff.knownInactivePeerList.Front(); e != nil; e = e.Next() {
|
||||
if peer, ok := e.Value.(*knownPeerDiff); ok {
|
||||
diff.root.getOrInitKnownPeer(peer.ip, peer.id).upgrade(peer.KnownPeer)
|
||||
} else {
|
||||
log.Warn("Invalid value in the inactive peer metrics list")
|
||||
}
|
||||
}
|
||||
diff.rootLock.Unlock()
|
||||
diff.clear()
|
||||
(*m)[ip][id] = val
|
||||
}
|
||||
|
||||
// collectPeerData gathers data about the peers and sends it to the clients.
|
||||
|
|
@ -194,15 +388,26 @@ func (db *Dashboard) collectPeerData() {
|
|||
defer ticker.Stop()
|
||||
|
||||
type registryFunc func(name string, i interface{})
|
||||
type collectorFunc func(traffic *map[string]float64) registryFunc
|
||||
type collectorFunc func(traffic *trafficMap) registryFunc
|
||||
|
||||
// trafficCollector generates a function that can be passed to
|
||||
// the prefixed peer registry in order to collect the metered
|
||||
// traffic data from each peer meter.
|
||||
trafficCollector := func(prefix string) collectorFunc {
|
||||
return func(traffic *map[string]float64) registryFunc {
|
||||
// This part makes is possible to collect the
|
||||
// traffic data into a map from outside.
|
||||
return func(traffic *trafficMap) registryFunc {
|
||||
// The function which can be passed to the registry.
|
||||
return func(name string, i interface{}) {
|
||||
if m, ok := i.(metrics.Meter); ok {
|
||||
(*traffic)[strings.TrimPrefix(name, prefix)] = float64(m.Count())
|
||||
// The name of the meter has the format: <common traffic prefix><IP>/<ID>
|
||||
if k := strings.Split(strings.TrimPrefix(name, prefix), "/"); len(k) == 2 {
|
||||
traffic.insert(k[0], k[1], float64(m.Count()))
|
||||
} else {
|
||||
log.Warn("Bad value used as meter", "name", name)
|
||||
log.Warn("Invalid meter name", "name", name, "prefix", prefix)
|
||||
}
|
||||
} else {
|
||||
log.Warn("Invalid meter type", "name", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -210,11 +415,19 @@ func (db *Dashboard) collectPeerData() {
|
|||
collectIngress := trafficCollector(p2p.MetricsInboundTraffic + "/")
|
||||
collectEgress := trafficCollector(p2p.MetricsOutboundTraffic + "/")
|
||||
|
||||
peers := NewPeerContainer(db.geodb, db.config.Refresh)
|
||||
db.peerLock.Lock()
|
||||
db.history.Network = &NetworkMessage{Peers: NewPeersMessage()}
|
||||
diff := newPeerDiff(db.history.Network.Peers, &db.peerLock, db.geodb, db.config.Refresh)
|
||||
db.history.Network = &NetworkMessage{
|
||||
Peers: peers,
|
||||
}
|
||||
db.peerLock.Unlock()
|
||||
|
||||
// diff contains peer events, which trigger operations that
|
||||
// will be executed on the peer tree after a metering period.
|
||||
diff := make([]*PeerEvent, 0, eventLimit)
|
||||
ingress, egress := new(trafficMap), new(trafficMap)
|
||||
*ingress, *egress = make(trafficMap), make(trafficMap)
|
||||
|
||||
for {
|
||||
select {
|
||||
case event := <-peerCh:
|
||||
|
|
@ -222,80 +435,121 @@ func (db *Dashboard) collectPeerData() {
|
|||
switch event.Type {
|
||||
case p2p.PeerConnected:
|
||||
connected := now.Add(-event.Elapsed)
|
||||
diff.insert(event.IP.String(), event.ID, &PeerSession{
|
||||
diff = append(diff, &PeerEvent{
|
||||
IP: event.IP.String(),
|
||||
ID: event.ID,
|
||||
Connected: &connected,
|
||||
})
|
||||
case p2p.PeerDisconnected:
|
||||
diff.insert(event.IP.String(), event.ID, &PeerSession{
|
||||
diff = append(diff, &PeerEvent{
|
||||
IP: event.IP.String(),
|
||||
ID: event.ID,
|
||||
Disconnected: &now,
|
||||
Ingress: ChartEntries{
|
||||
&ChartEntry{
|
||||
Time: now,
|
||||
Value: float64(event.Ingress),
|
||||
},
|
||||
},
|
||||
Egress: ChartEntries{
|
||||
&ChartEntry{
|
||||
Time: now,
|
||||
Value: float64(event.Egress),
|
||||
},
|
||||
},
|
||||
})
|
||||
// The disconnect event comes with the last metered traffic count,
|
||||
// because after the disconnection the peer's meter is removed
|
||||
// from the registry. It can happen, that between two metering
|
||||
// period the same peer disconnects multiple times, and appending
|
||||
// all the samples to the traffic arrays would shift the metering,
|
||||
// so only the last metering is stored, overwriting the previous one.
|
||||
ingress.insert(event.IP.String(), event.ID, float64(event.Ingress))
|
||||
egress.insert(event.IP.String(), event.ID, float64(event.Egress))
|
||||
case p2p.PeerHandshakeFailed:
|
||||
diff.insertUnknown(event.IP.String(), &UnknownPeer{
|
||||
Connected: now.Add(-event.Elapsed),
|
||||
Disconnected: now,
|
||||
connected := now.Add(-event.Elapsed)
|
||||
diff = append(diff, &PeerEvent{
|
||||
IP: event.IP.String(),
|
||||
Connected: &connected,
|
||||
Disconnected: &now,
|
||||
})
|
||||
default:
|
||||
log.Error("Unknown metered peer event type", "type", event.Type)
|
||||
}
|
||||
case <-ticker.C:
|
||||
ingress, egress := make(map[string]float64), make(map[string]float64)
|
||||
p2p.PeerIngressRegistry.Each(collectIngress(&ingress))
|
||||
p2p.PeerEgressRegistry.Each(collectEgress(&egress))
|
||||
// Collect the traffic samples from the registry.
|
||||
p2p.PeerIngressRegistry.Each(collectIngress(ingress))
|
||||
p2p.PeerEgressRegistry.Each(collectEgress(egress))
|
||||
|
||||
now := time.Now()
|
||||
appendSample := func(key string, ingress, egress float64) {
|
||||
if k := strings.Split(key, "/"); len(k) == 2 {
|
||||
diff.insert(k[0], k[1], &PeerSession{
|
||||
Ingress: ChartEntries{&ChartEntry{
|
||||
Time: now,
|
||||
Value: ingress,
|
||||
}},
|
||||
Egress: ChartEntries{&ChartEntry{
|
||||
Time: now,
|
||||
Value: egress,
|
||||
}},
|
||||
})
|
||||
} else {
|
||||
log.Warn("Invalid traffic key", "key", key)
|
||||
db.peerLock.Lock()
|
||||
// Usually the active peers don't produce events, and marking
|
||||
// them as active makes it sure that they won't be removed from
|
||||
// the tree. Only the active peers are registered into the peer
|
||||
// registry, so after the traffic collection the ingress and the
|
||||
// egress maps contain all the active peers.
|
||||
//
|
||||
// It is important to mark the active ones before the merge with
|
||||
// the diff, otherwise the active peers can be removed.
|
||||
//
|
||||
// After a metering period the active peers can become inactive,
|
||||
// so resetting the separator makes it sure, that only the active
|
||||
// peers move to the protected part of the list.
|
||||
peers.resetActiveSeparator()
|
||||
for ip, bundle := range *ingress {
|
||||
for id := range bundle {
|
||||
if _, ok := (*egress)[ip][id]; ok {
|
||||
peers.setActive(ip, id)
|
||||
}
|
||||
}
|
||||
for key, val := range ingress {
|
||||
appendSample(key, val, egress[key])
|
||||
}
|
||||
for key, val := range egress {
|
||||
if _, ok := ingress[key]; ok {
|
||||
var events []*PeerEvent
|
||||
for i := 0; i < len(diff); i++ {
|
||||
if diff[i].IP == "" {
|
||||
log.Warn("Peer event without IP", "event", *diff[i])
|
||||
continue
|
||||
}
|
||||
appendSample(key, ingress[key], val)
|
||||
// There are two main branches of peer events coming from the event
|
||||
// feed, one belongs to the known peers, one to the unknown peers.
|
||||
// If the event has node ID, it belongs to a known peer, otherwise
|
||||
// to an unknown one.
|
||||
if diff[i].ID == "" {
|
||||
events = append(events, peers.extendUnknown(diff[i])...)
|
||||
continue
|
||||
}
|
||||
for e := diff.knownInactivePeerList.Front(); e != nil; e = e.Next() {
|
||||
if peer, ok := e.Value.(*knownPeerDiff); ok {
|
||||
diff.insert(peer.ip, peer.id, &PeerSession{
|
||||
Ingress: ChartEntries{&ChartEntry{
|
||||
events = append(events, peers.extendKnown(diff[i])...)
|
||||
}
|
||||
// The insertion can produce additional peer events, such
|
||||
// as remove, location and initial samples events. These
|
||||
// are stored in the 'events', then appended to the 'diff'.
|
||||
diff = append(diff, events...)
|
||||
now := time.Now()
|
||||
// Update the peer tree using the events.
|
||||
for ip, bundle := range peers.Bundles {
|
||||
for id, peer := range bundle.KnownPeers {
|
||||
// Value is 0 if the traffic map doesn't have the
|
||||
// entry corresponding to the given IP and ID.
|
||||
i := &ChartEntry{
|
||||
Time: now,
|
||||
}},
|
||||
Egress: ChartEntries{&ChartEntry{
|
||||
Value: (*ingress)[ip][id],
|
||||
}
|
||||
e := &ChartEntry{
|
||||
Time: now,
|
||||
}},
|
||||
Value: (*egress)[ip][id],
|
||||
}
|
||||
peer.Ingress = append(peer.Ingress, i)
|
||||
peer.Egress = append(peer.Egress, e)
|
||||
if first := len(peer.Ingress) - sampleLimit; first > 0 {
|
||||
peer.Ingress = peer.Ingress[first:]
|
||||
}
|
||||
if first := len(peer.Egress) - sampleLimit; first > 0 {
|
||||
peer.Egress = peer.Egress[first:]
|
||||
}
|
||||
// Creating the traffic sample events.
|
||||
diff = append(diff, &PeerEvent{
|
||||
IP: ip,
|
||||
ID: id,
|
||||
Ingress: ChartEntries{i},
|
||||
Egress: ChartEntries{e},
|
||||
})
|
||||
}
|
||||
}
|
||||
db.sendToAll(&Message{Network: &NetworkMessage{Peers: deepcopy.Copy(diff.PeersMessage).(*PeersMessage)}})
|
||||
s, _ := json.MarshalIndent(deepcopy.Copy(diff), "", " ")
|
||||
fmt.Println(string(s))
|
||||
diff.dump()
|
||||
db.peerLock.Unlock()
|
||||
|
||||
db.sendToAll(&Message{Network: &NetworkMessage{
|
||||
Diff: append([]*PeerEvent{}, diff...)},
|
||||
})
|
||||
// Clear the traffic maps, and the event array,
|
||||
// prepare them for the next metering.
|
||||
*ingress, *egress = make(trafficMap), make(trafficMap)
|
||||
diff = diff[:0]
|
||||
case err := <-subPeer.Err():
|
||||
log.Warn("Peer subscription error", "err", err)
|
||||
return
|
||||
|
|
|
|||
Loading…
Reference in a new issue