* Copyright (c) 2020 Huawei Technologies Co., Ltd.
* A-Tune is licensed under the Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
* PURPOSE.
* See the Mulan PSL v2 for more details.
* Create: 2020-04-28
*/
package sysload
import (
"bufio"
"container/list"
"fmt"
"os"
"runtime"
"strconv"
"strings"
"time"
"gitee.com/openeuler/A-Tune/common/log"
"gitee.com/openeuler/A-Tune/common/topology"
"gitee.com/openeuler/A-Tune/common/utils"
)
import "C"
var Sysload SystemLoad
func init() {
Sysload.Init()
}
var clockTicks uint64
const ScaleShift uint64 = 10
const nsecPerSec uint64 = 1000000000
const cpuIDIndex = 3
type timeUsage struct {
lastUpdate time.Time
user uint64
system uint64
load int
}
type CPUStatType int
const (
CPUStatCPUNum CPUStatType = 0
CPUStatUser CPUStatType = 1
CPUStatNice CPUStatType = 2
CPUStatSystem CPUStatType = 3
CPUStatIdle CPUStatType = 4
CPUStatIOWait CPUStatType = 5
CPUStatIRQ CPUStatType = 6
CPUStatSoftIRQ CPUStatType = 7
CPUStatSteal CPUStatType = 8
CPUStatGuest CPUStatType = 9
CPUStatGuestNice CPUStatType = 10
CPUStatMAX CPUStatType = 11
)
type CPUStat struct {
lastUpdate time.Time
Fields [CPUStatMAX]uint64
}
func (cs *CPUStat) delta(newcs *CPUStat, oldcs *CPUStat) {
duration := uint64(newcs.lastUpdate.Sub(oldcs.lastUpdate)) / nsecPerSec * clockTicks
if duration == 0 {
return
}
for i := CPUStatCPUNum + 1; i < CPUStatMAX; i++ {
cs.Fields[i] = (scaleUp((newcs.Fields[i] - oldcs.Fields[i])) / (duration))
}
}
func (cs *CPUStat) load() int {
var sum uint64
sum = 0
for i := CPUStatCPUNum + 1; i < CPUStatMAX; i++ {
if i == CPUStatIdle {
continue
}
sum += cs.Fields[i]
}
return int(sum)
}
func (cs *CPUStat) idle() int {
return int(cs.Fields[CPUStatIdle])
}
func (cs *CPUStat) irq() int {
return int(cs.Fields[CPUStatIRQ] + cs.Fields[CPUStatSoftIRQ])
}
func init() {
clockTicks = uint64(C.sysconf(C._SC_CLK_TCK))
}
func scaleUp(i uint64) uint64 {
return i << ScaleShift
}
func (usage *timeUsage) init(now *time.Time, user uint64, system uint64) {
usage.lastUpdate = *now
usage.user = user
usage.system = system
}
func (usage *timeUsage) update(now *time.Time, user uint64, system uint64) {
duration := uint64(now.Sub(usage.lastUpdate)) / nsecPerSec * clockTicks
if duration == 0 {
return
}
usage.load = int(scaleUp((user-usage.user)+(system-usage.system)) / (duration))
usage.lastUpdate = *now
usage.user = user
usage.system = system
}
type TaskLoad struct {
id uint64
usage timeUsage
}
func (load *TaskLoad) Init(now *time.Time, user uint64, system uint64) {
load.usage.init(now, user, system)
}
func (load *TaskLoad) Update(now *time.Time, user uint64, system uint64) {
load.usage.update(now, user, system)
}
type CPULoad struct {
last CPUStat
stat CPUStat
}
func (load *CPULoad) Init(stat CPUStat) {
load.last = stat
load.stat = stat
}
func (load *CPULoad) Update(stat CPUStat) {
load.stat.delta(&stat, &load.last)
load.last = stat
}
type SystemLoad struct {
cpusLoad []CPULoad
tasksLoad list.List
}
func parseCPUStatLine(str string) (CPUStat, error) {
var stat CPUStat
var err error
statFields := strings.Fields(str)
for i := CPUStatCPUNum; i < CPUStatMAX; i++ {
if i == CPUStatCPUNum {
cpu := statFields[CPUStatCPUNum]
stat.Fields[i], err = strconv.ParseUint(cpu[cpuIDIndex:], utils.DecimalBase, utils.Uint64Bits)
if err != nil {
err = fmt.Errorf("convert cpu number from %s failed", cpu)
return stat, err
}
continue
}
stat.Fields[i], err = strconv.ParseUint(statFields[i], utils.DecimalBase, utils.Uint64Bits)
if err != nil {
err = fmt.Errorf("convert cpu user time from %s failed", statFields[i])
return stat, err
}
}
return stat, nil
}
func (sysload *SystemLoad) cpusLoadUpdate(init bool) {
file, err := os.Open(utils.ProcDir + "stat")
if err != nil {
log.Error(err)
return
}
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
now := time.Now()
for scanner.Scan() {
line := scanner.Text()
if line[0:cpuIDIndex] != "cpu" {
break
}
if line[cpuIDIndex] == ' ' {
continue
}
stat, err := parseCPUStatLine(line)
stat.lastUpdate = now
if err != nil {
log.Error(err)
continue
}
if init {
sysload.cpusLoad[stat.Fields[CPUStatCPUNum]].Init(stat)
} else {
sysload.cpusLoad[stat.Fields[CPUStatCPUNum]].Update(stat)
}
}
file.Close()
}
type taskStat struct {
user uint64
system uint64
}
func parseTaskStatLine(str string) (taskStat, error) {
const taskUserTimeIndex = 13
const taskSystemTimeIndex = 14
var stat taskStat
var err error
statFields := strings.Fields(str)
stat.user, err = strconv.ParseUint(statFields[taskUserTimeIndex], utils.DecimalBase, utils.Uint64Bits)
if err != nil {
err = fmt.Errorf("convert task user time from %s failed", statFields[taskUserTimeIndex])
return stat, err
}
stat.system, err = strconv.ParseUint(statFields[taskSystemTimeIndex], utils.DecimalBase, utils.Uint64Bits)
if err != nil {
err = fmt.Errorf("convert task system time from %s failed", statFields[taskSystemTimeIndex])
return stat, err
}
return stat, nil
}
func getPidFromTid(tid uint64) (uint64, error) {
var pid uint64
taskStatusPath := fmt.Sprintf(utils.ProcDir+"%d/status", tid)
file, err := os.Open(taskStatusPath)
if err != nil {
return pid, err
}
defer file.Close()
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, "Tgid") {
_, err := fmt.Sscanf(line, "Tgid: %d", &pid)
return pid, err
}
}
return pid, fmt.Errorf("can not find Tgid from status file")
}
func updateTaskLoad(load *TaskLoad, init bool) {
pid, err := getPidFromTid(load.id)
if err != nil {
log.Error(err)
return
}
taskStatPath := fmt.Sprintf(utils.ProcDir+"%d/stat", pid)
line := utils.ReadAllFile(taskStatPath)
if line == "" {
return
}
stat, err := parseTaskStatLine(line)
if err != nil {
log.Error(err)
return
}
now := time.Now()
if init {
load.Init(&now, stat.user, stat.system)
} else {
load.Update(&now, stat.user, stat.system)
}
}
func (sysload *SystemLoad) tasksLoadUpdate() {
for task := sysload.tasksLoad.Front(); task != nil; task = task.Next() {
updateTaskLoad((task.Value).(*TaskLoad), false)
}
}
func (sysload *SystemLoad) Init() {
cpuNum := runtime.NumCPU()
sysload.cpusLoad = make([]CPULoad, cpuNum)
sysload.tasksLoad.Init()
sysload.cpusLoadUpdate(true)
}
func (sysload *SystemLoad) AddTask(id uint64) {
var taskLoad TaskLoad
taskLoad.id = id
updateTaskLoad(&taskLoad, true)
sysload.tasksLoad.PushBack(&taskLoad)
}
func (sysload *SystemLoad) RemoveTask(id uint64) {
taskLoad := sysload.findTask(id)
if taskLoad != nil {
sysload.tasksLoad.Remove(taskLoad)
}
}
func (sysload *SystemLoad) Update() {
sysload.cpusLoadUpdate(false)
sysload.tasksLoadUpdate()
}
func (sysload *SystemLoad) GetTaskCpuPercent() int {
if sysload.tasksLoad.Len() == 0 {
return 0
}
sum := 0
for task := sysload.tasksLoad.Front(); task != nil; task = task.Next() {
taskLoad := (task.Value).(*TaskLoad)
sum += taskLoad.usage.load
}
cpuNum := runtime.NumCPU()
totalCpuTime := topology.GetLoad() + topology.GetIdle()
return sum * 100 * cpuNum / (sysload.tasksLoad.Len() * totalCpuTime)
}
func (sysload *SystemLoad) findTask(id uint64) *list.Element {
for taskLoad := sysload.tasksLoad.Front(); taskLoad != nil; taskLoad = taskLoad.Next() {
if (taskLoad.Value).(*TaskLoad).id == id {
return taskLoad
}
}
return nil
}
func (sysload *SystemLoad) GetCPULoad(cpu int) (int, int, int) {
if cpu > len(sysload.cpusLoad) {
return 0, 0, 0
}
load := sysload.cpusLoad[cpu].stat.load()
idle := sysload.cpusLoad[cpu].stat.idle()
irq := sysload.cpusLoad[cpu].stat.irq()
return load, idle, irq
}
func (sysload *SystemLoad) ClearCPULoad(cpu int) {
if cpu > len(sysload.cpusLoad) {
return
}
var stat CPUStat
sysload.cpusLoad[cpu].Init(stat)
}
func (sysload *SystemLoad) GetTaskLoad(id uint64) int {
taskLoad := sysload.findTask(id)
if taskLoad != nil {
return (taskLoad.Value).(*TaskLoad).usage.load
}
return 0
}
func NewSysload() *SystemLoad {
var sysload SystemLoad
sysload.Init()
for idx, load := range sysload.cpusLoad {
log.Infof("sysload: cpu: %d, load: %v", idx, load)
}
return &sysload
}