-
Notifications
You must be signed in to change notification settings - Fork 20.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Accounting metrics reporter (#18136)
- Loading branch information
1 parent
2714e8f
commit bba5fd8
Showing
6 changed files
with
305 additions
and
30 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
// Copyright 2018 The go-ethereum Authors | ||
// This file is part of the go-ethereum library. | ||
// | ||
// The go-ethereum library is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU Lesser General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
// | ||
// The go-ethereum library is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU Lesser General Public License for more details. | ||
// | ||
// You should have received a copy of the GNU Lesser General Public License | ||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
package protocols | ||
|
||
import ( | ||
"encoding/binary" | ||
"time" | ||
|
||
"github.com/ethereum/go-ethereum/log" | ||
"github.com/ethereum/go-ethereum/metrics" | ||
|
||
"github.com/syndtr/goleveldb/leveldb" | ||
) | ||
|
||
//AccountMetrics abstracts away the metrics DB and | ||
//the reporter to persist metrics | ||
type AccountingMetrics struct { | ||
reporter *reporter | ||
} | ||
|
||
//Close will be called when the node is being shutdown | ||
//for a graceful cleanup | ||
func (am *AccountingMetrics) Close() { | ||
close(am.reporter.quit) | ||
am.reporter.db.Close() | ||
} | ||
|
||
//reporter is an internal structure used to write p2p accounting related | ||
//metrics to a LevelDB. It will periodically write the accrued metrics to the DB. | ||
type reporter struct { | ||
reg metrics.Registry //the registry for these metrics (independent of other metrics) | ||
interval time.Duration //duration at which the reporter will persist metrics | ||
db *leveldb.DB //the actual DB | ||
quit chan struct{} //quit the reporter loop | ||
} | ||
|
||
//NewMetricsDB creates a new LevelDB instance used to persist metrics defined | ||
//inside p2p/protocols/accounting.go | ||
func NewAccountingMetrics(r metrics.Registry, d time.Duration, path string) *AccountingMetrics { | ||
var val = make([]byte, 8) | ||
var err error | ||
|
||
//Create the LevelDB | ||
db, err := leveldb.OpenFile(path, nil) | ||
if err != nil { | ||
log.Error(err.Error()) | ||
return nil | ||
} | ||
|
||
//Check for all defined metrics that there is a value in the DB | ||
//If there is, assign it to the metric. This means that the node | ||
//has been running before and that metrics have been persisted. | ||
metricsMap := map[string]metrics.Counter{ | ||
"account.balance.credit": mBalanceCredit, | ||
"account.balance.debit": mBalanceDebit, | ||
"account.bytes.credit": mBytesCredit, | ||
"account.bytes.debit": mBytesDebit, | ||
"account.msg.credit": mMsgCredit, | ||
"account.msg.debit": mMsgDebit, | ||
"account.peerdrops": mPeerDrops, | ||
"account.selfdrops": mSelfDrops, | ||
} | ||
//iterate the map and get the values | ||
for key, metric := range metricsMap { | ||
val, err = db.Get([]byte(key), nil) | ||
//until the first time a value is being written, | ||
//this will return an error. | ||
//it could be beneficial though to log errors later, | ||
//but that would require a different logic | ||
if err == nil { | ||
metric.Inc(int64(binary.BigEndian.Uint64(val))) | ||
} | ||
} | ||
|
||
//create the reporter | ||
rep := &reporter{ | ||
reg: r, | ||
interval: d, | ||
db: db, | ||
quit: make(chan struct{}), | ||
} | ||
|
||
//run the go routine | ||
go rep.run() | ||
|
||
m := &AccountingMetrics{ | ||
reporter: rep, | ||
} | ||
|
||
return m | ||
} | ||
|
||
//run is the goroutine which periodically sends the metrics to the configured LevelDB | ||
func (r *reporter) run() { | ||
intervalTicker := time.NewTicker(r.interval) | ||
|
||
for { | ||
select { | ||
case <-intervalTicker.C: | ||
//at each tick send the metrics | ||
if err := r.save(); err != nil { | ||
log.Error("unable to send metrics to LevelDB", "err", err) | ||
//If there is an error in writing, exit the routine; we assume here that the error is | ||
//severe and don't attempt to write again. | ||
//Also, this should prevent leaking when the node is stopped | ||
return | ||
} | ||
case <-r.quit: | ||
//graceful shutdown | ||
return | ||
} | ||
} | ||
} | ||
|
||
//send the metrics to the DB | ||
func (r *reporter) save() error { | ||
//create a LevelDB Batch | ||
batch := leveldb.Batch{} | ||
//for each metric in the registry (which is independent)... | ||
r.reg.Each(func(name string, i interface{}) { | ||
metric, ok := i.(metrics.Counter) | ||
if ok { | ||
//assuming every metric here to be a Counter (separate registry) | ||
//...create a snapshot... | ||
ms := metric.Snapshot() | ||
byteVal := make([]byte, 8) | ||
binary.BigEndian.PutUint64(byteVal, uint64(ms.Count())) | ||
//...and save the value to the DB | ||
batch.Put([]byte(name), byteVal) | ||
} | ||
}) | ||
return r.db.Write(&batch, nil) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
// Copyright 2018 The go-ethereum Authors | ||
// This file is part of the go-ethereum library. | ||
// | ||
// The go-ethereum library is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU Lesser General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
// | ||
// The go-ethereum library is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU Lesser General Public License for more details. | ||
// | ||
// You should have received a copy of the GNU Lesser General Public License | ||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
package protocols | ||
|
||
import ( | ||
"io/ioutil" | ||
"os" | ||
"path/filepath" | ||
"testing" | ||
"time" | ||
|
||
"github.com/ethereum/go-ethereum/log" | ||
) | ||
|
||
//TestReporter tests that the metrics being collected for p2p accounting | ||
//are being persisted and available after restart of a node. | ||
//It simulates restarting by just recreating the DB as if the node had restarted. | ||
func TestReporter(t *testing.T) { | ||
//create a test directory | ||
dir, err := ioutil.TempDir("", "reporter-test") | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
defer os.RemoveAll(dir) | ||
|
||
//setup the metrics | ||
log.Debug("Setting up metrics first time") | ||
reportInterval := 5 * time.Millisecond | ||
metrics := SetupAccountingMetrics(reportInterval, filepath.Join(dir, "test.db")) | ||
log.Debug("Done.") | ||
|
||
//do some metrics | ||
mBalanceCredit.Inc(12) | ||
mBytesCredit.Inc(34) | ||
mMsgDebit.Inc(9) | ||
|
||
//give the reporter time to write the metrics to DB | ||
time.Sleep(20 * time.Millisecond) | ||
|
||
//set the metrics to nil - this effectively simulates the node having shut down... | ||
mBalanceCredit = nil | ||
mBytesCredit = nil | ||
mMsgDebit = nil | ||
//close the DB also, or we can't create a new one | ||
metrics.Close() | ||
|
||
//setup the metrics again | ||
log.Debug("Setting up metrics second time") | ||
metrics = SetupAccountingMetrics(reportInterval, filepath.Join(dir, "test.db")) | ||
defer metrics.Close() | ||
log.Debug("Done.") | ||
|
||
//now check the metrics, they should have the same value as before "shutdown" | ||
if mBalanceCredit.Count() != 12 { | ||
t.Fatalf("Expected counter to be %d, but is %d", 12, mBalanceCredit.Count()) | ||
} | ||
if mBytesCredit.Count() != 34 { | ||
t.Fatalf("Expected counter to be %d, but is %d", 23, mBytesCredit.Count()) | ||
} | ||
if mMsgDebit.Count() != 9 { | ||
t.Fatalf("Expected counter to be %d, but is %d", 9, mMsgDebit.Count()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.