github.com/calmw/ethereum@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  	"strings"
    13  	"time"
    14  
    15  	"github.com/calmw/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  		cpuSysLoadTotal       = GetOrRegisterCounterFloat64("system/cpu/sysload/total", DefaultRegistry)
   148  		cpuSysWaitTotal       = GetOrRegisterCounterFloat64("system/cpu/syswait/total", DefaultRegistry)
   149  		cpuProcLoadTotal      = GetOrRegisterCounterFloat64("system/cpu/procload/total", DefaultRegistry)
   150  		cpuThreads            = GetOrRegisterGauge("system/cpu/threads", DefaultRegistry)
   151  		cpuGoroutines         = GetOrRegisterGauge("system/cpu/goroutines", DefaultRegistry)
   152  		cpuSchedLatency       = getOrRegisterRuntimeHistogram("system/cpu/schedlatency", secondsToNs, nil)
   153  		memPauses             = getOrRegisterRuntimeHistogram("system/memory/pauses", secondsToNs, nil)
   154  		memAllocs             = GetOrRegisterMeter("system/memory/allocs", DefaultRegistry)
   155  		memFrees              = GetOrRegisterMeter("system/memory/frees", DefaultRegistry)
   156  		memTotal              = GetOrRegisterGauge("system/memory/held", DefaultRegistry)
   157  		heapUsed              = GetOrRegisterGauge("system/memory/used", DefaultRegistry)
   158  		heapObjects           = GetOrRegisterGauge("system/memory/objects", DefaultRegistry)
   159  		diskReads             = GetOrRegisterMeter("system/disk/readcount", DefaultRegistry)
   160  		diskReadBytes         = GetOrRegisterMeter("system/disk/readdata", DefaultRegistry)
   161  		diskReadBytesCounter  = GetOrRegisterCounter("system/disk/readbytes", DefaultRegistry)
   162  		diskWrites            = GetOrRegisterMeter("system/disk/writecount", DefaultRegistry)
   163  		diskWriteBytes        = GetOrRegisterMeter("system/disk/writedata", DefaultRegistry)
   164  		diskWriteBytesCounter = GetOrRegisterCounter("system/disk/writebytes", DefaultRegistry)
   165  	)
   166  
   167  	var lastCollectTime time.Time
   168  
   169  	// Iterate loading the different stats and updating the meters.
   170  	now, prev := 0, 1
   171  	for ; ; now, prev = prev, now {
   172  		// Gather CPU times.
   173  		ReadCPUStats(&cpustats[now])
   174  		collectTime := time.Now()
   175  		secondsSinceLastCollect := collectTime.Sub(lastCollectTime).Seconds()
   176  		lastCollectTime = collectTime
   177  		if secondsSinceLastCollect > 0 {
   178  			sysLoad := cpustats[now].GlobalTime - cpustats[prev].GlobalTime
   179  			sysWait := cpustats[now].GlobalWait - cpustats[prev].GlobalWait
   180  			procLoad := cpustats[now].LocalTime - cpustats[prev].LocalTime
   181  			// Convert to integer percentage.
   182  			cpuSysLoad.Update(int64(sysLoad / secondsSinceLastCollect * 100))
   183  			cpuSysWait.Update(int64(sysWait / secondsSinceLastCollect * 100))
   184  			cpuProcLoad.Update(int64(procLoad / secondsSinceLastCollect * 100))
   185  			// increment counters (ms)
   186  			cpuSysLoadTotal.Inc(sysLoad)
   187  			cpuSysWaitTotal.Inc(sysWait)
   188  			cpuProcLoadTotal.Inc(procLoad)
   189  		}
   190  
   191  		// Threads
   192  		cpuThreads.Update(int64(threadCreateProfile.Count()))
   193  
   194  		// Go runtime metrics
   195  		readRuntimeStats(&rstats[now])
   196  
   197  		cpuGoroutines.Update(int64(rstats[now].Goroutines))
   198  		cpuSchedLatency.update(rstats[now].SchedLatency)
   199  		memPauses.update(rstats[now].GCPauses)
   200  
   201  		memAllocs.Mark(int64(rstats[now].GCAllocBytes - rstats[prev].GCAllocBytes))
   202  		memFrees.Mark(int64(rstats[now].GCFreedBytes - rstats[prev].GCFreedBytes))
   203  
   204  		memTotal.Update(int64(rstats[now].MemTotal))
   205  		heapUsed.Update(int64(rstats[now].MemTotal - rstats[now].HeapUnused - rstats[now].HeapFree - rstats[now].HeapReleased))
   206  		heapObjects.Update(int64(rstats[now].HeapObjects))
   207  
   208  		// Disk
   209  		if ReadDiskStats(&diskstats[now]) == nil {
   210  			diskReads.Mark(diskstats[now].ReadCount - diskstats[prev].ReadCount)
   211  			diskReadBytes.Mark(diskstats[now].ReadBytes - diskstats[prev].ReadBytes)
   212  			diskWrites.Mark(diskstats[now].WriteCount - diskstats[prev].WriteCount)
   213  			diskWriteBytes.Mark(diskstats[now].WriteBytes - diskstats[prev].WriteBytes)
   214  			diskReadBytesCounter.Inc(diskstats[now].ReadBytes - diskstats[prev].ReadBytes)
   215  			diskWriteBytesCounter.Inc(diskstats[now].WriteBytes - diskstats[prev].WriteBytes)
   216  		}
   217  
   218  		time.Sleep(refresh)
   219  	}
   220  }