memorylimiter.go 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. package monitor
  2. import (
  3. "fmt"
  4. "math"
  5. "runtime"
  6. "runtime/debug"
  7. "sync"
  8. "time"
  9. "github.com/opencost/opencost/core/pkg/log"
  10. "github.com/opencost/opencost/core/pkg/util/atomic"
  11. "github.com/opencost/opencost/core/pkg/util/monitor/memory"
  12. )
  13. var (
  14. once sync.Once
  15. memoryLimiter *MemoryLimiter
  16. )
  17. // MemoryLimiter is a heap usage monitor for the go runtime which will attempt to
  18. // dynamically set a GOMEMLIMIT value to best fit the heap usage. It will only
  19. // adjust the GOMEMLIMIT if the usage analysis results in an increase, and won't
  20. // try to "best fit" the current usage. It takes into account the initial GOMEMLIMIT
  21. // value as the baseline.
  22. type MemoryLimiter struct {
  23. runState atomic.AtomicRunState
  24. monitor *memory.MemoryLimitStats
  25. }
  26. // Start begins collecting heap allocation samples for automatically adjusting the go soft memory limit
  27. // for heap usage.
  28. func (ml *MemoryLimiter) Start(interval time.Duration) error {
  29. ml.runState.WaitForReset()
  30. if !ml.runState.Start() {
  31. return fmt.Errorf("memory limiter was already started")
  32. }
  33. // main limiter driver
  34. go func() {
  35. var memStats runtime.MemStats
  36. var prevLimit uint64
  37. // determine if mem limit was set prior by passing a negative
  38. // value to SetMemoryLimit, which will return the current value
  39. // without making any changes -- the default is MaxInt64
  40. goMemLimit := debug.SetMemoryLimit(-1)
  41. if goMemLimit == math.MaxInt64 {
  42. prevLimit = 0
  43. } else {
  44. prevLimit = uint64(goMemLimit)
  45. }
  46. // take initial heap measurement
  47. runtime.ReadMemStats(&memStats)
  48. ml.monitor.Record(memStats.HeapAlloc)
  49. for {
  50. select {
  51. case <-ml.runState.OnStop():
  52. ml.runState.Reset()
  53. return
  54. case <-time.After(interval):
  55. }
  56. // in the event that someone updates the limit outside of this monitor
  57. // we want to make sure that we synchronize the correct value
  58. goMemLimit = debug.SetMemoryLimit(-1)
  59. if goMemLimit != math.MaxInt64 && goMemLimit != int64(prevLimit) {
  60. prevLimit = uint64(goMemLimit)
  61. }
  62. // record and determine if we should update the memory limit
  63. runtime.ReadMemStats(&memStats)
  64. if softLimit, updated := ml.monitor.Record(memStats.HeapAlloc); updated {
  65. // we only allow the limit to increase for now, as this best reflects a
  66. // max stable set of samples. Worth observation and potentially updating
  67. // in the future
  68. if softLimit != 0 && softLimit > prevLimit {
  69. prevLimit = softLimit
  70. log.Debugf("Updating Go Memory Limit: %dmb", int64(softLimit/1024.0/1024.0))
  71. debug.SetMemoryLimit(int64(softLimit))
  72. }
  73. }
  74. }
  75. }()
  76. return nil
  77. }
  78. // Stops automatically adjusting the memory limiter
  79. func (ml *MemoryLimiter) Stop() error {
  80. if !ml.runState.Stop() {
  81. return fmt.Errorf("could not stop memory limiter - in the state of stopping or already stopped")
  82. }
  83. return nil
  84. }
  85. // returns the singleton instance of the memory limiter
  86. func getMemoryLimiter() *MemoryLimiter {
  87. once.Do(func() {
  88. config := memory.DefaultMemoryLimitConfig()
  89. memoryLimiter = &MemoryLimiter{
  90. monitor: memory.NewMemoryLimitStats(config),
  91. }
  92. })
  93. return memoryLimiter
  94. }
  95. // DefaultMemoryLimiterSampleInterval is the sample interval in which the auto limiter
  96. // gathers heap usage.
  97. const DefaultMemoryLimiterSampleInterval = time.Second
  98. func StartMemoryLimiter() error {
  99. return getMemoryLimiter().Start(DefaultMemoryLimiterSampleInterval)
  100. }
  101. func StopMemoryLimiter() error {
  102. return getMemoryLimiter().Stop()
  103. }