feat(tools/benchmark): introduce benchmark module (#24021)
Co-authored-by: Alex | Interchain Labs <alex@interchainlabs.io>
This commit is contained in:
co-authored by
Alex | Interchain Labs
parent
8bf5430d7e
commit
9539caae6e
@@ -0,0 +1,62 @@
|
||||
package module
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
modulev1 "cosmossdk.io/api/cosmos/benchmark/module/v1"
|
||||
"cosmossdk.io/core/appmodule"
|
||||
"cosmossdk.io/core/store"
|
||||
"cosmossdk.io/depinject"
|
||||
"cosmossdk.io/depinject/appconfig"
|
||||
"cosmossdk.io/log"
|
||||
gen "cosmossdk.io/tools/benchmark/generator"
|
||||
)
|
||||
|
||||
const (
|
||||
ModuleName = "benchmark"
|
||||
maxStoreKeyGenIterations = 100
|
||||
)
|
||||
|
||||
func init() {
|
||||
appconfig.RegisterModule(
|
||||
&modulev1.Module{},
|
||||
appconfig.Provide(
|
||||
ProvideModule,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
type KVStoreServiceFactory func([]byte) store.KVStoreService
|
||||
|
||||
type Input struct {
|
||||
depinject.In
|
||||
|
||||
Logger log.Logger
|
||||
Cfg *modulev1.Module
|
||||
StoreFactory KVStoreServiceFactory
|
||||
}
|
||||
|
||||
func ProvideModule(
|
||||
in Input,
|
||||
) (appmodule.AppModule, error) {
|
||||
cfg := in.Cfg
|
||||
kvMap := make(KVServiceMap)
|
||||
storeKeys, err := gen.StoreKeys(ModuleName, cfg.GenesisParams.Seed, cfg.GenesisParams.BucketCount)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, sk := range storeKeys {
|
||||
kvService := in.StoreFactory(unsafeStrToBytes(sk))
|
||||
kvMap[sk] = kvService
|
||||
}
|
||||
|
||||
return NewAppModule(cfg.GenesisParams, storeKeys, kvMap, in.Logger), nil
|
||||
}
|
||||
|
||||
type KVServiceMap map[string]store.KVStoreService
|
||||
|
||||
// unsafeStrToBytes uses unsafe to convert string into byte array. Returned bytes
|
||||
// must not be altered after this function is called as it will cause a segmentation fault.
|
||||
func unsafeStrToBytes(s string) []byte {
|
||||
return unsafe.Slice(unsafe.StringData(s), len(s)) // ref https://github.com/golang/go/issues/53003#issuecomment-1140276077
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package module
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"cosmossdk.io/tools/benchmark"
|
||||
gen "cosmossdk.io/tools/benchmark/generator"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/telemetry"
|
||||
)
|
||||
|
||||
var (
|
||||
_ benchmark.MsgServer = &Keeper{}
|
||||
metricOpKey = []string{"benchmark", "op"}
|
||||
metricGetKey = append(metricOpKey, "get")
|
||||
metricDelete = append(metricOpKey, "delete")
|
||||
metricInsertKey = append(metricOpKey, "insert")
|
||||
metricUpdateKey = append(metricOpKey, "update")
|
||||
metricTotalKey = []string{"benchmark", "total"}
|
||||
metricMissKey = []string{"benchmark", "miss"}
|
||||
)
|
||||
|
||||
type Keeper struct {
|
||||
kvServiceMap KVServiceMap
|
||||
validate bool
|
||||
errExit bool
|
||||
}
|
||||
|
||||
func NewKeeper(kvMap KVServiceMap) *Keeper {
|
||||
k := &Keeper{
|
||||
kvServiceMap: kvMap,
|
||||
validate: false,
|
||||
errExit: false,
|
||||
}
|
||||
return k
|
||||
}
|
||||
|
||||
func (k *Keeper) LoadTest(ctx context.Context, msg *benchmark.MsgLoadTest) (*benchmark.MsgLoadTestResponse, error) {
|
||||
res := &benchmark.MsgLoadTestResponse{}
|
||||
for _, op := range msg.Ops {
|
||||
telemetry.IncrCounter(1, metricTotalKey...)
|
||||
err := k.executeOp(ctx, op)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (k *Keeper) executeOp(ctx context.Context, op *benchmark.Op) error {
|
||||
svc, ok := k.kvServiceMap[op.Actor]
|
||||
key := gen.Bytes(op.Seed, op.KeyLength)
|
||||
if !ok {
|
||||
return fmt.Errorf("actor %s not found", op.Actor)
|
||||
}
|
||||
kv := svc.OpenKVStore(ctx)
|
||||
switch {
|
||||
case op.Delete:
|
||||
telemetry.IncrCounter(1, metricDelete...)
|
||||
if k.validate {
|
||||
exists, err := kv.Has(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exists {
|
||||
telemetry.IncrCounter(1, metricMissKey...)
|
||||
if k.errExit {
|
||||
return fmt.Errorf("key %d not found", op.Seed)
|
||||
}
|
||||
}
|
||||
}
|
||||
return kv.Delete(key)
|
||||
case op.ValueLength > 0:
|
||||
metricKey := metricInsertKey
|
||||
if op.Exists {
|
||||
metricKey = metricUpdateKey
|
||||
}
|
||||
telemetry.IncrCounter(1, metricKey...)
|
||||
if k.validate {
|
||||
exists, err := kv.Has(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exists != op.Exists {
|
||||
telemetry.IncrCounter(1, metricMissKey...)
|
||||
if k.errExit {
|
||||
return fmt.Errorf("key %d exists=%t, expected=%t", op.Seed, exists, op.Exists)
|
||||
}
|
||||
}
|
||||
}
|
||||
value := gen.Bytes(op.Seed, op.ValueLength)
|
||||
return kv.Set(key, value)
|
||||
case op.Iterations > 0:
|
||||
return fmt.Errorf("iterator not implemented")
|
||||
case op.ValueLength == 0:
|
||||
telemetry.IncrCounter(1, metricGetKey...)
|
||||
v, err := kv.Get(key)
|
||||
if v == nil {
|
||||
// always count a miss on GET since it requires no extra I/O
|
||||
telemetry.IncrCounter(1, metricMissKey...)
|
||||
if k.errExit {
|
||||
return fmt.Errorf("key %s not found", key)
|
||||
}
|
||||
}
|
||||
return err
|
||||
default:
|
||||
return fmt.Errorf("invalid op: %+v", op)
|
||||
}
|
||||
}
|
||||
|
||||
func (k *Keeper) set(ctx context.Context, actor string, key, value []byte) error {
|
||||
svc, ok := k.kvServiceMap[actor]
|
||||
if !ok {
|
||||
return fmt.Errorf("actor %s not found", actor)
|
||||
}
|
||||
kv := svc.OpenKVStore(ctx)
|
||||
return kv.Set(key, value)
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package module
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/grpc-ecosystem/grpc-gateway/runtime"
|
||||
"github.com/spf13/cobra"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
modulev1 "cosmossdk.io/api/cosmos/benchmark/module/v1"
|
||||
_ "cosmossdk.io/api/cosmos/benchmark/v1" // for some reason this is required to make msg server registration work
|
||||
"cosmossdk.io/core/appmodule"
|
||||
"cosmossdk.io/log"
|
||||
"cosmossdk.io/tools/benchmark"
|
||||
"cosmossdk.io/tools/benchmark/client/cli"
|
||||
gen "cosmossdk.io/tools/benchmark/generator"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/types/module"
|
||||
"github.com/cosmos/cosmos-sdk/types/msgservice"
|
||||
)
|
||||
|
||||
var (
|
||||
_ appmodule.AppModule = &AppModule{}
|
||||
_ module.HasGenesis = &AppModule{}
|
||||
)
|
||||
|
||||
type AppModule struct {
|
||||
keeper *Keeper
|
||||
storeKeys []string
|
||||
genesisParams *modulev1.GeneratorParams
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
func NewAppModule(
|
||||
genesisParams *modulev1.GeneratorParams,
|
||||
storeKeys []string,
|
||||
kvMap KVServiceMap,
|
||||
logger log.Logger,
|
||||
) *AppModule {
|
||||
return &AppModule{
|
||||
genesisParams: genesisParams,
|
||||
keeper: NewKeeper(kvMap),
|
||||
storeKeys: storeKeys,
|
||||
log: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AppModule) DefaultGenesis(_ codec.JSONCodec) json.RawMessage {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *AppModule) ExportGenesis(_ sdk.Context, _ codec.JSONCodec) json.RawMessage {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *AppModule) InitGenesis(ctx sdk.Context, _ codec.JSONCodec, _ json.RawMessage) {
|
||||
a.genesisParams.BucketCount = uint64(len(a.storeKeys))
|
||||
g := gen.NewGenerator(gen.Options{GeneratorParams: a.genesisParams})
|
||||
i := 0
|
||||
for kv := range g.GenesisSet() {
|
||||
i++
|
||||
if i%100_000 == 0 {
|
||||
a.log.Warn("benchmark: init genesis", "progress", i, "total", a.genesisParams.GenesisCount)
|
||||
}
|
||||
sk := a.storeKeys[kv.StoreKey]
|
||||
key := gen.Bytes(kv.Key.Seed(), kv.Key.Length())
|
||||
value := gen.Bytes(kv.Value.Seed(), kv.Value.Length())
|
||||
err := a.keeper.set(ctx, sk, key, value)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AppModule) ValidateGenesis(_ codec.JSONCodec, _ client.TxEncodingConfig, _ json.RawMessage) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *AppModule) RegisterGRPCGatewayRoutes(client.Context, *runtime.ServeMux) {
|
||||
}
|
||||
|
||||
// RegisterServices registers module services.
|
||||
func (a *AppModule) RegisterServices(registrar grpc.ServiceRegistrar) error {
|
||||
benchmark.RegisterMsgServer(registrar, a.keeper)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *AppModule) RegisterInterfaces(registry codectypes.InterfaceRegistry) {
|
||||
registry.RegisterImplementations(
|
||||
(*sdk.Msg)(nil),
|
||||
&benchmark.MsgLoadTest{})
|
||||
msgservice.RegisterMsgServiceDesc(registry, &benchmark.Msg_serviceDesc)
|
||||
}
|
||||
|
||||
func (a *AppModule) GetTxCmd() *cobra.Command {
|
||||
return cli.NewTxCmd(a.genesisParams)
|
||||
}
|
||||
|
||||
func (a *AppModule) IsOnePerModuleType() {}
|
||||
|
||||
func (a *AppModule) IsAppModule() {}
|
||||
Reference in New Issue
Block a user