1
16
17 package cgroups
18
19 import (
20 "fmt"
21 "os"
22 "path"
23 "strings"
24 "testing"
25
26 v1 "github.com/containerd/cgroups/stats/v1"
27 )
28
29 const memoryData = `cache 1
30 rss 2
31 rss_huge 3
32 mapped_file 4
33 dirty 5
34 writeback 6
35 pgpgin 7
36 pgpgout 8
37 pgfault 9
38 pgmajfault 10
39 inactive_anon 11
40 active_anon 12
41 inactive_file 13
42 active_file 14
43 unevictable 15
44 hierarchical_memory_limit 16
45 hierarchical_memsw_limit 17
46 total_cache 18
47 total_rss 19
48 total_rss_huge 20
49 total_mapped_file 21
50 total_dirty 22
51 total_writeback 23
52 total_pgpgin 24
53 total_pgpgout 25
54 total_pgfault 26
55 total_pgmajfault 27
56 total_inactive_anon 28
57 total_active_anon 29
58 total_inactive_file 30
59 total_active_file 31
60 total_unevictable 32
61 `
62
63 const memoryOomControlData = `oom_kill_disable 1
64 under_oom 2
65 oom_kill 3
66 `
67
68 func TestParseMemoryStats(t *testing.T) {
69 var (
70 c = &memoryController{}
71 m = &v1.MemoryStat{}
72 r = strings.NewReader(memoryData)
73 )
74 if err := c.parseStats(r, m); err != nil {
75 t.Fatal(err)
76 }
77 index := []uint64{
78 m.Cache,
79 m.RSS,
80 m.RSSHuge,
81 m.MappedFile,
82 m.Dirty,
83 m.Writeback,
84 m.PgPgIn,
85 m.PgPgOut,
86 m.PgFault,
87 m.PgMajFault,
88 m.InactiveAnon,
89 m.ActiveAnon,
90 m.InactiveFile,
91 m.ActiveFile,
92 m.Unevictable,
93 m.HierarchicalMemoryLimit,
94 m.HierarchicalSwapLimit,
95 m.TotalCache,
96 m.TotalRSS,
97 m.TotalRSSHuge,
98 m.TotalMappedFile,
99 m.TotalDirty,
100 m.TotalWriteback,
101 m.TotalPgPgIn,
102 m.TotalPgPgOut,
103 m.TotalPgFault,
104 m.TotalPgMajFault,
105 m.TotalInactiveAnon,
106 m.TotalActiveAnon,
107 m.TotalInactiveFile,
108 m.TotalActiveFile,
109 m.TotalUnevictable,
110 }
111 for i, v := range index {
112 if v != uint64(i)+1 {
113 t.Errorf("expected value at index %d to be %d but received %d", i, i+1, v)
114 }
115 }
116 }
117
118 func TestParseMemoryOomControl(t *testing.T) {
119 var (
120 c = &memoryController{}
121 m = &v1.MemoryOomControl{}
122 r = strings.NewReader(memoryOomControlData)
123 )
124 if err := c.parseOomControlStats(r, m); err != nil {
125 t.Fatal(err)
126 }
127 index := []uint64{
128 m.OomKillDisable,
129 m.UnderOom,
130 m.OomKill,
131 }
132 for i, v := range index {
133 if v != uint64(i)+1 {
134 t.Errorf("expected value at index %d to be %d but received %d", i, i+1, v)
135 }
136 }
137 }
138
139 func TestMemoryController_Stat(t *testing.T) {
140
141 modules := []string{"", "memsw", "kmem", "kmem.tcp"}
142 metrics := []string{"usage_in_bytes", "max_usage_in_bytes", "failcnt", "limit_in_bytes"}
143 tmpRoot := buildMemoryMetrics(t, modules, metrics)
144
145
146 mc := NewMemory(tmpRoot)
147 stats := v1.Metrics{}
148 if err := mc.Stat("", &stats); err != nil {
149 t.Errorf("can't get stats: %v", err)
150 }
151
152
153 checkMemoryStatIsComplete(t, stats.Memory)
154 }
155
156 func TestMemoryController_Stat_IgnoreModules(t *testing.T) {
157
158 modules := []string{"", "kmem", "kmem.tcp"}
159 metrics := []string{"usage_in_bytes", "max_usage_in_bytes", "failcnt", "limit_in_bytes"}
160 tmpRoot := buildMemoryMetrics(t, modules, metrics)
161
162
163 mc := NewMemory(tmpRoot, IgnoreModules("memsw"))
164 stats := v1.Metrics{}
165 if err := mc.Stat("", &stats); err != nil {
166 t.Errorf("can't get stats: %v", err)
167 }
168
169
170 checkMemoryStatHasNoSwap(t, stats.Memory)
171 }
172
173 func TestMemoryController_Stat_OptionalSwap_HasSwap(t *testing.T) {
174
175 modules := []string{"", "memsw", "kmem", "kmem.tcp"}
176 metrics := []string{"usage_in_bytes", "max_usage_in_bytes", "failcnt", "limit_in_bytes"}
177 tmpRoot := buildMemoryMetrics(t, modules, metrics)
178
179
180 mc := NewMemory(tmpRoot, OptionalSwap())
181 stats := v1.Metrics{}
182 if err := mc.Stat("", &stats); err != nil {
183 t.Errorf("can't get stats: %v", err)
184 }
185
186
187 checkMemoryStatIsComplete(t, stats.Memory)
188 }
189
190 func TestMemoryController_Stat_OptionalSwap_NoSwap(t *testing.T) {
191
192 modules := []string{"", "kmem", "kmem.tcp"}
193 metrics := []string{"usage_in_bytes", "max_usage_in_bytes", "failcnt", "limit_in_bytes"}
194 tmpRoot := buildMemoryMetrics(t, modules, metrics)
195
196
197 mc := NewMemory(tmpRoot, OptionalSwap())
198 stats := v1.Metrics{}
199 if err := mc.Stat("", &stats); err != nil {
200 t.Errorf("can't get stats: %v", err)
201 }
202
203
204 checkMemoryStatHasNoSwap(t, stats.Memory)
205 }
206
207 func checkMemoryStatIsComplete(t *testing.T, mem *v1.MemoryStat) {
208 index := []uint64{
209 mem.Usage.Usage,
210 mem.Usage.Max,
211 mem.Usage.Failcnt,
212 mem.Usage.Limit,
213 mem.Swap.Usage,
214 mem.Swap.Max,
215 mem.Swap.Failcnt,
216 mem.Swap.Limit,
217 mem.Kernel.Usage,
218 mem.Kernel.Max,
219 mem.Kernel.Failcnt,
220 mem.Kernel.Limit,
221 mem.KernelTCP.Usage,
222 mem.KernelTCP.Max,
223 mem.KernelTCP.Failcnt,
224 mem.KernelTCP.Limit,
225 }
226 for i, v := range index {
227 if v != uint64(i) {
228 t.Errorf("expected value at index %d to be %d but received %d", i, i, v)
229 }
230 }
231 }
232
233 func checkMemoryStatHasNoSwap(t *testing.T, mem *v1.MemoryStat) {
234 if mem.Swap.Usage != 0 || mem.Swap.Limit != 0 ||
235 mem.Swap.Max != 0 || mem.Swap.Failcnt != 0 {
236 t.Errorf("swap memory should have been ignored. Got: %+v", mem.Swap)
237 }
238 index := []uint64{
239 mem.Usage.Usage,
240 mem.Usage.Max,
241 mem.Usage.Failcnt,
242 mem.Usage.Limit,
243 mem.Kernel.Usage,
244 mem.Kernel.Max,
245 mem.Kernel.Failcnt,
246 mem.Kernel.Limit,
247 mem.KernelTCP.Usage,
248 mem.KernelTCP.Max,
249 mem.KernelTCP.Failcnt,
250 mem.KernelTCP.Limit,
251 }
252 for i, v := range index {
253 if v != uint64(i) {
254 t.Errorf("expected value at index %d to be %d but received %d", i, i, v)
255 }
256 }
257 }
258
259
260 func buildMemoryMetrics(t *testing.T, modules []string, metrics []string) string {
261 tmpRoot := t.TempDir()
262 tmpDir := path.Join(tmpRoot, string(Memory))
263 if err := os.MkdirAll(tmpDir, defaultDirPerm); err != nil {
264 t.Fatal(err)
265 }
266 if err := os.WriteFile(path.Join(tmpDir, "memory.stat"), []byte(memoryData), defaultFilePerm); err != nil {
267 t.Fatal(err)
268 }
269 if err := os.WriteFile(path.Join(tmpDir, "memory.oom_control"), []byte(memoryOomControlData), defaultFilePerm); err != nil {
270 t.Fatal(err)
271 }
272 cnt := 0
273 for _, mod := range modules {
274 for _, metric := range metrics {
275 var fileName string
276 if mod == "" {
277 fileName = path.Join(tmpDir, strings.Join([]string{"memory", metric}, "."))
278 } else {
279 fileName = path.Join(tmpDir, strings.Join([]string{"memory", mod, metric}, "."))
280 }
281 if err := os.WriteFile(fileName, []byte(fmt.Sprintln(cnt)), defaultFilePerm); err != nil {
282 t.Fatal(err)
283 }
284 cnt++
285 }
286 }
287 return tmpRoot
288 }
289
View as plain text