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