Skip to content

Commit cbf2579

Browse files
authored
p2p, p2p/discover: add dial metrics (#27621)
This PR adds metrics for p2p dialing, which gives us visibility into the quality of the dial candidates returned by our discovery methods.
1 parent ea78280 commit cbf2579

File tree

13 files changed

+277
-51
lines changed

13 files changed

+277
-51
lines changed

eth/handler.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import (
3838
"github.com/ethereum/go-ethereum/ethdb"
3939
"github.com/ethereum/go-ethereum/event"
4040
"github.com/ethereum/go-ethereum/log"
41+
"github.com/ethereum/go-ethereum/metrics"
4142
"github.com/ethereum/go-ethereum/p2p"
4243
)
4344

@@ -424,6 +425,13 @@ func (h *handler) runSnapExtension(peer *snap.Peer, handler snap.Handler) error
424425
defer h.peerWG.Done()
425426

426427
if err := h.peers.registerSnapExtension(peer); err != nil {
428+
if metrics.Enabled {
429+
if peer.Inbound() {
430+
snap.IngressRegistrationErrorMeter.Mark(1)
431+
} else {
432+
snap.EgressRegistrationErrorMeter.Mark(1)
433+
}
434+
}
427435
peer.Log().Warn("Snapshot extension registration failed", "err", err)
428436
return err
429437
}

eth/protocols/eth/handshake.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@
1717
package eth
1818

1919
import (
20+
"errors"
2021
"fmt"
2122
"math/big"
2223
"time"
2324

2425
"github.com/ethereum/go-ethereum/common"
2526
"github.com/ethereum/go-ethereum/core/forkid"
27+
"github.com/ethereum/go-ethereum/metrics"
2628
"github.com/ethereum/go-ethereum/p2p"
2729
)
2830

@@ -59,9 +61,11 @@ func (p *Peer) Handshake(network uint64, td *big.Int, head common.Hash, genesis
5961
select {
6062
case err := <-errc:
6163
if err != nil {
64+
markError(p, err)
6265
return err
6366
}
6467
case <-timeout.C:
68+
markError(p, p2p.DiscReadTimeout)
6569
return p2p.DiscReadTimeout
6670
}
6771
}
@@ -105,3 +109,25 @@ func (p *Peer) readStatus(network uint64, status *StatusPacket, genesis common.H
105109
}
106110
return nil
107111
}
112+
113+
// markError registers the error with the corresponding metric.
114+
func markError(p *Peer, err error) {
115+
if !metrics.Enabled {
116+
return
117+
}
118+
m := meters.get(p.Inbound())
119+
switch errors.Unwrap(err) {
120+
case errNetworkIDMismatch:
121+
m.networkIDMismatch.Mark(1)
122+
case errProtocolVersionMismatch:
123+
m.protocolVersionMismatch.Mark(1)
124+
case errGenesisMismatch:
125+
m.genesisMismatch.Mark(1)
126+
case errForkIDRejected:
127+
m.forkidRejected.Mark(1)
128+
case p2p.DiscReadTimeout:
129+
m.timeoutError.Mark(1)
130+
default:
131+
m.peerError.Mark(1)
132+
}
133+
}

eth/protocols/eth/metrics.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Copyright 2023 The go-ethereum Authors
2+
// This file is part of the go-ethereum library.
3+
//
4+
// The go-ethereum 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 go-ethereum 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package eth
18+
19+
import "github.com/ethereum/go-ethereum/metrics"
20+
21+
// meters stores ingress and egress handshake meters.
22+
var meters bidirectionalMeters
23+
24+
// bidirectionalMeters stores ingress and egress handshake meters.
25+
type bidirectionalMeters struct {
26+
ingress *hsMeters
27+
egress *hsMeters
28+
}
29+
30+
// get returns the corresponding meter depending if ingress or egress is
31+
// desired.
32+
func (h *bidirectionalMeters) get(ingress bool) *hsMeters {
33+
if ingress {
34+
return h.ingress
35+
}
36+
return h.egress
37+
}
38+
39+
// hsMeters is a collection of meters which track metrics related to the
40+
// eth subprotocol handshake.
41+
type hsMeters struct {
42+
// peerError measures the number of errors related to incorrect peer
43+
// behaviour, such as invalid message code, size, encoding, etc.
44+
peerError metrics.Meter
45+
46+
// timeoutError measures the number of timeouts.
47+
timeoutError metrics.Meter
48+
49+
// networkIDMismatch measures the number of network id mismatch errors.
50+
networkIDMismatch metrics.Meter
51+
52+
// protocolVersionMismatch measures the number of differing protocol
53+
// versions.
54+
protocolVersionMismatch metrics.Meter
55+
56+
// genesisMismatch measures the number of differing genesises.
57+
genesisMismatch metrics.Meter
58+
59+
// forkidRejected measures the number of differing forkids.
60+
forkidRejected metrics.Meter
61+
}
62+
63+
// newHandshakeMeters registers and returns handshake meters for the given
64+
// base.
65+
func newHandshakeMeters(base string) *hsMeters {
66+
return &hsMeters{
67+
peerError: metrics.NewRegisteredMeter(base+"error/peer", nil),
68+
timeoutError: metrics.NewRegisteredMeter(base+"error/timeout", nil),
69+
networkIDMismatch: metrics.NewRegisteredMeter(base+"error/network", nil),
70+
protocolVersionMismatch: metrics.NewRegisteredMeter(base+"error/version", nil),
71+
genesisMismatch: metrics.NewRegisteredMeter(base+"error/genesis", nil),
72+
forkidRejected: metrics.NewRegisteredMeter(base+"error/forkid", nil),
73+
}
74+
}
75+
76+
func init() {
77+
meters = bidirectionalMeters{
78+
ingress: newHandshakeMeters("eth/protocols/eth/ingress/handshake/"),
79+
egress: newHandshakeMeters("eth/protocols/eth/egress/handshake/"),
80+
}
81+
}

