initial working version of all

This commit is contained in:
William Banfield
2022-09-01 17:49:25 -04:00
parent 729d3e1885
commit f02d22cf8f
9 changed files with 186 additions and 101 deletions
+3 -2
View File
@@ -1,8 +1,9 @@
GOMOD="github.com/tendermint/tendermint/test/loadtime"
OUTPUT?=build/loadtime
OUTPUT?=build/
build:
go build $(BUILD_FLAGS) -tags '$(BUILD_TAGS)' -o $(OUTPUT) .
go build $(BUILD_FLAGS) -tags '$(BUILD_TAGS)' -o $(OUTPUT)load ./cmd/load/
go build $(BUILD_FLAGS) -tags '$(BUILD_TAGS)' -o $(OUTPUT)report ./cmd/report/
.PHONY: build
check-proto-gen-deps:
+1 -1
View File
@@ -4,7 +4,7 @@ set -euo pipefail
# A basic invocation of the loadtime tool.
./build/loadtime \
./build/load \
-c 1 -T 10 -r 1000 -s 1024 \
--broadcast-tx-method sync \
--endpoints ws://localhost:26657/websocket
@@ -38,7 +38,10 @@ func main() {
}
func (f *ClientFactory) ValidateConfig(cfg loadtest.Config) error {
psb := payload.UnpaddedSizeBytes()
psb, err := payload.MaxUnpaddedSize()
if err != nil {
return err
}
if psb > cfg.Size {
return fmt.Errorf("payload size exceeds configured size")
}
@@ -54,9 +57,9 @@ func (f *ClientFactory) NewClient(cfg loadtest.Config) (loadtest.Client, error)
}
func (c *TxGenerator) GenerateTx() ([]byte, error) {
return payload.NewBytes(payload.Options{
Conns: c.conns,
Rate: c.rate,
Size: c.size,
return payload.NewBytes(&payload.Payload{
Connections: c.conns,
Rate: c.rate,
Size: c.size,
})
}
+1 -3
View File
@@ -2,8 +2,6 @@ package main
import (
"fmt"
"math"
"time"
"github.com/tendermint/tendermint/store"
"github.com/tendermint/tendermint/test/loadtime/report"
@@ -26,5 +24,5 @@ func main() {
fmt.Println(r.Min)
fmt.Println(r.Max)
fmt.Println(r.Avg)
fmt.Println(int64(time.Duration(math.MaxInt64) / (2 * time.Second)))
fmt.Println(r.StdDev)
}
+87
View File
@@ -0,0 +1,87 @@
package payload
import (
"bytes"
"crypto/rand"
"errors"
"fmt"
"math"
"google.golang.org/protobuf/proto"
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
)
const keyPrefix = "a="
// NewBytes generates a new payload and returns the encoded representation of
// the payload as a slice of bytes. NewBytes uses the fields on the Options
// to create the payload.
func NewBytes(p *Payload) ([]byte, error) {
p.Padding = make([]byte, 1)
if p.Time == nil {
p.Time = timestamppb.Now()
}
us, err := CalculateUnpaddedSize(p)
if err != nil {
return nil, err
}
if p.Size < uint64(us) {
return nil, fmt.Errorf("configured size %d not large enough to fit unpadded transaction of size %d", p.Size, us)
}
p.Padding = make([]byte, p.Size-uint64(us))
_, 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(keyPrefix), b...), nil
}
// FromBytes extracts a paylod from the byte representation of the payload.
// FromBytes leaves the padding untouched, returning it to the caller to handle
// or discard per their preference.
func FromBytes(b []byte) (*Payload, error) {
p := &Payload{}
tr := bytes.TrimPrefix(b, []byte(keyPrefix))
if bytes.Equal(b, tr) {
return nil, errors.New("payload bytes missing key prefix")
}
err := proto.Unmarshal(tr, p)
if err != nil {
return nil, err
}
return p, nil
}
// MaxUnpaddedSize returns the maximum size that a payload may be if no padding
// is included.
func MaxUnpaddedSize() (int, error) {
p := &Payload{
Time: timestamppb.Now(),
Connections: math.MaxUint64,
Rate: math.MaxUint64,
Size: math.MaxUint64,
Padding: make([]byte, 1),
}
return CalculateUnpaddedSize(p)
}
// CalculateUnpaddedSize calculates the size of the passed in payload for the
// purpose of determining how much padding to add to add to reach the target size.
// CalculateUnpaddedSize returns an error if the payload Padding field is longer than 1.
func CalculateUnpaddedSize(p *Payload) (int, error) {
if len(p.Padding) != 1 {
return 0, fmt.Errorf("expected length of padding to be 1, received %d", len(p.Padding))
}
b, err := proto.Marshal(p)
if err != nil {
return 0, err
}
return len(b) + len(keyPrefix), nil
}
@@ -16,17 +16,32 @@ func TestSize(t *testing.T) {
}
func TestRoundTrip(t *testing.T) {
const (
testConns = 512
testRate = 4
)
b, err := payload.NewBytes(payload.Options{
Size: 1024,
Size: payloadSizeTarget,
Conns: testConns,
Rate: testRate,
})
if err != nil {
t.Fatalf("generating payload %s", err)
}
if len(b) < payloadSizeTarget {
t.Fatalf("payload size %d less than expected %d", len(b), payloadSizeTarget)
}
p, err := payload.FromBytes(b)
if err != nil {
t.Fatalf("reading payload %s", err)
}
if p.Size != 1024 {
t.Fatalf("payload size value %d does not match expected %d", p.Size, 1024)
if p.Size != payloadSizeTarget {
t.Fatalf("payload size value %d does not match expected %d", p.Size, payloadSizeTarget)
}
if p.Connections != testConns {
t.Fatalf("payload size value %d does not match expected %d", p.Size, payloadSizeTarget)
}
if p.Size != payloadSizeTarget {
t.Fatalf("payload size value %d does not match expected %d", p.Size, payloadSizeTarget)
}
}
-76
View File
@@ -1,76 +0,0 @@
package payload
import (
"bytes"
"crypto/rand"
"fmt"
"math"
"google.golang.org/protobuf/proto"
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
)
func init() {
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 {
panic(err)
}
payloadSizeBytes = len(b)
}
const keyPrefix = "a="
var payloadSizeBytes int
type Options struct {
Conns uint64
Rate uint64
Size uint64
}
func UnpaddedSizeBytes() int {
return payloadSizeBytes
}
func NewBytes(o Options) ([]byte, error) {
if o.Size < uint64(UnpaddedSizeBytes()) {
return nil, fmt.Errorf("configured size %d not large enough to fit unpadded transaction size %d", o.Size, UnpaddedSizeBytes())
}
p := &Payload{
Time: timestamppb.Now(),
Connections: o.Conns,
Rate: o.Rate,
Size: o.Size,
Padding: make([]byte, o.Size-uint64(UnpaddedSizeBytes())),
}
_, 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(keyPrefix), b...), nil
// return b, nil
}
func FromBytes(b []byte) (*Payload, error) {
p := &Payload{}
tr := bytes.TrimPrefix(b, []byte(keyPrefix))
err := proto.Unmarshal(tr, p)
if err != nil {
return nil, err
}
return p, nil
}
+38 -7
View File
@@ -1,6 +1,7 @@
package report
import (
"fmt"
"math"
"sync"
"time"
@@ -9,20 +10,33 @@ import (
"github.com/tendermint/tendermint/types"
)
type blockStore interface {
// BlockStore defines the set of methods needed by the report generator from
// Tendermint's store.Blockstore type. Using an interface allows for tests to
// more easily simulate the required behavior without having to use the more
// complex real API.
type BlockStore interface {
Height() int64
Base() int64
LoadBlock(int64) *types.Block
}
// Report contains the data calculated from reading the timestamped transactions
// of each block found in the blockstore.
type Report struct {
Max, Min, Avg time.Duration
StdDev int64
All []time.Duration
ErrorCount int
Max, Min, Avg, StdDev time.Duration
// ErrorCount is the number of parsing errors encountered while reading the
// transaction data. Parsing errors may occur if a transaction not generated
// by the payload package is submitted to the chain.
ErrorCount int
// All contains all data points gathered from all valid transactions.
All []time.Duration
}
func GenerateFromBlockStore(s blockStore) (Report, error) {
// GenerateFromBlockStore creates a Report using the data in the provided
// BlockStore.
func GenerateFromBlockStore(s BlockStore) (Report, error) {
type payloadData struct {
l time.Duration
err error
@@ -48,7 +62,7 @@ func GenerateFromBlockStore(s blockStore) (Report, error) {
pdc <- payloadData{err: err}
continue
}
l := p.Time.AsTime().Sub(b.bt)
l := b.bt.Sub(p.Time.AsTime())
pdc <- payloadData{l: l}
}
}()
@@ -89,5 +103,22 @@ func GenerateFromBlockStore(s blockStore) (Report, error) {
sum += int64(pd.l)
}
r.Avg = time.Duration(sum / int64(len(r.All)))
r.StdDev = time.Duration(stddev(r.All))
return r, nil
}
func stddev(l []time.Duration) int64 {
var s1 int64
for _, x := range l {
s1 += int64(x)
}
u := s1 / int64(len(l))
var s2 int64
for _, x := range l {
s2 += (int64(x) - u) * (int64(x) - u)
}
r := s2 / (int64(len(l)))
sqr := math.Sqrt(float64(r))
fmt.Println(sqr)
return int64(sqr)
}
+30 -4
View File
@@ -7,6 +7,7 @@ import (
"github.com/tendermint/tendermint/test/loadtime/payload"
"github.com/tendermint/tendermint/test/loadtime/report"
"github.com/tendermint/tendermint/types"
"google.golang.org/protobuf/types/known/timestamppb"
)
type mockBlockStore struct {
@@ -27,13 +28,16 @@ func (m *mockBlockStore) LoadBlock(i int64) *types.Block {
}
func TestGenerateReport(t *testing.T) {
b1, err := payload.NewBytes(payload.Options{
tn := time.Now()
b1, err := payload.NewBytes(&payload.Payload{
Time: timestamppb.New(tn.Add(-6 * time.Second)),
Size: 1024,
})
if err != nil {
t.Fatalf("generating payload %s", err)
}
b2, err := payload.NewBytes(payload.Options{
b2, err := payload.NewBytes(&payload.Payload{
Time: timestamppb.New(tn.Add(-4 * time.Second)),
Size: 1024,
})
if err != nil {
@@ -43,12 +47,17 @@ func TestGenerateReport(t *testing.T) {
blocks: []*types.Block{
{
Header: types.Header{
Time: time.Now(),
Time: tn,
},
Data: types.Data{
Txs: []types.Tx{b1, b2},
},
},
{
Data: types.Data{
Txs: []types.Tx{[]byte("error")},
},
},
},
}
r, err := report.GenerateFromBlockStore(s)
@@ -56,6 +65,23 @@ func TestGenerateReport(t *testing.T) {
t.Fatalf("generating report %s", err)
}
if len(r.All) != 2 {
t.Fatalf("report contained different number of data points from expected. Expected %d but contained %d", 1, len(r.All))
t.Fatalf("report contained different number of data points from expected. Expected %d but contained %d", 2, len(r.All))
}
if r.ErrorCount != 1 {
t.Fatalf("ErrorCount did not match expected. Expected %d but contained %d", 1, r.ErrorCount)
}
if r.Avg != 5*time.Second {
t.Fatalf("Avg did not match expected. Expected %s but contained %s", 5*time.Second, r.Avg)
}
if r.Min != 4*time.Second {
t.Fatalf("Avg did not match expected. Expected %s but contained %s", 4*time.Second, r.Min)
}
if r.Max != 6*time.Second {
t.Fatalf("Avg did not match expected. Expected %s but contained %s", 6*time.Second, r.Max)
}
// Verified using online standard deviation calculator:
// https://www.calculator.net/standard-deviation-calculator.html?numberinputs=6%2C+4&ctype=p&x=84&y=27
if r.StdDev != time.Second {
t.Fatalf("StdDev did not match expected. Expected %s but contained %s", time.Second, r.StdDev)
}
}