Skip to content

Commit

Permalink
feat: 加入bbr rate limiter
Browse files Browse the repository at this point in the history
  • Loading branch information
Ficoto authored and zenghur committed May 8, 2021
1 parent 4678acb commit de930f7
Show file tree
Hide file tree
Showing 39 changed files with 3,106 additions and 18 deletions.
7 changes: 7 additions & 0 deletions cpukit/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
## stat/sys

System Information

## 项目简介

获取Linux平台下的系统信息,包括cpu主频、cpu使用率等
126 changes: 126 additions & 0 deletions cpukit/cgroup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package cpukit

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
}
// fix possible_cpu:https://www.ibm.com/support/knowledgecenter/en/linuxonibm/com.ibm.linux.z.lgdd/lgdd_r_posscpusparm.html
if u != 0 {
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
}
250 changes: 250 additions & 0 deletions cpukit/cgroupCPU.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
package cpukit

import (
"bufio"
"fmt"
"os"
"strconv"
"strings"

"github.com/pkg/errors"
pscpu "github.com/shirou/gopsutil/cpu"
)

type cgroupCPU struct {
frequency uint64
quota float64
cores uint64

preSystem uint64
preTotal uint64
usage uint64
}

func newCgroupCPU() (cpu *cgroupCPU, err error) {
var cores int
cores, err = pscpu.Counts(true)
if err != nil || cores == 0 {
var cpus []uint64
cpus, err = perCPUUsage()
if err != nil {
err = errors.Errorf("perCPUUsage() failed!err:=%v", err)
return
}
cores = len(cpus)
}

sets, err := cpuSets()
if err != nil {
err = errors.Errorf("cpuSets() failed!err:=%v", err)
return
}
quota := float64(len(sets))
cq, err := cpuQuota()
if err == nil && cq != -1 {
var period uint64
if period, err = cpuPeriod(); err != nil {
err = errors.Errorf("cpuPeriod() failed!err:=%v", err)
return
}
limit := float64(cq) / float64(period)
if limit < quota {
quota = limit
}
}
maxFreq := cpuMaxFreq()

preSystem, err := systemCPUUsage()
if err != nil {
err = errors.Errorf("systemCPUUsage() failed!err:=%v", err)
return
}
preTotal, err := totalCPUUsage()
if err != nil {
err = errors.Errorf("totalCPUUsage() failed!err:=%v", err)
return
}
cpu = &cgroupCPU{
frequency: maxFreq,
quota: quota,
cores: uint64(cores),
preSystem: preSystem,
preTotal: preTotal,
}
return
}

func (cpu *cgroupCPU) Usage() (u uint64, err error) {
var (
total uint64
system uint64
)
total, err = totalCPUUsage()
if err != nil {
return
}
system, err = systemCPUUsage()
if err != nil {
return
}
if system != cpu.preSystem {
u = uint64(float64((total-cpu.preTotal)*cpu.cores*1e3) / (float64(system-cpu.preSystem) * cpu.quota))
}
cpu.preSystem = system
cpu.preTotal = total
return
}

func (cpu *cgroupCPU) Info() Info {
return Info{
Frequency: cpu.frequency,
Quota: cpu.quota,
}
}

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
}

//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
}
11 changes: 11 additions & 0 deletions cpukit/cgroup_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// +build linux

package cpukit

import (
"testing"
)

func TestCGroup(t *testing.T) {
// TODO
}
Loading

0 comments on commit de930f7

Please sign in to comment.