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