Skip to content

Commit a63df6f

Browse files
authored
Merge pull request #4 from kilnfi/feat/add-network-watcher
feat: add a watcher to collect data about the network
2 parents 5c40217 + 42879c3 commit a63df6f

File tree

8 files changed

+516
-26
lines changed

8 files changed

+516
-26
lines changed

cmd/watcher/app/config/config.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,19 @@ type Config struct {
1313
Network string `mapstructure:"network"`
1414
Blockfrost BlockFrostConfig `mapstructure:"blockfrost"`
1515
PoolWatcherConfig PoolWatcherConfig `mapstructure:"pool-watcher"`
16+
NetworkWatcherConfig NetworkWatcherConfig `mapstructure:"network-watcher"`
1617
}
1718

1819
type PoolWatcherConfig struct {
1920
Enabled bool `mapstructure:"enabled"`
2021
RefreshInterval int `mapstructure:"refresh-interval"`
2122
}
2223

24+
type NetworkWatcherConfig struct {
25+
Enabled bool `mapstructure:"enabled"`
26+
RefreshInterval int `mapstructure:"refresh-interval"`
27+
}
28+
2329
type HTTPConfig struct {
2430
Host string `mapstructure:"host"`
2531
Port int `mapstructure:"port"`

cmd/watcher/app/watcher.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ func NewWatcherCommand() *cobra.Command {
8282
cmd.Flags().IntP("blockfrost-timeout", "", 60, "Timeout for requests to the Blockfrost API (in seconds)")
8383
cmd.Flags().BoolP("pool-watcher-enabled", "", true, "Enable pool watcher")
8484
cmd.Flags().IntP("pool-watcher-refresh-interval", "", 60, "Interval at which the pool watcher collects data about the monitored pools (in seconds)")
85+
cmd.Flags().BoolP("network-watcher-enabled", "", true, "Enable network watcher")
86+
cmd.Flags().IntP("network-watcher-refresh-interval", "", 60, "Interval at which the network watcher collects data about the network (in seconds)")
8587

8688
// bind flag to viper
8789
checkError(viper.BindPFlag("log-level", cmd.Flag("log-level")), "unable to bind log-level flag")
@@ -94,6 +96,8 @@ func NewWatcherCommand() *cobra.Command {
9496
checkError(viper.BindPFlag("blockfrost.timeout", cmd.Flag("blockfrost-timeout")), "unable to bind blockfrost-timeout flag")
9597
checkError(viper.BindPFlag("pool-watcher.enabled", cmd.Flag("pool-watcher-enabled")), "unable to bind pool-watcher-enabled flag")
9698
checkError(viper.BindPFlag("pool-watcher.refresh-interval", cmd.Flag("pool-watcher-refresh-interval")), "unable to bind pool-watcher-refresh-interval flag")
99+
checkError(viper.BindPFlag("network-watcher.enabled", cmd.Flag("network-watcher-enabled")), "unable to bind network-watcher-enabled flag")
100+
checkError(viper.BindPFlag("network-watcher.refresh-interval", cmd.Flag("network-watcher-refresh-interval")), "unable to bind network-watcher-refresh-interval flag")
97101

98102
return cmd
99103
}
@@ -155,6 +159,11 @@ func run(_ *cobra.Command, _ []string) error {
155159
return fmt.Errorf("unable to start http server: %w", err)
156160
}
157161

162+
// Start Network Watcher
163+
if cfg.NetworkWatcherConfig.Enabled {
164+
startNetworkWatcher(ctx, eg, blockfrost, metrics)
165+
}
166+
158167
// Start Pool Watcher
159168
if cfg.PoolWatcherConfig.Enabled {
160169
startPoolWatcher(ctx, eg, blockfrost, metrics, cfg.Pools)
@@ -246,6 +255,30 @@ func startPoolWatcher(
246255
})
247256
}
248257

258+
func startNetworkWatcher(
259+
ctx context.Context,
260+
eg *errgroup.Group,
261+
blockfrost blockfrost.Client,
262+
metrics *metrics.Collection,
263+
) {
264+
eg.Go(func() error {
265+
options := watcher.NetworkWatcherOptions{
266+
// to change
267+
RefreshInterval: time.Second * time.Duration(cfg.PoolWatcherConfig.RefreshInterval),
268+
Network: cfg.Network,
269+
}
270+
logger.Info(
271+
"starting watcher",
272+
slog.String("component", "network-watcher"),
273+
)
274+
networkWatcher := watcher.NewNetworkWatcher(blockfrost, metrics, options)
275+
if err := networkWatcher.Start(ctx); err != nil {
276+
return fmt.Errorf("unable to start network watcher: %w", err)
277+
}
278+
return nil
279+
})
280+
}
281+
249282
// checkError is a helper function to log an error and exit the program
250283
// used for the flag parsing
251284
func checkError(err error, msg string) {

internal/metrics/metrics.go

Lines changed: 95 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,95 @@ import (
66
)
77

88
type Collection struct {
9-
RelaysPerPool *prometheus.GaugeVec
10-
PoolsPledgeMet *prometheus.GaugeVec
11-
PoolsSaturationLevel *prometheus.GaugeVec
12-
MonitoredValidatorsCount *prometheus.GaugeVec
9+
ChainID *prometheus.GaugeVec
10+
EpochDuration prometheus.Gauge
11+
NetworkEpoch prometheus.Gauge
12+
NextEpochStartTime prometheus.Gauge
13+
NetworkBlockHeight prometheus.Gauge
14+
NetworkSlot prometheus.Gauge
15+
NetworkEpochSlot prometheus.Gauge
16+
NetworkTotalPools prometheus.Gauge
17+
NetworkCurrentEpochProposedBlocks prometheus.Gauge
18+
NetworkActiveStake prometheus.Gauge
19+
RelaysPerPool *prometheus.GaugeVec
20+
PoolsPledgeMet *prometheus.GaugeVec
21+
PoolsSaturationLevel *prometheus.GaugeVec
22+
MonitoredValidatorsCount *prometheus.GaugeVec
1323
}
1424

1525
func NewCollection() *Collection {
1626
return &Collection{
27+
ChainID: prometheus.NewGaugeVec(
28+
prometheus.GaugeOpts{
29+
Namespace: "cardano_validator_watcher",
30+
Name: "chain_id",
31+
Help: "Chain ID",
32+
},
33+
[]string{"chain_id"},
34+
),
35+
EpochDuration: prometheus.NewGauge(
36+
prometheus.GaugeOpts{
37+
Namespace: "cardano_validator_watcher",
38+
Name: "epoch_duration",
39+
Help: "Duration of an epoch in days",
40+
},
41+
),
42+
NetworkEpoch: prometheus.NewGauge(
43+
prometheus.GaugeOpts{
44+
Namespace: "cardano_validator_watcher",
45+
Name: "network_epoch",
46+
Help: "Current epoch number",
47+
},
48+
),
49+
NextEpochStartTime: prometheus.NewGauge(
50+
prometheus.GaugeOpts{
51+
Namespace: "cardano_validator_watcher",
52+
Name: "next_epoch_start_time",
53+
Help: "start time of the next epoch in seconds",
54+
},
55+
),
56+
NetworkBlockHeight: prometheus.NewGauge(
57+
prometheus.GaugeOpts{
58+
Namespace: "cardano_validator_watcher",
59+
Name: "network_block_height",
60+
Help: "Latest known block height",
61+
},
62+
),
63+
NetworkSlot: prometheus.NewGauge(
64+
prometheus.GaugeOpts{
65+
Namespace: "cardano_validator_watcher",
66+
Name: "network_slot",
67+
Help: "Latest known slot",
68+
},
69+
),
70+
NetworkEpochSlot: prometheus.NewGauge(
71+
prometheus.GaugeOpts{
72+
Namespace: "cardano_validator_watcher",
73+
Name: "network_epoch_slot",
74+
Help: "Latest known epoch slot",
75+
},
76+
),
77+
NetworkTotalPools: prometheus.NewGauge(
78+
prometheus.GaugeOpts{
79+
Namespace: "cardano_validator_watcher",
80+
Name: "network_pools",
81+
Help: "Total number of pools in the network",
82+
},
83+
),
84+
NetworkCurrentEpochProposedBlocks: prometheus.NewGauge(
85+
prometheus.GaugeOpts{
86+
Namespace: "cardano_validator_watcher",
87+
Name: "network_blocks_proposed_current_epoch",
88+
Help: "Number of blocks proposed in the current epoch by the network",
89+
},
90+
),
91+
NetworkActiveStake: prometheus.NewGauge(
92+
prometheus.GaugeOpts{
93+
Namespace: "cardano_validator_watcher",
94+
Name: "network_active_stake",
95+
Help: "Total active stake in the network",
96+
},
97+
),
1798
RelaysPerPool: prometheus.NewGaugeVec(
1899
prometheus.GaugeOpts{
19100
Namespace: "cardano_validator_watcher",
@@ -52,6 +133,16 @@ func NewCollection() *Collection {
52133
func (m *Collection) MustRegister(reg prometheus.Registerer) {
53134
reg.MustRegister(collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}))
54135
reg.MustRegister(collectors.NewGoCollector())
136+
reg.MustRegister(m.ChainID)
137+
reg.MustRegister(m.EpochDuration)
138+
reg.MustRegister(m.NetworkEpoch)
139+
reg.MustRegister(m.NextEpochStartTime)
140+
reg.MustRegister(m.NetworkBlockHeight)
141+
reg.MustRegister(m.NetworkSlot)
142+
reg.MustRegister(m.NetworkEpochSlot)
143+
reg.MustRegister(m.NetworkTotalPools)
144+
reg.MustRegister(m.NetworkCurrentEpochProposedBlocks)
145+
reg.MustRegister(m.NetworkActiveStake)
55146
reg.MustRegister(m.RelaysPerPool)
56147
reg.MustRegister(m.PoolsPledgeMet)
57148
reg.MustRegister(m.PoolsSaturationLevel)

internal/metrics/metrics_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ func TestMustRegister(t *testing.T) {
1717
metrics := NewCollection()
1818

1919
// register metrics that need labels
20+
metrics.ChainID.WithLabelValues("test_chain").Set(1)
2021
metrics.RelaysPerPool.WithLabelValues("pool_name", "pool_id", "pool_instance").Set(5)
2122
metrics.PoolsPledgeMet.WithLabelValues("pool_name", "pool_id", "pool_instance").Set(1)
2223
metrics.PoolsSaturationLevel.WithLabelValues("pool_name", "pool_id", "pool_instance").Set(85)
@@ -26,7 +27,7 @@ func TestMustRegister(t *testing.T) {
2627
metrics.MustRegister(registry)
2728

2829
// The expected number of metrics to be registered, based on the definitions provided in the Collection struct.
29-
expectedMetricsCount := 4
30+
expectedMetricsCount := 14
3031

3132
var totalRegisteredMetrics int
3233
size, _ := registry.Gather()

internal/watcher/helpers_test.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package watcher
2+
3+
import (
4+
"context"
5+
"testing"
6+
"time"
7+
8+
blockfrostmocks "github.com/kilnfi/cardano-validator-watcher/internal/blockfrost/mocks"
9+
"github.com/kilnfi/cardano-validator-watcher/internal/metrics"
10+
"github.com/kilnfi/cardano-validator-watcher/internal/pools"
11+
"github.com/prometheus/client_golang/prometheus"
12+
)
13+
14+
type clients struct {
15+
bf *blockfrostmocks.MockClient
16+
}
17+
18+
func setupPools(t *testing.T) pools.Pools {
19+
t.Helper()
20+
return pools.Pools{
21+
{
22+
ID: "pool-0",
23+
Instance: "pool-0",
24+
Key: "key",
25+
Name: "pool-0",
26+
Exclude: false,
27+
},
28+
{
29+
ID: "pool-1",
30+
Instance: "pool-1",
31+
Key: "key",
32+
Name: "pool-1",
33+
Exclude: true,
34+
},
35+
}
36+
}
37+
38+
func setupClients(t *testing.T) *clients {
39+
t.Helper()
40+
41+
return &clients{
42+
bf: blockfrostmocks.NewMockClient(t),
43+
}
44+
}
45+
46+
func setupRegistry(t *testing.T) *struct {
47+
registry *prometheus.Registry
48+
metrics *metrics.Collection
49+
metricsExpectedOutput string
50+
metricsUnderTest []string
51+
} {
52+
t.Helper()
53+
54+
registry := prometheus.NewRegistry()
55+
Collection := metrics.NewCollection()
56+
Collection.MustRegister(registry)
57+
58+
return &struct {
59+
registry *prometheus.Registry
60+
metrics *metrics.Collection
61+
metricsExpectedOutput string
62+
metricsUnderTest []string
63+
}{
64+
registry: registry,
65+
metrics: Collection,
66+
metricsExpectedOutput: "",
67+
metricsUnderTest: []string{},
68+
}
69+
}
70+
71+
func setupContextWithTimeout(t *testing.T, d time.Duration) context.Context {
72+
t.Helper()
73+
74+
ctx, cancel := context.WithCancel(context.Background())
75+
go func() {
76+
time.AfterFunc(d, cancel)
77+
}()
78+
return ctx
79+
}

0 commit comments

Comments
 (0)