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  }