-
Notifications
You must be signed in to change notification settings - Fork 15
/
watchdog_linux_test.go
130 lines (105 loc) · 3.13 KB
/
watchdog_linux_test.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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
package watchdog
import (
"fmt"
"log"
"os"
"runtime"
"runtime/debug"
"testing"
"time"
"github.com/containerd/cgroups"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/benbjohnson/clock"
"github.com/stretchr/testify/require"
)
// retained will hoard unreclaimable byte buffers in the heap.
var retained [][]byte
func TestCgroupsDriven_Create_Isolated(t *testing.T) {
skipIfNotIsolated(t)
if os.Getpid() == 1 {
// we are running in Docker and cannot create a cgroup.
t.Skipf("cannot create a cgroup while running in non-privileged docker")
}
// new cgroup limit.
var limit = uint64(32 << 20) // 32MiB.
createMemoryCgroup(t, limit)
testCgroupsWatchdog(t, limit)
}
func TestCgroupsDriven_Docker_Isolated(t *testing.T) {
skipIfNotIsolated(t)
if os.Getpid() != 1 {
// we are not running in a container.
t.Skipf("test only runs inside a container")
}
testCgroupsWatchdog(t, uint64(DockerMemLimit))
}
func testCgroupsWatchdog(t *testing.T, limit uint64) {
t.Cleanup(func() {
retained = nil
})
runtime.GC() // first GC to clear any junk from other tests.
debug.SetGCPercent(100000000) // disable GC.
clk := clock.NewMock()
Clock = clk
notifyCh := make(chan struct{}, 1)
NotifyGC = func() {
notifyCh <- struct{}{}
}
err, stopFn := CgroupDriven(5*time.Second, NewAdaptivePolicy(0.5))
require.NoError(t, err)
defer stopFn()
time.Sleep(200 * time.Millisecond) // give time for the watchdog to init.
maxSlabs := limit / (1 << 20) // number of 1MiB slabs to take up the entire limit.
// first tick; nothing should happen.
clk.Add(5 * time.Second)
time.Sleep(200 * time.Millisecond)
require.Len(t, notifyCh, 0) // no GC has taken place.
// allocate 50% of limit in heap (to be added to other mem usage).
for i := 0; i < (int(maxSlabs))/2; i++ {
retained = append(retained, func() []byte {
b := make([]byte, 1*1024*1024)
for i := range b {
b[i] = 0xff
}
return b
}())
}
// second tick; used = just over 50%; will trigger GC.
clk.Add(5 * time.Second)
time.Sleep(200 * time.Millisecond)
require.NotNil(t, <-notifyCh)
var memstats runtime.MemStats
runtime.ReadMemStats(&memstats)
require.EqualValues(t, 2, memstats.NumForcedGC)
}
// createMemoryCgroup creates a memory cgroup to restrict the memory available
// to this test.
func createMemoryCgroup(t *testing.T, limit uint64) {
l := int64(limit)
path := cgroups.NestedPath(fmt.Sprintf("/%d", time.Now().UnixNano()))
cgroup, err := cgroups.New(cgroups.V1, path, &specs.LinuxResources{
Memory: &specs.LinuxMemory{
Limit: &l,
Swap: &l,
},
})
require.NoError(t, err, "failed to create a cgroup")
t.Cleanup(func() {
root, err := cgroups.Load(cgroups.V1, cgroups.RootPath)
if err != nil {
t.Logf("failed to resolve root cgroup: %s", err)
return
}
if err = root.Add(cgroups.Process{Pid: pid}); err != nil {
t.Logf("failed to move process to root cgroup: %s", err)
return
}
if err = cgroup.Delete(); err != nil {
t.Logf("failed to clean up temp cgroup: %s", err)
}
})
log.Printf("cgroup created")
// add process to cgroup.
err = cgroup.Add(cgroups.Process{Pid: pid})
require.NoError(t, err)
}