Create & Init Project...

This commit is contained in:
2019-04-22 18:49:16 +08:00
commit fc4fa37393
25440 changed files with 4054998 additions and 0 deletions

View File

@@ -0,0 +1,60 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = [
"cgroup_test.go",
"stat_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = ["//vendor/github.com/stretchr/testify/assert:go_default_library"],
)
go_library(
name = "go_default_library",
srcs = [
"cgroup.go",
"cpu.go",
"sysconfig_notcgo.go",
"util.go",
] + select({
"@io_bazel_rules_go//go/platform:linux": [
"cpu_linux.go",
],
"@io_bazel_rules_go//go/platform:darwin": [
"cpu_darwin.go",
],
"//conditions:default": [
"cpu_other.go",
],
}),
importpath = "go-common/library/stat/sys/cpu",
tags = ["manual"],
visibility = ["//visibility:public"],
deps = [
"//library/log:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,5 @@
### os/stat
##### Version 1.0.0
1. 可以获取cpu使用率\cpu核心数\cpu最高主频

View File

@@ -0,0 +1,8 @@
# Owner
caoguoliang
# Author
caoguoliang
# Reviewer
maojian

View File

@@ -0,0 +1,7 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- caoguoliang
reviewers:
- caoguoliang
- maojian

View File

@@ -0,0 +1,12 @@
#### os/stat
> System Information
##### 项目简介
获取Linux平台下的系统信息包括cpu主频、cpu使用率等
##### 编译环境
- **请只用 Golang v1.8.x 以上版本编译执行**

View File

@@ -0,0 +1,125 @@
// +build linux
package cpu
import (
"bufio"
"fmt"
"io"
"os"
"path"
"strconv"
"strings"
)
const cgroupRootDir = "/sys/fs/cgroup"
// cgroup Linux cgroup
type cgroup struct {
cgroupSet map[string]string
}
// CPUCFSQuotaUs cpu.cfs_quota_us
func (c *cgroup) CPUCFSQuotaUs() (int64, error) {
data, err := readFile(path.Join(c.cgroupSet["cpu"], "cpu.cfs_quota_us"))
if err != nil {
return 0, err
}
return strconv.ParseInt(data, 10, 64)
}
// CPUCFSPeriodUs cpu.cfs_period_us
func (c *cgroup) CPUCFSPeriodUs() (uint64, error) {
data, err := readFile(path.Join(c.cgroupSet["cpu"], "cpu.cfs_period_us"))
if err != nil {
return 0, err
}
return parseUint(data)
}
// CPUAcctUsage cpuacct.usage
func (c *cgroup) CPUAcctUsage() (uint64, error) {
data, err := readFile(path.Join(c.cgroupSet["cpuacct"], "cpuacct.usage"))
if err != nil {
return 0, err
}
return parseUint(data)
}
// CPUAcctUsagePerCPU cpuacct.usage_percpu
func (c *cgroup) CPUAcctUsagePerCPU() ([]uint64, error) {
data, err := readFile(path.Join(c.cgroupSet["cpuacct"], "cpuacct.usage_percpu"))
if err != nil {
return nil, err
}
var usage []uint64
for _, v := range strings.Fields(string(data)) {
var u uint64
if u, err = parseUint(v); err != nil {
return nil, err
}
usage = append(usage, u)
}
return usage, nil
}
// CPUSetCPUs cpuset.cpus
func (c *cgroup) CPUSetCPUs() ([]uint64, error) {
data, err := readFile(path.Join(c.cgroupSet["cpuset"], "cpuset.cpus"))
if err != nil {
return nil, err
}
cpus, err := ParseUintList(data)
if err != nil {
return nil, err
}
var sets []uint64
for k := range cpus {
sets = append(sets, uint64(k))
}
return sets, nil
}
// CurrentcGroup get current process cgroup
func currentcGroup() (*cgroup, error) {
pid := os.Getpid()
cgroupFile := fmt.Sprintf("/proc/%d/cgroup", pid)
cgroupSet := make(map[string]string)
fp, err := os.Open(cgroupFile)
if err != nil {
return nil, err
}
defer fp.Close()
buf := bufio.NewReader(fp)
for {
line, err := buf.ReadString('\n')
if err != nil {
if err == io.EOF {
break
}
return nil, err
}
col := strings.Split(strings.TrimSpace(line), ":")
if len(col) != 3 {
return nil, fmt.Errorf("invalid cgroup format %s", line)
}
dir := col[2]
// When dir is not equal to /, it must be in docker
if dir != "/" {
cgroupSet[col[1]] = path.Join(cgroupRootDir, col[1])
if strings.Contains(col[1], ",") {
for _, k := range strings.Split(col[1], ",") {
cgroupSet[k] = path.Join(cgroupRootDir, k)
}
}
} else {
cgroupSet[col[1]] = path.Join(cgroupRootDir, col[1], col[2])
if strings.Contains(col[1], ",") {
for _, k := range strings.Split(col[1], ",") {
cgroupSet[k] = path.Join(cgroupRootDir, k, col[2])
}
}
}
}
return &cgroup{cgroupSet: cgroupSet}, nil
}

View File

@@ -0,0 +1,11 @@
// +build linux
package cpu
import (
"testing"
)
func TestCGroup(t *testing.T) {
// TODO
}

110
library/stat/sys/cpu/cpu.go Normal file
View File

@@ -0,0 +1,110 @@
package cpu
import (
"fmt"
"go-common/library/log"
"sync/atomic"
"time"
)
var (
cores uint64
maxFreq uint64
quota float64
usage uint64
preSystem uint64
preTotal uint64
)
func init() {
cpus, err := perCPUUsage()
if err != nil {
panic(fmt.Errorf("stat/sys/cpu: perCPUUsage() failed!err:=%v", err))
}
cores = uint64(len(cpus))
sets, err := cpuSets()
if err != nil {
panic(fmt.Errorf("stat/sys/cpu: cpuSets() failed!err:=%v", err))
}
quota = float64(len(sets))
cq, err := cpuQuota()
if err == nil {
if cq != -1 {
var period uint64
if period, err = cpuPeriod(); err != nil {
panic(fmt.Errorf("stat/sys/cpu: cpuPeriod() failed!err:=%v", err))
}
limit := float64(cq) / float64(period)
if limit < quota {
quota = limit
}
}
}
maxFreq = cpuMaxFreq()
preSystem, err = systemCPUUsage()
if err != nil {
panic(fmt.Errorf("sys/cpu: systemCPUUsage() failed!err:=%v", err))
}
preTotal, err = totalCPUUsage()
if err != nil {
panic(fmt.Errorf("sys/cpu: totalCPUUsage() failed!err:=%v", err))
}
go func() {
ticker := time.NewTicker(time.Millisecond * 250)
defer ticker.Stop()
for {
<-ticker.C
cpu := refreshCPU()
if cpu != 0 {
atomic.StoreUint64(&usage, cpu)
}
}
}()
}
func refreshCPU() (u uint64) {
total, err := totalCPUUsage()
if err != nil {
log.Warn("os/stat: get totalCPUUsage failed,error(%v)", err)
return
}
system, err := systemCPUUsage()
if err != nil {
log.Warn("os/stat: get systemCPUUsage failed,error(%v)", err)
return
}
if system != preSystem {
u = uint64(float64((total-preTotal)*cores*1e3) / (float64(system-preSystem) * quota))
}
preSystem = system
preTotal = total
return u
}
// Stat cpu stat.
type Stat struct {
Usage uint64 // cpu use ratio.
}
// Info cpu info.
type Info struct {
Frequency uint64
Quota float64
}
// ReadStat read cpu stat.
func ReadStat(stat *Stat) {
stat.Usage = atomic.LoadUint64(&usage)
}
// GetInfo get cpu info.
func GetInfo() Info {
return Info{
Frequency: maxFreq,
Quota: quota,
}
}

View File

@@ -0,0 +1,20 @@
// +build darwin
package cpu
var su uint64 = 10
var tu uint64 = 10
func systemCPUUsage() (usage uint64, err error) {
su += 1000
return su, nil
}
func totalCPUUsage() (usage uint64, err error) {
tu += 500
return tu, nil
}
func perCPUUsage() (usage []uint64, err error) { return []uint64{10, 10, 10, 10}, nil }
func cpuSets() (sets []uint64, err error) { return []uint64{0, 1, 2, 3}, nil }
func cpuQuota() (quota int64, err error) { return 100, nil }
func cpuPeriod() (peroid uint64, err error) { return 10, nil }
func cpuMaxFreq() (feq uint64) { return 10 }

View File

@@ -0,0 +1,147 @@
// +build linux
package cpu
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
"github.com/pkg/errors"
)
const nanoSecondsPerSecond = 1e9
// ErrNoCFSLimit is no quota limit
var ErrNoCFSLimit = errors.Errorf("no quota limit")
var clockTicksPerSecond = uint64(GetClockTicks())
// systemCPUUsage returns the host system's cpu usage in
// nanoseconds. An error is returned if the format of the underlying
// file does not match.
//
// Uses /proc/stat defined by POSIX. Looks for the cpu
// statistics line and then sums up the first seven fields
// provided. See man 5 proc for details on specific field
// information.
func systemCPUUsage() (usage uint64, err error) {
var (
line string
f *os.File
)
if f, err = os.Open("/proc/stat"); err != nil {
return
}
bufReader := bufio.NewReaderSize(nil, 128)
defer func() {
bufReader.Reset(nil)
f.Close()
}()
bufReader.Reset(f)
for err == nil {
if line, err = bufReader.ReadString('\n'); err != nil {
err = errors.WithStack(err)
return
}
parts := strings.Fields(line)
switch parts[0] {
case "cpu":
if len(parts) < 8 {
err = errors.WithStack(fmt.Errorf("bad format of cpu stats"))
return
}
var totalClockTicks uint64
for _, i := range parts[1:8] {
var v uint64
if v, err = strconv.ParseUint(i, 10, 64); err != nil {
err = errors.WithStack(fmt.Errorf("error parsing cpu stats"))
return
}
totalClockTicks += v
}
usage = (totalClockTicks * nanoSecondsPerSecond) / clockTicksPerSecond
return
}
}
err = errors.Errorf("bad stats format")
return
}
func totalCPUUsage() (usage uint64, err error) {
var cg *cgroup
if cg, err = currentcGroup(); err != nil {
return
}
return cg.CPUAcctUsage()
}
func perCPUUsage() (usage []uint64, err error) {
var cg *cgroup
if cg, err = currentcGroup(); err != nil {
return
}
return cg.CPUAcctUsagePerCPU()
}
func cpuSets() (sets []uint64, err error) {
var cg *cgroup
if cg, err = currentcGroup(); err != nil {
return
}
return cg.CPUSetCPUs()
}
func cpuQuota() (quota int64, err error) {
var cg *cgroup
if cg, err = currentcGroup(); err != nil {
return
}
return cg.CPUCFSQuotaUs()
}
func cpuPeriod() (peroid uint64, err error) {
var cg *cgroup
if cg, err = currentcGroup(); err != nil {
return
}
return cg.CPUCFSPeriodUs()
}
func cpuFreq() uint64 {
lines, err := readLines("/proc/cpuinfo")
if err != nil {
return 0
}
for _, line := range lines {
fields := strings.Split(line, ":")
if len(fields) < 2 {
continue
}
key := strings.TrimSpace(fields[0])
value := strings.TrimSpace(fields[1])
if key == "cpu MHz" || key == "clock" {
// treat this as the fallback value, thus we ignore error
if t, err := strconv.ParseFloat(strings.Replace(value, "MHz", "", 1), 64); err == nil {
return uint64(t * 1000.0 * 1000.0)
}
}
}
return 0
}
func cpuMaxFreq() uint64 {
feq := cpuFreq()
data, err := readFile("/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq")
if err != nil {
return feq
}
// override the max freq from /proc/cpuinfo
cfeq, err := parseUint(data)
if err == nil {
feq = cfeq
}
return feq
}

View File

@@ -0,0 +1,11 @@
// +build windows
package cpu
func systemCPUUsage() (usage uint64, err error) { return 10, nil }
func totalCPUUsage() (usage uint64, err error) { return 10, nil }
func perCPUUsage() (usage []uint64, err error) { return []uint64{10, 10, 10, 10}, nil }
func cpuSets() (sets []uint64, err error) { return []uint64{0, 1, 2, 3}, nil }
func cpuQuota() (quota int64, err error) { return 100, nil }
func cpuPeriod() (peroid uint64, err error) { return 10, nil }
func cpuMaxFreq() (feq uint64) { return 10 }

View File

@@ -0,0 +1,20 @@
package cpu
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestStat(t *testing.T) {
time.Sleep(time.Second * 2)
var s Stat
var i Info
ReadStat(&s)
i = GetInfo()
assert.NotZero(t, s.Usage)
assert.NotZero(t, i.Frequency)
assert.NotZero(t, i.Quota)
}

View File

@@ -0,0 +1,14 @@
package cpu
//GetClockTicks get the OS's ticks per second
func GetClockTicks() int {
// TODO figure out a better alternative for platforms where we're missing cgo
//
// TODO Windows. This could be implemented using Win32 QueryPerformanceFrequency().
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms644905(v=vs.85).aspx
//
// An example of its usage can be found here.
// https://msdn.microsoft.com/en-us/library/windows/desktop/dn553408(v=vs.85).aspx
return 100
}

View File

@@ -0,0 +1,121 @@
package cpu
import (
"bufio"
"io/ioutil"
"os"
"strconv"
"strings"
"github.com/pkg/errors"
)
func readFile(path string) (string, error) {
contents, err := ioutil.ReadFile(path)
if err != nil {
return "", errors.Wrapf(err, "os/stat: read file(%s) failed!", path)
}
return strings.TrimSpace(string(contents)), nil
}
func parseUint(s string) (uint64, error) {
v, err := strconv.ParseUint(s, 10, 64)
if err != nil {
intValue, intErr := strconv.ParseInt(s, 10, 64)
// 1. Handle negative values greater than MinInt64 (and)
// 2. Handle negative values lesser than MinInt64
if intErr == nil && intValue < 0 {
return 0, nil
} else if intErr != nil &&
intErr.(*strconv.NumError).Err == strconv.ErrRange &&
intValue < 0 {
return 0, nil
}
return 0, errors.Wrapf(err, "os/stat: parseUint(%s) failed!", s)
}
return v, nil
}
// ParseUintList parses and validates the specified string as the value
// found in some cgroup file (e.g. cpuset.cpus, cpuset.mems), which could be
// one of the formats below. Note that duplicates are actually allowed in the
// input string. It returns a map[int]bool with available elements from val
// set to true.
// Supported formats:
// 7
// 1-6
// 0,3-4,7,8-10
// 0-0,0,1-7
// 03,1-3 <- this is gonna get parsed as [1,2,3]
// 3,2,1
// 0-2,3,1
func ParseUintList(val string) (map[int]bool, error) {
if val == "" {
return map[int]bool{}, nil
}
availableInts := make(map[int]bool)
split := strings.Split(val, ",")
errInvalidFormat := errors.Errorf("os/stat: invalid format: %s", val)
for _, r := range split {
if !strings.Contains(r, "-") {
v, err := strconv.Atoi(r)
if err != nil {
return nil, errInvalidFormat
}
availableInts[v] = true
} else {
split := strings.SplitN(r, "-", 2)
min, err := strconv.Atoi(split[0])
if err != nil {
return nil, errInvalidFormat
}
max, err := strconv.Atoi(split[1])
if err != nil {
return nil, errInvalidFormat
}
if max < min {
return nil, errInvalidFormat
}
for i := min; i <= max; i++ {
availableInts[i] = true
}
}
}
return availableInts, nil
}
// ReadLines reads contents from a file and splits them by new lines.
// A convenience wrapper to ReadLinesOffsetN(filename, 0, -1).
func readLines(filename string) ([]string, error) {
return readLinesOffsetN(filename, 0, -1)
}
// ReadLinesOffsetN reads contents from file and splits them by new line.
// The offset tells at which line number to start.
// The count determines the number of lines to read (starting from offset):
// n >= 0: at most n lines
// n < 0: whole file
func readLinesOffsetN(filename string, offset uint, n int) ([]string, error) {
f, err := os.Open(filename)
if err != nil {
return []string{""}, err
}
defer f.Close()
var ret []string
r := bufio.NewReader(f)
for i := 0; i < n+int(offset) || n < 0; i++ {
line, err := r.ReadString('\n')
if err != nil {
break
}
if i < int(offset) {
continue
}
ret = append(ret, strings.Trim(line, "\n"))
}
return ret, nil
}