Skip to content

Commit

Permalink
app/peerinfo: added builder_api_enabled metric (#2890)
Browse files Browse the repository at this point in the history
Adds `app_peerinfo_builder_api_enabled` metric (gauge) indicating if the `peer` has builder API enabled.

category: feature
ticket: #2879
  • Loading branch information
pinebit authored Feb 20, 2024
1 parent c5823b5 commit 8ac72af
Show file tree
Hide file tree
Showing 12 changed files with 270 additions and 120 deletions.
6 changes: 3 additions & 3 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ func Run(ctx context.Context, conf Config) (err error) {

sender := new(p2p.Sender)

wirePeerInfo(life, tcpNode, peerIDs, cluster.InitialMutationHash, sender)
wirePeerInfo(life, tcpNode, peerIDs, cluster.InitialMutationHash, sender, conf.BuilderAPI)

qbftDebug := newQBFTDebugger()

Expand Down Expand Up @@ -274,9 +274,9 @@ func Run(ctx context.Context, conf Config) (err error) {
}

// wirePeerInfo wires the peerinfo protocol.
func wirePeerInfo(life *lifecycle.Manager, tcpNode host.Host, peers []peer.ID, lockHash []byte, sender *p2p.Sender) {
func wirePeerInfo(life *lifecycle.Manager, tcpNode host.Host, peers []peer.ID, lockHash []byte, sender *p2p.Sender, builderEnabled bool) {
gitHash, _ := version.GitCommit()
peerInfo := peerinfo.New(tcpNode, peers, version.Version, lockHash, gitHash, sender.SendReceive)
peerInfo := peerinfo.New(tcpNode, peers, version.Version, lockHash, gitHash, sender.SendReceive, builderEnabled)
life.RegisterStart(lifecycle.AsyncAppCtx, lifecycle.StartPeerInfo, lifecycle.HookFuncCtx(peerInfo.Run))
}

Expand Down
3 changes: 2 additions & 1 deletion app/peerinfo/adhoc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,13 @@ func TestDoOnce(t *testing.T) {
lockHash := []byte("123")
gitHash := "abc"
// Register the server handler that either
_ = peerinfo.New(server, []peer.ID{server.ID(), client.ID()}, vers, lockHash, gitHash, p2p.SendReceive)
_ = peerinfo.New(server, []peer.ID{server.ID(), client.ID()}, vers, lockHash, gitHash, p2p.SendReceive, true)

info, _, ok, err := peerinfo.DoOnce(context.Background(), client, server.ID())
require.NoError(t, err)
require.True(t, ok)
require.Equal(t, vers.String(), info.CharonVersion)
require.Equal(t, gitHash, info.GitHash)
require.Equal(t, lockHash, info.LockHash)
require.True(t, info.BuilderApiEnabled)
}
7 changes: 7 additions & 0 deletions app/peerinfo/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,11 @@ var (
Name: "version_support",
Help: "Set to 1 if the peer's version is supported by (compatible with) the current version, else 0 if unsupported.",
}, []string{"peer"})

peerBuilderAPIEnabledGauge = promauto.NewGaugeVec(prometheus.GaugeOpts{
Namespace: "app",
Subsystem: "peerinfo",
Name: "builder_api_enabled",
Help: "Set to 1 if builder API is enabled on this peer, else 0 if disabled.",
}, []string{"peer"})
)
105 changes: 66 additions & 39 deletions app/peerinfo/peerinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,25 @@ func Protocols() []protocol.ID {
type (
tickerProvider func() (<-chan time.Time, func())
nowFunc func() time.Time
metricSubmitter func(peerID peer.ID, clockOffset time.Duration, version, gitHash string, startTime time.Time)
metricSubmitter func(peerID peer.ID, clockOffset time.Duration, version, gitHash string, startTime time.Time, builderAPIEnabled bool)
)

// New returns a new peer info protocol instance.
func New(tcpNode host.Host, peers []peer.ID, version version.SemVer, lockHash []byte, gitHash string,
sendFunc p2p.SendReceiveFunc,
sendFunc p2p.SendReceiveFunc, builderEnabled bool,
) *PeerInfo {
// Set own version and git hash and start time metrics.
name := p2p.PeerName(tcpNode.ID())
peerVersion.WithLabelValues(name, version.String()).Set(1)
peerGitHash.WithLabelValues(name, gitHash).Set(1)
peerStartGauge.WithLabelValues(name).Set(float64(time.Now().Unix()))

if builderEnabled {
peerBuilderAPIEnabledGauge.WithLabelValues(name).Set(1)
} else {
peerBuilderAPIEnabledGauge.WithLabelValues(name).Set(0)
}

for i, p := range peers {
peerIndexGauge.WithLabelValues(p2p.PeerName(p)).Set(float64(i))
}
Expand All @@ -62,22 +68,24 @@ func New(tcpNode host.Host, peers []peer.ID, version version.SemVer, lockHash []
}

return newInternal(tcpNode, peers, version, lockHash, gitHash, sendFunc, p2p.RegisterHandler,
tickerProvider, time.Now, newMetricsSubmitter())
tickerProvider, time.Now, newMetricsSubmitter(), builderEnabled)
}

// NewForT returns a new peer info protocol instance for testing only.
func NewForT(_ *testing.T, tcpNode host.Host, peers []peer.ID, version version.SemVer, lockHash []byte, gitHash string,
sendFunc p2p.SendReceiveFunc, registerHandler p2p.RegisterHandlerFunc,
tickerProvider tickerProvider, nowFunc nowFunc, metricSubmitter metricSubmitter,
builderAPIEnabled bool,
) *PeerInfo {
return newInternal(tcpNode, peers, version, lockHash, gitHash, sendFunc, registerHandler,
tickerProvider, nowFunc, metricSubmitter)
tickerProvider, nowFunc, metricSubmitter, builderAPIEnabled)
}

