Skip to content

Commit

Permalink
chore: transform url arg into a flag (#138)
Browse files Browse the repository at this point in the history
* chore(loadtest): url is now a flag instead of an arg

* chore: rename url flag into rpc-endpoint like cast

* chore: gen doc

* chore: update doc and test script

* chore: rename `rpc-endpoint` into `rpc-url`

* chore: move `ValidateUrl` to `util` pkg

* chore: same thing for monitor cmd

* chore: update doc

* chore: same thing for `rpcfuzz`

* chore: clean up

* chore: more clean up

* chore: remove url arg from uniswapv3 loadtest cmd

* chore: gen doc

* chore: add zerolog setup

* chore: update usage
  • Loading branch information
leovct authored Oct 21, 2023
1 parent 8a40b24 commit 6d912a6
Show file tree
Hide file tree
Showing 16 changed files with 387 additions and 347 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ $ make geth-loadtest
You can view the state of the chain using `polycli`.

```bash
$ polycli monitor http://127.0.0.1:8545
$ polycli monitor --rpc-url http://127.0.0.1:8545
```

![polycli monitor](doc/assets/monitor.gif)
Expand Down Expand Up @@ -145,7 +145,7 @@ true
You can then generate some load to make sure that blocks with transactions are being created. Note that the chain id of local geth is `1337`.

```bash
$ polycli loadtest --verbosity 700 --chain-id 1337 --concurrency 1 --requests 1000 --rate-limit 5 --mode c http://127.0.0.1:8545
$ polycli loadtest --verbosity 700 --chain-id 1337 --concurrency 1 --requests 1000 --rate-limit 5 --mode c --rpc-url http://127.0.0.1:8545
```

# Contributing
Expand Down
63 changes: 30 additions & 33 deletions cmd/loadtest/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,16 @@ package loadtest
import (
"crypto/ecdsa"
_ "embed"
"errors"
"fmt"
"math/big"
"math/rand"
"net/url"
"sync"
"time"

ethcommon "github.com/ethereum/go-ethereum/common"
"github.com/maticnetwork/polygon-cli/rpctypes"
"github.com/maticnetwork/polygon-cli/util"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"golang.org/x/time/rate"
)
Expand All @@ -38,14 +36,14 @@ type (
}
loadTestParams struct {
// inputs
RPCUrl *string
Requests *int64
Concurrency *int64
BatchSize *uint64
TimeLimit *int64
ToRandom *bool
CallOnly *bool
CallOnlyLatestBlock *bool
URL *url.URL
ChainID *uint64
PrivateKey *string
ToAddress *string
Expand Down Expand Up @@ -156,43 +154,41 @@ var (

// LoadtestCmd represents the loadtest command
var LoadtestCmd = &cobra.Command{
Use: "loadtest url",
Use: "loadtest",
Short: "Run a generic load test against an Eth/EVM style JSON-RPC endpoint.",
Long: loadtestUsage,
RunE: func(cmd *cobra.Command, args []string) error {
err := runLoadTest(cmd.Context())
if err != nil {
return err
}
return nil
},
Args: func(cmd *cobra.Command, args []string) error {
Args: cobra.NoArgs,
PreRunE: func(cmd *cobra.Command, args []string) error {
zerolog.DurationFieldUnit = time.Second
zerolog.DurationFieldInteger = true

if len(args) != 1 {
return errors.New("expected exactly one argument")
}
url, err := url.Parse(args[0])
if err != nil {
log.Error().Err(err).Msg("Unable to parse url input error")
return err
}
if url.Scheme != "http" && url.Scheme != "https" && url.Scheme != "ws" && url.Scheme != "wss" {
return fmt.Errorf("the scheme %s is not supported", url.Scheme)
}
inputLoadTestParams.URL = url
return checkLoadtestFlags()
},
RunE: func(cmd *cobra.Command, args []string) error {
return runLoadTest(cmd.Context())
},
}

if *inputLoadTestParams.AdaptiveBackoffFactor <= 0.0 {
return errors.New("the backoff factor needs to be non-zero positive")
}
func checkLoadtestFlags() error {
ltp := inputLoadTestParams

if *inputLoadTestParams.ContractCallBlockInterval == 0 {
return errors.New("the contract call block interval must be strictly positive")
}
// Check `rpc-url` flag.
if ltp.RPCUrl == nil {
panic("RPC URL is empty")
}
if err := util.ValidateUrl(*ltp.RPCUrl); err != nil {
return err
}

return nil
},
if ltp.AdaptiveBackoffFactor != nil && *ltp.AdaptiveBackoffFactor <= 0.0 {
return fmt.Errorf("the backoff factor needs to be non-zero positive")
}

if ltp.ContractCallBlockInterval != nil && *ltp.ContractCallBlockInterval == 0 {
return fmt.Errorf("the contract call block interval must be strictly positive")
}

return nil
}

func init() {
Expand All @@ -204,6 +200,7 @@ func initFlags() {
ltp := new(loadTestParams)

// Persistent flags.
ltp.RPCUrl = LoadtestCmd.PersistentFlags().StringP("rpc-url", "r", "http://localhost:8545", "The RPC endpoint url")
ltp.Requests = LoadtestCmd.PersistentFlags().Int64P("requests", "n", 1, "Number of requests to perform for the benchmarking session. The default is to just perform a single request which usually leads to non-representative benchmarking results.")
ltp.Concurrency = LoadtestCmd.PersistentFlags().Int64P("concurrency", "c", 1, "Number of requests to perform concurrently. Default is one request at a time.")
ltp.TimeLimit = LoadtestCmd.PersistentFlags().Int64P("time-limit", "t", -1, "Maximum number of seconds to spend for benchmarking. Use this to benchmark within a fixed total amount of time. Per default there is no time limit.")
Expand Down
2 changes: 1 addition & 1 deletion cmd/loadtest/loadtest.go
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ func runLoadTest(ctx context.Context) error {
overallTimer = new(time.Timer)
}

rpc, err := ethrpc.DialContext(ctx, inputLoadTestParams.URL.String())
rpc, err := ethrpc.DialContext(ctx, *inputLoadTestParams.RPCUrl)
if err != nil {
log.Error().Err(err).Msg("Unable to dial rpc")
return err
Expand Down
6 changes: 3 additions & 3 deletions cmd/loadtest/loadtestUsage.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,13 @@ The default private key is: `42b6e34dc21598a807dc19d7784c71b2a7a01f6480dc6f58258
Here is a simple example that runs 1000 requests at a max rate of 1 request per second against the http rpc endpoint on localhost. It's running in transaction mode so it will perform simple transactions send to the default address.

```bash
$ polycli loadtest --verbosity 700 --chain-id 1256 --concurrency 1 --requests 1000 --rate-limit 1 --mode t http://localhost:8888
$ polycli loadtest --verbosity 700 --chain-id 1256 --concurrency 1 --requests 1000 --rate-limit 1 --mode t --rpc-url http://localhost:8888
```

Another example, a bit slower, and that specifically calls the [LOG4](https://www.evm.codes/#a4) function in the load test contract in a loop for 25,078 iterations. That number was picked specifically to require almost all of the gas for a single transaction.

```bash
$ polycli loadtest --verbosity 700 --chain-id 1256 --concurrency 1 --requests 50 --rate-limit 0.5 --mode f --function 164 --iterations 25078 http://private.validator-001.devnet02.pos-v3.polygon.private:8545
$ polycli loadtest --verbosity 700 --chain-id 1256 --concurrency 1 --requests 50 --rate-limit 0.5 --mode f --function 164 --iterations 25078 --rpc-url http://private.validator-001.devnet02.pos-v3.polygon.private:8545
```

### Load Test Contract
Expand All @@ -74,6 +74,6 @@ The codebase has a contract that used for load testing. It's written in Yul and
3. Run `abigen`
- `$ abigen --abi LoadTester.abi --pkg contracts --type LoadTester --bin LoadTester.bin --out loadtester.go`
4. Run the loadtester to enure it deploys and runs successfully
- `$ polycli loadtest --verbosity 700 http://127.0.0.1:8541`
- `$ polycli loadtest --verbosity 700 --rpc-url http://127.0.0.1:8541`

```
41 changes: 3 additions & 38 deletions cmd/loadtest/uniswapv3.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"errors"
"fmt"
"math/big"
"net/url"
"time"

"github.com/spf13/cobra"
Expand All @@ -16,7 +15,6 @@ import (
"github.com/ethereum/go-ethereum/ethclient"
uniswapv3loadtest "github.com/maticnetwork/polygon-cli/cmd/loadtest/uniswapv3"
"github.com/maticnetwork/polygon-cli/contracts/uniswapv3"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)

Expand All @@ -30,8 +28,9 @@ var uniswapV3LoadTestCmd = &cobra.Command{
Use: "uniswapv3 url",
Short: "Run Uniswapv3-like load test against an Eth/EVm style JSON-RPC endpoint.",
Long: uniswapv3Usage,
Args: cobra.NoArgs,
PreRunE: func(cmd *cobra.Command, args []string) error {
return checkFlags()
return checkUniswapV3LoadtestFlags()
},
RunE: func(cmd *cobra.Command, args []string) error {
// Override root command `mode` flag.
Expand All @@ -44,25 +43,9 @@ var uniswapV3LoadTestCmd = &cobra.Command{
}
return nil
},
Args: func(cmd *cobra.Command, args []string) error {
zerolog.DurationFieldUnit = time.Second
zerolog.DurationFieldInteger = true

if len(args) != 1 {
return errors.New("expected exactly one argument")
}

url, err := validateUrl(args[0])
if err != nil {
return err
}
inputLoadTestParams.URL = url

return nil
},
}

func checkFlags() error {
func checkUniswapV3LoadtestFlags() error {
// Check pool fees.
switch fees := *uniswapv3LoadTestParams.PoolFees; fees {
case float64(uniswapv3loadtest.StableTier), float64(uniswapv3loadtest.StandardTier), float64(uniswapv3loadtest.ExoticTier):
Expand All @@ -79,24 +62,6 @@ func checkFlags() error {
return nil
}

func validateUrl(input string) (*url.URL, error) {
url, err := url.Parse(input)
if err != nil {
log.Error().Err(err).Msg("Unable to parse url input error")
return nil, err
}

if url.Scheme == "" {
return nil, errors.New("the scheme has not been specified")
}
switch url.Scheme {
case "http", "https", "ws", "wss":
return url, nil
default:
return nil, fmt.Errorf("the scheme %s is not supported", url.Scheme)
}
}

type params struct {
UniswapFactoryV3, UniswapMulticall, UniswapProxyAdmin, UniswapTickLens, UniswapNFTLibDescriptor, UniswapNonfungibleTokenPositionDescriptor, UniswapUpgradeableProxy, UniswapNonfungiblePositionManager, UniswapMigrator, UniswapStaker, UniswapQuoterV2, UniswapSwapRouter, WETH9, UniswapPoolToken0, UniswapPoolToken1 *string
PoolFees *float64
Expand Down
4 changes: 2 additions & 2 deletions cmd/loadtest/uniswapv3Usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ The `uniswapv3` command is a subcommand of the `loadtest` tool. It is meant to g
You can either chose to deploy the full UniswapV3 contract suite.

```sh
polycli loadtest uniswapv3 http://localhost:8545
polycli loadtest uniswapv3
```

Or to use pre-deployed contracts to speed up the process.

```bash
polycli loadtest uniswapv3 http://localhost:8545 \
polycli loadtest uniswapv3 \
--uniswap-factory-v3-address 0xc5f46e00822c828e1edcc12cf98b5a7b50c9e81b \
--uniswap-migrator-address 0x24951726c5d22a3569d5474a1e74734a09046cd9 \
--uniswap-multicall-address 0x0e695f36ade2a12abea51622e80f105e125d1d6e \
Expand Down
70 changes: 70 additions & 0 deletions cmd/monitor/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package monitor

import (
_ "embed"
"fmt"
"strconv"
"time"

"github.com/maticnetwork/polygon-cli/util"
"github.com/spf13/cobra"
)

var (
//go:embed usage.md
usage string

// flags
rpcUrl string
batchSizeValue string
intervalStr string
)

// MonitorCmd represents the monitor command
var MonitorCmd = &cobra.Command{
Use: "monitor",
Short: "Monitor blocks using a JSON-RPC endpoint.",
Long: usage,
Args: cobra.NoArgs,
PreRunE: func(cmd *cobra.Command, args []string) error {
return checkFlags()
},
RunE: func(cmd *cobra.Command, args []string) error {
return monitor(cmd.Context())
},
}

func init() {
MonitorCmd.PersistentFlags().StringVarP(&rpcUrl, "rpc-url", "r", "http://localhost:8545", "The RPC endpoint url")
MonitorCmd.PersistentFlags().StringVarP(&batchSizeValue, "batch-size", "b", "auto", "Number of requests per batch")
MonitorCmd.PersistentFlags().StringVarP(&intervalStr, "interval", "i", "5s", "Amount of time between batch block rpc calls")
}

func checkFlags() (err error) {
// Check rpc-url flag.
if err = util.ValidateUrl(rpcUrl); err != nil {
return
}

// Check interval duration flag.
interval, err = time.ParseDuration(intervalStr)
if err != nil {
return err
}

// Check batch-size flag.
if batchSizeValue == "auto" {
batchSize = -1
} else {
batchSize, err = strconv.Atoi(batchSizeValue)
if batchSize == 0 {
return fmt.Errorf("batch-size can't be equal to zero")
}
if err != nil {
// Failed to convert to int, handle the error
return fmt.Errorf("batch-size needs to be an integer")
}
}

return nil
}
Loading

0 comments on commit 6d912a6

Please sign in to comment.