les, les/flowcontrol: implement LES/3 (#19329)
les, les/flowcontrol: implement LES/3
This commit is contained in:
227
les/csvlogger/csvlogger.go
Normal file
227
les/csvlogger/csvlogger.go
Normal file
@@ -0,0 +1,227 @@
|
||||
// Copyright 2019 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 csvlogger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/mclock"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
// Logger is a metrics/events logger that writes logged values and events into a comma separated file
|
||||
type Logger struct {
|
||||
file *os.File
|
||||
started mclock.AbsTime
|
||||
channels []*Channel
|
||||
period time.Duration
|
||||
stopCh, stopped chan struct{}
|
||||
storeCh chan string
|
||||
eventHeader string
|
||||
}
|
||||
|
||||
// NewLogger creates a new Logger
|
||||
func NewLogger(fileName string, updatePeriod time.Duration, eventHeader string) *Logger {
|
||||
if fileName == "" {
|
||||
return nil
|
||||
}
|
||||
f, err := os.Create(fileName)
|
||||
if err != nil {
|
||||
log.Error("Error creating log file", "name", fileName, "error", err)
|
||||
return nil
|
||||
}
|
||||
return &Logger{
|
||||
file: f,
|
||||
period: updatePeriod,
|
||||
stopCh: make(chan struct{}),
|
||||
storeCh: make(chan string, 1),
|
||||
eventHeader: eventHeader,
|
||||
}
|
||||
}
|
||||
|
||||
// NewChannel creates a new value logger channel that writes values in a single
|
||||
// column. If the relative change of the value is bigger than the given threshold
|
||||
// then a new line is added immediately (threshold can also be 0).
|
||||
func (l *Logger) NewChannel(name string, threshold float64) *Channel {
|
||||
if l == nil {
|
||||
return nil
|
||||
}
|
||||
c := &Channel{
|
||||
logger: l,
|
||||
name: name,
|
||||
threshold: threshold,
|
||||
}
|
||||
l.channels = append(l.channels, c)
|
||||
return c
|
||||
}
|
||||
|
||||
// NewMinMaxChannel creates a new value logger channel that writes the minimum and
|
||||
// maximum of the tracked value in two columns. It never triggers adding a new line.
|
||||
// If zeroDefault is true then 0 is written to both min and max columns if no update
|
||||
// was given during the last period. If it is false then the last update will appear
|
||||
// in both columns.
|
||||
func (l *Logger) NewMinMaxChannel(name string, zeroDefault bool) *Channel {
|
||||
if l == nil {
|
||||
return nil
|
||||
}
|
||||
c := &Channel{
|
||||
logger: l,
|
||||
name: name,
|
||||
minmax: true,
|
||||
mmZeroDefault: zeroDefault,
|
||||
}
|
||||
l.channels = append(l.channels, c)
|
||||
return c
|
||||
}
|
||||
|
||||
func (l *Logger) store(event string) {
|
||||
s := fmt.Sprintf("%g", float64(mclock.Now()-l.started)/1000000000)
|
||||
for _, ch := range l.channels {
|
||||
s += ", " + ch.store()
|
||||
}
|
||||
if event != "" {
|
||||
s += ", " + event
|
||||
}
|
||||
l.file.WriteString(s + "\n")
|
||||
}
|
||||
|
||||
// Start writes the header line and starts the logger
|
||||
func (l *Logger) Start() {
|
||||
if l == nil {
|
||||
return
|
||||
}
|
||||
l.started = mclock.Now()
|
||||
s := "Time"
|
||||
for _, ch := range l.channels {
|
||||
s += ", " + ch.header()
|
||||
}
|
||||
if l.eventHeader != "" {
|
||||
s += ", " + l.eventHeader
|
||||
}
|
||||
l.file.WriteString(s + "\n")
|
||||
go func() {
|
||||
timer := time.NewTimer(l.period)
|
||||
for {
|
||||
select {
|
||||
case <-timer.C:
|
||||
l.store("")
|
||||
timer.Reset(l.period)
|
||||
case event := <-l.storeCh:
|
||||
l.store(event)
|
||||
if !timer.Stop() {
|
||||
<-timer.C
|
||||
}
|
||||
timer.Reset(l.period)
|
||||
case <-l.stopCh:
|
||||
close(l.stopped)
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Stop stops the logger and closes the file
|
||||
func (l *Logger) Stop() {
|
||||
if l == nil {
|
||||
return
|
||||
}
|
||||
l.stopped = make(chan struct{})
|
||||
close(l.stopCh)
|
||||
<-l.stopped
|
||||
l.file.Close()
|
||||
}
|
||||
|
||||
// Event immediately adds a new line and adds the given event string in the last column
|
||||
func (l *Logger) Event(event string) {
|
||||
if l == nil {
|
||||
return
|
||||
}
|
||||
select {
|
||||
case l.storeCh <- event:
|
||||
case <-l.stopCh:
|
||||
}
|
||||
}
|
||||
|
||||
// Channel represents a logger channel tracking a single value
|
||||
type Channel struct {
|
||||
logger *Logger
|
||||
lock sync.Mutex
|
||||
name string
|
||||
threshold, storeMin, storeMax, lastValue, min, max float64
|
||||
minmax, mmSet, mmZeroDefault bool
|
||||
}
|
||||
|
||||
// Update updates the tracked value
|
||||
func (lc *Channel) Update(value float64) {
|
||||
if lc == nil {
|
||||
return
|
||||
}
|
||||
lc.lock.Lock()
|
||||
defer lc.lock.Unlock()
|
||||
|
||||
lc.lastValue = value
|
||||
if lc.minmax {
|
||||
if value > lc.max || !lc.mmSet {
|
||||
lc.max = value
|
||||
}
|
||||
if value < lc.min || !lc.mmSet {
|
||||
lc.min = value
|
||||
}
|
||||
lc.mmSet = true
|
||||
} else {
|
||||
if value < lc.storeMin || value > lc.storeMax {
|
||||
select {
|
||||
case lc.logger.storeCh <- "":
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (lc *Channel) store() (s string) {
|
||||
lc.lock.Lock()
|
||||
defer lc.lock.Unlock()
|
||||
|
||||
if lc.minmax {
|
||||
s = fmt.Sprintf("%g, %g", lc.min, lc.max)
|
||||
lc.mmSet = false
|
||||
if lc.mmZeroDefault {
|
||||
lc.min = 0
|
||||
} else {
|
||||
lc.min = lc.lastValue
|
||||
}
|
||||
lc.max = lc.min
|
||||
} else {
|
||||
s = fmt.Sprintf("%g", lc.lastValue)
|
||||
lc.storeMin = lc.lastValue * (1 - lc.threshold)
|
||||
lc.storeMax = lc.lastValue * (1 + lc.threshold)
|
||||
if lc.lastValue < 0 {
|
||||
lc.storeMin, lc.storeMax = lc.storeMax, lc.storeMin
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (lc *Channel) header() string {
|
||||
if lc.minmax {
|
||||
return lc.name + " (min), " + lc.name + " (max)"
|
||||
}
|
||||
return lc.name
|
||||
}
|
Reference in New Issue
Block a user