// newInternal returns a new instance for New or NewForT.
func newInternal(tcpNode host.Host, peers []peer.ID, version version.SemVer, lockHash []byte, gitHash string,
sendFunc p2p.SendReceiveFunc, registerHandler p2p.RegisterHandlerFunc,
tickerProvider tickerProvider, nowFunc nowFunc, metricSubmitter metricSubmitter,
builderAPIEnabled bool,
) *PeerInfo {
startTime := timestamppb.New(nowFunc())

Expand All @@ -86,11 +94,12 @@ func newInternal(tcpNode host.Host, peers []peer.ID, version version.SemVer, loc
func() proto.Message { return new(pbv1.PeerInfo) },
func(context.Context, peer.ID, proto.Message) (proto.Message, bool, error) {
return &pbv1.PeerInfo{
CharonVersion: version.String(),
LockHash: lockHash,
GitHash: gitHash,
SentAt: timestamppb.New(nowFunc()),
StartedAt: startTime,
CharonVersion: version.String(),
LockHash: lockHash,
GitHash: gitHash,
SentAt: timestamppb.New(nowFunc()),
StartedAt: startTime,
BuilderApiEnabled: builderAPIEnabled,
}, true, nil
},
)
Expand All @@ -104,33 +113,35 @@ func newInternal(tcpNode host.Host, peers []peer.ID, version version.SemVer, loc
}

return &PeerInfo{
sendFunc: sendFunc,
tcpNode: tcpNode,
peers: peers,
version: version,
lockHash: lockHash,
startTime: startTime,
metricSubmitter: metricSubmitter,
tickerProvider: tickerProvider,
nowFunc: nowFunc,
lockHashFilters: lockHashFilters,
versionFilters: versionFilters,
sendFunc: sendFunc,
tcpNode: tcpNode,
peers: peers,
version: version,
lockHash: lockHash,
startTime: startTime,
builderAPIEnabled: builderAPIEnabled,
metricSubmitter: metricSubmitter,
tickerProvider: tickerProvider,
nowFunc: nowFunc,
lockHashFilters: lockHashFilters,
versionFilters: versionFilters,
}
}

