github.com/mprishchepo/go-ethereum@v1.9.7-0.20191031044858-21506be82b68/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/Fantom-foundation/go-ethereum/common"
    28  	"github.com/Fantom-foundation/go-ethereum/common/mclock"
    29  	"github.com/Fantom-foundation/go-ethereum/core/rawdb"
    30  	"github.com/Fantom-foundation/go-ethereum/core/types"
    31  	"github.com/Fantom-foundation/go-ethereum/crypto"
    32  	"github.com/Fantom-foundation/go-ethereum/les/flowcontrol"
    33  	"github.com/Fantom-foundation/go-ethereum/log"
    34  	"github.com/Fantom-foundation/go-ethereum/p2p"
    35  	"github.com/Fantom-foundation/go-ethereum/p2p/enode"
    36  	"github.com/Fantom-foundation/go-ethereum/params"
    37  	"github.com/Fantom-foundation/go-ethereum/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 *peer, 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 *peer, index int) error {
    76  	if b.byHash {
    77  		return peer.RequestHeadersByHash(0, 0, b.hashes[index], b.amount, b.skip, b.reverse)
    78  	} else {
    79  		return peer.RequestHeadersByNumber(0, 0, uint64(b.offset+rand.Int63n(b.randMax)), b.amount, b.skip, b.reverse)
    80  	}
    81  }
    82  
    83  // benchmarkBodiesOrReceipts implements requestBenchmark
    84  type benchmarkBodiesOrReceipts struct {
    85  	receipts bool
    86  	hashes   []common.Hash
    87  }
    88  
    89  func (b *benchmarkBodiesOrReceipts) init(h *serverHandler, count int) error {
    90  	randMax := h.blockchain.CurrentHeader().Number.Int64() + 1
    91  	b.hashes = make([]common.Hash, count)
    92  	for i := range b.hashes {
    93  		b.hashes[i] = rawdb.ReadCanonicalHash(h.chainDb, uint64(rand.Int63n(randMax)))
    94  	}
    95  	return nil
    96  }
    97  
    98  func (b *benchmarkBodiesOrReceipts) request(peer *peer, index int) error {
    99  	if b.receipts {
   100  		return peer.RequestReceipts(0, 0, []common.Hash{b.hashes[index]})
   101  	} else {
   102  		return peer.RequestBodies(0, 0, []common.Hash{b.hashes[index]})
   103  	}
   104  }
   105  
   106  // benchmarkProofsOrCode implements requestBenchmark
   107  type benchmarkProofsOrCode struct {
   108  	code     bool
   109  	headHash common.Hash
   110  }
   111  
   112  func (b *benchmarkProofsOrCode) init(h *serverHandler, count int) error {
   113  	b.headHash = h.blockchain.CurrentHeader().Hash()
   114  	return nil
   115  }
   116  
   117  func (b *benchmarkProofsOrCode) request(peer *peer, index int) error {
   118  	key := make([]byte, 32)
   119  	rand.Read(key)
   120  	if b.code {
   121  		return peer.RequestCode(0, 0, []CodeReq{{BHash: b.headHash, AccKey: key}})
   122  	} else {
   123  		return peer.RequestProofs(0, 0, []ProofReq{{BHash: b.headHash, Key: key}})
   124  	}
   125  }
   126  
   127  // benchmarkHelperTrie implements requestBenchmark
   128  type benchmarkHelperTrie struct {
   129  	bloom                 bool
   130  	reqCount              int
   131  	sectionCount, headNum uint64
   132  }
   133  
   134  func (b *benchmarkHelperTrie) init(h *serverHandler, count int) error {
   135  	if b.bloom {
   136  		b.sectionCount, b.headNum, _ = h.server.bloomTrieIndexer.Sections()
   137  	} else {
   138  		b.sectionCount, _, _ = h.server.chtIndexer.Sections()
   139  		b.headNum = b.sectionCount*params.CHTFrequency - 1
   140  	}
   141  	if b.sectionCount == 0 {
   142  		return fmt.Errorf("no processed sections available")
   143  	}
   144  	return nil
   145  }
   146  
   147  func (b *benchmarkHelperTrie) request(peer *peer, index int) error {
   148  	reqs := make([]HelperTrieReq, b.reqCount)
   149  
   150  	if b.bloom {
   151  		bitIdx := uint16(rand.Intn(2048))
   152  		for i := range reqs {
   153  			key := make([]byte, 10)
   154  			binary.BigEndian.PutUint16(key[:2], bitIdx)
   155  			binary.BigEndian.PutUint64(key[2:], uint64(rand.Int63n(int64(b.sectionCount))))
   156  			reqs[i] = HelperTrieReq{Type: htBloomBits, TrieIdx: b.sectionCount - 1, Key: key}
   157  		}
   158  	} else {
   159  		for i := range reqs {
   160  			key := make([]byte, 8)
   161  			binary.BigEndian.PutUint64(key[:], uint64(rand.Int63n(int64(b.headNum))))
   162  			reqs[i] = HelperTrieReq{Type: htCanonical, TrieIdx: b.sectionCount - 1, Key: key, AuxReq: auxHeader}
   163  		}
   164  	}
   165  
   166  	return peer.RequestHelperTrieProofs(0, 0, reqs)
   167  }
   168  
   169  // benchmarkTxSend implements requestBenchmark
   170  type benchmarkTxSend struct {
   171  	txs types.Transactions
   172  }
   173  
   174  func (b *benchmarkTxSend) init(h *serverHandler, count int) error {
   175  	key, _ := crypto.GenerateKey()
   176  	addr := crypto.PubkeyToAddress(key.PublicKey)
   177  	signer := types.NewEIP155Signer(big.NewInt(18))
   178  	b.txs = make(types.Transactions, count)
   179  
   180  	for i := range b.txs {
   181  		data := make([]byte, txSizeCostLimit)
   182  		rand.Read(data)
   183  		tx, err := types.SignTx(types.NewTransaction(0, addr, new(big.Int), 0, new(big.Int), data), signer, key)
   184  		if err != nil {
   185  			panic(err)
   186  		}
   187  		b.txs[i] = tx
   188  	}
   189  	return nil
   190  }
   191  
   192  func (b *benchmarkTxSend) request(peer *peer, index int) error {
   193  	enc, _ := rlp.EncodeToBytes(types.Transactions{b.txs[index]})
   194  	return peer.SendTxs(0, 0, enc)
   195  }
   196  
   197  // benchmarkTxStatus implements requestBenchmark
   198  type benchmarkTxStatus struct{}
   199  
   200  func (b *benchmarkTxStatus) init(h *serverHandler, count int) error {
   201  	return nil
   202  }
   203  
   204  func (b *benchmarkTxStatus) request(peer *peer, index int) error {
   205  	var hash common.Hash
   206  	rand.Read(hash[:])
   207  	return peer.RequestTxStatus(0, 0, []common.Hash{hash})
   208  }
   209  
   210  // benchmarkSetup stores measurement data for a single benchmark type
   211  type benchmarkSetup struct {
   212  	req                   requestBenchmark
   213  	totalCount            int
   214  	totalTime, avgTime    time.Duration
   215  	maxInSize, maxOutSize uint32
   216  	err                   error
   217  }
   218  
   219  // runBenchmark runs a benchmark cycle for all benchmark types in the specified
   220  // number of passes
   221  func (h *serverHandler) runBenchmark(benchmarks []requestBenchmark, passCount int, targetTime time.Duration) []*benchmarkSetup {
   222  	setup := make([]*benchmarkSetup, len(benchmarks))
   223  	for i, b := range benchmarks {
   224  		setup[i] = &benchmarkSetup{req: b}
   225  	}
   226  	for i := 0; i < passCount; i++ {
   227  		log.Info("Running benchmark", "pass", i+1, "total", passCount)
   228  		todo := make([]*benchmarkSetup, len(benchmarks))
   229  		copy(todo, setup)
   230  		for len(todo) > 0 {
   231  			// select a random element
   232  			index := rand.Intn(len(todo))
   233  			next := todo[index]
   234  			todo[index] = todo[len(todo)-1]
   235  			todo = todo[:len(todo)-1]
   236  
   237  			if next.err == nil {
   238  				// calculate request count
   239  				count := 50
   240  				if next.totalTime > 0 {
   241  					count = int(uint64(next.totalCount) * uint64(targetTime) / uint64(next.totalTime))
   242  				}
   243  				if err := h.measure(next, count); err != nil {
   244  					next.err = err
   245  				}
   246  			}
   247  		}
   248  	}
   249  	log.Info("Benchmark completed")
   250  
   251  	for _, s := range setup {
   252  		if s.err == nil {
   253  			s.avgTime = s.totalTime / time.Duration(s.totalCount)
   254  		}
   255  	}
   256  	return setup
   257  }
   258  
   259  // meteredPipe implements p2p.MsgReadWriter and remembers the largest single
   260  // message size sent through the pipe
   261  type meteredPipe struct {
   262  	rw      p2p.MsgReadWriter
   263  	maxSize uint32
   264  }
   265  
   266  func (m *meteredPipe) ReadMsg() (p2p.Msg, error) {
   267  	return m.rw.ReadMsg()
   268  }
   269  
   270  func (m *meteredPipe) WriteMsg(msg p2p.Msg) error {
   271  	if msg.Size > m.maxSize {
   272  		m.maxSize = msg.Size
   273  	}
   274  	return m.rw.WriteMsg(msg)
   275  }
   276  
   277  // measure runs a benchmark for a single type in a single pass, with the given
   278  // number of requests
   279  func (h *serverHandler) measure(setup *benchmarkSetup, count int) error {
   280  	clientPipe, serverPipe := p2p.MsgPipe()
   281  	clientMeteredPipe := &meteredPipe{rw: clientPipe}
   282  	serverMeteredPipe := &meteredPipe{rw: serverPipe}
   283  	var id enode.ID
   284  	rand.Read(id[:])
   285  
   286  	clientPeer := newPeer(lpv2, NetworkId, false, p2p.NewPeer(id, "client", nil), clientMeteredPipe)
   287  	serverPeer := newPeer(lpv2, NetworkId, false, p2p.NewPeer(id, "server", nil), serverMeteredPipe)
   288  	serverPeer.sendQueue = newExecQueue(count)
   289  	serverPeer.announceType = announceTypeNone
   290  	serverPeer.fcCosts = make(requestCostTable)
   291  	c := &requestCosts{}
   292  	for code := range requests {
   293  		serverPeer.fcCosts[code] = c
   294  	}
   295  	serverPeer.fcParams = flowcontrol.ServerParams{BufLimit: 1, MinRecharge: 1}
   296  	serverPeer.fcClient = flowcontrol.NewClientNode(h.server.fcManager, serverPeer.fcParams)
   297  	defer serverPeer.fcClient.Disconnect()
   298  
   299  	if err := setup.req.init(h, count); err != nil {
   300  		return err
   301  	}
   302  
   303  	errCh := make(chan error, 10)
   304  	start := mclock.Now()
   305  
   306  	go func() {
   307  		for i := 0; i < count; i++ {
   308  			if err := setup.req.request(clientPeer, i); err != nil {
   309  				errCh <- err
   310  				return
   311  			}
   312  		}
   313  	}()
   314  	go func() {
   315  		for i := 0; i < count; i++ {
   316  			if err := h.handleMsg(serverPeer, &sync.WaitGroup{}); err != nil {
   317  				errCh <- err
   318  				return
   319  			}
   320  		}
   321  	}()
   322  	go func() {
   323  		for i := 0; i < count; i++ {
   324  			msg, err := clientPipe.ReadMsg()
   325  			if err != nil {
   326  				errCh <- err
   327  				return
   328  			}
   329  			var i interface{}
   330  			msg.Decode(&i)
   331  		}
   332  		// at this point we can be sure that the other two
   333  		// goroutines finished successfully too
   334  		close(errCh)
   335  	}()
   336  	select {
   337  	case err := <-errCh:
   338  		if err != nil {
   339  			return err
   340  		}
   341  	case <-h.closeCh:
   342  		clientPipe.Close()
   343  		serverPipe.Close()
   344  		return fmt.Errorf("Benchmark cancelled")
   345  	}
   346  
   347  	setup.totalTime += time.Duration(mclock.Now() - start)
   348  	setup.totalCount += count
   349  	setup.maxInSize = clientMeteredPipe.maxSize
   350  	setup.maxOutSize = serverMeteredPipe.maxSize
   351  	clientPipe.Close()
   352  	serverPipe.Close()
   353  	return nil
   354  }