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