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 }