github.com/rudderlabs/rudder-go-kit@v0.30.0/stats/periodic.go (about)

     1  package stats
     2  
     3  import (
     4  	"context"
     5  	"runtime"
     6  	"time"
     7  
     8  	"github.com/rudderlabs/rudder-go-kit/stats/metric"
     9  )
    10  
    11  // GaugeFunc is an interface that implements the setting of a gauge value
    12  // in a stats system. It should be expected that key will contain multiple
    13  // parts separated by the '.' character in the form used by statsd (e.x.
    14  // "mem.heap.alloc")
    15  type gaugeFunc func(key string, val uint64)
    16  
    17  // periodicStatsConfig is the configuration for the periodic stats collection
    18  type periodicStatsConfig struct {
    19  	enabled                 bool
    20  	statsCollectionInterval int64
    21  	enableCPUStats          bool
    22  	enableMemStats          bool
    23  	enableGCStats           bool
    24  	metricManager           metric.Manager
    25  }
    26  
    27  // runtimeStatsCollector implements the periodic grabbing of informational data from the
    28  // runtime package and outputting the values to a GaugeFunc.
    29  type runtimeStatsCollector struct {
    30  	// PauseDur represents the interval in between each set of stats output.
    31  	// Defaults to 10 seconds.
    32  	PauseDur time.Duration
    33  
    34  	// EnableCPU determines whether CPU statistics will be output. Defaults to true.
    35  	EnableCPU bool
    36  
    37  	// EnableMem determines whether memory statistics will be output. Defaults to true.
    38  	EnableMem bool
    39  
    40  	// EnableGC determines whether garbage collection statistics will be output. EnableMem
    41  	// must also be set to true for this to take affect. Defaults to true.
    42  	EnableGC bool
    43  
    44  	// done, when closed, is used to signal runtimeStatsCollector that is should stop collecting
    45  	// statistics and the Run function should return. If done is set, upon shutdown
    46  	// all gauges will be sent a final zero value to reset their values to 0.
    47  	done chan struct{}
    48  
    49  	gaugeFunc gaugeFunc
    50  }
    51  
    52  // New creates a new runtimeStatsCollector that will periodically output statistics to gaugeFunc. It
    53  // will also set the values of the exported fields to the described defaults. The values
    54  // of the exported defaults can be changed at any point before Run is called.
    55  func newRuntimeStatsCollector(gaugeFunc gaugeFunc) runtimeStatsCollector {
    56  	return runtimeStatsCollector{
    57  		PauseDur:  10 * time.Second,
    58  		EnableCPU: true,
    59  		EnableMem: true,
    60  		EnableGC:  true,
    61  		gaugeFunc: gaugeFunc,
    62  		done:      make(chan struct{}),
    63  	}
    64  }
    65  
    66  // Run gathers statistics from package runtime and outputs them to the configured GaugeFunc every
    67  // PauseDur. This function will not return until Done has been closed (or never if Done is nil),
    68  // therefore it should be called in its own goroutine.
    69  func (c runtimeStatsCollector) run(ctx context.Context) {
    70  	defer close(c.done)
    71  	defer c.zeroStats()
    72  	c.outputStats()
    73  
    74  	// Gauges are a 'snapshot' rather than a histogram. Pausing for some interval
    75  	// aims to get a 'recent' snapshot out before statsd flushes metrics.
    76  	tick := time.NewTicker(c.PauseDur)
    77  	defer tick.Stop()
    78  	for {
    79  		select {
    80  		case <-ctx.Done():
    81  			return
    82  		case <-tick.C:
    83  			c.outputStats()
    84  		}
    85  	}
    86  }
    87  
    88  type cpuStats struct {
    89  	NumGoroutine uint64
    90  	NumCgoCall   uint64
    91  }
    92  
    93  // zeroStats sets all the stat guages to zero. On shutdown we want to zero them out so they don't persist
    94  // at their last value until we start back up.
    95  func (c runtimeStatsCollector) zeroStats() {
    96  	if c.EnableCPU {
    97  		cStats := cpuStats{}
    98  		c.outputCPUStats(&cStats)
    99  	}
   100  	if c.EnableMem {
   101  		mStats := runtime.MemStats{}
   102  		c.outputMemStats(&mStats)
   103  		if c.EnableGC {
   104  			c.outputGCStats(&mStats)
   105  		}
   106  	}
   107  }
   108  
   109  func (c runtimeStatsCollector) outputStats() {
   110  	if c.EnableCPU {
   111  		cStats := cpuStats{
   112  			NumGoroutine: uint64(runtime.NumGoroutine()),
   113  			NumCgoCall:   uint64(runtime.NumCgoCall()),
   114  		}
   115  		c.outputCPUStats(&cStats)
   116  	}
   117  	if c.EnableMem {
   118  		m := &runtime.MemStats{}
   119  		runtime.ReadMemStats(m)
   120  		c.outputMemStats(m)
   121  		if c.EnableGC {
   122  			c.outputGCStats(m)
   123  		}
   124  	}
   125  }
   126  
   127  func (c runtimeStatsCollector) outputCPUStats(s *cpuStats) {
   128  	c.gaugeFunc("cpu.goroutines", s.NumGoroutine)
   129  	c.gaugeFunc("cpu.cgo_calls", s.NumCgoCall)
   130  }
   131  
   132  func (c runtimeStatsCollector) outputMemStats(m *runtime.MemStats) {
   133  	// General
   134  	c.gaugeFunc("mem.alloc", m.Alloc)
   135  	c.gaugeFunc("mem.total", m.TotalAlloc)
   136  	c.gaugeFunc("mem.sys", m.Sys)
   137  	c.gaugeFunc("mem.lookups", m.Lookups)
   138  	c.gaugeFunc("mem.malloc", m.Mallocs)
   139  	c.gaugeFunc("mem.frees", m.Frees)
   140  
   141  	// Heap
   142  	c.gaugeFunc("mem.heap.alloc", m.HeapAlloc)
   143  	c.gaugeFunc("mem.heap.sys", m.HeapSys)
   144  	c.gaugeFunc("mem.heap.idle", m.HeapIdle)
   145  	c.gaugeFunc("mem.heap.inuse", m.HeapInuse)
   146  	c.gaugeFunc("mem.heap.released", m.HeapReleased)
   147  	c.gaugeFunc("mem.heap.objects", m.HeapObjects)
   148  
   149  	// Stack
   150  	c.gaugeFunc("mem.stack.inuse", m.StackInuse)
   151  	c.gaugeFunc("mem.stack.sys", m.StackSys)
   152  	c.gaugeFunc("mem.stack.mspan_inuse", m.MSpanInuse)
   153  	c.gaugeFunc("mem.stack.mspan_sys", m.MSpanSys)
   154  	c.gaugeFunc("mem.stack.mcache_inuse", m.MCacheInuse)
   155  	c.gaugeFunc("mem.stack.mcache_sys", m.MCacheSys)
   156  
   157  	c.gaugeFunc("mem.othersys", m.OtherSys)
   158  }
   159  
   160  func (c runtimeStatsCollector) outputGCStats(m *runtime.MemStats) {
   161  	c.gaugeFunc("mem.gc.sys", m.GCSys)
   162  	c.gaugeFunc("mem.gc.next", m.NextGC)
   163  	c.gaugeFunc("mem.gc.last", m.LastGC)
   164  	c.gaugeFunc("mem.gc.pause_total", m.PauseTotalNs)
   165  	c.gaugeFunc("mem.gc.pause", m.PauseNs[(m.NumGC+255)%256])
   166  	c.gaugeFunc("mem.gc.count", uint64(m.NumGC))
   167  	c.gaugeFunc("mem.gc.cpu_percent", uint64(100*m.GCCPUFraction))
   168  }
   169  
   170  // metricStatsCollector implements the periodic grabbing of informational data from the
   171  // metric package and outputting the values as stats
   172  type metricStatsCollector struct {
   173  	stats         Stats
   174  	metricManager metric.Manager
   175  	// PauseDur represents the interval in between each set of stats output.
   176  	// Defaults to 60 seconds.
   177  	pauseDur time.Duration
   178  
   179  	// Done, when closed, is used to signal metricStatsCollector that is should stop collecting
   180  	// statistics and the run function should return.
   181  	done chan struct{}
   182  }
   183  
   184  // newMetricStatsCollector creates a new metricStatsCollector.
   185  func newMetricStatsCollector(stats Stats, metricManager metric.Manager) metricStatsCollector {
   186  	return metricStatsCollector{
   187  		stats:         stats,
   188  		metricManager: metricManager,
   189  		pauseDur:      60 * time.Second,
   190  		done:          make(chan struct{}),
   191  	}
   192  }
   193  
   194  // run gathers statistics from package metric and outputs them as
   195  func (c metricStatsCollector) run(ctx context.Context) {
   196  	defer close(c.done)
   197  	c.outputStats()
   198  
   199  	// Gauges are a 'snapshot' rather than a histogram. Pausing for some interval
   200  	// aims to get a 'recent' snapshot out before statsd flushes metrics.
   201  	tick := time.NewTicker(c.pauseDur)
   202  	defer tick.Stop()
   203  	for {
   204  		select {
   205  		case <-ctx.Done():
   206  			return
   207  		case <-tick.C:
   208  			c.outputStats()
   209  		}
   210  	}
   211  }
   212  
   213  func (c metricStatsCollector) outputStats() {
   214  	if c.metricManager == nil {
   215  		return
   216  	}
   217  	c.metricManager.GetRegistry(metric.PublishedMetrics).Range(func(key, value interface{}) bool {
   218  		m := key.(metric.Measurement)
   219  		switch value := value.(type) {
   220  		case metric.Gauge:
   221  			c.stats.NewTaggedStat(m.GetName(), GaugeType, m.GetTags()).
   222  				Gauge(value.Value())
   223  		case metric.Counter:
   224  			c.stats.NewTaggedStat(m.GetName(), CountType, m.GetTags()).
   225  				Count(int(value.Value()))
   226  		case metric.MovingAverage:
   227  			c.stats.NewTaggedStat(m.GetName(), GaugeType, m.GetTags()).
   228  				Gauge(value.Value())
   229  		}
   230  		return true
   231  	})
   232  }