feat(tools/benchmark): introduce benchmark module (#24021)

Co-authored-by: Alex | Interchain Labs <alex@interchainlabs.io>
This commit is contained in:
Đông Liều
2025-03-20 10:56:03 -04:00
committed by GitHub
co-authored by Alex | Interchain Labs
parent 8bf5430d7e
commit 9539caae6e
19 changed files with 7451 additions and 0 deletions
+30
View File
@@ -0,0 +1,30 @@
<!--
Guiding Principles:
Changelogs are for humans, not machines.
There should be an entry for every single version.
The same types of changes should be grouped.
Versions and sections should be linkable.
The latest version comes first.
The release date of each version is displayed.
Mention whether you follow Semantic Versioning.
Usage:
Change log entries are to be added to the Unreleased section under the
appropriate stanza (see below). Each entry should ideally include a tag and
the Github issue reference in the following format:
* (<tag>) [#<issue-number>] Changelog message.
Types of changes (Stanzas):
"Features" for new features.
"Improvements" for changes in existing functionality.
"Deprecated" for soon-to-be removed features.
"Bug Fixes" for any bug fixes.
"API Breaking" for breaking exported APIs used by developers building on SDK.
Ref: https://keepachangelog.com/en/1.0.0/
-->
# Changelog
## [Unreleased]
## [v0.2.0-rc.1](https://github.com/cosmos/cosmos-sdk/releases/tag/tools/benchmark/v0.2.0-rc.1) - 2024-12-18
* [#22778](https://github.com/cosmos/cosmos-sdk/pull/22778) - Initial commit
+24
View File
@@ -0,0 +1,24 @@
# cosmossdk.io/tools/benchmark
A benchmark module to test chain and storage performance. It can be used to holistically test
the end to end performance of a node. Given an initial configuration tools/benchmark provides:
* A possibly enormous sequence of key-value sets in InitGenesis distributed across n storekeys,
e.g. 20M keys across 5 store keys
* A client which syncs to genesis state then deterministically generates txs which contain a
configurable sequence of get, insert, update & delete operations
* A keeper which processes the above transactions and emits some telemetry data about them.
Client invocation looks like:
```bash
simdv2 tx benchmark load-test --from bob --yes --ops 1000 --pause 10 -v
```
On exit it dumps the generator state so that running again should still be in sync. It assumes
that any transaction accepted by the network was processed, which may not be the case, so miss
rate will probably increase over time. This isn't really a problem for tests.
Obviously this module is built to DOS a node by testing the upper bounds of chain performance;
when testing gas limits should be increased. It should not be included in chains by default but
is enabled in simapp for testing.
+545
View File
@@ -0,0 +1,545 @@
// Code generated by protoc-gen-gogo. DO NOT EDIT.
// source: cosmos/benchmark/v1/benchmark.proto
package benchmark
import (
fmt "fmt"
proto "github.com/cosmos/gogoproto/proto"
io "io"
math "math"
math_bits "math/bits"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
// Op is a message describing a benchmark operation.
type Op struct {
Seed uint64 `protobuf:"varint,1,opt,name=seed,proto3" json:"seed,omitempty"`
Actor string `protobuf:"bytes,2,opt,name=actor,proto3" json:"actor,omitempty"`
KeyLength uint64 `protobuf:"varint,3,opt,name=key_length,json=keyLength,proto3" json:"key_length,omitempty"`
ValueLength uint64 `protobuf:"varint,4,opt,name=value_length,json=valueLength,proto3" json:"value_length,omitempty"`
Iterations uint32 `protobuf:"varint,5,opt,name=iterations,proto3" json:"iterations,omitempty"`
Delete bool `protobuf:"varint,6,opt,name=delete,proto3" json:"delete,omitempty"`
Exists bool `protobuf:"varint,7,opt,name=exists,proto3" json:"exists,omitempty"`
}
func (m *Op) Reset() { *m = Op{} }
func (m *Op) String() string { return proto.CompactTextString(m) }
func (*Op) ProtoMessage() {}
func (*Op) Descriptor() ([]byte, []int) {
return fileDescriptor_dca1624628907df9, []int{0}
}
func (m *Op) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *Op) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
if deterministic {
return xxx_messageInfo_Op.Marshal(b, m, deterministic)
} else {
b = b[:cap(b)]
n, err := m.MarshalToSizedBuffer(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
}
func (m *Op) XXX_Merge(src proto.Message) {
xxx_messageInfo_Op.Merge(m, src)
}
func (m *Op) XXX_Size() int {
return m.Size()
}
func (m *Op) XXX_DiscardUnknown() {
xxx_messageInfo_Op.DiscardUnknown(m)
}
var xxx_messageInfo_Op proto.InternalMessageInfo
func (m *Op) GetSeed() uint64 {
if m != nil {
return m.Seed
}
return 0
}
func (m *Op) GetActor() string {
if m != nil {
return m.Actor
}
return ""
}
func (m *Op) GetKeyLength() uint64 {
if m != nil {
return m.KeyLength
}
return 0
}
func (m *Op) GetValueLength() uint64 {
if m != nil {
return m.ValueLength
}
return 0
}
func (m *Op) GetIterations() uint32 {
if m != nil {
return m.Iterations
}
return 0
}
func (m *Op) GetDelete() bool {
if m != nil {
return m.Delete
}
return false
}
func (m *Op) GetExists() bool {
if m != nil {
return m.Exists
}
return false
}
func init() {
proto.RegisterType((*Op)(nil), "cosmos.benchmark.v1.Op")
}
func init() {
proto.RegisterFile("cosmos/benchmark/v1/benchmark.proto", fileDescriptor_dca1624628907df9)
}
var fileDescriptor_dca1624628907df9 = []byte{
// 249 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x4c, 0x90, 0xb1, 0x4e, 0x84, 0x40,
0x10, 0x86, 0x99, 0x93, 0x43, 0x6f, 0xd4, 0x66, 0x35, 0x66, 0x0b, 0xdd, 0xa0, 0x36, 0x54, 0x90,
0x8b, 0x89, 0x0f, 0x60, 0x6d, 0x62, 0x42, 0x69, 0x63, 0x38, 0x98, 0x78, 0x04, 0x8e, 0xb9, 0xb0,
0x2b, 0xf1, 0xde, 0xc2, 0x47, 0xb2, 0xb4, 0xbc, 0xd2, 0xd2, 0xc0, 0x8b, 0x18, 0x77, 0xcf, 0x1c,
0xdd, 0x7c, 0x5f, 0xbe, 0x66, 0x7e, 0xbc, 0xcd, 0x59, 0xaf, 0x58, 0x27, 0x0b, 0x6a, 0xf2, 0xe5,
0x2a, 0x6b, 0xab, 0xa4, 0x9b, 0xef, 0x21, 0x5e, 0xb7, 0x6c, 0x58, 0x9c, 0xb9, 0x28, 0xde, 0xfb,
0x6e, 0x7e, 0xf3, 0x09, 0x38, 0x79, 0x5a, 0x0b, 0x81, 0xbe, 0x26, 0x2a, 0x24, 0x84, 0x10, 0xf9,
0xa9, 0xbd, 0xc5, 0x39, 0x4e, 0xb3, 0xdc, 0x70, 0x2b, 0x27, 0x21, 0x44, 0xb3, 0xd4, 0x81, 0xb8,
0x42, 0xac, 0x68, 0xf3, 0x52, 0x53, 0xf3, 0x6a, 0x96, 0xf2, 0xc0, 0xf6, 0xb3, 0x8a, 0x36, 0x8f,
0x56, 0x88, 0x6b, 0x3c, 0xe9, 0xb2, 0xfa, 0x8d, 0xfe, 0x03, 0xdf, 0x06, 0xc7, 0xd6, 0xed, 0x12,
0x85, 0x58, 0x1a, 0x6a, 0x33, 0x53, 0x72, 0xa3, 0xe5, 0x34, 0x84, 0xe8, 0x34, 0x1d, 0x19, 0x71,
0x81, 0x41, 0x41, 0x35, 0x19, 0x92, 0x41, 0x08, 0xd1, 0x51, 0xba, 0xa3, 0x3f, 0x4f, 0xef, 0xa5,
0x36, 0x5a, 0x1e, 0x3a, 0xef, 0xe8, 0xe1, 0xfe, 0xab, 0x57, 0xb0, 0xed, 0x15, 0xfc, 0xf4, 0x0a,
0x3e, 0x06, 0xe5, 0x6d, 0x07, 0xe5, 0x7d, 0x0f, 0xca, 0x7b, 0xbe, 0x74, 0x1f, 0xeb, 0xa2, 0x8a,
0x4b, 0x4e, 0x0c, 0x73, 0x3d, 0x9a, 0x68, 0x11, 0xd8, 0x59, 0xee, 0x7e, 0x03, 0x00, 0x00, 0xff,
0xff, 0x76, 0x0b, 0x27, 0x18, 0x3d, 0x01, 0x00, 0x00,
}
func (m *Op) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBuffer(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *Op) MarshalTo(dAtA []byte) (int, error) {
size := m.Size()
return m.MarshalToSizedBuffer(dAtA[:size])
}
func (m *Op) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i := len(dAtA)
_ = i
var l int
_ = l
if m.Exists {
i--
if m.Exists {
dAtA[i] = 1
} else {
dAtA[i] = 0
}
i--
dAtA[i] = 0x38
}
if m.Delete {
i--
if m.Delete {
dAtA[i] = 1
} else {
dAtA[i] = 0
}
i--
dAtA[i] = 0x30
}
if m.Iterations != 0 {
i = encodeVarintBenchmark(dAtA, i, uint64(m.Iterations))
i--
dAtA[i] = 0x28
}
if m.ValueLength != 0 {
i = encodeVarintBenchmark(dAtA, i, uint64(m.ValueLength))
i--
dAtA[i] = 0x20
}
if m.KeyLength != 0 {
i = encodeVarintBenchmark(dAtA, i, uint64(m.KeyLength))
i--
dAtA[i] = 0x18
}
if len(m.Actor) > 0 {
i -= len(m.Actor)
copy(dAtA[i:], m.Actor)
i = encodeVarintBenchmark(dAtA, i, uint64(len(m.Actor)))
i--
dAtA[i] = 0x12
}
if m.Seed != 0 {
i = encodeVarintBenchmark(dAtA, i, uint64(m.Seed))
i--
dAtA[i] = 0x8
}
return len(dAtA) - i, nil
}
func encodeVarintBenchmark(dAtA []byte, offset int, v uint64) int {
offset -= sovBenchmark(v)
base := offset
for v >= 1<<7 {
dAtA[offset] = uint8(v&0x7f | 0x80)
v >>= 7
offset++
}
dAtA[offset] = uint8(v)
return base
}
func (m *Op) Size() (n int) {
if m == nil {
return 0
}
var l int
_ = l
if m.Seed != 0 {
n += 1 + sovBenchmark(uint64(m.Seed))
}
l = len(m.Actor)
if l > 0 {
n += 1 + l + sovBenchmark(uint64(l))
}
if m.KeyLength != 0 {
n += 1 + sovBenchmark(uint64(m.KeyLength))
}
if m.ValueLength != 0 {
n += 1 + sovBenchmark(uint64(m.ValueLength))
}
if m.Iterations != 0 {
n += 1 + sovBenchmark(uint64(m.Iterations))
}
if m.Delete {
n += 2
}
if m.Exists {
n += 2
}
return n
}
func sovBenchmark(x uint64) (n int) {
return (math_bits.Len64(x|1) + 6) / 7
}
func sozBenchmark(x uint64) (n int) {
return sovBenchmark(uint64((x << 1) ^ uint64((int64(x) >> 63))))
}
func (m *Op) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowBenchmark
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: Op: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: Op: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field Seed", wireType)
}
m.Seed = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowBenchmark
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.Seed |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
case 2:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Actor", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowBenchmark
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthBenchmark
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthBenchmark
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Actor = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 3:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field KeyLength", wireType)
}
m.KeyLength = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowBenchmark
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.KeyLength |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
case 4:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field ValueLength", wireType)
}
m.ValueLength = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowBenchmark
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.ValueLength |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
case 5:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field Iterations", wireType)
}
m.Iterations = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowBenchmark
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.Iterations |= uint32(b&0x7F) << shift
if b < 0x80 {
break
}
}
case 6:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field Delete", wireType)
}
var v int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowBenchmark
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
v |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
m.Delete = bool(v != 0)
case 7:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field Exists", wireType)
}
var v int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowBenchmark
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
v |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
m.Exists = bool(v != 0)
default:
iNdEx = preIndex
skippy, err := skipBenchmark(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLengthBenchmark
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func skipBenchmark(dAtA []byte) (n int, err error) {
l := len(dAtA)
iNdEx := 0
depth := 0
for iNdEx < l {
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowBenchmark
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
wireType := int(wire & 0x7)
switch wireType {
case 0:
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowBenchmark
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
iNdEx++
if dAtA[iNdEx-1] < 0x80 {
break
}
}
case 1:
iNdEx += 8
case 2:
var length int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowBenchmark
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
length |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
if length < 0 {
return 0, ErrInvalidLengthBenchmark
}
iNdEx += length
case 3:
depth++
case 4:
if depth == 0 {
return 0, ErrUnexpectedEndOfGroupBenchmark
}
depth--
case 5:
iNdEx += 4
default:
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
}
if iNdEx < 0 {
return 0, ErrInvalidLengthBenchmark
}
if depth == 0 {
return iNdEx, nil
}
}
return 0, io.ErrUnexpectedEOF
}
var (
ErrInvalidLengthBenchmark = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowBenchmark = fmt.Errorf("proto: integer overflow")
ErrUnexpectedEndOfGroupBenchmark = fmt.Errorf("proto: unexpected end of group")
)
+186
View File
@@ -0,0 +1,186 @@
package cli
import (
"context"
"errors"
"os"
"os/signal"
"syscall"
"time"
"github.com/spf13/cobra"
modulev1 "cosmossdk.io/api/cosmos/benchmark/module/v1"
"cosmossdk.io/tools/benchmark"
gen "cosmossdk.io/tools/benchmark/generator"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
clienttx "github.com/cosmos/cosmos-sdk/client/tx"
)
func NewTxCmd(params *modulev1.GeneratorParams) *cobra.Command {
txCmd := &cobra.Command{
Use: "benchmark",
Short: "benchmark transaction subcommands",
DisableFlagParsing: true,
SuggestionsMinimumDistance: 2,
RunE: client.ValidateCmd,
}
txCmd.AddCommand(
NewLoadTestCmd(params),
)
return txCmd
}
func NewLoadTestCmd(params *modulev1.GeneratorParams) *cobra.Command {
var (
verbose bool
pause int64
numOps uint64
)
cmd := &cobra.Command{
Use: "load-test",
RunE: func(cmd *cobra.Command, args []string) (runErr error) {
clientCtx, err := client.GetClientTxContext(cmd)
if err != nil {
return err
}
ctx, cancelFn := context.WithCancel(cmd.Context())
go func() {
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
select {
case sig := <-sigCh:
cancelFn()
cmd.Printf("caught %s signal\n", sig.String())
case <-ctx.Done():
cancelFn()
}
}()
var (
successCount int
errCount int
since = time.Now()
last int
)
defer func() {
cmd.Printf("done! success_tx=%d err_tx=%d\n", successCount, errCount)
}()
accNum, accSeq, err := clientCtx.AccountRetriever.GetAccountNumberSequence(clientCtx, clientCtx.FromAddress)
if err != nil {
return err
}
txf, err := clienttx.NewFactoryCLI(clientCtx, cmd.Flags())
if err != nil {
return err
}
txf = txf.WithAccountNumber(accNum).WithChainID(clientCtx.ChainID).WithGas(1_000_000_000)
storeKeys, err := gen.StoreKeys("benchmark", params.Seed, params.BucketCount)
if err != nil {
return err
}
var seed uint64
for _, c := range clientCtx.FromAddress {
// root the generator seed in the account address
seed += uint64(c)
}
g := gen.NewGenerator(gen.Options{
HomeDir: clientCtx.HomeDir,
GeneratorParams: params,
InsertWeight: 0.25,
DeleteWeight: 0.05,
UpdateWeight: 0.50,
GetWeight: 0.20,
},
gen.WithGenesis(),
gen.WithSeed(seed),
)
if err = g.Load(); err != nil {
return err
}
defer func() {
if err = g.Close(); err != nil {
runErr = errors.Join(runErr, err)
}
}()
begin := time.Now()
ops := make([]*benchmark.Op, numOps)
for {
select {
case <-ctx.Done():
return nil
default:
}
if time.Since(since) > 5*time.Second {
cmd.Printf(
"success_tx=%d err_tx=%d seq=%d rate=%.2f/s overall=%.2f/s\n",
successCount, errCount, accSeq,
float64(successCount-last)/time.Since(since).Seconds(),
float64(successCount)/time.Since(begin).Seconds(),
)
since = time.Now()
last = successCount
}
for j := range numOps {
bucket, op, err := g.Next()
if err != nil {
return err
}
op.Actor = storeKeys[bucket]
ops[j] = op
}
msg := &benchmark.MsgLoadTest{
Caller: clientCtx.FromAddress,
Ops: ops,
}
txf = txf.WithSequence(accSeq)
tx, err := txf.BuildUnsignedTx(msg)
if err != nil {
return err
}
err = clienttx.Sign(clientCtx.CmdContext, txf, clientCtx.From, tx, true)
if err != nil {
return err
}
txBytes, err := clientCtx.TxConfig.TxEncoder()(tx.GetTx())
if err != nil {
return err
}
res, err := clientCtx.BroadcastTxAsync(txBytes)
if err != nil {
return err
}
if res.Code != 0 {
if verbose {
err = clientCtx.PrintProto(res)
if err != nil {
return err
}
}
errCount++
} else {
accSeq++
successCount++
}
if pause > 0 {
time.Sleep(time.Duration(pause) * time.Microsecond)
}
}
},
}
flags.AddTxFlagsToCmd(cmd)
cmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "print the response")
cmd.Flags().Uint64Var(&numOps, "ops", 1, "number of operations per transaction")
cmd.Flags().Int64Var(&pause, "pause", 0, "pause between transactions in microseconds")
return cmd
}
+381
View File
@@ -0,0 +1,381 @@
package gen
import (
"encoding"
"encoding/binary"
"errors"
"fmt"
"io"
"iter"
"math/rand/v2"
"os"
"github.com/cespare/xxhash/v2"
module "cosmossdk.io/api/cosmos/benchmark/module/v1"
"cosmossdk.io/tools/benchmark"
)
// Options is the configuration for the generator.
type Options struct {
*module.GeneratorParams
// HomeDir is for reading/writing state
HomeDir string
InsertWeight float64
UpdateWeight float64
GetWeight float64
DeleteWeight float64
}
// State is the state of the generator.
// It can be marshaled and unmarshaled to/from a binary format.
type State struct {
Src interface {
rand.Source
encoding.BinaryMarshaler
encoding.BinaryUnmarshaler
}
Keys [][]Payload
}
// Marshal writes the state to w.
func (s *State) Marshal(w io.Writer) error {
srcBz, err := s.Src.MarshalBinary()
if err != nil {
return err
}
var n int
n, err = w.Write(srcBz)
if err != nil {
return err
}
if n != 20 {
return fmt.Errorf("expected 20 bytes, got %d", n)
}
if err = binary.Write(w, binary.LittleEndian, uint64(len(s.Keys))); err != nil {
return err
}
for _, bucket := range s.Keys {
if err = binary.Write(w, binary.LittleEndian, uint64(len(bucket))); err != nil {
return err
}
for _, key := range bucket {
if err = binary.Write(w, binary.LittleEndian, key); err != nil {
return err
}
}
}
return nil
}
// Unmarshal reads the state from r.
func (s *State) Unmarshal(r io.Reader) error {
srcBz := make([]byte, 20)
if _, err := r.Read(srcBz); err != nil {
return err
}
s.Src = rand.NewPCG(0, 0)
if err := s.Src.UnmarshalBinary(srcBz); err != nil {
return err
}
var n uint64
if err := binary.Read(r, binary.LittleEndian, &n); err != nil {
return err
}
s.Keys = make([][]Payload, n)
for i := uint64(0); i < n; i++ {
var m uint64
if err := binary.Read(r, binary.LittleEndian, &m); err != nil {
return err
}
s.Keys[i] = make([]Payload, m)
for j := uint64(0); j < m; j++ {
if err := binary.Read(r, binary.LittleEndian, &s.Keys[i][j]); err != nil {
return err
}
}
}
return nil
}
// Generator generates operations for a benchmark transaction.
// The generator is stateful, keeping track of which keys have been inserted
// so that meaningful gets and deletes can be generated.
type Generator struct {
Options
rand *rand.Rand
state *State
}
type opt func(*Generator)
// NewGenerator creates a new generator with the given options.
func NewGenerator(opts Options, f ...opt) *Generator {
g := &Generator{
Options: opts,
state: &State{
Src: rand.NewPCG(opts.Seed, opts.Seed>>32),
},
}
g.rand = rand.New(g.state.Src)
for _, fn := range f {
fn(g)
}
return g
}
// WithGenesis sets the generator state to the genesis seed.
// When the generator is created, it will sync to genesis state.
// The benchmark client needs to do this so that it can generate meaningful tx operations.
func WithGenesis() func(*Generator) {
return func(g *Generator) {
// sync state to genesis seed
g.state.Keys = make([][]Payload, g.BucketCount)
if g.GeneratorParams != nil {
for kv := range g.GenesisSet() {
g.state.Keys[kv.StoreKey] = append(g.state.Keys[kv.StoreKey], kv.Key)
}
}
}
}
// WithSeed sets the seed for the generator.
func WithSeed(seed uint64) func(*Generator) {
return func(g *Generator) {
g.state.Src = rand.NewPCG(seed, seed>>32)
g.rand = rand.New(g.state.Src)
}
}
// Load loads the generator state from disk.
func (g *Generator) Load() error {
f := fmt.Sprintf("%s/data/generator_state.bin", g.HomeDir)
r, err := os.Open(f)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return nil
}
return err
}
return g.state.Unmarshal(r)
}
// Payload is a 2-tuple of seed and length.
// A seed is uint64 which is used to generate a byte slice of size length.
type Payload [2]uint64
// Seed returns the seed in the payload.
func (p Payload) Seed() uint64 {
return p[0]
}
// Length returns the length in the payload.
func (p Payload) Length() uint64 {
return p[1]
}
// Bytes returns the byte slice generated from the seed and length.
// The underlying byte slice is deterministically generated using the (very fast) xxhash algorithm.
func (p Payload) Bytes() []byte {
return Bytes(p.Seed(), p.Length())
}
func (p Payload) String() string {
return fmt.Sprintf("(%d, %d)", p.Seed(), p.Length())
}
func NewPayload(seed, length uint64) Payload {
return Payload{seed, length}
}
// KV is a key-value pair with a store key.
type KV struct {
StoreKey uint64
Key Payload
Value Payload
}
func (g *Generator) fetchKey(bucket uint64) (idx uint64, key Payload, err error) {
bucketLen := uint64(len(g.state.Keys[bucket]))
if bucketLen == 0 {
return 0, Payload{}, fmt.Errorf("no keys in bucket %d", bucket)
}
idx = g.rand.Uint64N(bucketLen)
return idx, g.state.Keys[bucket][idx], nil
}
func (g *Generator) deleteKey(bucket, idx uint64) {
g.state.Keys[bucket] = append(g.state.Keys[bucket][:idx], g.state.Keys[bucket][idx+1:]...)
}
func (g *Generator) setKey(bucket uint64, payload Payload) {
g.state.Keys[bucket] = append(g.state.Keys[bucket], payload)
}
// GenesisSet returns a sequence of key-value pairs for the genesis state.
// It is called by the server during InitGenesis to generate and set the initial state.
// The client uses WithGenesis to sync to the genesis state.
func (g *Generator) GenesisSet() iter.Seq[*KV] {
return func(yield func(*KV) bool) {
for range g.GenesisCount {
seed := g.rand.Uint64()
if !yield(&KV{
StoreKey: g.UintN(g.BucketCount),
Key: NewPayload(seed, g.getLength(g.KeyMean, g.KeyStdDev)),
Value: NewPayload(seed, g.getLength(g.ValueMean, g.ValueStdDev)),
}) {
return
}
}
}
}
// Next generates the next benchmark operation.
// The operation is one of insert, update, get, or delete.
// The tx client calls this function to deterministically generate the next operation.
func (g *Generator) Next() (uint64, *benchmark.Op, error) {
if g.InsertWeight+g.UpdateWeight+g.GetWeight+g.DeleteWeight != 1 {
return 0, nil, fmt.Errorf("weights must sum to 1")
}
var (
err error
key Payload
)
x := g.rand.Float64()
bucket := g.UintN(g.BucketCount)
op := &benchmark.Op{
Exists: true,
}
switch {
case x < g.InsertWeight:
// insert
op.Seed = g.rand.Uint64()
op.KeyLength = g.getLength(g.KeyMean, g.KeyStdDev)
op.ValueLength = g.getLength(g.ValueMean, g.ValueStdDev)
op.Exists = false
g.setKey(bucket, NewPayload(op.Seed, op.KeyLength))
case x < g.InsertWeight+g.UpdateWeight:
// update
_, key, err = g.fetchKey(bucket)
if err != nil {
return 0, nil, err
}
op.Seed = key.Seed()
op.KeyLength = key.Length()
op.ValueLength = g.getLength(g.ValueMean, g.ValueStdDev)
case x < g.InsertWeight+g.UpdateWeight+g.GetWeight:
// get
_, key, err = g.fetchKey(bucket)
if err != nil {
return 0, nil, err
}
op.Seed = key.Seed()
op.KeyLength = key.Length()
default:
// delete
var idx uint64
idx, key, err = g.fetchKey(bucket)
if err != nil {
return 0, nil, err
}
op.Delete = true
op.Seed = key.Seed()
op.KeyLength = key.Length()
g.deleteKey(bucket, idx)
}
return bucket, op, nil
}
// NormUint64 returns a random uint64 with a normal distribution.
func (g *Generator) NormUint64(mean, stdDev uint64) uint64 {
return uint64(g.rand.NormFloat64()*float64(stdDev) + float64(mean))
}
func (g *Generator) getLength(mean, stdDev uint64) uint64 {
length := g.NormUint64(mean, stdDev)
if length == 0 {
length = 1
}
return length
}
// UintN returns a random uint64 in the range [0, n).
func (g *Generator) UintN(n uint64) uint64 {
return g.rand.Uint64N(n)
}
func (g *Generator) Close() error {
f := fmt.Sprintf("%s/data/generator_state.bin", g.HomeDir)
w, err := os.Create(f)
if err != nil {
return err
}
return g.state.Marshal(w)
}
func encodeUint64(x uint64) []byte {
var b [8]byte
b[0] = byte(x)
b[1] = byte(x >> 8)
b[2] = byte(x >> 16)
b[3] = byte(x >> 24)
b[4] = byte(x >> 32)
b[5] = byte(x >> 40)
b[6] = byte(x >> 48)
b[7] = byte(x >> 56)
return b[:]
}
const maxStoreKeyGenIterations = 100
// StoreKeys deterministically generates a set of unique store keys from seed.
func StoreKeys(prefix string, seed, count uint64) ([]string, error) {
r := rand.New(rand.NewPCG(seed, seed>>32))
keys := make([]string, count)
seen := make(map[string]struct{})
var i, j uint64
for i < count {
if j > maxStoreKeyGenIterations {
return nil, fmt.Errorf("failed to generate %d unique store keys", count)
}
sk := fmt.Sprintf("%s_%x", prefix, Bytes(r.Uint64(), 8))
if _, ok := seen[sk]; ok {
j++
continue
}
keys[i] = sk
seen[sk] = struct{}{}
i++
j++
}
return keys, nil
}
// Bytes generates a byte slice of length length from seed.
// The byte slice is deterministically generated using the (very fast) xxhash algorithm.
func Bytes(seed, length uint64) []byte {
b := make([]byte, length)
rounds := length / 8
remainder := length % 8
var h uint64
for i := uint64(0); i < rounds; i++ {
h = xxhash.Sum64(encodeUint64(seed + i))
for j := uint64(0); j < 8; j++ {
b[i*8+j] = byte(h >> (8 * j))
}
}
if remainder > 0 {
h = xxhash.Sum64(encodeUint64(seed + rounds))
for j := uint64(0); j < remainder; j++ {
b[rounds*8+j] = byte(h >> (8 * j))
}
}
return b
}
+138
View File
@@ -0,0 +1,138 @@
package gen
import (
"bytes"
"testing"
"github.com/cespare/xxhash/v2"
"github.com/stretchr/testify/require"
benchmarkmodulev1 "cosmossdk.io/api/cosmos/benchmark/module/v1"
)
func Test_Genesis(t *testing.T) {
params := &benchmarkmodulev1.GeneratorParams{
Seed: 34,
BucketCount: 10,
GenesisCount: 2_000_000,
KeyMean: 64,
KeyStdDev: 8,
ValueMean: 1024,
ValueStdDev: 256,
}
g := NewGenerator(Options{GeneratorParams: params})
db := make(map[uint64]map[uint64]bool)
for kv := range g.GenesisSet() {
if _, ok := db[kv.StoreKey]; !ok {
db[kv.StoreKey] = make(map[uint64]bool)
}
db[kv.StoreKey][kv.Key[0]] = true
}
g = NewGenerator(Options{
GeneratorParams: params,
InsertWeight: 0.25,
DeleteWeight: 0.05,
UpdateWeight: 0.50,
GetWeight: 0.20,
}, WithGenesis())
for range 100_000 {
sk, op, err := g.Next()
require.NoError(t, err)
switch {
case op.Delete:
require.True(t, db[sk][op.Seed])
delete(db[sk], op.Seed)
case op.ValueLength > 0:
if op.Exists {
// update
require.True(t, db[sk][op.Seed])
} else {
// insert
require.False(t, db[sk][op.Seed])
}
db[sk][op.Seed] = true
case op.ValueLength == 0:
// get
require.True(t, db[sk][op.Seed])
default:
t.Fatalf("unexpected op: %v", op)
}
}
// Test state Marshal/Unmarshal
var buf bytes.Buffer
require.NoError(t, g.state.Marshal(&buf))
s := &State{}
require.NoError(t, s.Unmarshal(bytes.NewReader(buf.Bytes())))
require.Equal(t, len(g.state.Keys), len(s.Keys))
for i := range g.state.Keys {
require.Equal(t, len(g.state.Keys[i]), len(s.Keys[i]))
for j := range g.state.Keys[i] {
require.Equal(t, g.state.Keys[i][j], s.Keys[i][j])
}
}
}
func Test_Genesis_BytesKey(t *testing.T) {
params := &benchmarkmodulev1.GeneratorParams{
Seed: 34,
BucketCount: 10,
GenesisCount: 2_000_000,
KeyMean: 64,
KeyStdDev: 8,
ValueMean: 1024,
ValueStdDev: 256,
}
g := NewGenerator(Options{GeneratorParams: params})
db := make(map[uint64]map[uint64]bool)
for kv := range g.GenesisSet() {
if _, ok := db[kv.StoreKey]; !ok {
db[kv.StoreKey] = make(map[uint64]bool)
}
key := xxhash.Sum64(Bytes(kv.Key.Seed(), kv.Key.Length()))
db[kv.StoreKey][key] = true
}
g = NewGenerator(Options{
GeneratorParams: params,
InsertWeight: 0.25,
DeleteWeight: 0.05,
UpdateWeight: 0.50,
GetWeight: 0.20,
}, WithGenesis())
for range 1_000_000 {
sk, op, err := g.Next()
require.NoError(t, err)
key := xxhash.Sum64(Bytes(op.Seed, op.KeyLength))
switch {
case op.Delete:
require.True(t, db[sk][key])
delete(db[sk], key)
case op.ValueLength > 0:
if op.Exists {
// update
require.True(t, db[sk][key])
} else {
// insert
require.False(t, db[sk][key])
}
db[sk][key] = true
case op.ValueLength == 0:
// get
require.True(t, db[sk][key])
default:
t.Fatalf("unexpected op: %v", op)
}
}
}
func Test_Bytes_Deterministic(t *testing.T) {
seed := uint64(12345)
length := uint64(53)
expected := Bytes(seed, length)
for i := 0; i < 100; i++ {
result := Bytes(seed, length)
require.Equal(t, expected, result, "Bytes() should be deterministic")
}
}
+160
View File
@@ -0,0 +1,160 @@
module cosmossdk.io/tools/benchmark
go 1.23.5
require (
cosmossdk.io/api v0.8.2
cosmossdk.io/core v0.11.0
cosmossdk.io/depinject v1.1.0
cosmossdk.io/log v1.5.0
github.com/cespare/xxhash/v2 v2.3.0
github.com/cosmos/cosmos-sdk v0.53.0
github.com/cosmos/gogoproto v1.7.0
github.com/grpc-ecosystem/grpc-gateway v1.16.0
github.com/spf13/cobra v1.9.1
github.com/stretchr/testify v1.10.0
google.golang.org/grpc v1.71.0
)
require (
cosmossdk.io/collections v0.4.0 // indirect
cosmossdk.io/errors v1.0.1 // indirect
cosmossdk.io/math v1.5.0 // indirect
cosmossdk.io/store v1.1.1 // indirect
cosmossdk.io/x/tx v0.13.7 // indirect
filippo.io/edwards25519 v1.1.0 // indirect
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
github.com/99designs/keyring v1.2.2 // indirect
github.com/DataDog/datadog-go v3.2.0+incompatible // indirect
github.com/DataDog/zstd v1.5.6 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bgentry/speakeasy v0.2.0 // indirect
github.com/bytedance/sonic v1.13.1 // indirect
github.com/bytedance/sonic/loader v0.2.4 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cloudwego/base64x v0.1.5 // indirect
github.com/cockroachdb/apd/v3 v3.2.1 // indirect
github.com/cockroachdb/errors v1.11.3 // indirect
github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce // indirect
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect
github.com/cockroachdb/pebble v1.1.2 // indirect
github.com/cockroachdb/redact v1.1.5 // indirect
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect
github.com/cometbft/cometbft v0.38.17 // indirect
github.com/cometbft/cometbft-db v0.14.1 // indirect
github.com/cosmos/btcutil v1.0.5 // indirect
github.com/cosmos/cosmos-db v1.1.1 // indirect
github.com/cosmos/cosmos-proto v1.0.0-beta.5 // indirect
github.com/cosmos/go-bip39 v1.0.0 // indirect
github.com/cosmos/gogogateway v1.2.0 // indirect
github.com/cosmos/iavl v1.2.2 // indirect
github.com/cosmos/ics23/go v0.11.0 // indirect
github.com/cosmos/ledger-cosmos-go v0.14.0 // indirect
github.com/danieljoos/wincred v1.1.2 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect
github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect
github.com/dgraph-io/badger/v4 v4.2.0 // indirect
github.com/dgraph-io/ristretto v0.1.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/dvsekhvalnov/jose2go v1.6.0 // indirect
github.com/emicklei/dot v1.6.2 // indirect
github.com/fatih/color v1.15.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.8.0 // indirect
github.com/getsentry/sentry-go v0.27.0 // indirect
github.com/go-kit/kit v0.13.0 // indirect
github.com/go-kit/log v0.2.1 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
github.com/gogo/googleapis v1.4.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/glog v1.2.4 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/google/flatbuffers v1.12.1 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/gorilla/handlers v1.5.2 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
github.com/hashicorp/go-hclog v1.5.0 // indirect
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
github.com/hashicorp/go-metrics v0.5.4 // indirect
github.com/hashicorp/go-plugin v1.5.2 // indirect
github.com/hashicorp/golang-lru v1.0.2 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect
github.com/hdevalence/ed25519consensus v0.2.0 // indirect
github.com/huandu/skiplist v1.2.1 // indirect
github.com/iancoleman/strcase v0.3.0 // indirect
github.com/improbable-eng/grpc-web v0.15.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jmhodges/levigo v1.0.0 // indirect
github.com/klauspost/compress v1.17.11 // indirect
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/linxGnu/grocksdb v1.8.14 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
github.com/mtibben/percent v0.2.1 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a // indirect
github.com/oklog/run v1.1.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.21.1 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.62.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/rs/cors v1.11.1 // indirect
github.com/rs/zerolog v1.33.0 // indirect
github.com/sagikazarmark/locafero v0.7.0 // indirect
github.com/sasha-s/go-deadlock v0.3.5 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.12.0 // indirect
github.com/spf13/cast v1.7.1 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/spf13/viper v1.20.0 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect
github.com/tendermint/go-amino v0.16.0 // indirect
github.com/tidwall/btree v1.7.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/zondax/hid v0.9.2 // indirect
github.com/zondax/ledger-go v0.14.3 // indirect
go.etcd.io/bbolt v1.4.0-alpha.0.0.20240404170359-43604f3112c5 // indirect
go.opencensus.io v0.24.0 // indirect
go.uber.org/multierr v1.10.0 // indirect
golang.org/x/arch v0.15.0 // indirect
golang.org/x/crypto v0.36.0 // indirect
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
golang.org/x/net v0.35.0 // indirect
golang.org/x/sync v0.12.0 // indirect
golang.org/x/sys v0.31.0 // indirect
golang.org/x/term v0.30.0 // indirect
golang.org/x/text v0.23.0 // indirect
google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect
google.golang.org/protobuf v1.36.5 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gotest.tools/v3 v3.5.2 // indirect
nhooyr.io/websocket v1.8.6 // indirect
pgregory.net/rapid v1.2.0 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
)
replace (
cosmossdk.io/api => ../../api
github.com/cosmos/cosmos-sdk => ../../
)
File diff suppressed because it is too large Load Diff
+62
View File
@@ -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
}
+119
View File
@@ -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)
}
+105
View File
@@ -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() {}
+670
View File
@@ -0,0 +1,670 @@
// Code generated by protoc-gen-gogo. DO NOT EDIT.
// source: cosmos/benchmark/v1/tx.proto
package benchmark
import (
context "context"
fmt "fmt"
_ "github.com/cosmos/cosmos-sdk/types/msgservice"
_ "github.com/cosmos/cosmos-sdk/types/tx/amino"
grpc1 "github.com/cosmos/gogoproto/grpc"
proto "github.com/cosmos/gogoproto/proto"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
io "io"
math "math"
math_bits "math/bits"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
// MsgLoadTestOps defines a message containing a sequence of load test operations.
type MsgLoadTest struct {
Caller []byte `protobuf:"bytes,1,opt,name=caller,proto3" json:"caller,omitempty"`
Ops []*Op `protobuf:"bytes,2,rep,name=ops,proto3" json:"ops,omitempty"`
}
func (m *MsgLoadTest) Reset() { *m = MsgLoadTest{} }
func (m *MsgLoadTest) String() string { return proto.CompactTextString(m) }
func (*MsgLoadTest) ProtoMessage() {}
func (*MsgLoadTest) Descriptor() ([]byte, []int) {
return fileDescriptor_481e3d7f7138b75b, []int{0}
}
func (m *MsgLoadTest) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *MsgLoadTest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
if deterministic {
return xxx_messageInfo_MsgLoadTest.Marshal(b, m, deterministic)
} else {
b = b[:cap(b)]
n, err := m.MarshalToSizedBuffer(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
}
func (m *MsgLoadTest) XXX_Merge(src proto.Message) {
xxx_messageInfo_MsgLoadTest.Merge(m, src)
}
func (m *MsgLoadTest) XXX_Size() int {
return m.Size()
}
func (m *MsgLoadTest) XXX_DiscardUnknown() {
xxx_messageInfo_MsgLoadTest.DiscardUnknown(m)
}
var xxx_messageInfo_MsgLoadTest proto.InternalMessageInfo
func (m *MsgLoadTest) GetCaller() []byte {
if m != nil {
return m.Caller
}
return nil
}
func (m *MsgLoadTest) GetOps() []*Op {
if m != nil {
return m.Ops
}
return nil
}
// MsgLoadTestResponse defines a message containing the results of a load test operation.
type MsgLoadTestResponse struct {
TotalTime uint64 `protobuf:"varint,1,opt,name=total_time,json=totalTime,proto3" json:"total_time,omitempty"`
TotalErrors uint64 `protobuf:"varint,2,opt,name=total_errors,json=totalErrors,proto3" json:"total_errors,omitempty"`
}
func (m *MsgLoadTestResponse) Reset() { *m = MsgLoadTestResponse{} }
func (m *MsgLoadTestResponse) String() string { return proto.CompactTextString(m) }
func (*MsgLoadTestResponse) ProtoMessage() {}
func (*MsgLoadTestResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_481e3d7f7138b75b, []int{1}
}
func (m *MsgLoadTestResponse) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *MsgLoadTestResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
if deterministic {
return xxx_messageInfo_MsgLoadTestResponse.Marshal(b, m, deterministic)
} else {
b = b[:cap(b)]
n, err := m.MarshalToSizedBuffer(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
}
func (m *MsgLoadTestResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_MsgLoadTestResponse.Merge(m, src)
}
func (m *MsgLoadTestResponse) XXX_Size() int {
return m.Size()
}
func (m *MsgLoadTestResponse) XXX_DiscardUnknown() {
xxx_messageInfo_MsgLoadTestResponse.DiscardUnknown(m)
}
var xxx_messageInfo_MsgLoadTestResponse proto.InternalMessageInfo
func (m *MsgLoadTestResponse) GetTotalTime() uint64 {
if m != nil {
return m.TotalTime
}
return 0
}
func (m *MsgLoadTestResponse) GetTotalErrors() uint64 {
if m != nil {
return m.TotalErrors
}
return 0
}
func init() {
proto.RegisterType((*MsgLoadTest)(nil), "cosmos.benchmark.v1.MsgLoadTest")
proto.RegisterType((*MsgLoadTestResponse)(nil), "cosmos.benchmark.v1.MsgLoadTestResponse")
}
func init() { proto.RegisterFile("cosmos/benchmark/v1/tx.proto", fileDescriptor_481e3d7f7138b75b) }
var fileDescriptor_481e3d7f7138b75b = []byte{
// 322 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x49, 0xce, 0x2f, 0xce,
0xcd, 0x2f, 0xd6, 0x4f, 0x4a, 0xcd, 0x4b, 0xce, 0xc8, 0x4d, 0x2c, 0xca, 0xd6, 0x2f, 0x33, 0xd4,
0x2f, 0xa9, 0xd0, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x86, 0xc8, 0xea, 0xc1, 0x65, 0xf5,
0xca, 0x0c, 0xa5, 0x94, 0xb1, 0x69, 0x41, 0xa8, 0x00, 0xeb, 0x94, 0x12, 0x87, 0x2a, 0xca, 0x2d,
0x4e, 0x07, 0x49, 0xe7, 0x16, 0xa7, 0x43, 0x25, 0x04, 0x13, 0x73, 0x33, 0xf3, 0xf2, 0xf5, 0xc1,
0x24, 0x44, 0x48, 0xa9, 0x9b, 0x91, 0x8b, 0xdb, 0xb7, 0x38, 0xdd, 0x27, 0x3f, 0x31, 0x25, 0x24,
0xb5, 0xb8, 0x44, 0x48, 0x8c, 0x8b, 0x2d, 0x39, 0x31, 0x27, 0x27, 0xb5, 0x48, 0x82, 0x51, 0x81,
0x51, 0x83, 0x27, 0x08, 0xca, 0x13, 0xd2, 0xe4, 0x62, 0xce, 0x2f, 0x28, 0x96, 0x60, 0x52, 0x60,
0xd6, 0xe0, 0x36, 0x12, 0xd7, 0xc3, 0xe2, 0x36, 0x3d, 0xff, 0x82, 0x20, 0x90, 0x1a, 0x2b, 0xcb,
0xa6, 0xe7, 0x1b, 0xb4, 0xa0, 0xfa, 0xba, 0x9e, 0x6f, 0xd0, 0xd2, 0x84, 0x28, 0xd6, 0x2d, 0x4e,
0xc9, 0xd6, 0x2f, 0xc9, 0xcf, 0xcf, 0x41, 0x73, 0x3d, 0x92, 0xed, 0x4a, 0xe1, 0x5c, 0xc2, 0x48,
0xdc, 0xa0, 0xd4, 0xe2, 0x82, 0xfc, 0xbc, 0xe2, 0x54, 0x21, 0x59, 0x2e, 0xae, 0x92, 0xfc, 0x92,
0xc4, 0x9c, 0xf8, 0x92, 0xcc, 0xdc, 0x54, 0xb0, 0xc3, 0x58, 0x82, 0x38, 0xc1, 0x22, 0x21, 0x99,
0xb9, 0xa9, 0x42, 0x8a, 0x5c, 0x3c, 0x10, 0xe9, 0xd4, 0xa2, 0xa2, 0xfc, 0x22, 0x90, 0x23, 0x41,
0x0a, 0xb8, 0xc1, 0x62, 0xae, 0x60, 0x21, 0xa3, 0x14, 0x2e, 0x66, 0xdf, 0xe2, 0x74, 0xa1, 0x30,
0x2e, 0x0e, 0xb8, 0x4f, 0x15, 0xb0, 0x7a, 0x02, 0xc9, 0x7a, 0x29, 0x0d, 0x42, 0x2a, 0x60, 0x0e,
0x94, 0x62, 0x6d, 0x78, 0xbe, 0x41, 0x8b, 0xd1, 0xc9, 0xec, 0xc4, 0x23, 0x39, 0xc6, 0x0b, 0x8f,
0xe4, 0x18, 0x1f, 0x3c, 0x92, 0x63, 0x9c, 0xf0, 0x58, 0x8e, 0xe1, 0xc2, 0x63, 0x39, 0x86, 0x1b,
0x8f, 0xe5, 0x18, 0xa2, 0xa0, 0x51, 0x5d, 0x9c, 0x92, 0xad, 0x97, 0x99, 0x8f, 0x1e, 0x0a, 0x49,
0x6c, 0xe0, 0xb8, 0x30, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0x15, 0x3a, 0xfe, 0x19, 0x11, 0x02,
0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4
// MsgClient is the client API for Msg service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type MsgClient interface {
// LoadTest defines a method for executing a sequence of load test operations.
LoadTest(ctx context.Context, in *MsgLoadTest, opts ...grpc.CallOption) (*MsgLoadTestResponse, error)
}
type msgClient struct {
cc grpc1.ClientConn
}
func NewMsgClient(cc grpc1.ClientConn) MsgClient {
return &msgClient{cc}
}
func (c *msgClient) LoadTest(ctx context.Context, in *MsgLoadTest, opts ...grpc.CallOption) (*MsgLoadTestResponse, error) {
out := new(MsgLoadTestResponse)
err := c.cc.Invoke(ctx, "/cosmos.benchmark.v1.Msg/LoadTest", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// MsgServer is the server API for Msg service.
type MsgServer interface {
// LoadTest defines a method for executing a sequence of load test operations.
LoadTest(context.Context, *MsgLoadTest) (*MsgLoadTestResponse, error)
}
// UnimplementedMsgServer can be embedded to have forward compatible implementations.
type UnimplementedMsgServer struct {
}
func (*UnimplementedMsgServer) LoadTest(ctx context.Context, req *MsgLoadTest) (*MsgLoadTestResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method LoadTest not implemented")
}
func RegisterMsgServer(s grpc1.Server, srv MsgServer) {
s.RegisterService(&_Msg_serviceDesc, srv)
}
func _Msg_LoadTest_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(MsgLoadTest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(MsgServer).LoadTest(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/cosmos.benchmark.v1.Msg/LoadTest",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(MsgServer).LoadTest(ctx, req.(*MsgLoadTest))
}
return interceptor(ctx, in, info, handler)
}
var Msg_serviceDesc = _Msg_serviceDesc
var _Msg_serviceDesc = grpc.ServiceDesc{
ServiceName: "cosmos.benchmark.v1.Msg",
HandlerType: (*MsgServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "LoadTest",
Handler: _Msg_LoadTest_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "cosmos/benchmark/v1/tx.proto",
}
func (m *MsgLoadTest) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBuffer(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *MsgLoadTest) MarshalTo(dAtA []byte) (int, error) {
size := m.Size()
return m.MarshalToSizedBuffer(dAtA[:size])
}
func (m *MsgLoadTest) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i := len(dAtA)
_ = i
var l int
_ = l
if len(m.Ops) > 0 {
for iNdEx := len(m.Ops) - 1; iNdEx >= 0; iNdEx-- {
{
size, err := m.Ops[iNdEx].MarshalToSizedBuffer(dAtA[:i])
if err != nil {
return 0, err
}
i -= size
i = encodeVarintTx(dAtA, i, uint64(size))
}
i--
dAtA[i] = 0x12
}
}
if len(m.Caller) > 0 {
i -= len(m.Caller)
copy(dAtA[i:], m.Caller)
i = encodeVarintTx(dAtA, i, uint64(len(m.Caller)))
i--
dAtA[i] = 0xa
}
return len(dAtA) - i, nil
}
func (m *MsgLoadTestResponse) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBuffer(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *MsgLoadTestResponse) MarshalTo(dAtA []byte) (int, error) {
size := m.Size()
return m.MarshalToSizedBuffer(dAtA[:size])
}
func (m *MsgLoadTestResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i := len(dAtA)
_ = i
var l int
_ = l
if m.TotalErrors != 0 {
i = encodeVarintTx(dAtA, i, uint64(m.TotalErrors))
i--
dAtA[i] = 0x10
}
if m.TotalTime != 0 {
i = encodeVarintTx(dAtA, i, uint64(m.TotalTime))
i--
dAtA[i] = 0x8
}
return len(dAtA) - i, nil
}
func encodeVarintTx(dAtA []byte, offset int, v uint64) int {
offset -= sovTx(v)
base := offset
for v >= 1<<7 {
dAtA[offset] = uint8(v&0x7f | 0x80)
v >>= 7
offset++
}
dAtA[offset] = uint8(v)
return base
}
func (m *MsgLoadTest) Size() (n int) {
if m == nil {
return 0
}
var l int
_ = l
l = len(m.Caller)
if l > 0 {
n += 1 + l + sovTx(uint64(l))
}
if len(m.Ops) > 0 {
for _, e := range m.Ops {
l = e.Size()
n += 1 + l + sovTx(uint64(l))
}
}
return n
}
func (m *MsgLoadTestResponse) Size() (n int) {
if m == nil {
return 0
}
var l int
_ = l
if m.TotalTime != 0 {
n += 1 + sovTx(uint64(m.TotalTime))
}
if m.TotalErrors != 0 {
n += 1 + sovTx(uint64(m.TotalErrors))
}
return n
}
func sovTx(x uint64) (n int) {
return (math_bits.Len64(x|1) + 6) / 7
}
func sozTx(x uint64) (n int) {
return sovTx(uint64((x << 1) ^ uint64((int64(x) >> 63))))
}
func (m *MsgLoadTest) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowTx
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: MsgLoadTest: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: MsgLoadTest: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Caller", wireType)
}
var byteLen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowTx
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
byteLen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if byteLen < 0 {
return ErrInvalidLengthTx
}
postIndex := iNdEx + byteLen
if postIndex < 0 {
return ErrInvalidLengthTx
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Caller = append(m.Caller[:0], dAtA[iNdEx:postIndex]...)
if m.Caller == nil {
m.Caller = []byte{}
}
iNdEx = postIndex
case 2:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Ops", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowTx
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
msglen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
return ErrInvalidLengthTx
}
postIndex := iNdEx + msglen
if postIndex < 0 {
return ErrInvalidLengthTx
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Ops = append(m.Ops, &Op{})
if err := m.Ops[len(m.Ops)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipTx(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLengthTx
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *MsgLoadTestResponse) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowTx
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: MsgLoadTestResponse: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: MsgLoadTestResponse: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field TotalTime", wireType)
}
m.TotalTime = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowTx
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.TotalTime |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
case 2:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field TotalErrors", wireType)
}
m.TotalErrors = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowTx
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.TotalErrors |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
default:
iNdEx = preIndex
skippy, err := skipTx(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLengthTx
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func skipTx(dAtA []byte) (n int, err error) {
l := len(dAtA)
iNdEx := 0
depth := 0
for iNdEx < l {
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowTx
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
wireType := int(wire & 0x7)
switch wireType {
case 0:
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowTx
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
iNdEx++
if dAtA[iNdEx-1] < 0x80 {
break
}
}
case 1:
iNdEx += 8
case 2:
var length int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowTx
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
length |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
if length < 0 {
return 0, ErrInvalidLengthTx
}
iNdEx += length
case 3:
depth++
case 4:
if depth == 0 {
return 0, ErrUnexpectedEndOfGroupTx
}
depth--
case 5:
iNdEx += 4
default:
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
}
if iNdEx < 0 {
return 0, ErrInvalidLengthTx
}
if depth == 0 {
return iNdEx, nil
}
}
return 0, io.ErrUnexpectedEOF
}
var (
ErrInvalidLengthTx = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowTx = fmt.Errorf("proto: integer overflow")
ErrUnexpectedEndOfGroupTx = fmt.Errorf("proto: unexpected end of group")
)