| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227 |
- // 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
- }
|