type PeerInfo struct {
sendFunc p2p.SendReceiveFunc
tcpNode host.Host
peers []peer.ID
version version.SemVer
lockHash []byte
gitHash string
startTime *timestamppb.Timestamp
tickerProvider tickerProvider
metricSubmitter metricSubmitter
nowFunc func() time.Time
lockHashFilters map[peer.ID]z.Field
versionFilters map[peer.ID]z.Field
sendFunc p2p.SendReceiveFunc
tcpNode host.Host
peers []peer.ID
version version.SemVer
lockHash []byte
gitHash string
startTime *timestamppb.Timestamp
builderAPIEnabled bool
tickerProvider tickerProvider
metricSubmitter metricSubmitter
nowFunc func() time.Time
lockHashFilters map[peer.ID]z.Field
versionFilters map[peer.ID]z.Field
}

// Run runs the peer info protocol until the context is cancelled.
Expand Down Expand Up @@ -158,11 +169,12 @@ func (p *PeerInfo) sendOnce(ctx context.Context, now time.Time) {
}

req := &pbv1.PeerInfo{
CharonVersion: p.version.String(),
LockHash: p.lockHash,
GitHash: p.gitHash,
SentAt: timestamppb.New(now),
StartedAt: p.startTime,
CharonVersion: p.version.String(),
LockHash: p.lockHash,
GitHash: p.gitHash,
SentAt: timestamppb.New(now),
StartedAt: p.startTime,
BuilderApiEnabled: p.builderAPIEnabled,
}

go func(peerID peer.ID) {
Expand Down Expand Up @@ -209,7 +221,7 @@ func (p *PeerInfo) sendOnce(ctx context.Context, now time.Time) {
// Set peer compatibility to true.
peerCompatibleGauge.WithLabelValues(name).Set(1)

p.metricSubmitter(peerID, clockOffset, resp.CharonVersion, resp.GitHash, resp.StartedAt.AsTime())
p.metricSubmitter(peerID, clockOffset, resp.CharonVersion, resp.GitHash, resp.StartedAt.AsTime(), resp.BuilderApiEnabled)

// Log unexpected lock hash
if !bytes.Equal(resp.LockHash, p.lockHash) {
Expand All @@ -221,6 +233,15 @@ func (p *PeerInfo) sendOnce(ctx context.Context, now time.Time) {
p.lockHashFilters[peerID],
)
}

// Builder API shall be either enabled or disabled for both.
if resp.BuilderApiEnabled != p.builderAPIEnabled {
log.Warn(ctx, "Mismatching peer builder API status", nil,
z.Str("peer", name),
z.Bool("peer_builder_api_enabled", resp.BuilderApiEnabled),
z.Bool("builder_api_enabled", p.builderAPIEnabled),
)
}
}(peerID)
}
}
Expand Down Expand Up @@ -250,7 +271,7 @@ func supportedPeerVersion(peerVersion string, supported []version.SemVer) error
// newMetricsSubmitter returns a prometheus metric submitter.
func newMetricsSubmitter() metricSubmitter {
return func(peerID peer.ID, clockOffset time.Duration, version string, gitHash string,
startTime time.Time,
startTime time.Time, builderAPIEnabled bool,
) {
peerName := p2p.PeerName(peerID)

Expand Down Expand Up @@ -279,5 +300,11 @@ func newMetricsSubmitter() metricSubmitter {
peerVersion.WithLabelValues(peerName, version).Set(1)
peerGitHash.Reset(peerName)
peerGitHash.WithLabelValues(peerName, gitHash).Set(1)

if builderAPIEnabled {
peerBuilderAPIEnabledGauge.WithLabelValues(peerName).Set(1)
} else {
peerBuilderAPIEnabledGauge.WithLabelValues(peerName).Set(0)
}
}
}
40 changes: 40 additions & 0 deletions app/peerinfo/peerinfo_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,17 @@
package peerinfo

import (
"fmt"
"strings"
"testing"

"github.com/libp2p/go-libp2p/core/peer"
promtestutil "github.com/prometheus/client_golang/prometheus/testutil"
"github.com/stretchr/testify/require"

"github.com/obolnetwork/charon/app/version"
"github.com/obolnetwork/charon/p2p"
"github.com/obolnetwork/charon/testutil"
)

func TestSupporterVersion(t *testing.T) {
Expand Down Expand Up @@ -59,6 +65,40 @@ func TestSupporterVersion(t *testing.T) {
}
}

func TestPeerBuilderAPIEnabledGauge(t *testing.T) {
server := testutil.CreateHost(t, testutil.AvailableAddr(t))
client := testutil.CreateHost(t, testutil.AvailableAddr(t))

lockHash := []byte("123")
gitHash := "abc"
peerName := p2p.PeerName(server.ID())

tests := []struct {
name string
builderEnabled bool
expectedValue int
}{
{"builder enabled", true, 1},
{"builder disabled", false, 0},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
_ = New(server, []peer.ID{server.ID(), client.ID()}, version.Version, lockHash, gitHash, nil, test.builderEnabled)

expectedMetric := fmt.Sprintf(`
# HELP app_peerinfo_builder_api_enabled Set to 1 if builder API is enabled on this peer, else 0 if disabled.
# TYPE app_peerinfo_builder_api_enabled gauge
app_peerinfo_builder_api_enabled{ peer = "%s" } %d
`, peerName, test.expectedValue)

if err := promtestutil.CollectAndCompare(peerBuilderAPIEnabledGauge, strings.NewReader(expectedMetric), "app_peerinfo_builder_api_enabled"); err != nil {
require.NoError(t, err, "failed to collect metric")
}
})
}
}

