| package fs |
| |
| import ( |
| "strconv" |
| "testing" |
| |
| "github.com/opencontainers/runc/libcontainer/cgroups" |
| "github.com/opencontainers/runc/libcontainer/cgroups/fscommon" |
| "github.com/opencontainers/runc/libcontainer/configs" |
| ) |
| |
| const ( |
| memoryStatContents = `cache 512 |
| rss 1024` |
| memoryUsageContents = "2048\n" |
| memoryMaxUsageContents = "4096\n" |
| memoryFailcnt = "100\n" |
| memoryLimitContents = "8192\n" |
| memoryUseHierarchyContents = "1\n" |
| memoryNUMAStatContents = `total=44611 N0=32631 N1=7501 N2=1982 N3=2497 |
| file=44428 N0=32614 N1=7335 N2=1982 N3=2497 |
| anon=183 N0=17 N1=166 N2=0 N3=0 |
| unevictable=0 N0=0 N1=0 N2=0 N3=0 |
| hierarchical_total=768133 N0=509113 N1=138887 N2=20464 N3=99669 |
| hierarchical_file=722017 N0=496516 N1=119997 N2=20181 N3=85323 |
| hierarchical_anon=46096 N0=12597 N1=18890 N2=283 N3=14326 |
| hierarchical_unevictable=20 N0=0 N1=0 N2=0 N3=20 |
| ` |
| memoryNUMAStatNoHierarchyContents = `total=44611 N0=32631 N1=7501 N2=1982 N3=2497 |
| file=44428 N0=32614 N1=7335 N2=1982 N3=2497 |
| anon=183 N0=17 N1=166 N2=0 N3=0 |
| unevictable=0 N0=0 N1=0 N2=0 N3=0 |
| ` |
| // Some custom kernels has extra fields that should be ignored |
| memoryNUMAStatExtraContents = `numa_locality 0 0 0 0 0 0 0 0 0 0 |
| numa_exectime 0 |
| whatever=100 N0=0 |
| ` |
| ) |
| |
| func TestMemorySetMemory(t *testing.T) { |
| path := tempDir(t, "memory") |
| |
| const ( |
| memoryBefore = 314572800 // 300M |
| memoryAfter = 524288000 // 500M |
| reservationBefore = 209715200 // 200M |
| reservationAfter = 314572800 // 300M |
| ) |
| |
| writeFileContents(t, path, map[string]string{ |
| "memory.limit_in_bytes": strconv.Itoa(memoryBefore), |
| "memory.soft_limit_in_bytes": strconv.Itoa(reservationBefore), |
| }) |
| |
| r := &configs.Resources{ |
| Memory: memoryAfter, |
| MemoryReservation: reservationAfter, |
| } |
| memory := &MemoryGroup{} |
| if err := memory.Set(path, r); err != nil { |
| t.Fatal(err) |
| } |
| |
| value, err := fscommon.GetCgroupParamUint(path, "memory.limit_in_bytes") |
| if err != nil { |
| t.Fatal(err) |
| } |
| if value != memoryAfter { |
| t.Fatal("Got the wrong value, set memory.limit_in_bytes failed.") |
| } |
| |
| value, err = fscommon.GetCgroupParamUint(path, "memory.soft_limit_in_bytes") |
| if err != nil { |
| t.Fatal(err) |
| } |
| if value != reservationAfter { |
| t.Fatal("Got the wrong value, set memory.soft_limit_in_bytes failed.") |
| } |
| } |
| |
| func TestMemorySetMemoryswap(t *testing.T) { |
| path := tempDir(t, "memory") |
| |
| const ( |
| memoryswapBefore = 314572800 // 300M |
| memoryswapAfter = 524288000 // 500M |
| ) |
| |
| writeFileContents(t, path, map[string]string{ |
| "memory.memsw.limit_in_bytes": strconv.Itoa(memoryswapBefore), |
| }) |
| |
| r := &configs.Resources{ |
| MemorySwap: memoryswapAfter, |
| } |
| memory := &MemoryGroup{} |
| if err := memory.Set(path, r); err != nil { |
| t.Fatal(err) |
| } |
| |
| value, err := fscommon.GetCgroupParamUint(path, "memory.memsw.limit_in_bytes") |
| if err != nil { |
| t.Fatal(err) |
| } |
| if value != memoryswapAfter { |
| t.Fatal("Got the wrong value, set memory.memsw.limit_in_bytes failed.") |
| } |
| } |
| |
| func TestMemorySetMemoryLargerThanSwap(t *testing.T) { |
| path := tempDir(t, "memory") |
| |
| const ( |
| memoryBefore = 314572800 // 300M |
| memoryswapBefore = 524288000 // 500M |
| memoryAfter = 629145600 // 600M |
| memoryswapAfter = 838860800 // 800M |
| ) |
| |
| writeFileContents(t, path, map[string]string{ |
| "memory.limit_in_bytes": strconv.Itoa(memoryBefore), |
| "memory.memsw.limit_in_bytes": strconv.Itoa(memoryswapBefore), |
| // Set will call getMemoryData when memory and swap memory are |
| // both set, fake these fields so we don't get error. |
| "memory.usage_in_bytes": "0", |
| "memory.max_usage_in_bytes": "0", |
| "memory.failcnt": "0", |
| }) |
| |
| r := &configs.Resources{ |
| Memory: memoryAfter, |
| MemorySwap: memoryswapAfter, |
| } |
| memory := &MemoryGroup{} |
| if err := memory.Set(path, r); err != nil { |
| t.Fatal(err) |
| } |
| |
| value, err := fscommon.GetCgroupParamUint(path, "memory.limit_in_bytes") |
| if err != nil { |
| t.Fatal(err) |
| } |
| if value != memoryAfter { |
| t.Fatal("Got the wrong value, set memory.limit_in_bytes failed.") |
| } |
| |
| value, err = fscommon.GetCgroupParamUint(path, "memory.memsw.limit_in_bytes") |
| if err != nil { |
| t.Fatal(err) |
| } |
| if value != memoryswapAfter { |
| t.Fatal("Got the wrong value, set memory.memsw.limit_in_bytes failed.") |
| } |
| } |
| |
| func TestMemorySetSwapSmallerThanMemory(t *testing.T) { |
| path := tempDir(t, "memory") |
| |
| const ( |
| memoryBefore = 629145600 // 600M |
| memoryswapBefore = 838860800 // 800M |
| memoryAfter = 314572800 // 300M |
| memoryswapAfter = 524288000 // 500M |
| ) |
| |
| writeFileContents(t, path, map[string]string{ |
| "memory.limit_in_bytes": strconv.Itoa(memoryBefore), |
| "memory.memsw.limit_in_bytes": strconv.Itoa(memoryswapBefore), |
| }) |
| |
| r := &configs.Resources{ |
| Memory: memoryAfter, |
| MemorySwap: memoryswapAfter, |
| } |
| memory := &MemoryGroup{} |
| if err := memory.Set(path, r); err != nil { |
| t.Fatal(err) |
| } |
| |
| value, err := fscommon.GetCgroupParamUint(path, "memory.limit_in_bytes") |
| if err != nil { |
| t.Fatal(err) |
| } |
| if value != memoryAfter { |
| t.Fatalf("Got the wrong value (%d != %d), set memory.limit_in_bytes failed", value, memoryAfter) |
| } |
| |
| value, err = fscommon.GetCgroupParamUint(path, "memory.memsw.limit_in_bytes") |
| if err != nil { |
| t.Fatal(err) |
| } |
| if value != memoryswapAfter { |
| t.Fatalf("Got the wrong value (%d != %d), set memory.memsw.limit_in_bytes failed", value, memoryswapAfter) |
| } |
| } |
| |
| func TestMemorySetMemorySwappinessDefault(t *testing.T) { |
| path := tempDir(t, "memory") |
| |
| swappinessBefore := 60 // default is 60 |
| swappinessAfter := uint64(0) |
| |
| writeFileContents(t, path, map[string]string{ |
| "memory.swappiness": strconv.Itoa(swappinessBefore), |
| }) |
| |
| r := &configs.Resources{ |
| MemorySwappiness: &swappinessAfter, |
| } |
| memory := &MemoryGroup{} |
| if err := memory.Set(path, r); err != nil { |
| t.Fatal(err) |
| } |
| |
| value, err := fscommon.GetCgroupParamUint(path, "memory.swappiness") |
| if err != nil { |
| t.Fatal(err) |
| } |
| if value != swappinessAfter { |
| t.Fatalf("Got the wrong value (%d), set memory.swappiness = %d failed.", value, swappinessAfter) |
| } |
| } |
| |
| func TestMemoryStats(t *testing.T) { |
| path := tempDir(t, "memory") |
| writeFileContents(t, path, map[string]string{ |
| "memory.stat": memoryStatContents, |
| "memory.usage_in_bytes": memoryUsageContents, |
| "memory.limit_in_bytes": memoryLimitContents, |
| "memory.max_usage_in_bytes": memoryMaxUsageContents, |
| "memory.failcnt": memoryFailcnt, |
| "memory.memsw.usage_in_bytes": memoryUsageContents, |
| "memory.memsw.max_usage_in_bytes": memoryMaxUsageContents, |
| "memory.memsw.failcnt": memoryFailcnt, |
| "memory.memsw.limit_in_bytes": memoryLimitContents, |
| "memory.kmem.usage_in_bytes": memoryUsageContents, |
| "memory.kmem.max_usage_in_bytes": memoryMaxUsageContents, |
| "memory.kmem.failcnt": memoryFailcnt, |
| "memory.kmem.limit_in_bytes": memoryLimitContents, |
| "memory.use_hierarchy": memoryUseHierarchyContents, |
| "memory.numa_stat": memoryNUMAStatContents + memoryNUMAStatExtraContents, |
| }) |
| |
| memory := &MemoryGroup{} |
| actualStats := *cgroups.NewStats() |
| err := memory.GetStats(path, &actualStats) |
| if err != nil { |
| t.Fatal(err) |
| } |
| expectedStats := cgroups.MemoryStats{ |
| Cache: 512, |
| Usage: cgroups.MemoryData{Usage: 2048, MaxUsage: 4096, Failcnt: 100, Limit: 8192}, |
| SwapUsage: cgroups.MemoryData{Usage: 2048, MaxUsage: 4096, Failcnt: 100, Limit: 8192}, |
| SwapOnlyUsage: cgroups.MemoryData{Usage: 0, MaxUsage: 0, Failcnt: 0, Limit: 0}, |
| KernelUsage: cgroups.MemoryData{Usage: 2048, MaxUsage: 4096, Failcnt: 100, Limit: 8192}, |
| Stats: map[string]uint64{"cache": 512, "rss": 1024}, |
| UseHierarchy: true, |
| PageUsageByNUMA: cgroups.PageUsageByNUMA{ |
| PageUsageByNUMAInner: cgroups.PageUsageByNUMAInner{ |
| Total: cgroups.PageStats{Total: 44611, Nodes: map[uint8]uint64{0: 32631, 1: 7501, 2: 1982, 3: 2497}}, |
| File: cgroups.PageStats{Total: 44428, Nodes: map[uint8]uint64{0: 32614, 1: 7335, 2: 1982, 3: 2497}}, |
| Anon: cgroups.PageStats{Total: 183, Nodes: map[uint8]uint64{0: 17, 1: 166, 2: 0, 3: 0}}, |
| Unevictable: cgroups.PageStats{Total: 0, Nodes: map[uint8]uint64{0: 0, 1: 0, 2: 0, 3: 0}}, |
| }, |
| Hierarchical: cgroups.PageUsageByNUMAInner{ |
| Total: cgroups.PageStats{Total: 768133, Nodes: map[uint8]uint64{0: 509113, 1: 138887, 2: 20464, 3: 99669}}, |
| File: cgroups.PageStats{Total: 722017, Nodes: map[uint8]uint64{0: 496516, 1: 119997, 2: 20181, 3: 85323}}, |
| Anon: cgroups.PageStats{Total: 46096, Nodes: map[uint8]uint64{0: 12597, 1: 18890, 2: 283, 3: 14326}}, |
| Unevictable: cgroups.PageStats{Total: 20, Nodes: map[uint8]uint64{0: 0, 1: 0, 2: 0, 3: 20}}, |
| }, |
| }, |
| } |
| expectMemoryStatEquals(t, expectedStats, actualStats.MemoryStats) |
| } |
| |
| func TestMemoryStatsNoStatFile(t *testing.T) { |
| path := tempDir(t, "memory") |
| writeFileContents(t, path, map[string]string{ |
| "memory.usage_in_bytes": memoryUsageContents, |
| "memory.max_usage_in_bytes": memoryMaxUsageContents, |
| "memory.limit_in_bytes": memoryLimitContents, |
| }) |
| |
| memory := &MemoryGroup{} |
| actualStats := *cgroups.NewStats() |
| err := memory.GetStats(path, &actualStats) |
| if err != nil { |
| t.Fatal(err) |
| } |
| } |
| |
| func TestMemoryStatsNoUsageFile(t *testing.T) { |
| path := tempDir(t, "memory") |
| writeFileContents(t, path, map[string]string{ |
| "memory.stat": memoryStatContents, |
| "memory.max_usage_in_bytes": memoryMaxUsageContents, |
| "memory.limit_in_bytes": memoryLimitContents, |
| }) |
| |
| memory := &MemoryGroup{} |
| actualStats := *cgroups.NewStats() |
| err := memory.GetStats(path, &actualStats) |
| if err == nil { |
| t.Fatal("Expected failure") |
| } |
| } |
| |
| func TestMemoryStatsNoMaxUsageFile(t *testing.T) { |
| path := tempDir(t, "memory") |
| writeFileContents(t, path, map[string]string{ |
| "memory.stat": memoryStatContents, |
| "memory.usage_in_bytes": memoryUsageContents, |
| "memory.limit_in_bytes": memoryLimitContents, |
| }) |
| |
| memory := &MemoryGroup{} |
| actualStats := *cgroups.NewStats() |
| err := memory.GetStats(path, &actualStats) |
| if err == nil { |
| t.Fatal("Expected failure") |
| } |
| } |
| |
| func TestMemoryStatsNoLimitInBytesFile(t *testing.T) { |
| path := tempDir(t, "memory") |
| writeFileContents(t, path, map[string]string{ |
| "memory.stat": memoryStatContents, |
| "memory.usage_in_bytes": memoryUsageContents, |
| "memory.max_usage_in_bytes": memoryMaxUsageContents, |
| }) |
| |
| memory := &MemoryGroup{} |
| actualStats := *cgroups.NewStats() |
| err := memory.GetStats(path, &actualStats) |
| if err == nil { |
| t.Fatal("Expected failure") |
| } |
| } |
| |
| func TestMemoryStatsBadStatFile(t *testing.T) { |
| path := tempDir(t, "memory") |
| writeFileContents(t, path, map[string]string{ |
| "memory.stat": "rss rss", |
| "memory.usage_in_bytes": memoryUsageContents, |
| "memory.max_usage_in_bytes": memoryMaxUsageContents, |
| "memory.limit_in_bytes": memoryLimitContents, |
| }) |
| |
| memory := &MemoryGroup{} |
| actualStats := *cgroups.NewStats() |
| err := memory.GetStats(path, &actualStats) |
| if err == nil { |
| t.Fatal("Expected failure") |
| } |
| } |
| |
| func TestMemoryStatsBadUsageFile(t *testing.T) { |
| path := tempDir(t, "memory") |
| writeFileContents(t, path, map[string]string{ |
| "memory.stat": memoryStatContents, |
| "memory.usage_in_bytes": "bad", |
| "memory.max_usage_in_bytes": memoryMaxUsageContents, |
| "memory.limit_in_bytes": memoryLimitContents, |
| }) |
| |
| memory := &MemoryGroup{} |
| actualStats := *cgroups.NewStats() |
| err := memory.GetStats(path, &actualStats) |
| if err == nil { |
| t.Fatal("Expected failure") |
| } |
| } |
| |
| func TestMemoryStatsBadMaxUsageFile(t *testing.T) { |
| path := tempDir(t, "memory") |
| writeFileContents(t, path, map[string]string{ |
| "memory.stat": memoryStatContents, |
| "memory.usage_in_bytes": memoryUsageContents, |
| "memory.max_usage_in_bytes": "bad", |
| "memory.limit_in_bytes": memoryLimitContents, |
| }) |
| |
| memory := &MemoryGroup{} |
| actualStats := *cgroups.NewStats() |
| err := memory.GetStats(path, &actualStats) |
| if err == nil { |
| t.Fatal("Expected failure") |
| } |
| } |
| |
| func TestMemoryStatsBadLimitInBytesFile(t *testing.T) { |
| path := tempDir(t, "memory") |
| writeFileContents(t, path, map[string]string{ |
| "memory.stat": memoryStatContents, |
| "memory.usage_in_bytes": memoryUsageContents, |
| "memory.max_usage_in_bytes": memoryMaxUsageContents, |
| "memory.limit_in_bytes": "bad", |
| }) |
| |
| memory := &MemoryGroup{} |
| actualStats := *cgroups.NewStats() |
| err := memory.GetStats(path, &actualStats) |
| if err == nil { |
| t.Fatal("Expected failure") |
| } |
| } |
| |
| func TestMemorySetOomControl(t *testing.T) { |
| path := tempDir(t, "memory") |
| |
| const ( |
| oomKillDisable = 1 // disable oom killer, default is 0 |
| ) |
| |
| writeFileContents(t, path, map[string]string{ |
| "memory.oom_control": strconv.Itoa(oomKillDisable), |
| }) |
| |
| memory := &MemoryGroup{} |
| r := &configs.Resources{} |
| if err := memory.Set(path, r); err != nil { |
| t.Fatal(err) |
| } |
| |
| value, err := fscommon.GetCgroupParamUint(path, "memory.oom_control") |
| if err != nil { |
| t.Fatal(err) |
| } |
| if value != oomKillDisable { |
| t.Fatalf("Got the wrong value, set memory.oom_control failed.") |
| } |
| } |
| |
| func TestNoHierarchicalNumaStat(t *testing.T) { |
| path := tempDir(t, "memory") |
| writeFileContents(t, path, map[string]string{ |
| "memory.numa_stat": memoryNUMAStatNoHierarchyContents + memoryNUMAStatExtraContents, |
| }) |
| |
| actualStats, err := getPageUsageByNUMA(path) |
| if err != nil { |
| t.Fatal(err) |
| } |
| pageUsageByNUMA := cgroups.PageUsageByNUMA{ |
| PageUsageByNUMAInner: cgroups.PageUsageByNUMAInner{ |
| Total: cgroups.PageStats{Total: 44611, Nodes: map[uint8]uint64{0: 32631, 1: 7501, 2: 1982, 3: 2497}}, |
| File: cgroups.PageStats{Total: 44428, Nodes: map[uint8]uint64{0: 32614, 1: 7335, 2: 1982, 3: 2497}}, |
| Anon: cgroups.PageStats{Total: 183, Nodes: map[uint8]uint64{0: 17, 1: 166, 2: 0, 3: 0}}, |
| Unevictable: cgroups.PageStats{Total: 0, Nodes: map[uint8]uint64{0: 0, 1: 0, 2: 0, 3: 0}}, |
| }, |
| Hierarchical: cgroups.PageUsageByNUMAInner{}, |
| } |
| expectPageUsageByNUMAEquals(t, pageUsageByNUMA, actualStats) |
| } |
| |
| func TestBadNumaStat(t *testing.T) { |
| memoryNUMAStatBadContents := []struct { |
| desc, contents string |
| }{ |
| { |
| desc: "Nx where x is not a number", |
| contents: `total=44611 N0=44611, |
| file=44428 Nx=0 |
| `, |
| }, { |
| desc: "Nx where x > 255", |
| contents: `total=44611 N333=444`, |
| }, { |
| desc: "Nx argument missing", |
| contents: `total=44611 N0=123 N1=`, |
| }, { |
| desc: "Nx argument is not a number", |
| contents: `total=44611 N0=123 N1=a`, |
| }, { |
| desc: "Missing = after Nx", |
| contents: `total=44611 N0=123 N1`, |
| }, { |
| desc: "No Nx at non-first position", |
| contents: `total=44611 N0=32631 |
| file=44428 N0=32614 |
| anon=183 N0=12 badone |
| `, |
| }, |
| } |
| path := tempDir(t, "memory") |
| for _, c := range memoryNUMAStatBadContents { |
| writeFileContents(t, path, map[string]string{ |
| "memory.numa_stat": c.contents, |
| }) |
| |
| _, err := getPageUsageByNUMA(path) |
| if err == nil { |
| t.Errorf("case %q: expected error, got nil", c.desc) |
| } |
| } |
| } |
| |
| func TestWithoutNumaStat(t *testing.T) { |
| path := tempDir(t, "memory") |
| |
| actualStats, err := getPageUsageByNUMA(path) |
| if err != nil { |
| t.Fatal(err) |
| } |
| expectPageUsageByNUMAEquals(t, cgroups.PageUsageByNUMA{}, actualStats) |
| } |