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 }