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