github.com/Blockdaemon/celo-blockchain@v0.0.0-20200129231733-e667f6b08419/p2p/protocols/reporter.go (about) 1 // Copyright 2018 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 protocols 18 19 import ( 20 "encoding/binary" 21 "time" 22 23 "github.com/ethereum/go-ethereum/log" 24 "github.com/ethereum/go-ethereum/metrics" 25 "github.com/syndtr/goleveldb/leveldb" 26 ) 27 28 //AccountMetrics abstracts away the metrics DB and 29 //the reporter to persist metrics 30 type AccountingMetrics struct { 31 reporter *reporter 32 } 33 34 //Close will be called when the node is being shutdown 35 //for a graceful cleanup 36 func (am *AccountingMetrics) Close() { 37 close(am.reporter.quit) 38 am.reporter.db.Close() 39 } 40 41 //reporter is an internal structure used to write p2p accounting related 42 //metrics to a LevelDB. It will periodically write the accrued metrics to the DB. 43 type reporter struct { 44 reg metrics.Registry //the registry for these metrics (independent of other metrics) 45 interval time.Duration //duration at which the reporter will persist metrics 46 db *leveldb.DB //the actual DB 47 quit chan struct{} //quit the reporter loop 48 } 49 50 //NewMetricsDB creates a new LevelDB instance used to persist metrics defined 51 //inside p2p/protocols/accounting.go 52 func NewAccountingMetrics(r metrics.Registry, d time.Duration, path string) *AccountingMetrics { 53 var val = make([]byte, 8) 54 var err error 55 56 //Create the LevelDB 57 db, err := leveldb.OpenFile(path, nil) 58 if err != nil { 59 log.Error(err.Error()) 60 return nil 61 } 62 63 //Check for all defined metrics that there is a value in the DB 64 //If there is, assign it to the metric. This means that the node 65 //has been running before and that metrics have been persisted. 66 metricsMap := map[string]metrics.Counter{ 67 "account.balance.credit": mBalanceCredit, 68 "account.balance.debit": mBalanceDebit, 69 "account.bytes.credit": mBytesCredit, 70 "account.bytes.debit": mBytesDebit, 71 "account.msg.credit": mMsgCredit, 72 "account.msg.debit": mMsgDebit, 73 "account.peerdrops": mPeerDrops, 74 "account.selfdrops": mSelfDrops, 75 } 76 //iterate the map and get the values 77 for key, metric := range metricsMap { 78 val, err = db.Get([]byte(key), nil) 79 //until the first time a value is being written, 80 //this will return an error. 81 //it could be beneficial though to log errors later, 82 //but that would require a different logic 83 if err == nil { 84 metric.Inc(int64(binary.BigEndian.Uint64(val))) 85 } 86 } 87 88 //create the reporter 89 rep := &reporter{ 90 reg: r, 91 interval: d, 92 db: db, 93 quit: make(chan struct{}), 94 } 95 96 //run the go routine 97 go rep.run() 98 99 m := &AccountingMetrics{ 100 reporter: rep, 101 } 102 103 return m 104 } 105 106 //run is the goroutine which periodically sends the metrics to the configured LevelDB 107 func (r *reporter) run() { 108 intervalTicker := time.NewTicker(r.interval) 109 110 for { 111 select { 112 case <-intervalTicker.C: 113 //at each tick send the metrics 114 if err := r.save(); err != nil { 115 log.Error("unable to send metrics to LevelDB", "err", err) 116 //If there is an error in writing, exit the routine; we assume here that the error is 117 //severe and don't attempt to write again. 118 //Also, this should prevent leaking when the node is stopped 119 return 120 } 121 case <-r.quit: 122 //graceful shutdown 123 return 124 } 125 } 126 } 127 128 //send the metrics to the DB 129 func (r *reporter) save() error { 130 //create a LevelDB Batch 131 batch := leveldb.Batch{} 132 //for each metric in the registry (which is independent)... 133 r.reg.Each(func(name string, i interface{}) { 134 metric, ok := i.(metrics.Counter) 135 if ok { 136 //assuming every metric here to be a Counter (separate registry) 137 //...create a snapshot... 138 ms := metric.Snapshot() 139 byteVal := make([]byte, 8) 140 binary.BigEndian.PutUint64(byteVal, uint64(ms.Count())) 141 //...and save the value to the DB 142 batch.Put([]byte(name), byteVal) 143 } 144 }) 145 return r.db.Write(&batch, nil) 146 }