diff --git a/configs/benchmark.go b/configs/benchmark.go index 691f18d..85caea8 100644 --- a/configs/benchmark.go +++ b/configs/benchmark.go @@ -53,16 +53,21 @@ func (c Consensus) AddrURLs() ([]*url.URL, error) { } type Execution struct { - Address string `mapstructure:"address"` - Metrics ExecutionMetrics `mapstructure:"metrics"` + Addresses []string `mapstructure:"address"` + Metrics ExecutionMetrics `mapstructure:"metrics"` } -func (e Execution) AddrURL() (*url.URL, error) { - parsedURL, err := url.Parse(e.Address) - if err != nil { - return nil, errors.Join(err, errors.New("error parsing Execution address to URL type")) +func (e Execution) AddrURLs() ([]*url.URL, error) { + var urls []*url.URL + for _, addr := range e.Addresses { + parsedURL, err := url.Parse(addr) + if err != nil { + return nil, errors.Join(err, errors.New("error parsing Execution address to URL type")) + } + urls = append(urls, parsedURL) } - return parsedURL, nil + + return urls, nil } type SSV struct { @@ -113,12 +118,17 @@ func (b *Benchmark) Validate() (bool, error) { b.Consensus.Addresses = urls } - if b.Execution.Metrics.Peers.Enabled { - url, err := sanitizeURL(b.Execution.Address) - if err != nil { - return false, errors.Join(err, errors.New("execution client address was not a valid URL")) + if b.Execution.Metrics.Peers.Enabled || b.Execution.Metrics.Latency.Enabled { + var urls []string + for _, addr := range b.Execution.Addresses { + url, err := sanitizeURL(addr) + if err != nil { + return false, errors.Join(err, errors.New("execution client address was not a valid URL")) + } + urls = append(urls, url) } - b.Execution.Address = url + + b.Execution.Addresses = urls } if b.SSV.Metrics.Peers.Enabled || b.SSV.Metrics.Connections.Enabled { diff --git a/configs/config.yaml b/configs/config.yaml index 02a3182..240dbc7 100644 --- a/configs/config.yaml +++ b/configs/config.yaml @@ -19,7 +19,9 @@ benchmark: enabled: true execution: - address: + # Can be a single address or a collection of addresses. Both formats are supported: + # `address: http://127.0.0.1:8080` and `address: [http://127.0.0.1:8080, http://127.0.0.2:8080]`. + address: [] metrics: peers: enabled: true diff --git a/docs/benchmark.md b/docs/benchmark.md index 4c00658..51672d2 100644 --- a/docs/benchmark.md +++ b/docs/benchmark.md @@ -2,7 +2,7 @@ ## Description -The `benchmark` feature allows for evaluating the health and severity of various SSV client-related metrics over time by running the application as a daemon. The addresses of the SSV client, Consensus Client(s), and Execution Client need to be supplied. During runtime, the benchmark will communicate with these clients through API endpoints to gather various metrics that help troubleshoot underperforming SSV clients. Additionally, it will provide infrastructure-related metrics, such as CPU and memory usage, from the environment it is running on. The metrics will be provided in an aggregated manner when the application shuts down (the `--duration` flag sets the execution time. The process can also be interrupted manually). +The `benchmark` feature allows for evaluating the health and severity of various SSV client-related metrics over time by running the application as a daemon. The addresses of the SSV client, Consensus Client(s), and Execution Client(s) need to be supplied. During runtime, the benchmark will communicate with these clients through API endpoints to gather various metrics that help troubleshoot underperforming SSV clients. Additionally, it will provide infrastructure-related metrics, such as CPU and memory usage, from the environment it is running on. The metrics will be provided in an aggregated manner when the application shuts down (the `--duration` flag sets the execution time. The process can also be interrupted manually). The system is designed to be flexible, allowing different metrics to have their own set of conditions that determine their health status and severity levels. diff --git a/internal/benchmark/cmd.go b/internal/benchmark/cmd.go index 8589ef7..c96bf37 100644 --- a/internal/benchmark/cmd.go +++ b/internal/benchmark/cmd.go @@ -96,13 +96,13 @@ var CMD = &cobra.Command{ func addFlags(cobraCMD *cobra.Command) { cobraCMD.Flags().Duration(durationFlag, defaultExecutionDuration, "Duration for which the application will run to gather metrics, e.g. '5m'") cobraCMD.Flags().Uint16(serverPortFlag, defaultServerPort, "Web server port with metrics endpoint exposed, e.g. '8080'") - cobraCMD.Flags().String(consensusAddrFlag, "", "A comma-separated list of consensus client addresses, including the scheme (HTTP/HTTPS) and port. For example: `https://lighthouse:5052,https://prysm:5052`.") + cobraCMD.Flags().String(consensusAddrFlag, "", "A comma-separated list of consensus client addresses, including the scheme (HTTP/HTTPS) and port, e.g. `https://lighthouse:5052,https://prysm:5052`.") cobraCMD.Flags().Bool(consensusMetricClientFlag, true, "Enable consensus client metric") cobraCMD.Flags().Bool(consensusMetricLatencyFlag, true, "Enable consensus client latency metric") cobraCMD.Flags().Bool(consensusMetricPeersFlag, true, "Enable consensus client peers metric") cobraCMD.Flags().Bool(consensusMetricAttestationFlag, true, "Enable consensus client attestation metric") - cobraCMD.Flags().String(executionAddrFlag, "", "Execution client address with scheme (HTTP/HTTPS) and port, e.g. https://geth:8545") + cobraCMD.Flags().String(executionAddrFlag, "", "A comma-separated list of execution client addresses, including the scheme (HTTP/HTTPS) and port, e.g. `https://geth:8545,https://reth:8545`.") cobraCMD.Flags().Bool(executionMetricPeersFlag, true, "Enable execution client peers metric") cobraCMD.Flags().Bool(executionMetricLatencyFlag, true, "Enable execution client latency metric") diff --git a/internal/benchmark/metric.go b/internal/benchmark/metric.go index f079641..1ff6140 100644 --- a/internal/benchmark/metric.go +++ b/internal/benchmark/metric.go @@ -63,7 +63,7 @@ func LoadEnabledMetrics(config configs.Config) (map[metric.Group][]metricService if config.Benchmark.Consensus.Metrics.Attestation.Enabled { for i, addr := range configs.Values.Benchmark.Consensus.Addresses { - enabledMetrics[metric.Group(metric.Group(fmt.Sprintf("%s-%d", metric.ConsensusGroup, i+1)))] = append(enabledMetrics[metric.Group(fmt.Sprintf("%s-%d", metric.ConsensusGroup, i+1))], + enabledMetrics[metric.Group(fmt.Sprintf("%s-%d", metric.ConsensusGroup, i+1))] = append(enabledMetrics[metric.Group(fmt.Sprintf("%s-%d", metric.ConsensusGroup, i+1))], consensus.NewAttestationMetric( addr, "Attestation", @@ -77,29 +77,35 @@ func LoadEnabledMetrics(config configs.Config) (map[metric.Group][]metricService } if config.Benchmark.Execution.Metrics.Peers.Enabled { - enabledMetrics[metric.ExecutionGroup] = append(enabledMetrics[metric.ExecutionGroup], execution.NewPeerMetric( - configs.Values.Benchmark.Execution.Address, - "Peers", - time.Second*10, - []metric.HealthCondition[uint32]{ - {Name: execution.PeerCountMeasurement, Threshold: 5, Operator: metric.OperatorLessThanOrEqual, Severity: metric.SeverityHigh}, - {Name: execution.PeerCountMeasurement, Threshold: 20, Operator: metric.OperatorLessThanOrEqual, Severity: metric.SeverityMedium}, - {Name: execution.PeerCountMeasurement, Threshold: 40, Operator: metric.OperatorLessThanOrEqual, Severity: metric.SeverityLow}, - })) + for i, addr := range configs.Values.Benchmark.Execution.Addresses { + enabledMetrics[metric.Group(fmt.Sprintf("%s-%d", metric.ExecutionGroup, i+1))] = append(enabledMetrics[metric.Group(fmt.Sprintf("%s-%d", metric.ExecutionGroup, i+1))], + execution.NewPeerMetric( + addr, + "Peers", + time.Second*10, + []metric.HealthCondition[uint32]{ + {Name: execution.PeerCountMeasurement, Threshold: 5, Operator: metric.OperatorLessThanOrEqual, Severity: metric.SeverityHigh}, + {Name: execution.PeerCountMeasurement, Threshold: 20, Operator: metric.OperatorLessThanOrEqual, Severity: metric.SeverityMedium}, + {Name: execution.PeerCountMeasurement, Threshold: 40, Operator: metric.OperatorLessThanOrEqual, Severity: metric.SeverityLow}, + })) + } } if config.Benchmark.Execution.Metrics.Latency.Enabled { - executionClientURL, err := config.Benchmark.Execution.AddrURL() + executionClientURLs, err := config.Benchmark.Execution.AddrURLs() if err != nil { - return nil, errors.Join(err, errors.New("failed fetching Execution client address as URL")) + return nil, errors.Join(err, errors.New("failed fetching Execution client addresses as URLs")) + } + for i, url := range executionClientURLs { + enabledMetrics[metric.Group(fmt.Sprintf("%s-%d", metric.ExecutionGroup, i+1))] = append(enabledMetrics[metric.Group(fmt.Sprintf("%s-%d", metric.ExecutionGroup, i+1))], + execution.NewLatencyMetric( + url.Host, + "Latency", + time.Second*3, + []metric.HealthCondition[time.Duration]{ + {Name: execution.DurationP90Measurement, Threshold: time.Second, Operator: metric.OperatorGreaterThanOrEqual, Severity: metric.SeverityHigh}, + })) } - enabledMetrics[metric.ExecutionGroup] = append(enabledMetrics[metric.ExecutionGroup], execution.NewLatencyMetric( - executionClientURL.Host, - "Latency", - time.Second*3, - []metric.HealthCondition[time.Duration]{ - {Name: execution.DurationP90Measurement, Threshold: time.Second, Operator: metric.OperatorGreaterThanOrEqual, Severity: metric.SeverityHigh}, - })) } if config.Benchmark.SSV.Metrics.Peers.Enabled {