github.com/tacshi/go-ethereum@v0.0.0-20230616113857-84a434e20921/metrics/metrics.go (about)

     1  // Go port of Coda Hale's Metrics library
     2  //
     3  // <https://github.com/rcrowley/go-metrics>
     4  //
     5  // Coda Hale's original work: <https://github.com/codahale/metrics>
     6  package metrics
     7  
     8  import (
     9  	"os"
    10  	"runtime/metrics"
    11  	"runtime/pprof"
    12  	"strings"
    13  	"time"
    14  
    15  	"github.com/tacshi/go-ethereum/log"
    16  )
    17  
    18  // Enabled is checked by the constructor functions for all of the
    19  // standard metrics. If it is true, the metric returned is a stub.
    20  //
    21  // This global kill-switch helps quantify the observer effect and makes
    22  // for less cluttered pprof profiles.
    23  var Enabled = false
    24  
    25  // EnabledExpensive is a soft-flag meant for external packages to check if costly
    26  // metrics gathering is allowed or not. The goal is to separate standard metrics
    27  // for health monitoring and debug metrics that might impact runtime performance.
    28  var EnabledExpensive = false
    29  
    30  // enablerFlags is the CLI flag names to use to enable metrics collections.
    31  var enablerFlags = []string{"metrics"}
    32  
    33  // expensiveEnablerFlags is the CLI flag names to use to enable metrics collections.
    34  var expensiveEnablerFlags = []string{"metrics.expensive"}
    35  
    36  // Init enables or disables the metrics system. Since we need this to run before
    37  // any other code gets to create meters and timers, we'll actually do an ugly hack
    38  // and peek into the command line args for the metrics flag.
    39  func init() {
    40  	for _, arg := range os.Args {
    41  		flag := strings.TrimLeft(arg, "-")
    42  
    43  		for _, enabler := range enablerFlags {
    44  			if !Enabled && flag == enabler {
    45  				log.Info("Enabling metrics collection")
    46  				Enabled = true
    47  			}
    48  		}
    49  		for _, enabler := range expensiveEnablerFlags {
    50  			if !EnabledExpensive && flag == enabler {
    51  				log.Info("Enabling expensive metrics collection")
    52  				EnabledExpensive = true
    53  			}
    54  		}
    55  	}
    56  }
    57  
    58  var threadCreateProfile = pprof.Lookup("threadcreate")
    59  
    60  type runtimeStats struct {
    61  	GCPauses     *metrics.Float64Histogram
    62  	GCAllocBytes uint64
    63  	GCFreedBytes uint64
    64  
    65  	MemTotal     uint64
    66  	HeapObjects  uint64
    67  	HeapFree     uint64
    68  	HeapReleased uint64
    69  	HeapUnused   uint64
    70  
    71  	Goroutines   uint64
    72  	SchedLatency *metrics.Float64Histogram
    73  }
    74  
    75  var runtimeSamples = []metrics.Sample{
    76  	{Name: "/gc/pauses:seconds"}, // histogram
    77  	{Name: "/gc/heap/allocs:bytes"},
    78  	{Name: "/gc/heap/frees:bytes"},
    79  	{Name: "/memory/classes/total:bytes"},
    80  	{Name: "/memory/classes/heap/objects:bytes"},
    81  	{Name: "/memory/classes/heap/free:bytes"},
    82  	{Name: "/memory/classes/heap/released:bytes"},
    83  	{Name: "/memory/classes/heap/unused:bytes"},
    84  	{Name: "/sched/goroutines:goroutines"},
    85  	{Name: "/sched/latencies:seconds"}, // histogram
    86  }
    87  
    88  func readRuntimeStats(v *runtimeStats) {
    89  	metrics.Read(runtimeSamples)
    90  	for _, s := range runtimeSamples {
    91  		// Skip invalid/unknown metrics. This is needed because some metrics
    92  		// are unavailable in older Go versions, and attempting to read a 'bad'
    93  		// metric panics.
    94  		if s.Value.Kind() == metrics.KindBad {
    95  			continue
    96  		}
    97  
    98  		switch s.Name {
    99  		case "/gc/pauses:seconds":
   100  			v.GCPauses = s.Value.Float64Histogram()
   101  		case "/gc/heap/allocs:bytes":
   102  			v.GCAllocBytes = s.Value.Uint64()
   103  		case "/gc/heap/frees:bytes":
   104  			v.GCFreedBytes = s.Value.Uint64()
   105  		case "/memory/classes/total:bytes":
   106  			v.MemTotal = s.Value.Uint64()
   107  		case "/memory/classes/heap/objects:bytes":
   108  			v.HeapObjects = s.Value.Uint64()
   109  		case "/memory/classes/heap/free:bytes":
   110  			v.HeapFree = s.Value.Uint64()
   111  		case "/memory/classes/heap/released:bytes":
   112  			v.HeapReleased = s.Value.Uint64()
   113  		case "/memory/classes/heap/unused:bytes":
   114  			v.HeapUnused = s.Value.Uint64()
   115  		case "/sched/goroutines:goroutines":
   116  			v.Goroutines = s.Value.Uint64()
   117  		case "/sched/latencies:seconds":
   118  			v.SchedLatency = s.Value.Float64Histogram()
   119  		}
   120  	}
   121  }
   122  
   123  // CollectProcessMetrics periodically collects various metrics about the running process.
   124  func CollectProcessMetrics(refresh time.Duration) {
   125  	// Short circuit if the metrics system is disabled
   126  	if !Enabled {
   127  		return
   128  	}
   129  
   130  	// Create the various data collectors
   131  	var (
   132  		cpustats  = make([]CPUStats, 2)
   133  		diskstats = make([]DiskStats, 2)
   134  		rstats    = make([]runtimeStats, 2)
   135  	)
   136  
   137  	// This scale factor is used for the runtime's time metrics. It's useful to convert to
   138  	// ns here because the runtime gives times in float seconds, but runtimeHistogram can
   139  	// only provide integers for the minimum and maximum values.
   140  	const secondsToNs = float64(time.Second)
   141  
   142  	// Define the various metrics to collect
   143  	var (
   144  		cpuSysLoad            = GetOrRegisterGauge("system/cpu/sysload", DefaultRegistry)
   145  		cpuSysWait            = GetOrRegisterGauge("system/cpu/syswait", DefaultRegistry)
   146  		cpuProcLoad           = GetOrRegisterGauge("system/cpu/procload", DefaultRegistry)
   147  		cpuThreads            = GetOrRegisterGauge("system/cpu/threads", DefaultRegistry)
   148  		cpuGoroutines         = GetOrRegisterGauge("system/cpu/goroutines", DefaultRegistry)
   149  		cpuSchedLatency       = getOrRegisterRuntimeHistogram("system/cpu/schedlatency", secondsToNs, nil)
   150  		memPauses             = getOrRegisterRuntimeHistogram("system/memory/pauses", secondsToNs, nil)
   151  		memAllocs             = GetOrRegisterMeter("system/memory/allocs", DefaultRegistry)
   152  		memFrees              = GetOrRegisterMeter("system/memory/frees", DefaultRegistry)
   153  		memTotal              = GetOrRegisterGauge("system/memory/held", DefaultRegistry)
   154  		heapUsed              = GetOrRegisterGauge("system/memory/used", DefaultRegistry)
   155  		heapObjects           = GetOrRegisterGauge("system/memory/objects", DefaultRegistry)
   156  		diskReads             = GetOrRegisterMeter("system/disk/readcount", DefaultRegistry)
   157  		diskReadBytes         = GetOrRegisterMeter("system/disk/readdata", DefaultRegistry)
   158  		diskReadBytesCounter  = GetOrRegisterCounter("system/disk/readbytes", DefaultRegistry)
   159  		diskWrites            = GetOrRegisterMeter("system/disk/writecount", DefaultRegistry)
   160  		diskWriteBytes        = GetOrRegisterMeter("system/disk/writedata", DefaultRegistry)
   161  		diskWriteBytesCounter = GetOrRegisterCounter("system/disk/writebytes", DefaultRegistry)
   162  	)
   163  
   164  	var lastCollectTime time.Time
   165  
   166  	// Iterate loading the different stats and updating the meters.
   167  	now, prev := 0, 1
   168  	for ; ; now, prev = prev, now {
   169  		// Gather CPU times.
   170  		ReadCPUStats(&cpustats[now])
   171  		collectTime := time.Now()
   172  		secondsSinceLastCollect := collectTime.Sub(lastCollectTime).Seconds()
   173  		lastCollectTime = collectTime
   174  		if secondsSinceLastCollect > 0 {
   175  			sysLoad := (cpustats[now].GlobalTime - cpustats[prev].GlobalTime) / secondsSinceLastCollect
   176  			sysWait := (cpustats[now].GlobalWait - cpustats[prev].GlobalWait) / secondsSinceLastCollect
   177  			procLoad := (cpustats[now].LocalTime - cpustats[prev].LocalTime) / secondsSinceLastCollect
   178  			// Convert to integer percentage.
   179  			cpuSysLoad.Update(int64(sysLoad * 100))
   180  			cpuSysWait.Update(int64(sysWait * 100))
   181  			cpuProcLoad.Update(int64(procLoad * 100))
   182  		}
   183  
   184  		// Threads
   185  		cpuThreads.Update(int64(threadCreateProfile.Count()))
   186  
   187  		// Go runtime metrics
   188  		readRuntimeStats(&rstats[now])
   189  
   190  		cpuGoroutines.Update(int64(rstats[now].Goroutines))
   191  		cpuSchedLatency.update(rstats[now].SchedLatency)
   192  		memPauses.update(rstats[now].GCPauses)
   193  
   194  		memAllocs.Mark(int64(rstats[now].GCAllocBytes - rstats[prev].GCAllocBytes))
   195  		memFrees.Mark(int64(rstats[now].GCFreedBytes - rstats[prev].GCFreedBytes))
   196  
   197  		memTotal.Update(int64(rstats[now].MemTotal))
   198  		heapUsed.Update(int64(rstats[now].MemTotal - rstats[now].HeapUnused - rstats[now].HeapFree - rstats[now].HeapReleased))
   199  		heapObjects.Update(int64(rstats[now].HeapObjects))
   200  
   201  		// Disk
   202  		if ReadDiskStats(&diskstats[now]) == nil {
   203  			diskReads.Mark(diskstats[now].ReadCount - diskstats[prev].ReadCount)
   204  			diskReadBytes.Mark(diskstats[now].ReadBytes - diskstats[prev].ReadBytes)
   205  			diskWrites.Mark(diskstats[now].WriteCount - diskstats[prev].WriteCount)
   206  			diskWriteBytes.Mark(diskstats[now].WriteBytes - diskstats[prev].WriteBytes)
   207  			diskReadBytesCounter.Inc(diskstats[now].ReadBytes - diskstats[prev].ReadBytes)
   208  			diskWriteBytesCounter.Inc(diskstats[now].WriteBytes - diskstats[prev].WriteBytes)
   209  		}
   210  
   211  		time.Sleep(refresh)
   212  	}
   213  }