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  }