github.com/cryptogateway/go-paymex@v0.0.0-20210204174735-96277fb1e602/les/benchmark.go (about)

     1  // Copyright 2019 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package les
    18  
    19  import (
    20  	"encoding/binary"
    21  	"fmt"
    22  	"math/big"
    23  	"math/rand"
    24  	"sync"
    25  	"time"
    26  
    27  	"github.com/cryptogateway/go-paymex/common"
    28  	"github.com/cryptogateway/go-paymex/common/mclock"
    29  	"github.com/cryptogateway/go-paymex/core/rawdb"
    30  	"github.com/cryptogateway/go-paymex/core/types"
    31  	"github.com/cryptogateway/go-paymex/crypto"
    32  	"github.com/cryptogateway/go-paymex/les/flowcontrol"
    33  	"github.com/cryptogateway/go-paymex/log"
    34  	"github.com/cryptogateway/go-paymex/p2p"
    35  	"github.com/cryptogateway/go-paymex/p2p/enode"
    36  	"github.com/cryptogateway/go-paymex/params"
    37  	"github.com/cryptogateway/go-paymex/rlp"
    38  )
    39  
    40  // requestBenchmark is an interface for different randomized request generators
    41  type requestBenchmark interface {
    42  	// init initializes the generator for generating the given number of randomized requests
    43  	init(h *serverHandler, count int) error
    44  	// request initiates sending a single request to the given peer
    45  	request(peer *serverPeer, index int) error
    46  }
    47  
    48  // benchmarkBlockHeaders implements requestBenchmark
    49  type benchmarkBlockHeaders struct {
    50  	amount, skip    int
    51  	reverse, byHash bool
    52  	offset, randMax int64
    53  	hashes          []common.Hash
    54  }
    55  
    56  func (b *benchmarkBlockHeaders) init(h *serverHandler, count int) error {
    57  	d := int64(b.amount-1) * int64(b.skip+1)
    58  	b.offset = 0
    59  	b.randMax = h.blockchain.CurrentHeader().Number.Int64() + 1 - d
    60  	if b.randMax < 0 {
    61  		return fmt.Errorf("chain is too short")
    62  	}
    63  	if b.reverse {
    64  		b.offset = d
    65  	}
    66  	if b.byHash {
    67  		b.hashes = make([]common.Hash, count)
    68  		for i := range b.hashes {
    69  			b.hashes[i] = rawdb.ReadCanonicalHash(h.chainDb, uint64(b.offset+rand.Int63n(b.randMax)))
    70  		}
    71  	}
    72  	return nil
    73  }
    74  
    75  func (b *benchmarkBlockHeaders) request(peer *serverPeer, index int) error {
    76  	if b.byHash {
    77  		return peer.requestHeadersByHash(0, b.hashes[index], b.amount, b.skip, b.reverse)
    78  	}
    79  	return peer.requestHeadersByNumber(0, uint64(b.offset+rand.Int63n(b.randMax)), b.amount, b.skip, b.reverse)
    80  }
    81  
    82  // benchmarkBodiesOrReceipts implements requestBenchmark
    83  type benchmarkBodiesOrReceipts struct {
    84  	receipts bool
    85  	hashes   []common.Hash
    86  }
    87  
    88  func (b *benchmarkBodiesOrReceipts) init(h *serverHandler, count int) error {
    89  	randMax := h.blockchain.CurrentHeader().Number.Int64() + 1
    90  	b.hashes = make([]common.Hash, count)
    91  	for i := range b.hashes {
    92  		b.hashes[i] = rawdb.ReadCanonicalHash(h.chainDb, uint64(rand.Int63n(randMax)))
    93  	}
    94  	return nil
    95  }
    96  
    97  func (b *benchmarkBodiesOrReceipts) request(peer *serverPeer, index int) error {
    98  	if b.receipts {
    99  		return peer.requestReceipts(0, []common.Hash{b.hashes[index]})
   100  	}
   101  	return peer.requestBodies(0, []common.Hash{b.hashes[index]})
   102  }
   103  
   104  // benchmarkProofsOrCode implements requestBenchmark
   105  type benchmarkProofsOrCode struct {
   106  	code     bool
   107  	headHash common.Hash
   108  }
   109  
   110  func (b *benchmarkProofsOrCode) init(h *serverHandler, count int) error {
   111  	b.headHash = h.blockchain.CurrentHeader().Hash()
   112  	return nil
   113  }
   114  
   115  func (b *benchmarkProofsOrCode) request(peer *serverPeer, index int) error {
   116  	key := make([]byte, 32)
   117  	rand.Read(key)
   118  	if b.code {
   119  		return peer.requestCode(0, []CodeReq{{BHash: b.headHash, AccKey: key}})
   120  	}
   121  	return peer.requestProofs(0, []ProofReq{{BHash: b.headHash, Key: key}})
   122  }
   123  
   124  // benchmarkHelperTrie implements requestBenchmark
   125  type benchmarkHelperTrie struct {
   126  	bloom                 bool
   127  	reqCount              int
   128  	sectionCount, headNum uint64
   129  }
   130  
   131  func (b *benchmarkHelperTrie) init(h *serverHandler, count int) error {
   132  	if b.bloom {
   133  		b.sectionCount, b.headNum, _ = h.server.bloomTrieIndexer.Sections()
   134  	} else {
   135  		b.sectionCount, _, _ = h.server.chtIndexer.Sections()
   136  		b.headNum = b.sectionCount*params.CHTFrequency - 1
   137  	}
   138  	if b.sectionCount == 0 {
   139  		return fmt.Errorf("no processed sections available")
   140  	}
   141  	return nil
   142  }
   143  
   144  func (b *benchmarkHelperTrie) request(peer *serverPeer, index int) error {
   145  	reqs := make([]HelperTrieReq, b.reqCount)
   146  
   147  	if b.bloom {
   148  		bitIdx := uint16(rand.Intn(2048))
   149  		for i := range reqs {
   150  			key := make([]byte, 10)
   151  			binary.BigEndian.PutUint16(key[:2], bitIdx)
   152  			binary.BigEndian.PutUint64(key[2:], uint64(rand.Int63n(int64(b.sectionCount))))
   153  			reqs[i] = HelperTrieReq{Type: htBloomBits, TrieIdx: b.sectionCount - 1, Key: key}
   154  		}
   155  	} else {
   156  		for i := range reqs {
   157  			key := make([]byte, 8)
   158  			binary.BigEndian.PutUint64(key[:], uint64(rand.Int63n(int64(b.headNum))))
   159  			reqs[i] = HelperTrieReq{Type: htCanonical, TrieIdx: b.sectionCount - 1, Key: key, AuxReq: htAuxHeader}
   160  		}
   161  	}
   162  
   163  	return peer.requestHelperTrieProofs(0, reqs)
   164  }
   165  
   166  // benchmarkTxSend implements requestBenchmark
   167  type benchmarkTxSend struct {
   168  	txs types.Transactions
   169  }
   170  
   171  func (b *benchmarkTxSend) init(h *serverHandler, count int) error {
   172  	key, _ := crypto.GenerateKey()
   173  	addr := crypto.PubkeyToAddress(key.PublicKey)
   174  	signer := types.NewEIP155Signer(big.NewInt(18))
   175  	b.txs = make(types.Transactions, count)
   176  
   177  	for i := range b.txs {
   178  		data := make([]byte, txSizeCostLimit)
   179  		rand.Read(data)
   180  		tx, err := types.SignTx(types.NewTransaction(0, addr, new(big.Int), 0, new(big.Int), data), signer, key)
   181  		if err != nil {
   182  			panic(err)
   183  		}
   184  		b.txs[i] = tx
   185  	}
   186  	return nil
   187  }
   188  
   189  func (b *benchmarkTxSend) request(peer *serverPeer, index int) error {
   190  	enc, _ := rlp.EncodeToBytes(types.Transactions{b.txs[index]})
   191  	return peer.sendTxs(0, 1, enc)
   192  }
   193  
   194  // benchmarkTxStatus implements requestBenchmark
   195  type benchmarkTxStatus struct{}
   196  
   197  func (b *benchmarkTxStatus) init(h *serverHandler, count int) error {
   198  	return nil
   199  }
   200  
   201  func (b *benchmarkTxStatus) request(peer *serverPeer, index int) error {
   202  	var hash common.Hash
   203  	rand.Read(hash[:])
   204  	return peer.requestTxStatus(0, []common.Hash{hash})
   205  }
   206  
   207  // benchmarkSetup stores measurement data for a single benchmark type
   208  type benchmarkSetup struct {
   209  	req                   requestBenchmark
   210  	totalCount            int
   211  	totalTime, avgTime    time.Duration
   212  	maxInSize, maxOutSize uint32
   213  	err                   error
   214  }
   215  
   216  // runBenchmark runs a benchmark cycle for all benchmark types in the specified
   217  // number of passes
   218  func (h *serverHandler) runBenchmark(benchmarks []requestBenchmark, passCount int, targetTime time.Duration) []*benchmarkSetup {
   219  	setup := make([]*benchmarkSetup, len(benchmarks))
   220  	for i, b := range benchmarks {
   221  		setup[i] = &benchmarkSetup{req: b}
   222  	}
   223  	for i := 0; i < passCount; i++ {
   224  		log.Info("Running benchmark", "pass", i+1, "total", passCount)
   225  		todo := make([]*benchmarkSetup, len(benchmarks))
   226  		copy(todo, setup)
   227  		for len(todo) > 0 {
   228  			// select a random element
   229  			index := rand.Intn(len(todo))
   230  			next := todo[index]
   231  			todo[index] = todo[len(todo)-1]
   232  			todo = todo[:len(todo)-1]
   233  
   234  			if next.err == nil {
   235  				// calculate request count
   236  				count := 50
   237  				if next.totalTime > 0 {
   238  					count = int(uint64(next.totalCount) * uint64(targetTime) / uint64(next.totalTime))
   239  				}
   240  				if err := h.measure(next, count); err != nil {
   241  					next.err = err
   242  				}
   243  			}
   244  		}
   245  	}
   246  	log.Info("Benchmark completed")
   247  
   248  	for _, s := range setup {
   249  		if s.err == nil {
   250  			s.avgTime = s.totalTime / time.Duration(s.totalCount)
   251  		}
   252  	}
   253  	return setup
   254  }
   255  
   256  // meteredPipe implements p2p.MsgReadWriter and remembers the largest single
   257  // message size sent through the pipe
   258  type meteredPipe struct {
   259  	rw      p2p.MsgReadWriter
   260  	maxSize uint32
   261  }
   262  
   263  func (m *meteredPipe) ReadMsg() (p2p.Msg, error) {
   264  	return m.rw.ReadMsg()
   265  }
   266  
   267  func (m *meteredPipe) WriteMsg(msg p2p.Msg) error {
   268  	if msg.Size > m.maxSize {
   269  		m.maxSize = msg.Size
   270  	}
   271  	return m.rw.WriteMsg(msg)
   272  }
   273  
   274  // measure runs a benchmark for a single type in a single pass, with the given
   275  // number of requests
   276  func (h *serverHandler) measure(setup *benchmarkSetup, count int) error {
   277  	clientPipe, serverPipe := p2p.MsgPipe()
   278  	clientMeteredPipe := &meteredPipe{rw: clientPipe}
   279  	serverMeteredPipe := &meteredPipe{rw: serverPipe}
   280  	var id enode.ID
   281  	rand.Read(id[:])
   282  
   283  	peer1 := newServerPeer(lpv2, NetworkId, false, p2p.NewPeer(id, "client", nil), clientMeteredPipe)
   284  	peer2 := newClientPeer(lpv2, NetworkId, p2p.NewPeer(id, "server", nil), serverMeteredPipe)
   285  	peer2.announceType = announceTypeNone
   286  	peer2.fcCosts = make(requestCostTable)
   287  	c := &requestCosts{}
   288  	for code := range requests {
   289  		peer2.fcCosts[code] = c
   290  	}
   291  	peer2.fcParams = flowcontrol.ServerParams{BufLimit: 1, MinRecharge: 1}
   292  	peer2.fcClient = flowcontrol.NewClientNode(h.server.fcManager, peer2.fcParams)
   293  	defer peer2.fcClient.Disconnect()
   294  
   295  	if err := setup.req.init(h, count); err != nil {
   296  		return err
   297  	}
   298  
   299  	errCh := make(chan error, 10)
   300  	start := mclock.Now()
   301  
   302  	go func() {
   303  		for i := 0; i < count; i++ {
   304  			if err := setup.req.request(peer1, i); err != nil {
   305  				errCh <- err
   306  				return
   307  			}
   308  		}
   309  	}()
   310  	go func() {
   311  		for i := 0; i < count; i++ {
   312  			if err := h.handleMsg(peer2, &sync.WaitGroup{}); err != nil {
   313  				errCh <- err
   314  				return
   315  			}
   316  		}
   317  	}()
   318  	go func() {
   319  		for i := 0; i < count; i++ {
   320  			msg, err := clientPipe.ReadMsg()
   321  			if err != nil {
   322  				errCh <- err
   323  				return
   324  			}
   325  			var i interface{}
   326  			msg.Decode(&i)
   327  		}
   328  		// at this point we can be sure that the other two
   329  		// goroutines finished successfully too
   330  		close(errCh)
   331  	}()
   332  	select {
   333  	case err := <-errCh:
   334  		if err != nil {
   335  			return err
   336  		}
   337  	case <-h.closeCh:
   338  		clientPipe.Close()
   339  		serverPipe.Close()
   340  		return fmt.Errorf("Benchmark cancelled")
   341  	}
   342  
   343  	setup.totalTime += time.Duration(mclock.Now() - start)
   344  	setup.totalCount += count
   345  	setup.maxInSize = clientMeteredPipe.maxSize
   346  	setup.maxOutSize = serverMeteredPipe.maxSize
   347  	clientPipe.Close()
   348  	serverPipe.Close()
   349  	return nil
   350  }