Start adding some metrics to pubsub (#192)

* Start adding some metrics to pubsub

In order to visualize it's functionality
Still WIP

* more metrics

* add per topic metrics

* finishup with requested metrics

* add a metrisServer define to start local server

* PR fixes and cleanup
This commit is contained in:
Giovanni Petrantoni 2020-06-07 16:15:21 +09:00 committed by GitHub
parent 130c64f33a
commit a6a2a81711
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 65 additions and 4 deletions

View File

@ -8,7 +8,7 @@
## those terms. ## those terms.
import sequtils, tables, sets, strutils import sequtils, tables, sets, strutils
import chronos, chronicles import chronos, chronicles, metrics
import pubsub, import pubsub,
pubsubpeer, pubsubpeer,
timedcache, timedcache,

View File

@ -8,7 +8,7 @@
## those terms. ## those terms.
import tables, sets, options, sequtils, random import tables, sets, options, sequtils, random
import chronos, chronicles import chronos, chronicles, metrics
import pubsub, import pubsub,
floodsub, floodsub,
pubsubpeer, pubsubpeer,
@ -56,6 +56,10 @@ type
heartbeatCancel*: Future[void] # cancellation future for heartbeat interval heartbeatCancel*: Future[void] # cancellation future for heartbeat interval
heartbeatLock: AsyncLock # heartbeat lock to prevent two consecutive concurrent heartbeats heartbeatLock: AsyncLock # heartbeat lock to prevent two consecutive concurrent heartbeats
declareGauge(libp2p_gossipsub_peers_per_topic_mesh, "gossipsub peers per topic in mesh", labels = ["topic"])
declareGauge(libp2p_gossipsub_peers_per_topic_fanout, "gossipsub peers per topic in fanout", labels = ["topic"])
declareGauge(libp2p_gossipsub_peers_per_topic_gossipsub, "gossipsub peers per topic in gossipsub", labels = ["topic"])
method init(g: GossipSub) = method init(g: GossipSub) =
proc handler(conn: Connection, proto: string) {.async.} = proc handler(conn: Connection, proto: string) {.async.} =
## main protocol handler that gets triggered on every ## main protocol handler that gets triggered on every
@ -79,6 +83,7 @@ proc replenishFanout(g: GossipSub, topic: string) {.async.} =
if topic in g.gossipsub: if topic in g.gossipsub:
for p in g.gossipsub[topic]: for p in g.gossipsub[topic]:
if not g.fanout[topic].containsOrIncl(p): if not g.fanout[topic].containsOrIncl(p):
libp2p_gossipsub_peers_per_topic_fanout.inc(labelValues = [topic])
if g.fanout[topic].len == GossipSubD: if g.fanout[topic].len == GossipSubD:
break break
@ -100,16 +105,19 @@ proc rebalanceMesh(g: GossipSub, topic: string) {.async.} =
if topic in g.fanout and g.fanout[topic].len > 0: if topic in g.fanout and g.fanout[topic].len > 0:
id = sample(toSeq(g.fanout[topic])) id = sample(toSeq(g.fanout[topic]))
g.fanout[topic].excl(id) g.fanout[topic].excl(id)
libp2p_gossipsub_peers_per_topic_fanout.dec(labelValues = [topic])
trace "got fanout peer", peer = id trace "got fanout peer", peer = id
elif topic in g.gossipsub and g.gossipsub[topic].len > 0: elif topic in g.gossipsub and g.gossipsub[topic].len > 0:
id = sample(toSeq(g.gossipsub[topic])) id = sample(toSeq(g.gossipsub[topic]))
g.gossipsub[topic].excl(id) g.gossipsub[topic].excl(id)
libp2p_gossipsub_peers_per_topic_gossipsub.dec(labelValues = [topic])
trace "got gossipsub peer", peer = id trace "got gossipsub peer", peer = id
else: else:
trace "no more peers" trace "no more peers"
break break
g.mesh[topic].incl(id) g.mesh[topic].incl(id)
libp2p_gossipsub_peers_per_topic_mesh.inc(labelValues = [topic])
if id in g.peers: if id in g.peers:
let p = g.peers[id] let p = g.peers[id]
# send a graft message to the peer # send a graft message to the peer
@ -122,6 +130,7 @@ proc rebalanceMesh(g: GossipSub, topic: string) {.async.} =
trace "pruning peers", peers = g.mesh[topic].len trace "pruning peers", peers = g.mesh[topic].len
let id = toSeq(g.mesh[topic])[rand(0..<g.mesh[topic].len)] let id = toSeq(g.mesh[topic])[rand(0..<g.mesh[topic].len)]
g.mesh[topic].excl(id) g.mesh[topic].excl(id)
libp2p_gossipsub_peers_per_topic_mesh.dec(labelValues = [topic])
let p = g.peers[id] let p = g.peers[id]
# send a graft message to the peer # send a graft message to the peer
@ -175,6 +184,7 @@ proc getGossipPeers(g: GossipSub): Table[string, ControlMessage] {.gcsafe.} =
let id = toSeq(g.gossipsub[topic]).sample() let id = toSeq(g.gossipsub[topic]).sample()
g.gossipsub[topic].excl(id) g.gossipsub[topic].excl(id)
libp2p_gossipsub_peers_per_topic_gossipsub.dec(labelValues = [topic])
if id notin gossipPeers: if id notin gossipPeers:
if id notin result: if id notin result:
result[id] = ControlMessage() result[id] = ControlMessage()
@ -212,12 +222,15 @@ method handleDisconnect(g: GossipSub, peer: PubSubPeer) {.async.} =
for t in g.gossipsub.keys: for t in g.gossipsub.keys:
g.gossipsub[t].excl(peer.id) g.gossipsub[t].excl(peer.id)
libp2p_gossipsub_peers_per_topic_gossipsub.dec(labelValues = [t])
for t in g.mesh.keys: for t in g.mesh.keys:
g.mesh[t].excl(peer.id) g.mesh[t].excl(peer.id)
libp2p_gossipsub_peers_per_topic_mesh.dec(labelValues = [t])
for t in g.fanout.keys: for t in g.fanout.keys:
g.fanout[t].excl(peer.id) g.fanout[t].excl(peer.id)
libp2p_gossipsub_peers_per_topic_fanout.dec(labelValues = [t])
method subscribeToPeer*(p: GossipSub, method subscribeToPeer*(p: GossipSub,
conn: Connection) {.async.} = conn: Connection) {.async.} =
@ -237,10 +250,12 @@ method subscribeTopic*(g: GossipSub,
trace "adding subscription for topic", peer = peerId, name = topic trace "adding subscription for topic", peer = peerId, name = topic
# subscribe remote peer to the topic # subscribe remote peer to the topic
g.gossipsub[topic].incl(peerId) g.gossipsub[topic].incl(peerId)
libp2p_gossipsub_peers_per_topic_gossipsub.inc(labelValues = [topic])
else: else:
trace "removing subscription for topic", peer = peerId, name = topic trace "removing subscription for topic", peer = peerId, name = topic
# unsubscribe remote peer from the topic # unsubscribe remote peer from the topic
g.gossipsub[topic].excl(peerId) g.gossipsub[topic].excl(peerId)
libp2p_gossipsub_peers_per_topic_gossipsub.dec(labelValues = [topic])
if topic in g.topics: if topic in g.topics:
await g.rebalanceMesh(topic) await g.rebalanceMesh(topic)
@ -256,8 +271,10 @@ proc handleGraft(g: GossipSub,
if graft.topicID in g.topics: if graft.topicID in g.topics:
if g.mesh.len < GossipSubD: if g.mesh.len < GossipSubD:
g.mesh[graft.topicID].incl(peer.id) g.mesh[graft.topicID].incl(peer.id)
libp2p_gossipsub_peers_per_topic_mesh.inc(labelValues = [graft.topicID])
else: else:
g.gossipsub[graft.topicID].incl(peer.id) g.gossipsub[graft.topicID].incl(peer.id)
libp2p_gossipsub_peers_per_topic_gossipsub.inc(labelValues = [graft.topicID])
else: else:
respControl.prune.add(ControlPrune(topicID: graft.topicID)) respControl.prune.add(ControlPrune(topicID: graft.topicID))
@ -268,6 +285,7 @@ proc handlePrune(g: GossipSub, peer: PubSubPeer, prunes: seq[ControlPrune]) =
if prune.topicID in g.mesh: if prune.topicID in g.mesh:
g.mesh[prune.topicID].excl(peer.id) g.mesh[prune.topicID].excl(peer.id)
libp2p_gossipsub_peers_per_topic_mesh.dec(labelValues = [prune.topicID])
proc handleIHave(g: GossipSub, proc handleIHave(g: GossipSub,
peer: PubSubPeer, peer: PubSubPeer,

View File

@ -14,6 +14,7 @@ import pubsubpeer,
../protocol, ../protocol,
../../connection, ../../connection,
../../peerinfo ../../peerinfo
import metrics
export PubSubPeer export PubSubPeer
export PubSubObserver export PubSubObserver
@ -21,6 +22,12 @@ export PubSubObserver
logScope: logScope:
topic = "PubSub" topic = "PubSub"
declareGauge(libp2p_pubsub_peers, "pubsub peer instances")
declareGauge(libp2p_pubsub_topics, "pubsub subscribed topics")
declareGauge(libp2p_pubsub_validation_success, "pubsub successfully validated messages")
declareGauge(libp2p_pubsub_validation_failure, "pubsub failed validated messages")
declareGauge(libp2p_pubsub_peers_per_topic, "pubsub peers per topic", labels = ["topic"])
type type
TopicHandler* = proc(topic: string, TopicHandler* = proc(topic: string,
data: seq[byte]): Future[void] {.gcsafe.} data: seq[byte]): Future[void] {.gcsafe.}
@ -67,7 +74,10 @@ method subscribeTopic*(p: PubSub,
topic: string, topic: string,
subscribe: bool, subscribe: bool,
peerId: string) {.base, async.} = peerId: string) {.base, async.} =
discard if subscribe:
libp2p_pubsub_peers_per_topic.inc(labelValues = [topic])
else:
libp2p_pubsub_peers_per_topic.dec(labelValues = [topic])
method rpcHandler*(p: PubSub, method rpcHandler*(p: PubSub,
peer: PubSubPeer, peer: PubSubPeer,
@ -86,6 +96,8 @@ method handleDisconnect*(p: PubSub, peer: PubSubPeer) {.async, base.} =
## handle peer disconnects ## handle peer disconnects
if peer.id in p.peers: if peer.id in p.peers:
p.peers.del(peer.id) p.peers.del(peer.id)
# metrics
libp2p_pubsub_peers.dec()
proc cleanUpHelper(p: PubSub, peer: PubSubPeer) {.async.} = proc cleanUpHelper(p: PubSub, peer: PubSubPeer) {.async.} =
await p.cleanupLock.acquire() await p.cleanupLock.acquire()
@ -105,6 +117,8 @@ proc getPeer(p: PubSub,
# create new pubsub peer # create new pubsub peer
let peer = newPubSubPeer(peerInfo, proto) let peer = newPubSubPeer(peerInfo, proto)
trace "created new pubsub peer", peerId = peer.id trace "created new pubsub peer", peerId = peer.id
# metrics
libp2p_pubsub_peers.inc()
p.peers[peer.id] = peer p.peers[peer.id] = peer
peer.refs.inc # increment reference cound peer.refs.inc # increment reference cound
@ -177,8 +191,11 @@ method unsubscribe*(p: PubSub,
method unsubscribe*(p: PubSub, method unsubscribe*(p: PubSub,
topic: string, topic: string,
handler: TopicHandler): Future[void] {.base.} = handler: TopicHandler): Future[void] {.base.} =
# metrics
libp2p_pubsub_topics.dec()
## unsubscribe from a ``topic`` string ## unsubscribe from a ``topic`` string
result = p.unsubscribe(@[(topic, handler)]) p.unsubscribe(@[(topic, handler)])
method subscribe*(p: PubSub, method subscribe*(p: PubSub,
topic: string, topic: string,
@ -200,6 +217,9 @@ method subscribe*(p: PubSub,
for peer in p.peers.values: for peer in p.peers.values:
await p.sendSubs(peer, @[topic], true) await p.sendSubs(peer, @[topic], true)
# metrics
libp2p_pubsub_topics.inc()
method publish*(p: PubSub, method publish*(p: PubSub,
topic: string, topic: string,
data: seq[byte]) {.base, async.} = data: seq[byte]) {.base, async.} =
@ -260,6 +280,10 @@ method validate*(p: PubSub, message: Message): Future[bool] {.async, base.} =
let futs = await allFinished(pending) let futs = await allFinished(pending)
result = futs.allIt(not it.failed and it.read()) result = futs.allIt(not it.failed and it.read())
if result:
libp2p_pubsub_validation_success.inc()
else:
libp2p_pubsub_validation_failure.inc()
proc newPubSub*(P: typedesc[PubSub], proc newPubSub*(P: typedesc[PubSub],
peerInfo: PeerInfo, peerInfo: PeerInfo,

View File

@ -18,6 +18,7 @@ import rpc/[messages, message, protobuf],
../../crypto/crypto, ../../crypto/crypto,
../../protobuf/minprotobuf, ../../protobuf/minprotobuf,
../../utility ../../utility
import metrics
logScope: logScope:
topic = "PubSubPeer" topic = "PubSubPeer"
@ -41,6 +42,9 @@ type
RPCHandler* = proc(peer: PubSubPeer, msg: seq[RPCMsg]): Future[void] {.gcsafe.} RPCHandler* = proc(peer: PubSubPeer, msg: seq[RPCMsg]): Future[void] {.gcsafe.}
declareGauge(libp2p_pubsub_encoded_messages, "number of messages encoded")
declareGauge(libp2p_pubsub_decoded_messages, "number of messages decoded")
proc id*(p: PubSubPeer): string = p.peerInfo.id proc id*(p: PubSubPeer): string = p.peerInfo.id
proc isConnected*(p: PubSubPeer): bool = proc isConnected*(p: PubSubPeer): bool =
@ -84,6 +88,9 @@ proc handle*(p: PubSubPeer, conn: Connection) {.async.} =
await p.handler(p, @[msg]) await p.handler(p, @[msg])
p.recvdRpcCache.put(digest) p.recvdRpcCache.put(digest)
# metrics
libp2p_pubsub_decoded_messages.inc()
finally: finally:
trace "exiting pubsub peer read loop", peer = p.id trace "exiting pubsub peer read loop", peer = p.id
await conn.close() await conn.close()
@ -99,6 +106,9 @@ proc send*(p: PubSubPeer, msgs: seq[RPCMsg]) {.async.} =
var mm = m # hooks can modify the message var mm = m # hooks can modify the message
p.sendObservers(mm) p.sendObservers(mm)
# metrics
libp2p_pubsub_encoded_messages.inc()
let encoded = encodeRpcMsg(mm) let encoded = encodeRpcMsg(mm)
if encoded.buffer.len <= 0: if encoded.buffer.len <= 0:
trace "empty message, skipping", peer = p.id trace "empty message, skipping", peer = p.id

View File

@ -9,6 +9,7 @@
import options import options
import chronicles, stew/byteutils import chronicles, stew/byteutils
import metrics
import nimcrypto/sysrand import nimcrypto/sysrand
import messages, protobuf, import messages, protobuf,
../../../peer, ../../../peer,
@ -21,6 +22,9 @@ logScope:
const PubSubPrefix = "libp2p-pubsub:" const PubSubPrefix = "libp2p-pubsub:"
declareGauge(libp2p_pubsub_sig_verify_success, "pubsub successfully validated messages")
declareGauge(libp2p_pubsub_sig_verify_failure, "pubsub failed validated messages")
proc msgIdProvider(m: Message): string = proc msgIdProvider(m: Message): string =
## default msg id provider ## default msg id provider
crypto.toHex(m.seqno) & PeerID.init(m.fromPeer).pretty crypto.toHex(m.seqno) & PeerID.init(m.fromPeer).pretty
@ -59,6 +63,11 @@ proc verify*(m: Message, p: PeerInfo): bool =
trace "verifying signature", remoteSignature = remote trace "verifying signature", remoteSignature = remote
result = remote.verify(PubSubPrefix.toBytes() & buff.buffer, key) result = remote.verify(PubSubPrefix.toBytes() & buff.buffer, key)
if result:
libp2p_pubsub_sig_verify_success.inc()
else:
libp2p_pubsub_sig_verify_failure.inc()
proc newMessage*(p: PeerInfo, proc newMessage*(p: PeerInfo,
data: seq[byte], data: seq[byte],
topic: string, topic: string,