/
manager.go
112 lines (90 loc) · 2.64 KB
/
manager.go
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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
package cpu
import (
"errors"
"fmt"
"os"
"runtime"
"sync"
"time"
"github.com/go-kit/kit/log"
"github.com/go-kit/kit/log/level"
)
// CPU is the interface implementation that manages cpu failure injections
type CPU struct {
mu sync.Mutex
status status
done chan int
logger log.Logger
}
type status int
const (
recovered status = iota
started
)
// New will create a new CPU instance with the amount of threads to perform
// the injection and the channel that will be used to recover it
func New(logger log.Logger) *CPU {
return &CPU{
logger: logger,
}
}
// Start will perform a cpu failure injection by starting goroutines in for loops
func (cpu *CPU) Start(percentage int) (string, error) {
cpu.mu.Lock()
defer cpu.mu.Unlock()
if cpu.status == started {
return "Could not inject cpu failure", errors.New("CPU injection already running. Recover before starting another")
}
cpu.done = make(chan int)
if err := cpu.injection(percentage); err != nil {
return "Could not inject cpu failure", err
}
cpu.status = started
return constructStartMessage(cpu.logger, percentage), nil
}
// Recover will recover cpu failure by closing all channels
func (cpu *CPU) Recover() (string, error) {
cpu.mu.Lock()
defer cpu.mu.Unlock()
if cpu.status != started {
return "Could not recover cpu failure", errors.New("CPU injection is not running. Start it before trying to recover")
}
close(cpu.done)
cpu.status = recovered
return constructMessage(cpu.logger, "recovered"), nil
}
func (cpu *CPU) injection(percent int) error {
if percent < 0 || percent > 100 {
return fmt.Errorf("cpu injection percentage %d is out of bounds. should be 0 to 100", percent)
}
sleepBaseOnPercentage := time.Duration(1000 * (100 - percent) / 100)
for i := 0; i < runtime.NumCPU(); i++ {
time.Sleep(time.Duration(1000/runtime.NumCPU()) * time.Millisecond)
go func() {
ticker := time.NewTicker(1 * time.Second)
for {
select {
case <-cpu.done:
return
case <-ticker.C:
time.Sleep(sleepBaseOnPercentage * time.Millisecond)
default: //nolint:staticcheck
}
}
}()
}
_ = level.Info(cpu.logger).Log("msg", fmt.Sprintf("Starting cpu injection for %d%%", percent))
return nil
}
func constructStartMessage(logger log.Logger, percentage int) string {
message := constructMessage(logger, "started")
return fmt.Sprintf("%s at %d%%", message, percentage)
}
func constructMessage(logger log.Logger, action string) string {
hostname, err := os.Hostname()
if err != nil {
_ = level.Warn(logger).Log("msg", "Could not get hostname", "err", err)
hostname = "Unknown"
}
return fmt.Sprintf("Bot %s %s cpu injection", hostname, action)
}