feat(tools): speedtest (#25555)
Co-authored-by: Alex | Cosmos Labs <alex@cosmoslabs.io>
This commit is contained in:
parent
0d6228eaab
commit
94aebcd8ab
@ -113,6 +113,7 @@ func initRootCmd(
|
||||
confixcmd.ConfigCommand(),
|
||||
pruning.Cmd(newApp, simapp.DefaultNodeHome),
|
||||
snapshot.Cmd(newApp),
|
||||
NewBankSpeedTest(),
|
||||
)
|
||||
|
||||
server.AddCommandsWithStartCmdOptions(rootCmd, simapp.DefaultNodeHome, newApp, appExport, server.StartCmdOptions{
|
||||
|
||||
105
simapp/simd/cmd/speedtest.go
Normal file
105
simapp/simd/cmd/speedtest.go
Normal file
@ -0,0 +1,105 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
dbm "github.com/cosmos/cosmos-db"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"cosmossdk.io/log"
|
||||
"cosmossdk.io/simapp"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/baseapp"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
|
||||
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
|
||||
simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims"
|
||||
"github.com/cosmos/cosmos-sdk/tools/speedtest"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
|
||||
)
|
||||
|
||||
var r = rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
|
||||
func NewBankSpeedTest() *cobra.Command {
|
||||
dir, err := os.MkdirTemp("", "bankspeedtest-*")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
db, err := dbm.NewDB("app", dbm.PebbleDBBackend, dir)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
chainID := "foo"
|
||||
app := simapp.NewSimApp(log.NewNopLogger(), db, nil, true, simtestutil.NewAppOptionsWithFlagHome(dir), baseapp.SetChainID(chainID))
|
||||
gen := generator{
|
||||
app: app,
|
||||
accounts: make([]accountInfo, 0),
|
||||
}
|
||||
cmd := speedtest.NewCmd(gen.createAccount, gen.generateTx, app, app.AppCodec(), app.DefaultGenesis(), chainID)
|
||||
cmd.PostRunE = func(_ *cobra.Command, _ []string) error {
|
||||
return os.RemoveAll(dir)
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
type generator struct {
|
||||
app *simapp.SimApp
|
||||
accounts []accountInfo
|
||||
}
|
||||
|
||||
type accountInfo struct {
|
||||
privKey cryptotypes.PrivKey
|
||||
address sdk.AccAddress
|
||||
accNum uint64
|
||||
seqNum uint64
|
||||
}
|
||||
|
||||
func (g *generator) createAccount() (*authtypes.BaseAccount, sdk.Coins) {
|
||||
privKey := secp256k1.GenPrivKey()
|
||||
addr := sdk.AccAddress(privKey.PubKey().Address())
|
||||
accNum := len(g.accounts)
|
||||
baseAcc := authtypes.NewBaseAccount(addr, privKey.PubKey(), uint64(accNum), 0)
|
||||
|
||||
g.accounts = append(g.accounts, accountInfo{
|
||||
privKey: privKey,
|
||||
address: addr,
|
||||
accNum: uint64(accNum),
|
||||
seqNum: 0,
|
||||
})
|
||||
|
||||
return baseAcc, sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 1_000_000_000))
|
||||
}
|
||||
|
||||
func (g *generator) generateTx() []byte {
|
||||
senderIdx := r.Intn(len(g.accounts))
|
||||
recipientIdx := (senderIdx + 1 + r.Intn(len(g.accounts)-1)) % len(g.accounts)
|
||||
sender := g.accounts[senderIdx]
|
||||
recipient := g.accounts[recipientIdx]
|
||||
sendAmount := sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 1))
|
||||
msg := banktypes.NewMsgSend(sender.address, recipient.address, sendAmount)
|
||||
txConfig := g.app.TxConfig()
|
||||
// Build and sign transaction
|
||||
tx, err := simtestutil.GenSignedMockTx(
|
||||
r,
|
||||
txConfig,
|
||||
[]sdk.Msg{msg},
|
||||
sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 0)),
|
||||
simtestutil.DefaultGenTxGas,
|
||||
g.app.ChainID(),
|
||||
[]uint64{sender.accNum},
|
||||
[]uint64{sender.seqNum},
|
||||
sender.privKey,
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
txBytes, err := txConfig.TxEncoder()(tx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
g.accounts[senderIdx].seqNum++
|
||||
return txBytes
|
||||
}
|
||||
167
tools/speedtest/speedtest.go
Normal file
167
tools/speedtest/speedtest.go
Normal file
@ -0,0 +1,167 @@
|
||||
package speedtest
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/cometbft/cometbft/abci/types"
|
||||
cmtjson "github.com/cometbft/cometbft/libs/json"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
servertypes "github.com/cosmos/cosmos-sdk/server/types"
|
||||
simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
|
||||
)
|
||||
|
||||
type AccountCreator func() (*authtypes.BaseAccount, sdk.Coins)
|
||||
|
||||
type GenerateTx func() []byte
|
||||
|
||||
var (
|
||||
numAccounts = 10_000
|
||||
numTxsPerBlock = 4_000
|
||||
numBlocksToRun = 100
|
||||
blockMaxGas = math.MaxInt64
|
||||
blockMaxBytes = math.MaxInt64
|
||||
verifyTxs = false
|
||||
)
|
||||
|
||||
// NewCmd returns a command that will run an execution test on your application.
|
||||
// Balances and accounts are automatically added to the chain's state via AccountCreator.
|
||||
func NewCmd(
|
||||
createAccount AccountCreator,
|
||||
generateTx GenerateTx,
|
||||
app servertypes.ABCI,
|
||||
cdc codec.Codec,
|
||||
genState map[string]json.RawMessage,
|
||||
chainID string,
|
||||
) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "speedtest",
|
||||
Short: "execution speedtest",
|
||||
Long: "speedtest is a tool for measuring raw execution TPS of your application",
|
||||
Example: "speedtest --accounts 20000 --txs 2000 --blocks 10 --block-max-gas 1000000000 --block-max-bytes 1000000000 --verify-txs",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
accounts := make([]simtestutil.GenesisAccount, 0, numAccounts)
|
||||
balances := make([]banktypes.Balance, 0, numAccounts)
|
||||
for range numAccounts {
|
||||
account, balance := createAccount()
|
||||
genesisAcc := simtestutil.GenesisAccount{
|
||||
GenesisAccount: account,
|
||||
Coins: balance,
|
||||
}
|
||||
accounts = append(accounts, genesisAcc)
|
||||
balances = append(balances, banktypes.Balance{
|
||||
Address: account.Address,
|
||||
Coins: balance,
|
||||
})
|
||||
}
|
||||
|
||||
vals, err := simtestutil.CreateRandomValidatorSet()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
genAccs := make([]authtypes.GenesisAccount, 0, len(accounts))
|
||||
for _, acc := range accounts {
|
||||
genAccs = append(genAccs, acc.GenesisAccount)
|
||||
}
|
||||
genesisState, err := simtestutil.GenesisStateWithValSet(cdc, genState, vals, genAccs, balances...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// init chain must be called to stop deliverState from being nil
|
||||
stateBytes, err := cmtjson.MarshalIndent(genesisState, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cp := simtestutil.DefaultConsensusParams
|
||||
cp.Block.MaxGas = int64(blockMaxGas)
|
||||
cp.Block.MaxBytes = int64(blockMaxBytes)
|
||||
_, err = app.InitChain(&types.RequestInitChain{
|
||||
ChainId: chainID,
|
||||
Validators: []types.ValidatorUpdate{},
|
||||
ConsensusParams: cp,
|
||||
AppStateBytes: stateBytes,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to InitChain: %w", err)
|
||||
}
|
||||
|
||||
// commit genesis changes
|
||||
_, err = app.FinalizeBlock(&types.RequestFinalizeBlock{
|
||||
Height: 1,
|
||||
NextValidatorsHash: vals.Hash(),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to finalize genesis block: %w", err)
|
||||
}
|
||||
|
||||
blocks := make([][][]byte, 0, numBlocksToRun)
|
||||
for range numBlocksToRun {
|
||||
block := make([][]byte, 0, numBlocksToRun)
|
||||
for range numTxsPerBlock {
|
||||
tx := generateTx()
|
||||
block = append(block, tx)
|
||||
}
|
||||
blocks = append(blocks, block)
|
||||
}
|
||||
|
||||
elapsed, err := runBlocks(blocks, app, vals.Proposer.Address, verifyTxs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to run blocks: %w", err)
|
||||
}
|
||||
|
||||
cmd.Printf("Finished %d blocks in %s\n", numBlocksToRun, elapsed)
|
||||
numTxs := numBlocksToRun * numTxsPerBlock
|
||||
tps := float64(numTxs) / elapsed.Seconds()
|
||||
cmd.Printf("TPS: %f", tps)
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
cmd.Flags().IntVar(&numAccounts, "accounts", numAccounts, "number of accounts")
|
||||
cmd.Flags().IntVar(&numTxsPerBlock, "txs", numTxsPerBlock, "number of txs")
|
||||
cmd.Flags().IntVar(&numBlocksToRun, "blocks", numBlocksToRun, "number of blocks")
|
||||
cmd.Flags().BoolVar(&verifyTxs, "verify-txs", verifyTxs, "verify txs passed. this will loop over all tx results and ensure the code == 0.")
|
||||
cmd.Flags().IntVar(&blockMaxGas, "block-max-gas", blockMaxGas, "block max gas")
|
||||
cmd.Flags().IntVar(&blockMaxBytes, "block-max-bytes", blockMaxBytes, "block max bytes")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runBlocks(blocks [][][]byte, app servertypes.ABCI, proposer []byte, verify bool) (time.Duration, error) {
|
||||
start := time.Now()
|
||||
height := int64(1)
|
||||
for blockNum, txs := range blocks {
|
||||
res, err := app.FinalizeBlock(&types.RequestFinalizeBlock{
|
||||
Height: height,
|
||||
Txs: txs,
|
||||
Time: time.Now(),
|
||||
ProposerAddress: proposer,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to finalize block #%d: %w", blockNum, err)
|
||||
}
|
||||
if verify {
|
||||
for _, result := range res.TxResults {
|
||||
if result.Code != 0 {
|
||||
return 0, fmt.Errorf("tx failed in block %d: code=%d codespace=%s", blockNum, result.Code, result.Codespace)
|
||||
}
|
||||
}
|
||||
}
|
||||
_, err = app.Commit()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to commit block #%d: %w", blockNum, err)
|
||||
}
|
||||
height++
|
||||
}
|
||||
end := time.Since(start)
|
||||
return end, nil
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user