github.com/opencontainers/runc@v1.2.0-rc.1.0.20240520010911-492dc558cdd6/libcontainer/cgroups/fs/memory_test.go (about) 1 package fs 2 3 import ( 4 "strconv" 5 "testing" 6 7 "github.com/opencontainers/runc/libcontainer/cgroups" 8 "github.com/opencontainers/runc/libcontainer/cgroups/fscommon" 9 "github.com/opencontainers/runc/libcontainer/configs" 10 ) 11 12 const ( 13 memoryStatContents = `cache 512 14 rss 1024` 15 memoryUsageContents = "2048\n" 16 memoryMaxUsageContents = "4096\n" 17 memoryFailcnt = "100\n" 18 memoryLimitContents = "8192\n" 19 memoryUseHierarchyContents = "1\n" 20 memoryNUMAStatContents = `total=44611 N0=32631 N1=7501 N2=1982 N3=2497 21 file=44428 N0=32614 N1=7335 N2=1982 N3=2497 22 anon=183 N0=17 N1=166 N2=0 N3=0 23 unevictable=0 N0=0 N1=0 N2=0 N3=0 24 hierarchical_total=768133 N0=509113 N1=138887 N2=20464 N3=99669 25 hierarchical_file=722017 N0=496516 N1=119997 N2=20181 N3=85323 26 hierarchical_anon=46096 N0=12597 N1=18890 N2=283 N3=14326 27 hierarchical_unevictable=20 N0=0 N1=0 N2=0 N3=20 28 ` 29 memoryNUMAStatNoHierarchyContents = `total=44611 N0=32631 N1=7501 N2=1982 N3=2497 30 file=44428 N0=32614 N1=7335 N2=1982 N3=2497 31 anon=183 N0=17 N1=166 N2=0 N3=0 32 unevictable=0 N0=0 N1=0 N2=0 N3=0 33 ` 34 // Some custom kernels has extra fields that should be ignored 35 memoryNUMAStatExtraContents = `numa_locality 0 0 0 0 0 0 0 0 0 0 36 numa_exectime 0 37 whatever=100 N0=0 38 ` 39 ) 40 41 func TestMemorySetMemory(t *testing.T) { 42 path := tempDir(t, "memory") 43 44 const ( 45 memoryBefore = 314572800 // 300M 46 memoryAfter = 524288000 // 500M 47 reservationBefore = 209715200 // 200M 48 reservationAfter = 314572800 // 300M 49 ) 50 51 writeFileContents(t, path, map[string]string{ 52 "memory.limit_in_bytes": strconv.Itoa(memoryBefore), 53 "memory.soft_limit_in_bytes": strconv.Itoa(reservationBefore), 54 }) 55 56 r := &configs.Resources{ 57 Memory: memoryAfter, 58 MemoryReservation: reservationAfter, 59 } 60 memory := &MemoryGroup{} 61 if err := memory.Set(path, r); err != nil { 62 t.Fatal(err) 63 } 64 65 value, err := fscommon.GetCgroupParamUint(path, "memory.limit_in_bytes") 66 if err != nil { 67 t.Fatal(err) 68 } 69 if value != memoryAfter { 70 t.Fatal("Got the wrong value, set memory.limit_in_bytes failed.") 71 } 72 73 value, err = fscommon.GetCgroupParamUint(path, "memory.soft_limit_in_bytes") 74 if err != nil { 75 t.Fatal(err) 76 } 77 if value != reservationAfter { 78 t.Fatal("Got the wrong value, set memory.soft_limit_in_bytes failed.") 79 } 80 } 81 82 func TestMemorySetMemoryswap(t *testing.T) { 83 path := tempDir(t, "memory") 84 85 const ( 86 memoryswapBefore = 314572800 // 300M 87 memoryswapAfter = 524288000 // 500M 88 ) 89 90 writeFileContents(t, path, map[string]string{ 91 "memory.memsw.limit_in_bytes": strconv.Itoa(memoryswapBefore), 92 }) 93 94 r := &configs.Resources{ 95 MemorySwap: memoryswapAfter, 96 } 97 memory := &MemoryGroup{} 98 if err := memory.Set(path, r); err != nil { 99 t.Fatal(err) 100 } 101 102 value, err := fscommon.GetCgroupParamUint(path, "memory.memsw.limit_in_bytes") 103 if err != nil { 104 t.Fatal(err) 105 } 106 if value != memoryswapAfter { 107 t.Fatal("Got the wrong value, set memory.memsw.limit_in_bytes failed.") 108 } 109 } 110 111 func TestMemorySetMemoryLargerThanSwap(t *testing.T) { 112 path := tempDir(t, "memory") 113 114 const ( 115 memoryBefore = 314572800 // 300M 116 memoryswapBefore = 524288000 // 500M 117 memoryAfter = 629145600 // 600M 118 memoryswapAfter = 838860800 // 800M 119 ) 120 121 writeFileContents(t, path, map[string]string{ 122 "memory.limit_in_bytes": strconv.Itoa(memoryBefore), 123 "memory.memsw.limit_in_bytes": strconv.Itoa(memoryswapBefore), 124 // Set will call getMemoryData when memory and swap memory are 125 // both set, fake these fields so we don't get error. 126 "memory.usage_in_bytes": "0", 127 "memory.max_usage_in_bytes": "0", 128 "memory.failcnt": "0", 129 }) 130 131 r := &configs.Resources{ 132 Memory: memoryAfter, 133 MemorySwap: memoryswapAfter, 134 } 135 memory := &MemoryGroup{} 136 if err := memory.Set(path, r); err != nil { 137 t.Fatal(err) 138 } 139 140 value, err := fscommon.GetCgroupParamUint(path, "memory.limit_in_bytes") 141 if err != nil { 142 t.Fatal(err) 143 } 144 if value != memoryAfter { 145 t.Fatal("Got the wrong value, set memory.limit_in_bytes failed.") 146 } 147 148 value, err = fscommon.GetCgroupParamUint(path, "memory.memsw.limit_in_bytes") 149 if err != nil { 150 t.Fatal(err) 151 } 152 if value != memoryswapAfter { 153 t.Fatal("Got the wrong value, set memory.memsw.limit_in_bytes failed.") 154 } 155 } 156 157 func TestMemorySetSwapSmallerThanMemory(t *testing.T) { 158 path := tempDir(t, "memory") 159 160 const ( 161 memoryBefore = 629145600 // 600M 162 memoryswapBefore = 838860800 // 800M 163 memoryAfter = 314572800 // 300M 164 memoryswapAfter = 524288000 // 500M 165 ) 166 167 writeFileContents(t, path, map[string]string{ 168 "memory.limit_in_bytes": strconv.Itoa(memoryBefore), 169 "memory.memsw.limit_in_bytes": strconv.Itoa(memoryswapBefore), 170 }) 171 172 r := &configs.Resources{ 173 Memory: memoryAfter, 174 MemorySwap: memoryswapAfter, 175 } 176 memory := &MemoryGroup{} 177 if err := memory.Set(path, r); err != nil { 178 t.Fatal(err) 179 } 180 181 value, err := fscommon.GetCgroupParamUint(path, "memory.limit_in_bytes") 182 if err != nil { 183 t.Fatal(err) 184 } 185 if value != memoryAfter { 186 t.Fatalf("Got the wrong value (%d != %d), set memory.limit_in_bytes failed", value, memoryAfter) 187 } 188 189 value, err = fscommon.GetCgroupParamUint(path, "memory.memsw.limit_in_bytes") 190 if err != nil { 191 t.Fatal(err) 192 } 193 if value != memoryswapAfter { 194 t.Fatalf("Got the wrong value (%d != %d), set memory.memsw.limit_in_bytes failed", value, memoryswapAfter) 195 } 196 } 197 198 func TestMemorySetMemorySwappinessDefault(t *testing.T) { 199 path := tempDir(t, "memory") 200 201 swappinessBefore := 60 // default is 60 202 swappinessAfter := uint64(0) 203 204 writeFileContents(t, path, map[string]string{ 205 "memory.swappiness": strconv.Itoa(swappinessBefore), 206 }) 207 208 r := &configs.Resources{ 209 MemorySwappiness: &swappinessAfter, 210 } 211 memory := &MemoryGroup{} 212 if err := memory.Set(path, r); err != nil { 213 t.Fatal(err) 214 } 215 216 value, err := fscommon.GetCgroupParamUint(path, "memory.swappiness") 217 if err != nil { 218 t.Fatal(err) 219 } 220 if value != swappinessAfter { 221 t.Fatalf("Got the wrong value (%d), set memory.swappiness = %d failed.", value, swappinessAfter) 222 } 223 } 224 225 func TestMemoryStats(t *testing.T) { 226 path := tempDir(t, "memory") 227 writeFileContents(t, path, map[string]string{ 228 "memory.stat": memoryStatContents, 229 "memory.usage_in_bytes": memoryUsageContents, 230 "memory.limit_in_bytes": memoryLimitContents, 231 "memory.max_usage_in_bytes": memoryMaxUsageContents, 232 "memory.failcnt": memoryFailcnt, 233 "memory.memsw.usage_in_bytes": memoryUsageContents, 234 "memory.memsw.max_usage_in_bytes": memoryMaxUsageContents, 235 "memory.memsw.failcnt": memoryFailcnt, 236 "memory.memsw.limit_in_bytes": memoryLimitContents, 237 "memory.kmem.usage_in_bytes": memoryUsageContents, 238 "memory.kmem.max_usage_in_bytes": memoryMaxUsageContents, 239 "memory.kmem.failcnt": memoryFailcnt, 240 "memory.kmem.limit_in_bytes": memoryLimitContents, 241 "memory.use_hierarchy": memoryUseHierarchyContents, 242 "memory.numa_stat": memoryNUMAStatContents + memoryNUMAStatExtraContents, 243 }) 244 245 memory := &MemoryGroup{} 246 actualStats := *cgroups.NewStats() 247 err := memory.GetStats(path, &actualStats) 248 if err != nil { 249 t.Fatal(err) 250 } 251 expectedStats := cgroups.MemoryStats{ 252 Cache: 512, 253 Usage: cgroups.MemoryData{Usage: 2048, MaxUsage: 4096, Failcnt: 100, Limit: 8192}, 254 SwapUsage: cgroups.MemoryData{Usage: 2048, MaxUsage: 4096, Failcnt: 100, Limit: 8192}, 255 SwapOnlyUsage: cgroups.MemoryData{Usage: 0, MaxUsage: 0, Failcnt: 0, Limit: 0}, 256 KernelUsage: cgroups.MemoryData{Usage: 2048, MaxUsage: 4096, Failcnt: 100, Limit: 8192}, 257 Stats: map[string]uint64{"cache": 512, "rss": 1024}, 258 UseHierarchy: true, 259 PageUsageByNUMA: cgroups.PageUsageByNUMA{ 260 PageUsageByNUMAInner: cgroups.PageUsageByNUMAInner{ 261 Total: cgroups.PageStats{Total: 44611, Nodes: map[uint8]uint64{0: 32631, 1: 7501, 2: 1982, 3: 2497}}, 262 File: cgroups.PageStats{Total: 44428, Nodes: map[uint8]uint64{0: 32614, 1: 7335, 2: 1982, 3: 2497}}, 263 Anon: cgroups.PageStats{Total: 183, Nodes: map[uint8]uint64{0: 17, 1: 166, 2: 0, 3: 0}}, 264 Unevictable: cgroups.PageStats{Total: 0, Nodes: map[uint8]uint64{0: 0, 1: 0, 2: 0, 3: 0}}, 265 }, 266 Hierarchical: cgroups.PageUsageByNUMAInner{ 267 Total: cgroups.PageStats{Total: 768133, Nodes: map[uint8]uint64{0: 509113, 1: 138887, 2: 20464, 3: 99669}}, 268 File: cgroups.PageStats{Total: 722017, Nodes: map[uint8]uint64{0: 496516, 1: 119997, 2: 20181, 3: 85323}}, 269 Anon: cgroups.PageStats{Total: 46096, Nodes: map[uint8]uint64{0: 12597, 1: 18890, 2: 283, 3: 14326}}, 270 Unevictable: cgroups.PageStats{Total: 20, Nodes: map[uint8]uint64{0: 0, 1: 0, 2: 0, 3: 20}}, 271 }, 272 }, 273 } 274 expectMemoryStatEquals(t, expectedStats, actualStats.MemoryStats) 275 } 276 277 func TestMemoryStatsNoStatFile(t *testing.T) { 278 path := tempDir(t, "memory") 279 writeFileContents(t, path, map[string]string{ 280 "memory.usage_in_bytes": memoryUsageContents, 281 "memory.max_usage_in_bytes": memoryMaxUsageContents, 282 "memory.limit_in_bytes": memoryLimitContents, 283 }) 284 285 memory := &MemoryGroup{} 286 actualStats := *cgroups.NewStats() 287 err := memory.GetStats(path, &actualStats) 288 if err != nil { 289 t.Fatal(err) 290 } 291 } 292 293 func TestMemoryStatsNoUsageFile(t *testing.T) { 294 path := tempDir(t, "memory") 295 writeFileContents(t, path, map[string]string{ 296 "memory.stat": memoryStatContents, 297 "memory.max_usage_in_bytes": memoryMaxUsageContents, 298 "memory.limit_in_bytes": memoryLimitContents, 299 }) 300 301 memory := &MemoryGroup{} 302 actualStats := *cgroups.NewStats() 303 err := memory.GetStats(path, &actualStats) 304 if err == nil { 305 t.Fatal("Expected failure") 306 } 307 } 308 309 func TestMemoryStatsNoMaxUsageFile(t *testing.T) { 310 path := tempDir(t, "memory") 311 writeFileContents(t, path, map[string]string{ 312 "memory.stat": memoryStatContents, 313 "memory.usage_in_bytes": memoryUsageContents, 314 "memory.limit_in_bytes": memoryLimitContents, 315 }) 316 317 memory := &MemoryGroup{} 318 actualStats := *cgroups.NewStats() 319 err := memory.GetStats(path, &actualStats) 320 if err == nil { 321 t.Fatal("Expected failure") 322 } 323 } 324 325 func TestMemoryStatsNoLimitInBytesFile(t *testing.T) { 326 path := tempDir(t, "memory") 327 writeFileContents(t, path, map[string]string{ 328 "memory.stat": memoryStatContents, 329 "memory.usage_in_bytes": memoryUsageContents, 330 "memory.max_usage_in_bytes": memoryMaxUsageContents, 331 }) 332 333 memory := &MemoryGroup{} 334 actualStats := *cgroups.NewStats() 335 err := memory.GetStats(path, &actualStats) 336 if err == nil { 337 t.Fatal("Expected failure") 338 } 339 } 340 341 func TestMemoryStatsBadStatFile(t *testing.T) { 342 path := tempDir(t, "memory") 343 writeFileContents(t, path, map[string]string{ 344 "memory.stat": "rss rss", 345 "memory.usage_in_bytes": memoryUsageContents, 346 "memory.max_usage_in_bytes": memoryMaxUsageContents, 347 "memory.limit_in_bytes": memoryLimitContents, 348 }) 349 350 memory := &MemoryGroup{} 351 actualStats := *cgroups.NewStats() 352 err := memory.GetStats(path, &actualStats) 353 if err == nil { 354 t.Fatal("Expected failure") 355 } 356 } 357 358 func TestMemoryStatsBadUsageFile(t *testing.T) { 359 path := tempDir(t, "memory") 360 writeFileContents(t, path, map[string]string{ 361 "memory.stat": memoryStatContents, 362 "memory.usage_in_bytes": "bad", 363 "memory.max_usage_in_bytes": memoryMaxUsageContents, 364 "memory.limit_in_bytes": memoryLimitContents, 365 }) 366 367 memory := &MemoryGroup{} 368 actualStats := *cgroups.NewStats() 369 err := memory.GetStats(path, &actualStats) 370 if err == nil { 371 t.Fatal("Expected failure") 372 } 373 } 374 375 func TestMemoryStatsBadMaxUsageFile(t *testing.T) { 376 path := tempDir(t, "memory") 377 writeFileContents(t, path, map[string]string{ 378 "memory.stat": memoryStatContents, 379 "memory.usage_in_bytes": memoryUsageContents, 380 "memory.max_usage_in_bytes": "bad", 381 "memory.limit_in_bytes": memoryLimitContents, 382 }) 383 384 memory := &MemoryGroup{} 385 actualStats := *cgroups.NewStats() 386 err := memory.GetStats(path, &actualStats) 387 if err == nil { 388 t.Fatal("Expected failure") 389 } 390 } 391 392 func TestMemoryStatsBadLimitInBytesFile(t *testing.T) { 393 path := tempDir(t, "memory") 394 writeFileContents(t, path, map[string]string{ 395 "memory.stat": memoryStatContents, 396 "memory.usage_in_bytes": memoryUsageContents, 397 "memory.max_usage_in_bytes": memoryMaxUsageContents, 398 "memory.limit_in_bytes": "bad", 399 }) 400 401 memory := &MemoryGroup{} 402 actualStats := *cgroups.NewStats() 403 err := memory.GetStats(path, &actualStats) 404 if err == nil { 405 t.Fatal("Expected failure") 406 } 407 } 408 409 func TestMemorySetOomControl(t *testing.T) { 410 path := tempDir(t, "memory") 411 412 const ( 413 oomKillDisable = 1 // disable oom killer, default is 0 414 ) 415 416 writeFileContents(t, path, map[string]string{ 417 "memory.oom_control": strconv.Itoa(oomKillDisable), 418 }) 419 420 memory := &MemoryGroup{} 421 r := &configs.Resources{} 422 if err := memory.Set(path, r); err != nil { 423 t.Fatal(err) 424 } 425 426 value, err := fscommon.GetCgroupParamUint(path, "memory.oom_control") 427 if err != nil { 428 t.Fatal(err) 429 } 430 if value != oomKillDisable { 431 t.Fatalf("Got the wrong value, set memory.oom_control failed.") 432 } 433 } 434 435 func TestNoHierarchicalNumaStat(t *testing.T) { 436 path := tempDir(t, "memory") 437 writeFileContents(t, path, map[string]string{ 438 "memory.numa_stat": memoryNUMAStatNoHierarchyContents + memoryNUMAStatExtraContents, 439 }) 440 441 actualStats, err := getPageUsageByNUMA(path) 442 if err != nil { 443 t.Fatal(err) 444 } 445 pageUsageByNUMA := cgroups.PageUsageByNUMA{ 446 PageUsageByNUMAInner: cgroups.PageUsageByNUMAInner{ 447 Total: cgroups.PageStats{Total: 44611, Nodes: map[uint8]uint64{0: 32631, 1: 7501, 2: 1982, 3: 2497}}, 448 File: cgroups.PageStats{Total: 44428, Nodes: map[uint8]uint64{0: 32614, 1: 7335, 2: 1982, 3: 2497}}, 449 Anon: cgroups.PageStats{Total: 183, Nodes: map[uint8]uint64{0: 17, 1: 166, 2: 0, 3: 0}}, 450 Unevictable: cgroups.PageStats{Total: 0, Nodes: map[uint8]uint64{0: 0, 1: 0, 2: 0, 3: 0}}, 451 }, 452 Hierarchical: cgroups.PageUsageByNUMAInner{}, 453 } 454 expectPageUsageByNUMAEquals(t, pageUsageByNUMA, actualStats) 455 } 456 457 func TestBadNumaStat(t *testing.T) { 458 memoryNUMAStatBadContents := []struct { 459 desc, contents string 460 }{ 461 { 462 desc: "Nx where x is not a number", 463 contents: `total=44611 N0=44611, 464 file=44428 Nx=0 465 `, 466 }, { 467 desc: "Nx where x > 255", 468 contents: `total=44611 N333=444`, 469 }, { 470 desc: "Nx argument missing", 471 contents: `total=44611 N0=123 N1=`, 472 }, { 473 desc: "Nx argument is not a number", 474 contents: `total=44611 N0=123 N1=a`, 475 }, { 476 desc: "Missing = after Nx", 477 contents: `total=44611 N0=123 N1`, 478 }, { 479 desc: "No Nx at non-first position", 480 contents: `total=44611 N0=32631 481 file=44428 N0=32614 482 anon=183 N0=12 badone 483 `, 484 }, 485 } 486 path := tempDir(t, "memory") 487 for _, c := range memoryNUMAStatBadContents { 488 writeFileContents(t, path, map[string]string{ 489 "memory.numa_stat": c.contents, 490 }) 491 492 _, err := getPageUsageByNUMA(path) 493 if err == nil { 494 t.Errorf("case %q: expected error, got nil", c.desc) 495 } 496 } 497 } 498 499 func TestWithoutNumaStat(t *testing.T) { 500 path := tempDir(t, "memory") 501 502 actualStats, err := getPageUsageByNUMA(path) 503 if err != nil { 504 t.Fatal(err) 505 } 506 expectPageUsageByNUMAEquals(t, cgroups.PageUsageByNUMA{}, actualStats) 507 }