Compare commits

...

4 Commits

Author SHA1 Message Date
William Banfield
122c6e99b9 prepend kv store prefix to tx 2022-08-31 10:56:58 -04:00
William Banfield
92492dafaa cr fixes 2022-08-31 10:52:31 -04:00
William Banfield
17278b9dc6 add payload calculation 2022-08-31 10:50:07 -04:00
William Banfield
5df843a85b initial commit of loadtime 2022-08-30 18:13:50 -04:00
11 changed files with 785 additions and 3 deletions

1
.gitignore vendored
View File

@@ -38,6 +38,7 @@ terraform.tfstate
terraform.tfstate.backup
terraform.tfstate.d
test/app/grpc_client
test/loadtime/build
test/e2e/build
test/e2e/networks/*/
test/logs

8
go.mod
View File

@@ -27,9 +27,9 @@ require (
github.com/ory/dockertest v3.3.5+incompatible
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.13.0
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475
github.com/rs/cors v1.8.2
github.com/sasha-s/go-deadlock v0.2.1-0.20190427202633-1595213edefa
github.com/sasha-s/go-deadlock v0.3.1
github.com/snikch/goodman v0.0.0-20171125024755-10e37e294daa
github.com/spf13/cobra v1.5.0
github.com/spf13/viper v1.12.0
@@ -147,6 +147,7 @@ require (
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hexops/gotextdiff v1.0.3 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/informalsystems/tm-load-test v1.0.0 // indirect
github.com/jdxcode/netrc v0.0.0-20210204082910-926c7f70242a // indirect
github.com/jgautheron/goconst v1.5.1 // indirect
github.com/jhump/protocompile v0.0.0-20220216033700-d705409f108f // indirect
@@ -175,7 +176,7 @@ require (
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
github.com/mbilski/exhaustivestruct v1.2.0 // indirect
github.com/mgechev/revive v1.2.1 // indirect
github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643 // indirect
github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/moby/buildkit v0.10.3 // indirect
@@ -211,6 +212,7 @@ require (
github.com/ryanrolds/sqlclosecheck v0.3.0 // indirect
github.com/sanposhiho/wastedassign/v2 v2.0.6 // indirect
github.com/sashamelentyev/usestdlibvars v1.8.0 // indirect
github.com/satori/go.uuid v1.2.0 // indirect
github.com/securego/gosec/v2 v2.12.0 // indirect
github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect
github.com/sirupsen/logrus v1.9.0 // indirect

366
go.sum

File diff suppressed because it is too large Load Diff

32
test/loadtime/Makefile Normal file
View File

@@ -0,0 +1,32 @@
GOMOD="github.com/tendermint/tendermint/test/loadtime"
OUTPUT?=build/loadtime
build:
go build $(BUILD_FLAGS) -tags '$(BUILD_TAGS)' -o $(OUTPUT) .
.PHONY: build
check-proto-gen-deps:
ifeq (,$(shell which protoc))
$(error "protoc is required for Protobuf generation. See instructions for your platform on how to install it.")
endif
ifeq (,$(shell which protoc-gen-go))
$(error "protoc-gen-go is required for Protobuf generation. See instructions for your platform on how to install it.")
endif
.PHONY: check-proto-gen-deps
check-proto-format-deps:
ifeq (,$(shell which clang-format))
$(error "clang-format is required for Protobuf formatting. See instructions for your platform on how to install it.")
endif
.PHONY: check-proto-format-deps
proto-format: check-proto-format-deps
@echo "Formatting Protobuf files"
@find . -name '*.proto' -exec clang-format -i {} \;
.PHONY: proto-format
proto-gen: check-proto-gen-deps
@echo "Generating Protobuf files"
@find . -name '*.proto' -exec protoc \
--go_out=paths=source_relative:. {} \;
.PHONY: proto-gen

32
test/loadtime/README.md Normal file
View File

@@ -0,0 +1,32 @@
# loadtime
This directory contains `loadtime`, a tool for generating transaction load against Tendermint.
`loadtime` generates transactions that contain the timestamp corresponding to when they were generated
as well as additional metadata to track the variables used when generating the load.
## Building loadtime
The `Makefile` contains a target for building the `loadtime` tool.
The following command will build the tool and place the resulting binary in `./build/loadtime`.
```bash
make build
```
## Use
`loadtime` leverages the [tm-load-test](https://github.com/informalsystems/tm-load-test)
framework. As a result, all flags and options specified on the `tm-load-test` apply to
`loadtime`.
Below is a basic invocation for generating load against a Tendermint websocket running
on `localhost:25567`
```bash
loadtime \
-c 1 -T 10 -r 1000 -s 1024 \
--broadcast-tx-method sync \
--endpoints ws://localhost:26657/websocket
```

11
test/loadtime/basic.sh Executable file
View File

@@ -0,0 +1,11 @@
#!/bin/sh
set -euo pipefail
# A basic invocation of the loadtime tool.
./build/loadtime \
-c 1 -T 10 -r 1000 -s 1024 \
--broadcast-tx-method sync \
--endpoints ws://localhost:26657/websocket

89
test/loadtime/main.go Normal file
View File

@@ -0,0 +1,89 @@
package main
import (
"crypto/rand"
"fmt"
"github.com/informalsystems/tm-load-test/pkg/loadtest"
"github.com/tendermint/tendermint/test/loadtime/payload"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/timestamppb"
)
// Ensure all of the interfaces are correctly satisfied.
var (
_ loadtest.ClientFactory = (*ClientFactory)(nil)
_ loadtest.Client = (*TxGenerator)(nil)
)
// ClientFactory implements the loadtest.ClientFactory interface.
type ClientFactory struct{}
// TxGenerator is responsible for generating transactions.
// TxGenerator holds the set of information that will be used to generate
// each transaction.
type TxGenerator struct {
conns uint64
rate uint64
size uint64
payloadSizeBytes uint64
}
func main() {
if err := loadtest.RegisterClientFactory("loadtime-client", &ClientFactory{}); err != nil {
panic(err)
}
loadtest.Run(&loadtest.CLIConfig{
AppName: "loadtime",
AppShortDesc: "Generate timestamped transaction load.",
AppLongDesc: "loadtime generates transaction load for the purpose of measuring the end-to-end latency of a transaction from submission to execution in a Tendermint network.",
DefaultClientFactory: "loadtime-client",
})
}
func (f *ClientFactory) ValidateConfig(cfg loadtest.Config) error {
psb, err := payload.CalculateUnpaddedSizeBytes()
if err != nil {
return err
}
if psb > cfg.Size {
return fmt.Errorf("payload size exceeds configured size")
}
return nil
}
func (f *ClientFactory) NewClient(cfg loadtest.Config) (loadtest.Client, error) {
psb, err := payload.CalculateUnpaddedSizeBytes()
if err != nil {
return nil, err
}
return &TxGenerator{
conns: uint64(cfg.Connections),
rate: uint64(cfg.Rate),
size: uint64(cfg.Size),
payloadSizeBytes: uint64(psb),
}, nil
}
func (c *TxGenerator) GenerateTx() ([]byte, error) {
p := &payload.Payload{
Time: timestamppb.Now(),
Connections: c.conns,
Rate: c.rate,
Size: c.size,
Padding: make([]byte, c.size-c.payloadSizeBytes),
}
_, err := rand.Read(p.Padding)
if err != nil {
return nil, err
}
b, err := proto.Marshal(p)
if err != nil {
return nil, err
}
// prepend a single key so that the kv store only ever stores a single
// transaction instead of storing all tx and ballooning in size.
return append([]byte("a="), b...), nil
}

View File

@@ -0,0 +1,190 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.27.1
// protoc v3.20.1
// source: payload/payload.proto
package payload
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type Payload struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Connections uint64 `protobuf:"varint,1,opt,name=connections,proto3" json:"connections,omitempty"`
Rate uint64 `protobuf:"varint,2,opt,name=rate,proto3" json:"rate,omitempty"`
Size uint64 `protobuf:"varint,3,opt,name=size,proto3" json:"size,omitempty"`
Time *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=time,proto3" json:"time,omitempty"`
Padding []byte `protobuf:"bytes,5,opt,name=padding,proto3" json:"padding,omitempty"`
}
func (x *Payload) Reset() {
*x = Payload{}
if protoimpl.UnsafeEnabled {
mi := &file_payload_payload_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Payload) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Payload) ProtoMessage() {}
func (x *Payload) ProtoReflect() protoreflect.Message {
mi := &file_payload_payload_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Payload.ProtoReflect.Descriptor instead.
func (*Payload) Descriptor() ([]byte, []int) {
return file_payload_payload_proto_rawDescGZIP(), []int{0}
}
func (x *Payload) GetConnections() uint64 {
if x != nil {
return x.Connections
}
return 0
}
func (x *Payload) GetRate() uint64 {
if x != nil {
return x.Rate
}
return 0
}
func (x *Payload) GetSize() uint64 {
if x != nil {
return x.Size
}
return 0
}
func (x *Payload) GetTime() *timestamppb.Timestamp {
if x != nil {
return x.Time
}
return nil
}
func (x *Payload) GetPadding() []byte {
if x != nil {
return x.Padding
}
return nil
}
var File_payload_payload_proto protoreflect.FileDescriptor
var file_payload_payload_proto_rawDesc = []byte{
0x0a, 0x15, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x2f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61,
0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x10, 0x6c, 0x6f, 0x61, 0x64, 0x74, 0x69, 0x6d,
0x65, 0x2e, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73,
0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x9d, 0x01, 0x0a, 0x07, 0x50,
0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x20, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63,
0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x63, 0x6f, 0x6e,
0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x61, 0x74, 0x65,
0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x72, 0x61, 0x74, 0x65, 0x12, 0x12, 0x0a, 0x04,
0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65,
0x12, 0x2e, 0x0a, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a,
0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x04, 0x74, 0x69, 0x6d, 0x65,
0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x05, 0x20, 0x01, 0x28,
0x0c, 0x52, 0x07, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x42, 0x38, 0x5a, 0x36, 0x67, 0x69,
0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x6d,
0x69, 0x6e, 0x74, 0x2f, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x74, 0x2f, 0x74,
0x65, 0x73, 0x74, 0x2f, 0x6c, 0x6f, 0x61, 0x64, 0x74, 0x69, 0x6d, 0x65, 0x2f, 0x70, 0x61, 0x79,
0x6c, 0x6f, 0x61, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_payload_payload_proto_rawDescOnce sync.Once
file_payload_payload_proto_rawDescData = file_payload_payload_proto_rawDesc
)
func file_payload_payload_proto_rawDescGZIP() []byte {
file_payload_payload_proto_rawDescOnce.Do(func() {
file_payload_payload_proto_rawDescData = protoimpl.X.CompressGZIP(file_payload_payload_proto_rawDescData)
})
return file_payload_payload_proto_rawDescData
}
var file_payload_payload_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_payload_payload_proto_goTypes = []interface{}{
(*Payload)(nil), // 0: loadtime.payload.Payload
(*timestamppb.Timestamp)(nil), // 1: google.protobuf.Timestamp
}
var file_payload_payload_proto_depIdxs = []int32{
1, // 0: loadtime.payload.Payload.time:type_name -> google.protobuf.Timestamp
1, // [1:1] is the sub-list for method output_type
1, // [1:1] is the sub-list for method input_type
1, // [1:1] is the sub-list for extension type_name
1, // [1:1] is the sub-list for extension extendee
0, // [0:1] is the sub-list for field type_name
}
func init() { file_payload_payload_proto_init() }
func file_payload_payload_proto_init() {
if File_payload_payload_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_payload_payload_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Payload); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_payload_payload_proto_rawDesc,
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_payload_payload_proto_goTypes,
DependencyIndexes: file_payload_payload_proto_depIdxs,
MessageInfos: file_payload_payload_proto_msgTypes,
}.Build()
File_payload_payload_proto = out.File
file_payload_payload_proto_rawDesc = nil
file_payload_payload_proto_goTypes = nil
file_payload_payload_proto_depIdxs = nil
}

View File

@@ -0,0 +1,17 @@
syntax = "proto3";
package loadtime.payload;
option go_package = "github.com/tendermint/tendermint/test/loadtime/payload";
import "google/protobuf/timestamp.proto";
// Payload is the structure of the loadtime transaction. Proto has a compact
// encoded representation, making it ideal for the loadtime usecase which aims to
// keep the generated transactions small.
message Payload {
uint64 connections = 1;
uint64 rate = 2;
uint64 size = 3;
google.protobuf.Timestamp time = 4;
bytes padding = 5;
}

View File

@@ -0,0 +1,23 @@
package payload
import (
"math"
"google.golang.org/protobuf/proto"
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
)
func CalculateUnpaddedSizeBytes() (int, error) {
p := &Payload{
Time: timestamppb.Now(),
Connections: math.MaxUint64,
Rate: math.MaxUint64,
Size: math.MaxUint64,
Padding: make([]byte, 1),
}
b, err := proto.Marshal(p)
if err != nil {
return 0, err
}
return len(b), nil
}

View File

@@ -0,0 +1,19 @@
package payload_test
import (
"testing"
"github.com/tendermint/tendermint/test/loadtime/payload"
)
const payloadSizeTarget = 1024 // 1kb
func TestCalculateSize(t *testing.T) {
s, err := payload.CalculateUnpaddedSizeBytes()
if err != nil {
t.Fatalf("calculating unpadded size %s", err)
}
if s > payloadSizeTarget {
t.Fatalf("unpadded payload size %d exceeds target %d", s, payloadSizeTarget)
}
}