github.com/dubbogo/gost@v1.14.0/runtime/sys.go (about) 1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package gxruntime 19 20 import ( 21 "bufio" 22 "io/ioutil" 23 "os" 24 "runtime" 25 "runtime/pprof" 26 "strconv" 27 "strings" 28 "time" 29 ) 30 31 import ( 32 "github.com/shirou/gopsutil/v3/mem" 33 "github.com/shirou/gopsutil/v3/process" 34 ) 35 36 import ( 37 "github.com/dubbogo/gost/path/filepath" 38 ) 39 40 // CurrentPID returns the process id of the caller. 41 var CurrentPID = os.Getpid() 42 43 const ( 44 cgroupMemLimitPath = "/sys/fs/cgroup/memory/memory.limit_in_bytes" 45 46 _cgroupPath = "/proc/self/cgroup" 47 _dockerPath = "/docker" 48 _kubepodsPath = "/kubepods" 49 _cpuPeriodPath = "/sys/fs/cgroup/cpu/cpu.cfs_period_us" 50 _cpuQuotaPath = "/sys/fs/cgroup/cpu/cpu.cfs_quota_us" 51 ) 52 53 // GetCPUNum gets current os's cpu number 54 func GetCPUNum() int { 55 if isContainer() { 56 cpus, _ := numCPU() 57 return cpus 58 } 59 return runtime.NumCPU() 60 } 61 62 // GetMemoryStat gets current os's memory size in bytes 63 func GetMemoryStat() (total, used, free uint64, usedPercent float64) { 64 stat, err := mem.VirtualMemory() 65 if err != nil { 66 return 0, 0, 0, 0 67 } 68 69 return stat.Total, stat.Used, stat.Free, stat.UsedPercent 70 } 71 72 // IsCgroup checks whether current os is a container or not 73 func IsCgroup() bool { 74 ok, _ := gxfilepath.Exists(cgroupMemLimitPath) 75 if ok { 76 return true 77 } 78 79 return false 80 } 81 82 // GetCgroupMemoryLimit returns a container's total memory in bytes 83 func GetCgroupMemoryLimit() (uint64, error) { 84 return readUint(cgroupMemLimitPath) 85 } 86 87 // GetThreadNum gets current process's thread number 88 func GetThreadNum() int { 89 return pprof.Lookup("threadcreate").Count() 90 } 91 92 // GetGoroutineNum gets current process's goroutine number 93 func GetGoroutineNum() int { 94 return runtime.NumGoroutine() 95 } 96 97 // GetProcessCPUStat gets current process's cpu stat 98 func GetProcessCPUStat() (float64, error) { 99 p, err := process.NewProcess(int32(CurrentPID)) 100 if err != nil { 101 return 0, err 102 } 103 104 cpuPercent, err := p.Percent(time.Second) 105 if err != nil { 106 return 0, err 107 } 108 109 // The default percent is if you use one core, then 100%, two core, 200% 110 // but it's inconvenient to calculate the proper percent 111 // here we multiply by core number, so we can set a percent bar more intuitively 112 cpuPercent = cpuPercent / float64(runtime.GOMAXPROCS(-1)) 113 114 return cpuPercent, nil 115 } 116 117 // GetProcessMemoryStat gets current process's memory usage percent 118 func GetProcessMemoryPercent() (float32, error) { 119 p, err := process.NewProcess(int32(CurrentPID)) 120 if err != nil { 121 return 0, err 122 } 123 124 memPercent, err := p.MemoryPercent() 125 if err != nil { 126 return 0, err 127 } 128 129 return memPercent, nil 130 } 131 132 // GetProcessMemoryStat gets current process's memory usage in Byte 133 func GetProcessMemoryStat() (uint64, error) { 134 p, err := process.NewProcess(int32(CurrentPID)) 135 if err != nil { 136 return 0, err 137 } 138 139 memInfo, err := p.MemoryInfo() 140 if err != nil { 141 return 0, err 142 } 143 144 return memInfo.RSS, nil 145 } 146 147 // copied from https://github.com/containerd/cgroups/blob/318312a373405e5e91134d8063d04d59768a1bff/utils.go#L251 148 func parseUint(s string, base, bitSize int) (uint64, error) { 149 v, err := strconv.ParseUint(s, base, bitSize) 150 if err != nil { 151 intValue, intErr := strconv.ParseInt(s, base, bitSize) 152 // 1. Handle negative values greater than MinInt64 (and) 153 // 2. Handle negative values lesser than MinInt64 154 if intErr == nil && intValue < 0 { 155 return 0, nil 156 } else if intErr != nil && 157 intErr.(*strconv.NumError).Err == strconv.ErrRange && 158 intValue < 0 { 159 return 0, nil 160 } 161 return 0, err 162 } 163 return v, nil 164 } 165 166 // copied from https://github.com/containerd/cgroups/blob/318312a373405e5e91134d8063d04d59768a1bff/utils.go#L243 167 func readUint(path string) (uint64, error) { 168 v, err := ioutil.ReadFile(path) 169 if err != nil { 170 return 0, err 171 } 172 return parseUint(strings.TrimSpace(string(v)), 10, 64) 173 } 174 175 // GetCgroupProcessMemoryPercent gets current process's memory usage percent in cgroup env 176 func GetCgroupProcessMemoryPercent() (float64, error) { 177 p, err := process.NewProcess(int32(os.Getpid())) 178 if err != nil { 179 return 0, err 180 } 181 182 mem, err := p.MemoryInfo() 183 if err != nil { 184 return 0, err 185 } 186 187 memLimit, err := GetCgroupMemoryLimit() 188 if err != nil { 189 return 0, err 190 } 191 192 // mem.RSS / cgroup limit in bytes 193 memPercent := float64(mem.RSS) * 100 / float64(memLimit) 194 195 return memPercent, nil 196 } 197 198 // readLinesFromFile reads the lines from a file. 199 func readLinesFromFile(filepath string) []string { 200 res := make([]string, 0) 201 f, err := os.Open(filepath) 202 if err != nil { 203 return res 204 } 205 defer f.Close() 206 buff := bufio.NewReader(f) 207 for { 208 line, _, err := buff.ReadLine() 209 if err != nil { 210 return res 211 } 212 res = append(res, string(line)) 213 } 214 } 215 216 // isContainer returns true if the process is running in a container. 217 func isContainer() bool { 218 lines := readLinesFromFile(_cgroupPath) 219 for _, line := range lines { 220 if strings.Contains(line, _dockerPath) || 221 strings.Contains(line, _kubepodsPath) { 222 return true 223 } 224 } 225 return false 226 } 227 228 // numCPU returns the CPU quota 229 func numCPU() (num int, err error) { 230 if !isContainer() { 231 return runtime.NumCPU(), nil 232 } 233 234 // If the container is running in a cgroup, we can use the cgroup cpu 235 // quota to limit the number of CPUs. 236 period, err := readUint(_cpuPeriodPath) 237 if err != nil { 238 return runtime.NumCPU(), err 239 } 240 quota, err := readUint(_cpuQuotaPath) 241 if err != nil { 242 return runtime.NumCPU(), err 243 } 244 245 // The number of CPUs is the quota divided by the period. 246 // See https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt 247 if quota <= 0 || period <= 0 { 248 return runtime.NumCPU(), err 249 } 250 251 return int(quota) / int(period), nil 252 }