func semvers(s ...string) []version.SemVer {
var resp []version.SemVer
for _, v := range s {
Expand Down
7 changes: 4 additions & 3 deletions app/peerinfo/peerinfo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ func TestPeerInfo(t *testing.T) {
tickProvider := func() (<-chan time.Time, func()) {
return nil, func() {}
}
metricSubmitter := func(peer.ID, time.Duration, string, string, time.Time) {
metricSubmitter := func(peer.ID, time.Duration, string, string, time.Time, bool) {
panic("unexpected metric submitted")
}

Expand All @@ -101,7 +101,7 @@ func TestPeerInfo(t *testing.T) {

var submittedMutex sync.Mutex
var submitted int
metricSubmitter = func(peerID peer.ID, clockOffset time.Duration, version, gitHash string, startTime time.Time) {
metricSubmitter = func(peerID peer.ID, clockOffset time.Duration, version, gitHash string, startTime time.Time, builderEnabled bool) {
for i, tcpNode := range tcpNodes {
if tcpNode.ID() != peerID {
continue
Expand All @@ -110,6 +110,7 @@ func TestPeerInfo(t *testing.T) {
require.Equal(t, node.Version.String(), version)
require.Equal(t, gitCommit, gitHash)
require.Equal(t, nowFunc(i)().Unix(), startTime.Unix())
require.True(t, builderEnabled)

submittedMutex.Lock()
submitted++
Expand All @@ -125,7 +126,7 @@ func TestPeerInfo(t *testing.T) {
}

peerInfo := peerinfo.NewForT(t, tcpNodes[i], peers, node.Version, node.LockHash, gitCommit, p2p.SendReceive, p2p.RegisterHandler,
tickProvider, nowFunc(i), metricSubmitter)
tickProvider, nowFunc(i), metricSubmitter, true)

peerInfos = append(peerInfos, peerInfo)
}
Expand Down
25 changes: 18 additions & 7 deletions app/peerinfo/peerinfopb/v1/peerinfo.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions app/peerinfo/peerinfopb/v1/peerinfo.proto
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ message PeerInfo {
optional google.protobuf.Timestamp sent_at = 3;
string git_hash = 4;
optional google.protobuf.Timestamp started_at = 5;
bool builder_api_enabled = 6;

// TODO(corver): Always populate timestamps when sending, then make them required after subsequent release.
}
Loading

0 comments on commit 8ac72af

Please sign in to comment.