3

chaosblade 生成指定 CPU 利用率负载的原理

 2 years ago
source link: https://blog.triplez.cn/posts/chaosblade-specific-cpu-load/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

chaosblade 生成指定 CPU 利用率负载的原理

January 26, 2022 · 2 分钟 · TripleZ

最近笔者在做的一个降级功能,与机器资源情况密切相关。然而在测试时发现控制 CPU 利用率来构造测试条件,并不是一个容易的事情。借助时间片的思想,笔者用一个非常简单的 shell 一定程度上解决了这个问题。但转念一想,对于混沌测试的软件,这应该是个必备能力。查找了一下 chaosblade 的相关资料,果然支持生成指定 CPU 利用率的负载。故读了读其这部分源码,看看它是怎么实现的。

使用 chaosblade 来构造指定 CPU 利用率的负载非常简单:

blade create cpu load --cpu-percent 80

即能够生成使 CPU 利用率到达 80% 的负载。

burncpu 的核心逻辑位于这里: https://github.com/chaosblade-io/chaosblade-exec-os/blob/318c52d83a851bc75012abc7d880d4f440f1f972/exec/bin/burncpu/burncpu.go#L140-L168

func burnCpu() {

	runtime.GOMAXPROCS(cpuCount)

	var totalCpuPercent []float64
	var curProcess *process.Process
	var curCpuPercent float64
	var err error

	totalCpuPercent, err = cpu.Percent(time.Second, false)  // 获取当前所有 CPU 一秒内平均利用率
	// ...
	curProcess, err = process.NewProcess(int32(os.Getpid()))
	// ...
	curCpuPercent, err = curProcess.CPUPercent()  // 获取当前进程的 CPU 利用率
	// ...
	otherCpuPercent := (100.0 - (totalCpuPercent[0] - curCpuPercent)) / 100.0  // 除去已有进程,可操作的 CPU 利用率。值的范围为 [0, 1]
	go func() {
		t := time.NewTicker(3 * time.Second)
		for {
			select {
			// timer 3s
			case <-t.C:
				totalCpuPercent, err = cpu.Percent(time.Second, false)
				// ...
				curCpuPercent, err = curProcess.CPUPercent()
				// ...
				otherCpuPercent = (100.0 - (totalCpuPercent[0] - curCpuPercent)) / 100.0
			}
		}
	}()  // 每 3s 更新一次 totalCpuPercent, curCpuPercent 和 otherCpuPercent

	if climbTime == 0 {  // 不需要爬坡时间
		slopePercent = float64(cpuPercent)  // 爬坡值与目标值一致
	} else {
		// ...
	}

    // cpuCount 是由 runtime.NumCPU() 得来,获取的是当前 CPU 的逻辑核数量
	for i := 0; i < cpuCount; i++ {
		go func() {
			busy := int64(0)
			idle := int64(0)
			all := int64(10000000)    // 设定 10ms 为一个周期
			dx := 0.0
			ds := time.Duration(0)
			for i := 0; ; i = (i + 1) % 1000 {  // 死循环
				startTime := time.Now().UnixNano()
				if i == 0 {  // 每 1000 次进入
                    // 这个赋值语句是整个 burncpu 的灵魂。
                    // 我们最终希望获得的是 slopePercent% 的 CPU 利用率
                    // 应该生成的 CPU 压力即为 (slopePercent - totalCpuPercent[0])%
                    // 理想条件下,我们只需对 (slopePercent - totalCpuPercent[0]) 个 0.1ms 时间片设置为 busy 状态即可
                    // 但由于系统中存在其他进程,burncpu 无法真正获得到 (slopePercent - totalCpuPercent[0]) 个 0.1ms 时间片
                    // 因此需要按比例放大时间片的个数,而这个比例则是当时 burncpu 实际可用的 CPU 利用率
                    // 将这个赋值语句转换为如下方程,则更好理解了:
                    //   slopePercent  = totalCpuPercent + dx * otherCpuPercent
                    //       ^                 ^           ^                 ^
                    // 最终获得的CPU利用率 当前CPU利用率 一个周期内busy的时间片个数 burnCpu真正可操作的CPU比例
					dx = (slopePercent - totalCpuPercent[0]) / otherCpuPercent
					busy = busy + int64(dx*100000)  // 有 dx 个 0.1ms 需要为 busy 状态
					if busy < 0 {
						busy = 0
					}
					idle = all - busy
					if idle < 0 {
						idle = 0
					}
					ds, _ = time.ParseDuration(strconv.FormatInt(idle, 10) + "ns")
				}
				for time.Now().UnixNano()-startTime < busy {
				}  // 阻塞 CPU,使 CPU 位于 busy 状态,直至设定的时间片结束
				time.Sleep(ds) // 空闲 CPU,使 CPU 处于 idle 状态,直至设定的时间片结束
				runtime.Gosched()
			}
		}()
	}
	select {}  // 阻塞 burnCpu 函数,保活 goroutines
}

知识共享许可协议
本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK