Skip to content

Commit 4b76927

Browse files
authored
Add metrics to simulator (#706)
* add tps * get block build metrics * Add issuance time, confirmed time, issued-> acceptance time, verify time * Log times at end of batch * cleaner * address comments * remove unused code block * avoids taking len of channel * nits * pass in stringID * move to loader * remove unused field * revert file back * cleaner * lint * make it work for ws or for rpc * protect * endpoint * no return on defer * sep to a funciton * have blockchainidstr passed in * typo * pass in metrics through config * address comments * address more comments and edit err policy of metrics functions * add more logging to load_test * typo * better check * fix endpoints * typo: * individual * histogram * address feedback: * remove metrics from default * address comments * simplify time metrics * better explanation * address comments * address comments * cleanup * more cleanup * rename vars for clarity * ws * cleanup * address comments * ws * expose metrics add flag * fix blocking issue of http server and gracefully stop it * cleanup * use constant * add issuance to confirmation metrics * ws * simplify metrics server * Bump avalanchego to v1.10.5 and bump Subnet-EVM for v0.5.3 release (#757) * Bump avalanchego to v1.10.5 and bump Subnet-EVM for v0.5.3 release * bump anr version to v1.7.1 * handle control c * print out output * clean up * clean up * remove go routines to close client * address comments * memory leak * fix * print
1 parent 5dbfa82 commit 4b76927

File tree

7 files changed

+222
-24
lines changed

7 files changed

+222
-24
lines changed

cmd/simulator/config/flags.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313
"github.com/spf13/viper"
1414
)
1515

16-
const Version = "v0.1.0"
16+
const Version = "v0.1.1"
1717

1818
const (
1919
ConfigFilePathKey = "config-file"
@@ -27,6 +27,7 @@ const (
2727
VersionKey = "version"
2828
TimeoutKey = "timeout"
2929
BatchSizeKey = "batch-size"
30+
MetricsPortKey = "metrics-port"
3031
)
3132

3233
var (
@@ -44,6 +45,7 @@ type Config struct {
4445
KeyDir string `json:"key-dir"`
4546
Timeout time.Duration `json:"timeout"`
4647
BatchSize uint64 `json:"batch-size"`
48+
MetricsPort uint64 `json:"metrics-port"`
4749
}
4850

4951
func BuildConfig(v *viper.Viper) (Config, error) {
@@ -56,6 +58,7 @@ func BuildConfig(v *viper.Viper) (Config, error) {
5658
KeyDir: v.GetString(KeyDirKey),
5759
Timeout: v.GetDuration(TimeoutKey),
5860
BatchSize: v.GetUint64(BatchSizeKey),
61+
MetricsPort: v.GetUint64(MetricsPortKey),
5962
}
6063
if len(c.Endpoints) == 0 {
6164
return c, ErrNoEndpoints
@@ -118,4 +121,5 @@ func addSimulatorFlags(fs *pflag.FlagSet) {
118121
fs.Duration(TimeoutKey, 5*time.Minute, "Specify the timeout for the simulator to complete (0 indicates no timeout)")
119122
fs.String(LogLevelKey, "info", "Specify the log level to use in the simulator")
120123
fs.Uint64(BatchSizeKey, 100, "Specify the batchsize for the worker to issue and confirm txs")
124+
fs.Uint64(MetricsPortKey, 8082, "Specify the port to use for the metrics server")
121125
}

cmd/simulator/load/funder.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"math/big"
1111

1212
"github.com/ava-labs/subnet-evm/cmd/simulator/key"
13+
"github.com/ava-labs/subnet-evm/cmd/simulator/metrics"
1314
"github.com/ava-labs/subnet-evm/cmd/simulator/txs"
1415
"github.com/ava-labs/subnet-evm/core/types"
1516
"github.com/ava-labs/subnet-evm/ethclient"
@@ -21,7 +22,7 @@ import (
2122
// DistributeFunds ensures that each address in keys has at least [minFundsPerAddr] by sending funds
2223
// from the key with the highest starting balance.
2324
// This function returns a set of at least [numKeys] keys, each having a minimum balance [minFundsPerAddr].
24-
func DistributeFunds(ctx context.Context, client ethclient.Client, keys []*key.Key, numKeys int, minFundsPerAddr *big.Int) ([]*key.Key, error) {
25+
func DistributeFunds(ctx context.Context, client ethclient.Client, keys []*key.Key, numKeys int, minFundsPerAddr *big.Int, m *metrics.Metrics) ([]*key.Key, error) {
2526
if len(keys) < numKeys {
2627
return nil, fmt.Errorf("insufficient number of keys %d < %d", len(keys), numKeys)
2728
}
@@ -107,7 +108,7 @@ func DistributeFunds(ctx context.Context, client ethclient.Client, keys []*key.K
107108
return nil, fmt.Errorf("failed to generate fund distribution sequence from %s of length %d", maxFundsKey.Address, len(needFundsAddrs))
108109
}
109110
worker := NewSingleAddressTxWorker(ctx, client, maxFundsKey.Address)
110-
txFunderAgent := txs.NewIssueNAgent[*types.Transaction](txSequence, worker, numTxs)
111+
txFunderAgent := txs.NewIssueNAgent[*types.Transaction](txSequence, worker, numTxs, m)
111112

112113
if err := txFunderAgent.Execute(ctx); err != nil {
113114
return nil, err

cmd/simulator/load/loader.go

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,36 @@ package load
66
import (
77
"context"
88
"crypto/ecdsa"
9+
"errors"
910
"fmt"
11+
"io/ioutil"
1012
"math/big"
13+
"net/http"
14+
"os"
15+
"os/signal"
16+
"strconv"
17+
"strings"
18+
"syscall"
1119

1220
"github.com/ava-labs/subnet-evm/cmd/simulator/config"
1321
"github.com/ava-labs/subnet-evm/cmd/simulator/key"
22+
"github.com/ava-labs/subnet-evm/cmd/simulator/metrics"
1423
"github.com/ava-labs/subnet-evm/cmd/simulator/txs"
1524
"github.com/ava-labs/subnet-evm/core/types"
1625
"github.com/ava-labs/subnet-evm/ethclient"
1726
"github.com/ava-labs/subnet-evm/params"
1827
"github.com/ethereum/go-ethereum/common"
1928
ethcrypto "github.com/ethereum/go-ethereum/crypto"
2029
"github.com/ethereum/go-ethereum/log"
30+
"github.com/prometheus/client_golang/prometheus"
31+
"github.com/prometheus/client_golang/prometheus/promhttp"
2132
"golang.org/x/sync/errgroup"
2233
)
2334

35+
const (
36+
MetricsEndpoint = "/metrics" // Endpoint for the Prometheus Metrics Server
37+
)
38+
2439
// ExecuteLoader creates txSequences from [config] and has txAgents execute the specified simulation.
2540
func ExecuteLoader(ctx context.Context, config config.Config) error {
2641
if config.Timeout > 0 {
@@ -29,6 +44,24 @@ func ExecuteLoader(ctx context.Context, config config.Config) error {
2944
defer cancel()
3045
}
3146

47+
// Create buffered sigChan to receive SIGINT notifications
48+
sigChan := make(chan os.Signal, 1)
49+
signal.Notify(sigChan, syscall.SIGINT)
50+
51+
// Create context with cancel
52+
ctx, cancel := context.WithCancel(ctx)
53+
54+
go func() {
55+
// Blocks until we receive a SIGINT notification or if parent context is done
56+
select {
57+
case <-sigChan:
58+
case <-ctx.Done():
59+
}
60+
61+
// Cancel the child context and end all processes
62+
cancel()
63+
}()
64+
3265
// Construct the arguments for the load simulator
3366
clients := make([]ethclient.Client, 0, len(config.Endpoints))
3467
for i := 0; i < config.Workers; i++ {
@@ -62,8 +95,14 @@ func ExecuteLoader(ctx context.Context, config config.Config) error {
6295
// to fund gas for all of their transactions.
6396
maxFeeCap := new(big.Int).Mul(big.NewInt(params.GWei), big.NewInt(config.MaxFeeCap))
6497
minFundsPerAddr := new(big.Int).Mul(maxFeeCap, big.NewInt(int64(config.TxsPerWorker*params.TxGas)))
98+
99+
// Create metrics
100+
reg := prometheus.NewRegistry()
101+
m := metrics.NewMetrics(reg)
102+
metricsPort := strconv.Itoa(int(config.MetricsPort))
103+
65104
log.Info("Distributing funds", "numTxsPerWorker", config.TxsPerWorker, "minFunds", minFundsPerAddr)
66-
keys, err = DistributeFunds(ctx, clients[0], keys, config.Workers, minFundsPerAddr)
105+
keys, err = DistributeFunds(ctx, clients[0], keys, config.Workers, minFundsPerAddr, m)
67106
if err != nil {
68107
return err
69108
}
@@ -112,7 +151,7 @@ func ExecuteLoader(ctx context.Context, config config.Config) error {
112151
log.Info("Constructing tx agents...", "numAgents", config.Workers)
113152
agents := make([]txs.Agent[*types.Transaction], 0, config.Workers)
114153
for i := 0; i < config.Workers; i++ {
115-
agents = append(agents, txs.NewIssueNAgent[*types.Transaction](txSequences[i], NewSingleAddressTxWorker(ctx, clients[i], senders[i]), config.BatchSize))
154+
agents = append(agents, txs.NewIssueNAgent[*types.Transaction](txSequences[i], NewSingleAddressTxWorker(ctx, clients[i], senders[i]), config.BatchSize, m))
116155
}
117156

118157
log.Info("Starting tx agents...")
@@ -124,10 +163,59 @@ func ExecuteLoader(ctx context.Context, config config.Config) error {
124163
})
125164
}
126165

166+
go startMetricsServer(ctx, metricsPort, reg)
167+
127168
log.Info("Waiting for tx agents...")
128169
if err := eg.Wait(); err != nil {
129170
return err
130171
}
131172
log.Info("Tx agents completed successfully.")
173+
174+
printOutputFromMetricsServer(metricsPort)
132175
return nil
133176
}
177+
178+
func startMetricsServer(ctx context.Context, metricsPort string, reg *prometheus.Registry) {
179+
// Create a prometheus server to expose individual tx metrics
180+
server := &http.Server{
181+
Addr: fmt.Sprintf(":%s", metricsPort),
182+
}
183+
184+
// Start up go routine to listen for SIGINT notifications to gracefully shut down server
185+
go func() {
186+
// Blocks until signal is received
187+
<-ctx.Done()
188+
189+
if err := server.Shutdown(ctx); err != nil {
190+
log.Error("Metrics server error: %v", err)
191+
}
192+
log.Info("Received a SIGINT signal: Gracefully shutting down metrics server")
193+
}()
194+
195+
// Start metrics server
196+
http.Handle(MetricsEndpoint, promhttp.HandlerFor(reg, promhttp.HandlerOpts{Registry: reg}))
197+
log.Info(fmt.Sprintf("Metrics Server: localhost:%s%s", metricsPort, MetricsEndpoint))
198+
if err := server.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
199+
log.Error("Metrics server error: %v", err)
200+
}
201+
}
202+
203+
func printOutputFromMetricsServer(metricsPort string) {
204+
// Get response from server
205+
resp, err := http.Get(fmt.Sprintf("http://localhost:%s%s", metricsPort, MetricsEndpoint))
206+
if err != nil {
207+
log.Error("cannot get response from metrics servers", "err", err)
208+
return
209+
}
210+
// Read response body
211+
respBody, err := ioutil.ReadAll(resp.Body)
212+
if err != nil {
213+
log.Error("cannot read response body", "err", err)
214+
return
215+
}
216+
// Print out formatted individual metrics
217+
parts := strings.Split(string(respBody), "\n")
218+
for _, s := range parts {
219+
fmt.Printf(" \t\t\t%s\n", s)
220+
}
221+
}

cmd/simulator/metrics/metrics.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// (c) 2023, Ava Labs, Inc. All rights reserved.
2+
// See the file LICENSE for licensing terms.
3+
4+
package metrics
5+
6+
import (
7+
"github.com/prometheus/client_golang/prometheus"
8+
)
9+
10+
type Metrics struct {
11+
// Summary of the quantiles of Individual Issuance Tx Times
12+
IssuanceTxTimes prometheus.Summary
13+
// Summary of the quantiles of Individual Confirmation Tx Times
14+
ConfirmationTxTimes prometheus.Summary
15+
// Summary of the quantiles of Individual Issuance To Confirmation Tx Times
16+
IssuanceToConfirmationTxTimes prometheus.Summary
17+
}
18+
19+
// NewMetrics creates and returns a Metrics and registers it with a Collector
20+
func NewMetrics(reg prometheus.Registerer) *Metrics {
21+
m := &Metrics{
22+
IssuanceTxTimes: prometheus.NewSummary(prometheus.SummaryOpts{
23+
Name: "tx_issuance_time",
24+
Help: "Individual Tx Issuance Times for a Load Test",
25+
Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
26+
}),
27+
ConfirmationTxTimes: prometheus.NewSummary(prometheus.SummaryOpts{
28+
Name: "tx_confirmation_time",
29+
Help: "Individual Tx Confirmation Times for a Load Test",
30+
Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
31+
}),
32+
IssuanceToConfirmationTxTimes: prometheus.NewSummary(prometheus.SummaryOpts{
33+
Name: "tx_issuance_to_confirmation_time",
34+
Help: "Individual Tx Issuance To Confirmation Times for a Load Test",
35+
Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
36+
}),
37+
}
38+
reg.MustRegister(m.IssuanceTxTimes)
39+
reg.MustRegister(m.ConfirmationTxTimes)
40+
reg.MustRegister(m.IssuanceToConfirmationTxTimes)
41+
return m
42+
}

0 commit comments

Comments
 (0)