github.com/songzhibin97/gkit@v1.2.13/gctuner/tuner.go (about) 1 // Package gctuner implements https://github.com/bytedance/gopkg 2 package gctuner 3 4 import ( 5 "fmt" 6 "github.com/docker/go-units" 7 mem_util "github.com/shirou/gopsutil/mem" 8 "io/ioutil" 9 "math" 10 "os" 11 "runtime/debug" 12 "strconv" 13 "strings" 14 "sync/atomic" 15 ) 16 17 var ( 18 maxGCPercent uint32 = 500 19 minGCPercent uint32 = 50 20 ) 21 22 var defaultGCPercent uint32 = 100 23 24 func init() { 25 gogcEnv := os.Getenv("GOGC") 26 gogc, err := strconv.ParseInt(gogcEnv, 10, 32) 27 if err != nil { 28 return 29 } 30 defaultGCPercent = uint32(gogc) 31 } 32 33 // Tuning sets the threshold of heap which will be respect by gc tuner. 34 // When Tuning, the env GOGC will not be take effect. 35 // threshold: disable tuning if threshold == 0 36 func Tuning(threshold uint64) { 37 // disable gc tuner if percent is zero 38 if threshold <= 0 && globalTuner != nil { 39 globalTuner.stop() 40 globalTuner = nil 41 return 42 } 43 44 if globalTuner == nil { 45 globalTuner = newTuner(threshold) 46 return 47 } 48 globalTuner.setThreshold(threshold) 49 } 50 51 // GetGCPercent returns the current GCPercent. 52 func GetGCPercent() uint32 { 53 if globalTuner == nil { 54 return defaultGCPercent 55 } 56 return globalTuner.getGCPercent() 57 } 58 59 // GetMaxGCPercent returns the max gc percent value. 60 func GetMaxGCPercent() uint32 { 61 return atomic.LoadUint32(&maxGCPercent) 62 } 63 64 // SetMaxGCPercent sets the new max gc percent value. 65 func SetMaxGCPercent(n uint32) uint32 { 66 return atomic.SwapUint32(&maxGCPercent, n) 67 } 68 69 // GetMinGCPercent returns the min gc percent value. 70 func GetMinGCPercent() uint32 { 71 return atomic.LoadUint32(&minGCPercent) 72 } 73 74 // SetMinGCPercent sets the new min gc percent value. 75 func SetMinGCPercent(n uint32) uint32 { 76 return atomic.SwapUint32(&minGCPercent, n) 77 } 78 79 // only allow one gc tuner in one process 80 var globalTuner *tuner = nil 81 82 /* Heap 83 _______________ => limit: host/cgroup memory hard limit 84 | | 85 |---------------| => threshold: increase GCPercent when gc_trigger < threshold 86 | | 87 |---------------| => gc_trigger: heap_live + heap_live * GCPercent / 100 88 | | 89 |---------------| 90 | heap_live | 91 |_______________| 92 93 Go runtime only trigger GC when hit gc_trigger which affected by GCPercent and heap_live. 94 So we can change GCPercent dynamically to tuning GC performance. 95 */ 96 type tuner struct { 97 finalizer *finalizer 98 gcPercent uint32 99 threshold uint64 // high water level, in bytes 100 } 101 102 // tuning check the memory inuse and tune GC percent dynamically. 103 // Go runtime ensure that it will be called serially. 104 func (t *tuner) tuning() { 105 inuse := readMemoryInuse() 106 threshold := t.getThreshold() 107 // stop gc tuning 108 if threshold <= 0 { 109 return 110 } 111 t.setGCPercent(calcGCPercent(inuse, threshold)) 112 return 113 } 114 115 // threshold = inuse + inuse * (gcPercent / 100) 116 // => gcPercent = (threshold - inuse) / inuse * 100 117 // if threshold < inuse*2, so gcPercent < 100, and GC positively to avoid OOM 118 // if threshold > inuse*2, so gcPercent > 100, and GC negatively to reduce GC times 119 func calcGCPercent(inuse, threshold uint64) uint32 { 120 // invalid params 121 if inuse == 0 || threshold == 0 { 122 return defaultGCPercent 123 } 124 // inuse heap larger than threshold, use min percent 125 if threshold <= inuse { 126 return minGCPercent 127 } 128 gcPercent := uint32(math.Floor(float64(threshold-inuse) / float64(inuse) * 100)) 129 if gcPercent < minGCPercent { 130 return minGCPercent 131 } else if gcPercent > maxGCPercent { 132 return maxGCPercent 133 } 134 return gcPercent 135 } 136 137 func newTuner(threshold uint64) *tuner { 138 t := &tuner{ 139 gcPercent: defaultGCPercent, 140 threshold: threshold, 141 } 142 t.finalizer = newFinalizer(t.tuning) // start tuning 143 return t 144 } 145 146 func (t *tuner) stop() { 147 t.finalizer.stop() 148 } 149 150 func (t *tuner) setThreshold(threshold uint64) { 151 atomic.StoreUint64(&t.threshold, threshold) 152 } 153 154 func (t *tuner) getThreshold() uint64 { 155 return atomic.LoadUint64(&t.threshold) 156 } 157 158 func (t *tuner) setGCPercent(percent uint32) uint32 { 159 atomic.StoreUint32(&t.gcPercent, percent) 160 return uint32(debug.SetGCPercent(int(percent))) 161 } 162 163 func (t *tuner) getGCPercent() uint32 { 164 return atomic.LoadUint32(&t.gcPercent) 165 } 166 167 // TuningWithFromHuman 168 // eg. "b/B", "k/K" "kb/Kb" "mb/Mb", "gb/Gb" "tb/Tb" "pb/Pb". 169 func TuningWithFromHuman(threshold string) { 170 parseThreshold, err := units.FromHumanSize(threshold) 171 if err != nil { 172 fmt.Println("format err:", err) 173 return 174 } 175 Tuning(uint64(parseThreshold)) 176 } 177 178 // TuningWithAuto By automatic calculation of the total amount 179 func TuningWithAuto(isContainer bool) { 180 var ( 181 threshold uint64 182 err error 183 ) 184 if isContainer { 185 threshold, err = getCGroupMemoryLimit() 186 } else { 187 threshold, err = getNormalMemoryLimit() 188 } 189 if err != nil { 190 fmt.Println("get memery err:", err) 191 return 192 } 193 Tuning(uint64(float64(threshold) * 0.7)) 194 } 195 196 const cgroupMemLimitPath = "/sys/fs/cgroup/memory/memory.limit_in_bytes" 197 198 func getCGroupMemoryLimit() (uint64, error) { 199 usage, err := readUint(cgroupMemLimitPath) 200 if err != nil { 201 return 0, err 202 } 203 machineMemory, err := mem_util.VirtualMemory() 204 if err != nil { 205 return 0, err 206 } 207 limit := uint64(math.Min(float64(usage), float64(machineMemory.Total))) 208 return limit, nil 209 } 210 211 func getNormalMemoryLimit() (uint64, error) { 212 machineMemory, err := mem_util.VirtualMemory() 213 if err != nil { 214 return 0, err 215 } 216 return machineMemory.Total, nil 217 } 218 219 // copied from https://github.com/containerd/cgroups/blob/318312a373405e5e91134d8063d04d59768a1bff/utils.go#L251 220 func parseUint(s string, base, bitSize int) (uint64, error) { 221 v, err := strconv.ParseUint(s, base, bitSize) 222 if err != nil { 223 intValue, intErr := strconv.ParseInt(s, base, bitSize) 224 // 1. Handle negative values greater than MinInt64 (and) 225 // 2. Handle negative values lesser than MinInt64 226 if intErr == nil && intValue < 0 { 227 return 0, nil 228 } else if intErr != nil && 229 intErr.(*strconv.NumError).Err == strconv.ErrRange && 230 intValue < 0 { 231 return 0, nil 232 } 233 return 0, err 234 } 235 return v, nil 236 } 237 238 func readUint(path string) (uint64, error) { 239 v, err := ioutil.ReadFile(path) 240 if err != nil { 241 return 0, err 242 } 243 return parseUint(strings.TrimSpace(string(v)), 10, 64) 244 }