mirror of https://github.com/ethereum/go-ethereum
les, les/flowcontrol: implement LES/3 (#19329)
les, les/flowcontrol: implement LES/3pull/19646/head
parent
3d58268bba
commit
58497f46bd
@ -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 |
||||
} |
Loading…
Reference in new issue