github.com/opencontainers/runc@v1.2.0-rc.1.0.20240520010911-492dc558cdd6/libcontainer/cgroups/fs2/memory_test.go (about)

     1  package fs2
     2  
     3  import (
     4  	"os"
     5  	"path/filepath"
     6  	"strings"
     7  	"testing"
     8  
     9  	"github.com/opencontainers/runc/libcontainer/cgroups"
    10  )
    11  
    12  const exampleMemoryStatData = `anon 790425600
    13  file 6502666240
    14  kernel_stack 7012352
    15  pagetables 8867840
    16  percpu 2445520
    17  sock 40960
    18  shmem 6721536
    19  file_mapped 656187392
    20  file_dirty 1122304
    21  file_writeback 0
    22  swapcached 10
    23  anon_thp 438304768
    24  file_thp 0
    25  shmem_thp 0
    26  inactive_anon 892223488
    27  active_anon 2973696
    28  inactive_file 5307346944
    29  active_file 1179316224
    30  unevictable 31477760
    31  slab_reclaimable 348866240
    32  slab_unreclaimable 10099808
    33  slab 358966048
    34  workingset_refault_anon 0
    35  workingset_refault_file 0
    36  workingset_activate_anon 0
    37  workingset_activate_file 0
    38  workingset_restore_anon 0
    39  workingset_restore_file 0
    40  workingset_nodereclaim 0
    41  pgfault 103216687
    42  pgmajfault 6879
    43  pgrefill 0
    44  pgscan 0
    45  pgsteal 0
    46  pgactivate 1110217
    47  pgdeactivate 292
    48  pglazyfree 267
    49  pglazyfreed 0
    50  thp_fault_alloc 57411
    51  thp_collapse_alloc 443`
    52  
    53  func TestStatMemoryPodCgroupNotFound(t *testing.T) {
    54  	// We're using a fake cgroupfs.
    55  	cgroups.TestMode = true
    56  	fakeCgroupDir := t.TempDir()
    57  
    58  	// only write memory.stat to ensure pod cgroup usage
    59  	// still reads memory.current.
    60  	statPath := filepath.Join(fakeCgroupDir, "memory.stat")
    61  	if err := os.WriteFile(statPath, []byte(exampleMemoryStatData), 0o644); err != nil {
    62  		t.Fatal(err)
    63  	}
    64  
    65  	gotStats := cgroups.NewStats()
    66  
    67  	// use a fake root path to mismatch the file we wrote.
    68  	// this triggers the non-root path which should fail to find memory.current.
    69  	err := statMemory(fakeCgroupDir, gotStats)
    70  	if err == nil {
    71  		t.Errorf("expected error when statting memory for cgroupv2 root, but was nil")
    72  	}
    73  
    74  	if !strings.Contains(err.Error(), "memory.current: no such file or directory") {
    75  		t.Errorf("expected error to contain 'memory.current: no such file or directory', but was %s", err.Error())
    76  	}
    77  }
    78  
    79  func TestStatMemoryPodCgroup(t *testing.T) {
    80  	// We're using a fake cgroupfs.
    81  	cgroups.TestMode = true
    82  	fakeCgroupDir := t.TempDir()
    83  
    84  	statPath := filepath.Join(fakeCgroupDir, "memory.stat")
    85  	if err := os.WriteFile(statPath, []byte(exampleMemoryStatData), 0o644); err != nil {
    86  		t.Fatal(err)
    87  	}
    88  
    89  	if err := os.WriteFile(filepath.Join(fakeCgroupDir, "memory.current"), []byte("123456789"), 0o644); err != nil {
    90  		t.Fatal(err)
    91  	}
    92  
    93  	if err := os.WriteFile(filepath.Join(fakeCgroupDir, "memory.max"), []byte("999999999"), 0o644); err != nil {
    94  		t.Fatal(err)
    95  	}
    96  
    97  	if err := os.WriteFile(filepath.Join(fakeCgroupDir, "memory.peak"), []byte("987654321"), 0o644); err != nil {
    98  		t.Fatal(err)
    99  	}
   100  
   101  	gotStats := cgroups.NewStats()
   102  
   103  	// use a fake root path to trigger the pod cgroup lookup.
   104  	err := statMemory(fakeCgroupDir, gotStats)
   105  	if err != nil {
   106  		t.Errorf("expected no error when statting memory for cgroupv2 root, but got %#+v", err)
   107  	}
   108  
   109  	// result should be "memory.current"
   110  	var expectedUsageBytes uint64 = 123456789
   111  	if gotStats.MemoryStats.Usage.Usage != expectedUsageBytes {
   112  		t.Errorf("parsed cgroupv2 memory.stat doesn't match expected result: \ngot %#v\nexpected %#v\n", gotStats.MemoryStats.Usage.Usage, expectedUsageBytes)
   113  	}
   114  
   115  	// result should be "memory.max"
   116  	var expectedLimitBytes uint64 = 999999999
   117  	if gotStats.MemoryStats.Usage.Limit != expectedLimitBytes {
   118  		t.Errorf("parsed cgroupv2 memory.stat doesn't match expected result: \ngot %#v\nexpected %#v\n", gotStats.MemoryStats.Usage.Limit, expectedLimitBytes)
   119  	}
   120  
   121  	// result should be "memory.peak"
   122  	var expectedMaxUsageBytes uint64 = 987654321
   123  	if gotStats.MemoryStats.Usage.MaxUsage != expectedMaxUsageBytes {
   124  		t.Errorf("parsed cgroupv2 memory.stat doesn't match expected result: \ngot %#v\nexpected %#v\n", gotStats.MemoryStats.Usage.MaxUsage, expectedMaxUsageBytes)
   125  	}
   126  }
   127  
   128  func TestRootStatsFromMeminfo(t *testing.T) {
   129  	stats := &cgroups.Stats{
   130  		MemoryStats: cgroups.MemoryStats{
   131  			Stats: map[string]uint64{
   132  				"anon": 790425600,
   133  				"file": 6502666240,
   134  			},
   135  		},
   136  	}
   137  
   138  	if err := rootStatsFromMeminfo(stats); err != nil {
   139  		t.Fatal(err)
   140  	}
   141  
   142  	// result is anon + file
   143  	var expectedUsageBytes uint64 = 7293091840
   144  	if stats.MemoryStats.Usage.Usage != expectedUsageBytes {
   145  		t.Errorf("parsed cgroupv2 memory.stat doesn't match expected result: \ngot %d\nexpected %d\n", stats.MemoryStats.Usage.Usage, expectedUsageBytes)
   146  	}
   147  
   148  	// swap is adjusted to mem+swap
   149  	if stats.MemoryStats.SwapUsage.Usage < stats.MemoryStats.Usage.Usage {
   150  		t.Errorf("swap usage %d should be at least mem usage %d", stats.MemoryStats.SwapUsage.Usage, stats.MemoryStats.Usage.Usage)
   151  	}
   152  	if stats.MemoryStats.SwapUsage.Limit < stats.MemoryStats.Usage.Limit {
   153  		t.Errorf("swap limit %d should be at least mem limit %d", stats.MemoryStats.SwapUsage.Limit, stats.MemoryStats.Usage.Limit)
   154  	}
   155  }