@@ -5,11 +5,13 @@ import (
55 "fmt"
66 "log"
77 "math"
8+ "math/rand/v2"
89 "runtime"
910 "sync"
1011 "time"
1112
1213 "eat/cmd/cpu_affinity"
14+ "eat/cmd/sysinfo"
1315)
1416
1517func busyWork (ctx context.Context ) {
@@ -142,3 +144,153 @@ func eatCPU(ctx context.Context, wg *sync.WaitGroup, c float64, cpuAffinitiesEat
142144
143145 fmt .Printf ("Ate %2.3f CPU cores\n " , c )
144146}
147+
148+ func maintainCpuUsage (ctx context.Context , wg * sync.WaitGroup , coreNum float64 , usagePercent float64 , cpuAffinitiesEat []uint , cpuMonitor sysinfo.SystemCPUMonitor ) {
149+ if coreNum == 0 {
150+ coreNum = float64 (runtime .NumCPU ())
151+ }
152+ fmt .Printf ("CPU usage will be maintaining at minimum %.3f%%, eating %.3f cores, be patient...\n " , usagePercent , coreNum )
153+
154+ wg .Add (1 )
155+ go func () {
156+ defer wg .Done ()
157+ MaintainCpuUsage (ctx , coreNum , usagePercent , cpuAffinitiesEat , cpuMonitor )
158+ }()
159+ }
160+
161+ func MaintainCpuUsage (ctx context.Context , coreNum float64 , usagePercent float64 , cpuAffinitiesEat []uint , cpuMonitor sysinfo.SystemCPUMonitor ) {
162+ runtime .GOMAXPROCS (runtime .NumCPU ())
163+
164+ fullCores := int (coreNum )
165+ partialCoreRatio := coreNum - float64 (fullCores )
166+
167+ const maxIdleDuration = 1 * time .Second
168+ const minIdleDuration = 1 * time .Millisecond
169+ const initIdleDurationAdjustRatio float64 = 0.1
170+ const minIdleDurationAdjustRatio = 0.002
171+ var idleDuration = maxIdleDuration
172+ var dynIdleDurationAdjustRatio float64 = initIdleDurationAdjustRatio
173+ var stopWork = false
174+ var cur float64 = 0
175+ var ctxDone = false
176+
177+ var fixIdleDuration = func () {
178+ var err error
179+ cur , err = cpuMonitor .GetCPUUsage ()
180+ if err != nil {
181+ log .Printf ("MaintainCpuUsage: get cpu usage failed, reason: %s" , err .Error ())
182+ return
183+ }
184+ // When the cpu usage fluctuates greatly, increase idleDurationAdjustRatio to stabilize the cpu usage
185+ if dynIdleDurationAdjustRatio == minIdleDurationAdjustRatio {
186+ if cur > usagePercent + 20 || cur < usagePercent - 20 {
187+ dynIdleDurationAdjustRatio = initIdleDurationAdjustRatio
188+ } else if cur > usagePercent + 10 || cur < usagePercent - 10 {
189+ dynIdleDurationAdjustRatio = initIdleDurationAdjustRatio * 0.5
190+ }
191+ }
192+ if cur > usagePercent {
193+ idleDuration = time .Duration (float64 (idleDuration ) * (1 + dynIdleDurationAdjustRatio ))
194+ } else if cur < usagePercent {
195+ idleDuration = time .Duration (float64 (idleDuration ) * (1 - dynIdleDurationAdjustRatio ))
196+ }
197+ if idleDuration < minIdleDuration {
198+ idleDuration = minIdleDuration
199+ } else if idleDuration > maxIdleDuration {
200+ idleDuration = maxIdleDuration
201+ stopWork = true
202+ } else {
203+ stopWork = false
204+ }
205+ // gradually decrease the idle duration adjustment ratio, make the idle duration more stable
206+ dynIdleDurationAdjustRatio -= 0.001
207+ dynIdleDurationAdjustRatio = max (minIdleDurationAdjustRatio , dynIdleDurationAdjustRatio )
208+ }
209+ var worker = func (wg * sync.WaitGroup , idx int , workerName string , work func ()) {
210+ defer wg .Done ()
211+ cleanup , err := setCpuAffWrapper (idx , cpuAffinitiesEat )
212+ if err != nil {
213+ fmt .Printf ("Error: %s failed to set cpu affinities, reason: %s\n " , workerName , err .Error ())
214+ return
215+ }
216+ if cleanup != nil {
217+ fmt .Printf ("Worker %s: CPU affinities set to %d\n " , workerName , cpuAffinitiesEat [idx ])
218+ defer cleanup ()
219+ }
220+ for {
221+ if ctxDone {
222+ return
223+ }
224+ if ! stopWork {
225+ work ()
226+ }
227+ // if idleDuration is less than 1ms, do not sleep, directly execute fixIdleDuration
228+ if idleDuration > time .Millisecond * 1 {
229+ time .Sleep (idleDuration )
230+ }
231+ }
232+ }
233+ cpuIntensiveTask := GenerateCPUIntensiveTask (time .Microsecond * 2000 ) // 2ms is empirical data
234+ wg := & sync.WaitGroup {}
235+ for i := 0 ; i < fullCores ; i ++ {
236+ wg .Add (1 )
237+ workerName := fmt .Sprintf ("%d@fullCore" , i )
238+ go worker (wg , i , workerName , cpuIntensiveTask )
239+ }
240+ if partialCoreRatio > 0 {
241+ wg .Add (1 )
242+ workerName := fmt .Sprintf ("%d@partCore" , fullCores )
243+ go worker (wg , fullCores , workerName , cpuIntensiveTask )
244+ }
245+ fmt .Print ("\033 [?25l" ) // hide cursor
246+ defer fmt .Print ("\033 [?25h" ) // show cursor
247+
248+ ticker := time .NewTicker (time .Millisecond * 300 )
249+ defer ticker .Stop ()
250+ for {
251+ select {
252+ case <- ctx .Done ():
253+ ctxDone = true
254+ fmt .Println ("MaintainCpuUsage: quit due to context being cancelled" )
255+ wg .Wait ()
256+ return
257+ case <- ticker .C :
258+ fixIdleDuration ()
259+ printCpuInfo (cur , idleDuration , dynIdleDurationAdjustRatio )
260+ }
261+ }
262+ }
263+
264+ func printCpuInfo (usagePercent float64 , idleDuration time.Duration , ratio float64 ) {
265+ // clear current line
266+ fmt .Print ("\033 [2K" )
267+ fmt .Printf ("Idle: %s\n " , idleDuration )
268+ fmt .Printf ("Ratio: %.3f%%\n " , ratio )
269+ fmt .Printf ("CPU usage:\033 [32m%6.2f%%\033 [0m\n " , usagePercent )
270+ fmt .Print ("\033 [3A\033 [G" )
271+ }
272+
273+ // GenerateCPUIntensiveTask returns a function that performs a duration CPU-intensive task
274+ func GenerateCPUIntensiveTask (duration time.Duration ) func () {
275+ const N = 1000
276+ start := time .Now ()
277+ iteration := 0
278+ var cnt int64 = 0
279+ var i int
280+ for time .Since (start ) < duration {
281+ for i = 0 ; i < N ; i ++ {
282+ cnt = cnt * rand .Int64N (100 )
283+ iteration ++
284+ }
285+ }
286+ return func () {
287+ var cnt2 int64 = cnt
288+ for i = 0 ; i < iteration ; i ++ {
289+ cnt2 = cnt2 * rand .Int64N (100 )
290+ cnt ++
291+ }
292+ // KeepAlive ensures that the variable is not optimized away by the compiler
293+ runtime .KeepAlive (cnt )
294+ runtime .KeepAlive (cnt2 )
295+ }
296+ }
0 commit comments