...

Source file src/github.com/containerd/cgroups/memory_test.go

Documentation: github.com/containerd/cgroups

     1  /*
     2     Copyright The containerd Authors.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    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  	// GIVEN a cgroups folder with all the memory metrics
   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  	// WHEN the memory controller reads the metrics stats
   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  	// THEN all the memory stats have been completely loaded in memory
   153  	checkMemoryStatIsComplete(t, stats.Memory)
   154  }
   155  
   156  func TestMemoryController_Stat_IgnoreModules(t *testing.T) {
   157  	// GIVEN a cgroups folder that accounts for all the metrics BUT swap memory
   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  	// WHEN the memory controller explicitly ignores memsw module and reads the data
   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  	// THEN the swap memory stats are not loaded but all the other memory metrics are
   170  	checkMemoryStatHasNoSwap(t, stats.Memory)
   171  }
   172  
   173  func TestMemoryController_Stat_OptionalSwap_HasSwap(t *testing.T) {
   174  	// GIVEN a cgroups folder with all the memory metrics
   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  	// WHEN a memory controller that ignores swap only if it is missing reads stats
   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  	// THEN all the memory stats have been completely loaded in memory
   187  	checkMemoryStatIsComplete(t, stats.Memory)
   188  }
   189  
   190  func TestMemoryController_Stat_OptionalSwap_NoSwap(t *testing.T) {
   191  	// GIVEN a cgroups folder that accounts for all the metrics BUT swap memory
   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  	// WHEN a memory controller that ignores swap only if it is missing reads stats
   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  	// THEN the swap memory stats are not loaded but all the other memory metrics are
   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  // buildMemoryMetrics creates fake cgroups memory entries in a temporary dir. Returns the fake cgroups root
   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