github.com/mongodb/grip@v0.0.0-20240213223901-f906268d82b9/message/golang_info.go (about) 1 package message 2 3 import ( 4 "runtime" 5 "sync" 6 "time" 7 8 "github.com/mongodb/grip/level" 9 ) 10 11 var goStatsCache *goStats 12 13 func init() { goStatsCache = &goStats{} } 14 15 type goStatsData struct { 16 previous int64 17 current int64 18 } 19 20 func (d goStatsData) diff() int64 { return d.current - d.previous } 21 22 type goStats struct { 23 cgoCalls goStatsData 24 mallocCounter goStatsData 25 freesCounter goStatsData 26 gcRate goStatsData 27 gcPause uint64 28 lastGC time.Time 29 lastCollection time.Time 30 durSinceLastUpdate time.Duration 31 32 sync.Mutex 33 } 34 35 func (s *goStats) update() *runtime.MemStats { 36 now := time.Now() 37 38 m := runtime.MemStats{} 39 runtime.ReadMemStats(&m) 40 41 s.lastGC = time.Unix(0, int64(m.LastGC)) 42 s.gcPause = m.PauseNs[(m.NumGC+255)%256] 43 44 s.cgoCalls.previous = s.cgoCalls.current 45 s.cgoCalls.current = runtime.NumCgoCall() 46 47 s.mallocCounter.previous = s.mallocCounter.current 48 s.mallocCounter.current = int64(m.Mallocs) 49 50 s.freesCounter.previous = s.freesCounter.current 51 s.freesCounter.current = int64(m.Frees) 52 53 s.gcRate.previous = s.gcRate.current 54 s.gcRate.current = int64(m.NumGC) 55 56 s.durSinceLastUpdate = now.Sub(s.lastCollection) 57 s.lastCollection = now 58 59 return &m 60 } 61 62 type statRate struct { 63 Delta int64 `bson:"delta" json:"delta" yaml:"delta"` 64 Duration time.Duration `bson:"duration" json:"duration" yaml:"duration"` 65 } 66 67 func (s *goStats) getRate(stat int64) statRate { 68 if s.durSinceLastUpdate == 0 { 69 return statRate{} 70 } 71 72 return statRate{Delta: stat, Duration: s.durSinceLastUpdate} 73 } 74 75 func (s *goStats) cgo() statRate { return s.getRate(s.cgoCalls.diff()) } 76 func (s *goStats) mallocs() statRate { return s.getRate(s.mallocCounter.diff()) } 77 func (s *goStats) frees() statRate { return s.getRate(s.freesCounter.diff()) } 78 func (s *goStats) gcs() statRate { return s.getRate(s.gcRate.diff()) } 79 80 func (s statRate) float() float64 { 81 if s.Duration == 0 { 82 return 0 83 } 84 return float64(s.Delta) / float64(s.Duration) 85 } 86 func (s statRate) int() int64 { 87 if s.Duration == 0 { 88 return 0 89 } 90 return s.Delta / int64(s.Duration) 91 } 92 93 // CollectBasicGoStats returns some very basic runtime statistics about the 94 // current go process, using runtime.MemStats and 95 // runtime.NumGoroutine. 96 // 97 // The data reported for the runtime event metrics (e.g. mallocs, 98 // frees, gcs, and cgo calls,) are the counts since the last time 99 // metrics were collected, and are reported as rates calculated since 100 // the last time the statics were collected. 101 // 102 // Values are cached between calls, to produce the deltas. For the 103 // best results, collect these messages on a regular interval. 104 // 105 // Internally, this uses message.Fields message type, which means the 106 // order of the fields when serialized is not defined and applications 107 // cannot manipulate the Raw value of this composer. 108 // 109 // The basic idea is taken from https://github.com/YoSmudge/go-stats. 110 func CollectBasicGoStats() Composer { 111 goStatsCache.Lock() 112 defer goStatsCache.Unlock() 113 m := goStatsCache.update() 114 115 return MakeFields(Fields{ 116 "memory.objects.HeapObjects": m.HeapObjects, 117 "memory.summary.Alloc": m.Alloc, 118 "memory.summary.System": m.HeapSys, 119 "memory.heap.Idle": m.HeapIdle, 120 "memory.heap.InUse": m.HeapInuse, 121 "memory.counters.mallocs": goStatsCache.mallocs().float(), 122 "memory.counters.frees": goStatsCache.frees().float(), 123 "gc.rate.perSecond": goStatsCache.gcs().float(), 124 "gc.pause.sinceLast.span": int64(time.Since(goStatsCache.lastGC)), 125 "gc.pause.sinceLast.string": time.Since(goStatsCache.lastGC).String(), 126 "gc.pause.last.duration.span": goStatsCache.gcPause, 127 "gc.pause.last.span": time.Duration(goStatsCache.gcPause).String(), 128 "goroutines.total": runtime.NumGoroutine(), 129 "cgo.calls": goStatsCache.cgo().float(), 130 }) 131 } 132 133 // GoRuntimeInfo provides 134 type GoRuntimeInfo struct { 135 HeapObjects uint64 `bson:"memory.objects.heap" json:"memory.objects.heap" yaml:"memory.objects.heap"` 136 Alloc uint64 `bson:"memory.summary.alloc" json:"memory.summary.alloc" yaml:"memory.summary.alloc"` 137 HeapSystem uint64 `bson:"memory.summary.system" json:"memory.summary.system" yaml:"memory.summary.system"` 138 HeapIdle uint64 `bson:"memory.heap.idle" json:"memory.heap.idle" yaml:"memory.heap.idle"` 139 HeapInUse uint64 `bson:"memory.heap.used" json:"memory.heap.used" yaml:"memory.heap.used"` 140 Mallocs int64 `bson:"memory.counters.mallocs" json:"memory.counters.mallocs" yaml:"memory.counters.mallocs"` 141 Frees int64 `bson:"memory.counters.frees" json:"memory.counters.frees" yaml:"memory.counters.frees"` 142 GC int64 `bson:"gc.rate" json:"gc.rate" yaml:"gc.rate"` 143 GCPause time.Duration `bson:"gc.pause.duration.last" json:"gc.pause.last" yaml:"gc.pause.last"` 144 GCLatency time.Duration `bson:"gc.pause.duration.latency" json:"gc.pause.duration.latency" yaml:"gc.pause.duration.latency"` 145 Goroutines int64 `bson:"goroutines.total" json:"goroutines.total" yaml:"goroutines.total"` 146 CgoCalls int64 `bson:"cgo.calls" json:"cgo.calls" yaml:"cgo.calls"` 147 148 Message string `bson:"message" json:"message" yaml:"message"` 149 Base `json:"metadata,omitempty" bson:"metadata,omitempty" yaml:"metadata,omitempty"` 150 151 loggable bool 152 useDeltas bool 153 useRates bool 154 rendered string 155 } 156 157 // CollectGoStatsTotals constructs a Composer, which is a 158 // GoRuntimeInfo internally, that contains data collected from the Go 159 // runtime about the state of memory use and garbage collection. 160 // 161 // The data reported for the runtime event metrics (e.g. mallocs, 162 // frees, gcs, and cgo calls,) are totals collected since the 163 // beginning on the runtime. 164 func CollectGoStatsTotals() Composer { 165 s := &GoRuntimeInfo{} 166 s.build() 167 168 return s 169 } 170 171 // MakeGoStatsTotals has the same semantics as CollectGoStatsTotals, 172 // but additionally allows you to set a message string to annotate the 173 // data. 174 func MakeGoStatsTotals(msg string) Composer { 175 s := &GoRuntimeInfo{Message: msg} 176 s.build() 177 178 return s 179 } 180 181 // NewGoStatsTotals has the same semantics as CollectGoStatsTotals, 182 // but additionally allows you to set a message string and log level 183 // to annotate the data. 184 func NewGoStatsTotals(p level.Priority, msg string) Composer { 185 s := &GoRuntimeInfo{Message: msg} 186 s.build() 187 _ = s.SetPriority(p) 188 return s 189 } 190 191 // CollectGoStatsDeltas constructs a Composer, which is a 192 // GoRuntimeInfo internally, that contains data collected from the Go 193 // runtime about the state of memory use and garbage collection. 194 // 195 // The data reported for the runtime event metrics (e.g. mallocs, 196 // frees, gcs, and cgo calls,) are the counts since the last time 197 // metrics were collected. 198 // 199 // Values are cached between calls, to produce the deltas. For the 200 // best results, collect these messages on a regular interval. 201 func CollectGoStatsDeltas() Composer { 202 s := &GoRuntimeInfo{useDeltas: true} 203 s.build() 204 205 return s 206 } 207 208 // MakeGoStatsDeltas has the same semantics as CollectGoStatsDeltas, 209 // but additionally allows you to set a message string to annotate the 210 // data. 211 func MakeGoStatsDeltas(msg string) Composer { 212 s := &GoRuntimeInfo{Message: msg, useDeltas: true} 213 s.build() 214 return s 215 } 216 217 // NewGoStatsDeltas has the same semantics as CollectGoStatsDeltas, 218 // but additionally allows you to set a message string to annotate the 219 // data. 220 func NewGoStatsDeltas(p level.Priority, msg string) Composer { 221 s := &GoRuntimeInfo{Message: msg, useDeltas: true} 222 s.build() 223 _ = s.SetPriority(p) 224 return s 225 } 226 227 // CollectGoStatsRates constructs a Composer, which is a 228 // GoRuntimeInfo internally, that contains data collected from the Go 229 // runtime about the state of memory use and garbage collection. 230 // 231 // The data reported for the runtime event metrics (e.g. mallocs, 232 // frees, gcs, and cgo calls,) are the counts since the last time 233 // metrics were collected, divided by the time since the last 234 // time the metric was collected, to produce a rate, which is 235 // calculated using integer division. 236 // 237 // For the best results, collect these messages on a regular interval. 238 func CollectGoStatsRates() Composer { 239 s := &GoRuntimeInfo{useRates: true} 240 s.build() 241 242 return s 243 } 244 245 // MakeGoStatsRates has the same semantics as CollectGoStatsRates, 246 // but additionally allows you to set a message string to annotate the 247 // data. 248 func MakeGoStatsRates(msg string) Composer { 249 s := &GoRuntimeInfo{Message: msg, useRates: true} 250 s.build() 251 return s 252 } 253 254 // NewGoStatsRates has the same semantics as CollectGoStatsRates, 255 // but additionally allows you to set a message string to annotate the 256 // data. 257 func NewGoStatsRates(p level.Priority, msg string) Composer { 258 s := &GoRuntimeInfo{Message: msg, useRates: true} 259 s.build() 260 _ = s.SetPriority(p) 261 return s 262 } 263 264 func (s *GoRuntimeInfo) doCollect() { _ = s.Collect(true) } 265 266 func (s *GoRuntimeInfo) build() { 267 goStatsCache.Lock() 268 defer goStatsCache.Unlock() 269 m := goStatsCache.update() 270 271 s.HeapObjects = m.HeapObjects 272 s.Alloc = m.Alloc 273 s.HeapSystem = m.HeapSys 274 s.HeapIdle = m.HeapIdle 275 s.HeapInUse = m.HeapInuse 276 s.Goroutines = int64(runtime.NumGoroutine()) 277 278 s.GCLatency = time.Since(goStatsCache.lastGC) 279 s.GCPause = time.Duration(goStatsCache.gcPause) 280 281 if s.useDeltas { 282 s.Mallocs = goStatsCache.mallocs().Delta 283 s.Frees = goStatsCache.frees().Delta 284 s.GC = goStatsCache.gcs().Delta 285 s.CgoCalls = goStatsCache.cgo().Delta 286 } else if s.useRates { 287 s.Mallocs = goStatsCache.mallocs().int() 288 s.Frees = goStatsCache.frees().int() 289 s.GC = goStatsCache.gcs().int() 290 s.CgoCalls = goStatsCache.cgo().int() 291 } else { 292 s.Mallocs = goStatsCache.mallocCounter.current 293 s.Frees = goStatsCache.freesCounter.current 294 s.GC = goStatsCache.gcRate.current 295 s.CgoCalls = goStatsCache.cgoCalls.current 296 } 297 298 s.loggable = true 299 } 300 301 // Loggable returns true when the GoRuntimeInfo structure is 302 // populated. Loggable is part of the Composer interface. 303 func (s *GoRuntimeInfo) Loggable() bool { return s.loggable } 304 305 // Raw is part of the Composer interface and returns the GoRuntimeInfo 306 // object itself. 307 func (s *GoRuntimeInfo) Raw() interface{} { s.doCollect(); return s } 308 func (s *GoRuntimeInfo) String() string { 309 s.doCollect() 310 311 if s.rendered == "" { 312 s.rendered = renderStatsString(s.Message, s) 313 } 314 315 return s.rendered 316 }