github.com/core-coin/go-core/v2@v2.1.9/les/benchmark.go (about)

     1  // Copyright 2019 by the Authors
     2  // This file is part of the go-core library.
     3  //
     4  // The go-core 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-core 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-core library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package les
    18  
    19  import (
    20  	crand "crypto/rand"
    21  	"encoding/binary"
    22  	"fmt"
    23  	"math/big"
    24  	"math/rand"
    25  	"sync"
    26  	"time"
    27  
    28  	"github.com/core-coin/go-core/v2/common"
    29  	"github.com/core-coin/go-core/v2/common/mclock"
    30  	"github.com/core-coin/go-core/v2/core/rawdb"
    31  	"github.com/core-coin/go-core/v2/core/types"
    32  	"github.com/core-coin/go-core/v2/crypto"
    33  	"github.com/core-coin/go-core/v2/les/flowcontrol"
    34  	"github.com/core-coin/go-core/v2/log"
    35  	"github.com/core-coin/go-core/v2/p2p"
    36  	"github.com/core-coin/go-core/v2/p2p/enode"
    37  	"github.com/core-coin/go-core/v2/params"
    38  	"github.com/core-coin/go-core/v2/rlp"
    39  )
    40  
    41  // requestBenchmark is an interface for different randomized request generators
    42  type requestBenchmark interface {
    43  	// init initializes the generator for generating the given number of randomized requests
    44  	init(h *serverHandler, count int) error
    45  	// request initiates sending a single request to the given peer
    46  	request(peer *serverPeer, index int) error
    47  }
    48  
    49  // benchmarkBlockHeaders implements requestBenchmark
    50  type benchmarkBlockHeaders struct {
    51  	amount, skip    int
    52  	reverse, byHash bool
    53  	offset, randMax int64
    54  	hashes          []common.Hash
    55  }
    56  
    57  func (b *benchmarkBlockHeaders) init(h *serverHandler, count int) error {
    58  	d := int64(b.amount-1) * int64(b.skip+1)
    59  	b.offset = 0
    60  	b.randMax = h.blockchain.CurrentHeader().Number.Int64() + 1 - d
    61  	if b.randMax < 0 {
    62  		return fmt.Errorf("chain is too short")
    63  	}
    64  	if b.reverse {
    65  		b.offset = d
    66  	}
    67  	if b.byHash {
    68  		b.hashes = make([]common.Hash, count)
    69  		for i := range b.hashes {
    70  			b.hashes[i] = rawdb.ReadCanonicalHash(h.chainDb, uint64(b.offset+rand.Int63n(b.randMax)))
    71  		}
    72  	}
    73  	return nil
    74  }
    75  
    76  func (b *benchmarkBlockHeaders) request(peer *serverPeer, index int) error {
    77  	if b.byHash {
    78  		return peer.requestHeadersByHash(0, b.hashes[index], b.amount, b.skip, b.reverse)
    79  	}
    80  	return peer.requestHeadersByNumber(0, uint64(b.offset+rand.Int63n(b.randMax)), b.amount, b.skip, b.reverse)
    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 *serverPeer, index int) error {
    99  	if b.receipts {
   100  		return peer.requestReceipts(0, []common.Hash{b.hashes[index]})
   101  	}
   102  	return peer.requestBodies(0, []common.Hash{b.hashes[index]})
   103  }
   104  
   105  // benchmarkProofsOrCode implements requestBenchmark
   106  type benchmarkProofsOrCode struct {
   107  	code     bool
   108  	headHash common.Hash
   109  }
   110  
   111  func (b *benchmarkProofsOrCode) init(h *serverHandler, count int) error {
   112  	b.headHash = h.blockchain.CurrentHeader().Hash()
   113  	return nil
   114  }
   115  
   116  func (b *benchmarkProofsOrCode) request(peer *serverPeer, index int) error {
   117  	key := make([]byte, 32)
   118  	rand.Read(key)
   119  	if b.code {
   120  		return peer.requestCode(0, []CodeReq{{BHash: b.headHash, AccKey: key}})
   121  	}
   122  	return peer.requestProofs(0, []ProofReq{{BHash: b.headHash, Key: key}})
   123  }
   124  
   125  // benchmarkHelperTrie implements requestBenchmark
   126  type benchmarkHelperTrie struct {
   127  	bloom                 bool
   128  	reqCount              int
   129  	sectionCount, headNum uint64
   130  }
   131  
   132  func (b *benchmarkHelperTrie) init(h *serverHandler, count int) error {
   133  	if b.bloom {
   134  		b.sectionCount, b.headNum, _ = h.server.bloomTrieIndexer.Sections()
   135  	} else {
   136  		b.sectionCount, _, _ = h.server.chtIndexer.Sections()
   137  		b.headNum = b.sectionCount*params.CHTFrequency - 1
   138  	}
   139  	if b.sectionCount == 0 {
   140  		return fmt.Errorf("no processed sections available")
   141  	}
   142  	return nil
   143  }
   144  
   145  func (b *benchmarkHelperTrie) request(peer *serverPeer, index int) error {
   146  	reqs := make([]HelperTrieReq, b.reqCount)
   147  
   148  	if b.bloom {
   149  		bitIdx := uint16(rand.Intn(2048))
   150  		for i := range reqs {
   151  			key := make([]byte, 10)
   152  			binary.BigEndian.PutUint16(key[:2], bitIdx)
   153  			binary.BigEndian.PutUint64(key[2:], uint64(rand.Int63n(int64(b.sectionCount))))
   154  			reqs[i] = HelperTrieReq{Type: htBloomBits, TrieIdx: b.sectionCount - 1, Key: key}
   155  		}
   156  	} else {
   157  		for i := range reqs {
   158  			key := make([]byte, 8)
   159  			binary.BigEndian.PutUint64(key[:], uint64(rand.Int63n(int64(b.headNum))))
   160  			reqs[i] = HelperTrieReq{Type: htCanonical, TrieIdx: b.sectionCount - 1, Key: key, AuxReq: auxHeader}
   161  		}
   162  	}
   163  
   164  	return peer.requestHelperTrieProofs(0, reqs)
   165  }
   166  
   167  // benchmarkTxSend implements requestBenchmark
   168  type benchmarkTxSend struct {
   169  	txs types.Transactions
   170  }
   171  
   172  func (b *benchmarkTxSend) init(h *serverHandler, count int) error {
   173  	key, _ := crypto.GenerateKey(crand.Reader)
   174  	signer := types.NewNucleusSigner(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, key.Address(), 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  }