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 }