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 }