eth/protocols/snap/metrics.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright 2023 The go-ethereum Authors
2+
// This file is part of the go-ethereum library.
3+
//
4+
// The go-ethereum 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 go-ethereum 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package snap
18+
19+
import (
20+
metrics "github.com/ethereum/go-ethereum/metrics"
21+
)
22+
23+
var (
24+
ingressRegistrationErrorName = "eth/protocols/snap/ingress/registration/error"
25+
egressRegistrationErrorName = "eth/protocols/snap/egress/registration/error"
26+
27+
IngressRegistrationErrorMeter = metrics.NewRegisteredMeter(ingressRegistrationErrorName, nil)
28+
EgressRegistrationErrorMeter = metrics.NewRegisteredMeter(egressRegistrationErrorName, nil)
29+
)

p2p/dial.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -521,13 +521,14 @@ func (t *dialTask) resolve(d *dialScheduler) bool {
521521

522522
// dial performs the actual connection attempt.
523523
func (t *dialTask) dial(d *dialScheduler, dest *enode.Node) error {
524+
dialMeter.Mark(1)
524525
fd, err := d.dialer.Dial(d.ctx, t.dest)
525526
if err != nil {
526527
d.log.Trace("Dial error", "id", t.dest.ID(), "addr", nodeAddr(t.dest), "conn", t.flags, "err", cleanupDialErr(err))
528+
dialConnectionError.Mark(1)
527529
return &dialError{err}
528530
}
529-
mfd := newMeteredConn(fd, false, &net.TCPAddr{IP: dest.IP(), Port: dest.TCP()})
530-
return d.setupFunc(mfd, t.flags, dest)
531+
return d.setupFunc(newMeteredConn(fd), t.flags, dest)
531532
}
532533

533534
func (t *dialTask) String() string {

p2p/discover/metrics.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package discover
1818

1919
import (
20+
"fmt"
2021
"net"
2122

2223
"github.com/ethereum/go-ethereum/metrics"
@@ -32,10 +33,17 @@ const (
3233
)
3334

3435
var (
36+
bucketsCounter []metrics.Counter
3537
ingressTrafficMeter = metrics.NewRegisteredMeter(ingressMeterName, nil)
3638
egressTrafficMeter = metrics.NewRegisteredMeter(egressMeterName, nil)
3739
)
3840

41+
func init() {
42+
for i := 0; i < nBuckets; i++ {
43+
bucketsCounter = append(bucketsCounter, metrics.NewRegisteredCounter(fmt.Sprintf("%s/bucket/%d/count", moduleName, i), nil))
44+
}
45+
}
46+
3947
// meteredConn is a wrapper around a net.UDPConn that meters both the
4048
// inbound and outbound network traffic.
4149
type meteredUdpConn struct {

p2p/discover/table.go

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import (
3434

3535
"github.com/ethereum/go-ethereum/common"
3636
"github.com/ethereum/go-ethereum/log"
37+
"github.com/ethereum/go-ethereum/metrics"
3738
"github.com/ethereum/go-ethereum/p2p/enode"
3839
"github.com/ethereum/go-ethereum/p2p/netutil"
3940
)
@@ -80,7 +81,8 @@ type Table struct {
8081
closeReq chan struct{}
8182
closed chan struct{}
8283

83-
nodeAddedHook func(*node) // for testing
84+
nodeAddedHook func(*bucket, *node)
85+
nodeRemovedHook func(*bucket, *node)
8486
}
8587

8688
// transport is implemented by the UDP transports.
@@ -98,6 +100,7 @@ type bucket struct {
98100
entries []*node // live entries, sorted by time of last contact
99101
replacements []*node // recently seen nodes to be used if revalidation fails
100102
ips netutil.DistinctNetSet
103+
index int
101104
}
102105

103106
func newTable(t transport, db *enode.DB, cfg Config) (*Table, error) {
@@ -119,7 +122,8 @@ func newTable(t transport, db *enode.DB, cfg Config) (*Table, error) {
119122
}
120123
for i := range tab.buckets {
121124
tab.buckets[i] = &bucket{
122-
ips: netutil.DistinctNetSet{Subnet: bucketSubnet, Limit: bucketIPLimit},
125+
index: i,
126+
ips: netutil.DistinctNetSet{Subnet: bucketSubnet, Limit: bucketIPLimit},
123127
}
124128
}
125129
tab.seedRand()
@@ -128,6 +132,22 @@ func newTable(t transport, db *enode.DB, cfg Config) (*Table, error) {
128132
return tab, nil
129133
}
130134

135+
func newMeteredTable(t transport, db *enode.DB, cfg Config) (*Table, error) {
136+
tab, err := newTable(t, db, cfg)
137+
if err != nil {
138+
return nil, err
139+
}
140+
if metrics.Enabled {
141+
tab.nodeAddedHook = func(b *bucket, n *node) {
142+
bucketsCounter[b.index].Inc(1)
143+
}
144+
tab.nodeRemovedHook = func(b *bucket, n *node) {
145+
bucketsCounter[b.index].Dec(1)
146+
}
147+
}
148+
return tab, nil
149+
}
150+
131151
// Nodes returns all nodes contained in the table.
132152
func (tab *Table) Nodes() []*enode.Node {
133153
if !tab.isInitDone() {
@@ -495,7 +515,7 @@ func (tab *Table) addSeenNode(n *node) {
495515
n.addedAt = time.Now()
496516

497517
if tab.nodeAddedHook != nil {
498-
tab.nodeAddedHook(n)
518+
tab.nodeAddedHook(b, n)
499519
}
500520
}
501521

@@ -539,7 +559,7 @@ func (tab *Table) addVerifiedNode(n *node) {
539559
n.addedAt = time.Now()
540560

541561
if tab.nodeAddedHook != nil {
542-
tab.nodeAddedHook(n)
562+
tab.nodeAddedHook(b, n)
543563
}
544564
}
545565

@@ -638,8 +658,16 @@ func (tab *Table) bumpInBucket(b *bucket, n *node) bool {
638658
}
639659

640660
func (tab *Table) deleteInBucket(b *bucket, n *node) {
661+
// Check if the node is actually in the bucket so the removed hook
662+
// isn't called multiple times for the same node.
663+
if !contains(b.entries, n.ID()) {
664+
return
665+
}
641666
b.entries = deleteNode(b.entries, n)
642667
tab.removeIP(b, n.IP())
668+
if tab.nodeRemovedHook != nil {
669+
tab.nodeRemovedHook(b, n)
670+
}
643671
}
644672

645673
func contains(ns []*node, id enode.ID) bool {

p2p/discover/v4_udp.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ func ListenV4(c UDPConn, ln *enode.LocalNode, cfg Config) (*UDPv4, error) {
142142
log: cfg.Log,
143143
}
144144

145-
tab, err := newTable(t, ln.Database(), cfg)
145+
tab, err := newMeteredTable(t, ln.Database(), cfg)
146146
if err != nil {
147147
return nil, err
148148
}

p2p/discover/v4_udp_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,7 @@ func TestUDPv4_pingMatchIP(t *testing.T) {
394394
func TestUDPv4_successfulPing(t *testing.T) {
395395
test := newUDPTest(t)
396396
added := make(chan *node, 1)
397-
test.table.nodeAddedHook = func(n *node) { added <- n }
397+
test.table.nodeAddedHook = func(b *bucket, n *node) { added <- n }
398398
defer test.close()
399399

400400
// The remote side sends a ping packet to initiate the exchange.

p2p/discover/v5_udp.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ func newUDPv5(conn UDPConn, ln *enode.LocalNode, cfg Config) (*UDPv5, error) {
174174
cancelCloseCtx: cancelCloseCtx,
175175
}
176176
t.talk = newTalkSystem(t)
177-
tab, err := newTable(t, t.db, cfg)
177+
tab, err := newMeteredTable(t, t.db, cfg)
178178
if err != nil {
179179
return nil, err
180180
}

0 commit comments

Comments
 (0)