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  }