Files
tidb/pkg/util/cpu/cpu.go

133 lines
3.2 KiB
Go

// 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 (
"os"
"runtime"
"sync"
"time"
sigar "github.com/cloudfoundry/gosigar"
"github.com/pingcap/failpoint"
"github.com/pingcap/log"
"github.com/pingcap/tidb/pkg/metrics"
"github.com/pingcap/tidb/pkg/util/cgroup"
"github.com/pingcap/tidb/pkg/util/mathutil"
"go.uber.org/atomic"
"go.uber.org/zap"
)
var cpuUsage atomic.Float64
// If your kernel is lower than linux 4.7, you cannot get the cpu usage in the container.
var unsupported atomic.Bool
// GetCPUUsage returns the cpu usage of the current process.
func GetCPUUsage() (float64, bool) {
return cpuUsage.Load(), unsupported.Load()
}
// Observer is used to observe the cpu usage of the current process.
type Observer struct {
utime int64
stime int64
now int64
exit chan struct{}
cpu mathutil.ExponentialMovingAverage
wg sync.WaitGroup
}
// NewCPUObserver returns a cpu observer.
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() {
_, err := cgroup.GetCgroupCPU()
if err != nil {
unsupported.Store(true)
log.Error("GetCgroupCPU", zap.Error(err))
return
}
c.wg.Add(1)
go func() {
ticker := time.NewTicker(100 * time.Millisecond)
defer func() {
ticker.Stop()
c.wg.Done()
}()
for {
select {
case <-ticker.C:
curr := c.observe()
c.cpu.Add(curr)
cpuUsage.Store(c.cpu.Get())
metrics.EMACPUUsageGauge.Set(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 {
user, sys, err := getCPUTime()
if err != nil {
log.Error("getCPUTime", zap.Error(err))
}
cgroupCPU, _ := cgroup.GetCgroupCPU()
cpuShare := cgroupCPU.CPUShares()
now := time.Now().UnixNano()
dur := float64(now - c.now)
utime := user * 1e6
stime := sys * 1e6
urate := float64(utime-c.utime) / dur
srate := float64(stime-c.stime) / dur
c.now = now
c.utime = utime
c.stime = stime
return (srate + urate) / cpuShare
}
// getCPUTime returns the cumulative user/system time (in ms) since the process start.
func getCPUTime() (userTimeMillis, sysTimeMillis int64, err error) {
pid := os.Getpid()
cpuTime := sigar.ProcTime{}
if err := cpuTime.Get(pid); err != nil {
return 0, 0, err
}
return int64(cpuTime.User), int64(cpuTime.Sys), nil
}
// GetCPUCount returns the number of logical CPUs usable by the current process.
func GetCPUCount() int {
failpoint.Inject("mockNumCpu", func(val failpoint.Value) {
failpoint.Return(val.(int))
})
return runtime.GOMAXPROCS(0)
}