diff --git a/DEPS.bzl b/DEPS.bzl index c6ff78e509ea0..30e21a5bf9be0 100644 --- a/DEPS.bzl +++ b/DEPS.bzl @@ -450,6 +450,14 @@ def go_deps(): sum = "h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI=", version = "v0.3.4", ) + go_repository( + name = "com_github_cloudfoundry_gosigar", + build_file_proto_mode = "disable", + importpath = "github.com/cloudfoundry/gosigar", + sum = "h1:T3MoGdugg1vdHn8Az7wDn7cZ4+QCjZph+eXf2CjSjo4=", + version = "v1.3.4", + ) + go_repository( name = "com_github_cloudykit_fastprinter", build_file_proto_mode = "disable_global", @@ -4442,8 +4450,8 @@ def go_deps(): name = "org_golang_x_sys", build_file_proto_mode = "disable_global", importpath = "golang.org/x/sys", - sum = "h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=", - version = "v0.2.0", + sum = "h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=", + version = "v0.3.0", ) go_repository( name = "org_golang_x_term", diff --git a/go.mod b/go.mod index 8c68ec0d5137c..89087e673b6ef 100644 --- a/go.mod +++ b/go.mod @@ -19,6 +19,7 @@ require ( github.com/charithe/durationcheck v0.0.9 github.com/cheggaaa/pb/v3 v3.0.8 github.com/cheynewallace/tabby v1.1.1 + github.com/cloudfoundry/gosigar v1.3.4 github.com/cockroachdb/errors v1.8.1 github.com/cockroachdb/pebble v0.0.0-20210719141320-8c3bd06debb5 github.com/coocood/freecache v1.2.1 @@ -110,7 +111,7 @@ require ( golang.org/x/net v0.2.0 golang.org/x/oauth2 v0.2.0 golang.org/x/sync v0.1.0 - golang.org/x/sys v0.2.0 + golang.org/x/sys v0.3.0 golang.org/x/term v0.2.0 golang.org/x/text v0.4.0 golang.org/x/time v0.2.0 diff --git a/go.sum b/go.sum index 31210331beeb6..665f711c368fb 100644 --- a/go.sum +++ b/go.sum @@ -165,6 +165,8 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudfoundry/gosigar v1.3.4 h1:T3MoGdugg1vdHn8Az7wDn7cZ4+QCjZph+eXf2CjSjo4= +github.com/cloudfoundry/gosigar v1.3.4/go.mod h1:g9r7ETZ1tpvJCT9TpqxO53+5BUZiM2FDSFSENzjK5Z8= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -1314,8 +1316,8 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220909162455-aba9fc2a8ff2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0 h1:z85xZCsEl7bi/KwbNADeBYoOP0++7W1ipu+aGnpwzRM= diff --git a/util/cgroup/BUILD.bazel b/util/cgroup/BUILD.bazel index a5c5e3f75d358..58848ac153701 100644 --- a/util/cgroup/BUILD.bazel +++ b/util/cgroup/BUILD.bazel @@ -21,7 +21,10 @@ go_library( go_test( name = "cgroup_test", - srcs = ["cgroup_mock_test.go"], + srcs = [ + "cgroup_cpu_test.go", + "cgroup_mock_test.go", + ], embed = [":cgroup"], flaky = True, deps = ["@com_github_stretchr_testify//require"], diff --git a/util/cgroup/cgroup_cpu.go b/util/cgroup/cgroup_cpu.go index 29092c96914b6..bafa319f5ad62 100644 --- a/util/cgroup/cgroup_cpu.go +++ b/util/cgroup/cgroup_cpu.go @@ -67,3 +67,12 @@ func getCgroupCPU(root string) (CPUUsage, error) { return res, nil } + +// CPUShares returns the number of CPUs this cgroup can be expected to +// max out. If there's no limit, NumCPU is returned. +func (c CPUUsage) CPUShares() float64 { + if c.Period <= 0 || c.Quota <= 0 { + return float64(c.NumCPU) + } + return float64(c.Quota) / float64(c.Period) +} diff --git a/util/cgroup/cgroup_cpu_linux.go b/util/cgroup/cgroup_cpu_linux.go index bd3aba59100db..0322e6282e5a4 100644 --- a/util/cgroup/cgroup_cpu_linux.go +++ b/util/cgroup/cgroup_cpu_linux.go @@ -23,15 +23,6 @@ import ( "strings" ) -// CPUShares returns the number of CPUs this cgroup can be expected to -// max out. If there's no limit, NumCPU is returned. -func (c CPUUsage) CPUShares() float64 { - if c.Period <= 0 || c.Quota <= 0 { - return float64(c.NumCPU) - } - return float64(c.Quota) / float64(c.Period) -} - // GetCgroupCPU returns the CPU usage and quota for the current cgroup. func GetCgroupCPU() (CPUUsage, error) { cpuusage, err := getCgroupCPU("/") diff --git a/util/cgroup/cgroup_cpu_test.go b/util/cgroup/cgroup_cpu_test.go new file mode 100644 index 0000000000000..481ed3e32ccf8 --- /dev/null +++ b/util/cgroup/cgroup_cpu_test.go @@ -0,0 +1,50 @@ +// Copyright 2022 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build linux + +package cgroup + +import ( + "runtime" + "sync" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestGetCgroupCPU(t *testing.T) { + exit := make(chan struct{}) + var wg sync.WaitGroup + for i := 0; i < 10; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for { + select { + case <-exit: + return + default: + runtime.Gosched() + } + } + }() + } + cpu, err := GetCgroupCPU() + require.NoError(t, err) + require.NotZero(t, cpu.Period) + require.Less(t, int64(1), cpu.Period) + close(exit) + wg.Wait() +} diff --git a/util/cpu/BUILD.bazel b/util/cpu/BUILD.bazel index 23f95ddbbf79b..739a62a2c438a 100644 --- a/util/cpu/BUILD.bazel +++ b/util/cpu/BUILD.bazel @@ -1,4 +1,4 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "cpu", @@ -7,9 +7,17 @@ go_library( visibility = ["//visibility:public"], deps = [ "//util/cgroup", - "@com_github_elastic_gosigar//:gosigar", + "//util/mathutil", + "@com_github_cloudfoundry_gosigar//:go_default_library", "@com_github_pingcap_log//:log", "@org_uber_go_atomic//:atomic", "@org_uber_go_zap//:zap", ], ) + +go_test( + name = "cpu_test", + srcs = ["cpu_test.go"], + embed = [":cpu"], + deps = ["@com_github_stretchr_testify//require"], +) diff --git a/util/cpu/cpu.go b/util/cpu/cpu.go index 18a8dc9876064..b39b8db10d8e8 100644 --- a/util/cpu/cpu.go +++ b/util/cpu/cpu.go @@ -16,11 +16,13 @@ package cpu import ( "os" + "sync" "time" - "github.com/elastic/gosigar" + "github.com/cloudfoundry/gosigar" "github.com/pingcap/log" "github.com/pingcap/tidb/util/cgroup" + "github.com/pingcap/tidb/util/mathutil" "go.uber.org/atomic" "go.uber.org/zap" ) @@ -38,6 +40,8 @@ type Observer struct { stime int64 now int64 exit chan struct{} + cpu mathutil.ExponentialMovingAverage + wg sync.WaitGroup } // NewCPUObserver returns a cpu observer. @@ -45,32 +49,34 @@ func NewCPUObserver() *Observer { return &Observer{ exit: make(chan struct{}), now: time.Now().UnixNano(), + cpu: *mathutil.NewExponentialMovingAverage(0.95, 10), } } // Start starts the cpu observer. func (c *Observer) Start() { - ticker := time.NewTicker(500 * time.Millisecond) + ticker := time.NewTicker(100 * time.Millisecond) defer ticker.Stop() - const decay = 0.95 - for { - select { - case <-ticker.C: - // EMA - // https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average - prevCPU := cpuUsage.Load() - curr := c.observe() - result := prevCPU*decay + curr*(1.0-decay) - cpuUsage.Store(result) - case <-c.exit: - return + c.wg.Add(1) + go func() { + defer c.wg.Done() + for { + select { + case <-ticker.C: + curr := c.observe() + c.cpu.Add(curr) + cpuUsage.Store(c.cpu.Get()) + case <-c.exit: + return + } } - } + }() } // Stop stops the cpu observer. func (c *Observer) Stop() { close(c.exit) + c.wg.Wait() } func (c *Observer) observe() float64 { diff --git a/util/cpu/cpu_test.go b/util/cpu/cpu_test.go new file mode 100644 index 0000000000000..6c7e863f9060a --- /dev/null +++ b/util/cpu/cpu_test.go @@ -0,0 +1,51 @@ +// Copyright 2022 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cpu + +import ( + "runtime" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestCPUValue(t *testing.T) { + Observer := NewCPUObserver() + Observer.Start() + exit := make(chan struct{}) + var wg sync.WaitGroup + for i := 0; i < 10; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for { + select { + case <-exit: + return + default: + runtime.Gosched() + } + } + }() + } + time.Sleep(30 * time.Second) + require.Greater(t, Observer.observe(), 0.0) + require.Less(t, Observer.observe(), 1.0) + Observer.Stop() + close(exit) + wg.Wait() +}