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