最近笔者在做的一个降级功能,与机器资源情况密切相关。然而在测试时发现控制 CPU 利用率来构造测试条件,并不是一个容易的事情。借助时间片的思想,笔者用一个非常简单的 shell 一定程度上解决了这个问题。但转念一想,对于混沌测试的软件,这应该是个必备能力。查找了一下 chaosblade 的相关资料,果然支持生成指定 CPU 利用率的负载。故读了读其这部分源码,看看它是怎么实现的。
使用 chaosblade
来构造指定 CPU 利用率的负载非常简单:
1
| 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
| 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 国际许可协议进行许可。