github.com/google/cadvisor@v0.49.1/resctrl/utils_test.go (about)

     1  //go:build linux
     2  // +build linux
     3  
     4  // Copyright 2021 Google Inc. All Rights Reserved.
     5  //
     6  // Licensed under the Apache License, Version 2.0 (the "License");
     7  // you may not use this file except in compliance with the License.
     8  // You may obtain a copy of the License at
     9  //
    10  //     http://www.apache.org/licenses/LICENSE-2.0
    11  //
    12  // Unless required by applicable law or agreed to in writing, software
    13  // distributed under the License is distributed on an "AS IS" BASIS,
    14  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    15  // See the License for the specific language governing permissions and
    16  // limitations under the License.
    17  
    18  // Utilities tests.
    19  //
    20  // Mocked environment:
    21  // - "container" first container with {1, 2, 3} processes.
    22  // - "another" second container with {5, 6} processes.
    23  package resctrl
    24  
    25  import (
    26  	"fmt"
    27  	"os"
    28  	"path/filepath"
    29  	"testing"
    30  
    31  	"github.com/opencontainers/runc/libcontainer/cgroups"
    32  	"github.com/opencontainers/runc/libcontainer/intelrdt"
    33  
    34  	"github.com/stretchr/testify/assert"
    35  )
    36  
    37  func init() {
    38  	// All the test cases in this file uses "fake" cgroups (not the real
    39  	// cgroupfs). This setting relaxes filesystem type check in cgroups
    40  	// package so it can work with fake cgroups.
    41  	cgroups.TestMode = true
    42  }
    43  
    44  func mockAllGetContainerPids() ([]string, error) {
    45  	return []string{"1", "2", "3", "5", "6"}, nil
    46  }
    47  
    48  func mockGetContainerPids() ([]string, error) {
    49  	return []string{"1", "2", "3"}, nil
    50  }
    51  
    52  func mockAnotherGetContainerPids() ([]string, error) {
    53  	return []string{"5", "6"}, nil
    54  }
    55  
    56  func touch(path string) error {
    57  	file, err := os.OpenFile(path, os.O_CREATE, os.ModePerm)
    58  	if err != nil {
    59  		return err
    60  	}
    61  	return file.Close()
    62  }
    63  
    64  func touchDir(path string) error {
    65  	err := os.MkdirAll(path, os.ModePerm)
    66  	if err != nil {
    67  		return err
    68  	}
    69  	return nil
    70  }
    71  
    72  func fillPids(path string, pids []int) error {
    73  	f, err := os.OpenFile(path, os.O_WRONLY, os.ModePerm)
    74  	if err != nil {
    75  		return err
    76  	}
    77  	defer f.Close()
    78  	for _, pid := range pids {
    79  		_, err := fmt.Fprintln(f, pid)
    80  		if err != nil {
    81  			return err
    82  		}
    83  	}
    84  	return nil
    85  }
    86  
    87  func mockResctrl() string {
    88  	path, _ := os.MkdirTemp("", "resctrl")
    89  
    90  	var files = []struct {
    91  		path  string
    92  		touch func(string) error
    93  	}{
    94  		// Mock root files.
    95  		{
    96  			filepath.Join(path, cpusFileName),
    97  			touch,
    98  		},
    99  		{
   100  			filepath.Join(path, cpusListFileName),
   101  			touch,
   102  		},
   103  		{
   104  			filepath.Join(path, infoDirName),
   105  			touchDir,
   106  		},
   107  		{
   108  			filepath.Join(path, monDataDirName),
   109  			touchDir,
   110  		},
   111  		{
   112  			filepath.Join(path, monGroupsDirName),
   113  			touchDir,
   114  		},
   115  		{
   116  			filepath.Join(path, schemataFileName),
   117  			touch,
   118  		},
   119  		{
   120  			filepath.Join(path, modeFileName),
   121  			touch,
   122  		},
   123  		{
   124  			filepath.Join(path, sizeFileName),
   125  			touch,
   126  		},
   127  		{
   128  			filepath.Join(path, tasksFileName),
   129  			touch,
   130  		},
   131  		// Create custom CLOSID "m1".
   132  		{
   133  			filepath.Join(path, "m1"),
   134  			touchDir,
   135  		},
   136  		{
   137  			filepath.Join(path, "m1", cpusFileName),
   138  			touch,
   139  		},
   140  		{
   141  			filepath.Join(path, "m1", cpusListFileName),
   142  			touch,
   143  		},
   144  		{
   145  			filepath.Join(path, "m1", monDataDirName),
   146  			touchDir,
   147  		},
   148  		{
   149  			filepath.Join(path, "m1", monGroupsDirName),
   150  			touchDir,
   151  		},
   152  		{
   153  			filepath.Join(path, "m1", schemataFileName),
   154  			touch,
   155  		},
   156  		{
   157  			filepath.Join(path, "m1", tasksFileName),
   158  			touch,
   159  		},
   160  		{
   161  			filepath.Join(path, "m1", monGroupsDirName, "test"),
   162  			touchDir,
   163  		},
   164  		{
   165  			filepath.Join(path, "m1", monGroupsDirName, "test", tasksFileName),
   166  			touch,
   167  		},
   168  	}
   169  	for _, file := range files {
   170  		err := file.touch(file.path)
   171  		if err != nil {
   172  			return ""
   173  		}
   174  	}
   175  
   176  	// Mock root group task file.
   177  	err := fillPids(filepath.Join(path, tasksFileName), []int{1, 2, 3, 4})
   178  	if err != nil {
   179  		return ""
   180  	}
   181  
   182  	// Mock custom CLOSID "m1" task file.
   183  	err = fillPids(filepath.Join(path, "m1", tasksFileName), []int{5, 6, 7, 8, 9, 10})
   184  	if err != nil {
   185  		return ""
   186  	}
   187  	// Mock custom mon group "test" task file.
   188  	err = fillPids(filepath.Join(path, "m1", monGroupsDirName, "test", tasksFileName), []int{7, 8})
   189  	if err != nil {
   190  		return ""
   191  	}
   192  
   193  	return path
   194  }
   195  
   196  func mockResctrlMonData(path string) {
   197  
   198  	_ = touchDir(filepath.Join(path, monDataDirName, "mon_L3_00"))
   199  	_ = touchDir(filepath.Join(path, monDataDirName, "mon_L3_01"))
   200  
   201  	var files = []struct {
   202  		path  string
   203  		value string
   204  	}{
   205  		{
   206  			filepath.Join(path, monDataDirName, "mon_L3_00", llcOccupancyFileName),
   207  			"1111",
   208  		},
   209  		{
   210  			filepath.Join(path, monDataDirName, "mon_L3_00", mbmLocalBytesFileName),
   211  			"2222",
   212  		},
   213  		{
   214  			filepath.Join(path, monDataDirName, "mon_L3_00", mbmTotalBytesFileName),
   215  			"3333",
   216  		},
   217  		{
   218  			filepath.Join(path, monDataDirName, "mon_L3_01", llcOccupancyFileName),
   219  			"3333",
   220  		},
   221  		{
   222  			filepath.Join(path, monDataDirName, "mon_L3_01", mbmLocalBytesFileName),
   223  			"1111",
   224  		},
   225  		{
   226  			filepath.Join(path, monDataDirName, "mon_L3_01", mbmTotalBytesFileName),
   227  			"3333",
   228  		},
   229  	}
   230  
   231  	for _, file := range files {
   232  		_ = touch(file.path)
   233  		_ = os.WriteFile(file.path, []byte(file.value), os.ModePerm)
   234  	}
   235  }
   236  
   237  func mockContainersPids() string {
   238  	path, _ := os.MkdirTemp("", "cgroup")
   239  	// container
   240  	_ = touchDir(filepath.Join(path, "container"))
   241  	_ = touch(filepath.Join(path, "container", cgroups.CgroupProcesses))
   242  	err := fillPids(filepath.Join(path, "container", cgroups.CgroupProcesses), []int{1, 2, 3})
   243  	if err != nil {
   244  		return ""
   245  	}
   246  	// another
   247  	_ = touchDir(filepath.Join(path, "another"))
   248  	_ = touch(filepath.Join(path, "another", cgroups.CgroupProcesses))
   249  	err = fillPids(filepath.Join(path, "another", cgroups.CgroupProcesses), []int{5})
   250  	if err != nil {
   251  		return ""
   252  	}
   253  
   254  	return path
   255  }
   256  
   257  func mockProcFs() string {
   258  	path, _ := os.MkdirTemp("", "proc")
   259  
   260  	var files = []struct {
   261  		path  string
   262  		touch func(string) error
   263  	}{
   264  		// container
   265  		{
   266  			filepath.Join(path, "1", processTask, "1"),
   267  			touchDir,
   268  		},
   269  		{
   270  			filepath.Join(path, "2", processTask, "2"),
   271  			touchDir,
   272  		},
   273  		{
   274  			filepath.Join(path, "3", processTask, "3"),
   275  			touchDir,
   276  		},
   277  		{
   278  			filepath.Join(path, "4", processTask, "4"),
   279  			touchDir,
   280  		},
   281  		// another
   282  		{
   283  			filepath.Join(path, "5", processTask, "5"),
   284  			touchDir,
   285  		},
   286  		{
   287  			filepath.Join(path, "6", processTask, "6"),
   288  			touchDir,
   289  		},
   290  	}
   291  
   292  	for _, file := range files {
   293  		_ = file.touch(file.path)
   294  	}
   295  
   296  	return path
   297  }
   298  
   299  func checkError(t *testing.T, err error, expected string) {
   300  	if expected != "" {
   301  		assert.EqualError(t, err, expected)
   302  	} else {
   303  		assert.NoError(t, err)
   304  	}
   305  }
   306  
   307  func TestPrepareMonitoringGroup(t *testing.T) {
   308  	rootResctrl = mockResctrl()
   309  	defer os.RemoveAll(rootResctrl)
   310  
   311  	pidsPath = mockContainersPids()
   312  	defer os.RemoveAll(pidsPath)
   313  
   314  	processPath = mockProcFs()
   315  	defer os.RemoveAll(processPath)
   316  
   317  	var testCases = []struct {
   318  		container        string
   319  		getContainerPids func() ([]string, error)
   320  		expected         string
   321  		err              string
   322  	}{
   323  		{
   324  			"container",
   325  			mockGetContainerPids,
   326  			filepath.Join(rootResctrl, monGroupsDirName, "cadvisor-container"),
   327  			"",
   328  		},
   329  		{
   330  			"another",
   331  			mockAnotherGetContainerPids,
   332  			filepath.Join(rootResctrl, "m1", monGroupsDirName, "cadvisor-another"),
   333  			"",
   334  		},
   335  		{
   336  			"/",
   337  			mockAllGetContainerPids,
   338  			rootResctrl,
   339  			"",
   340  		},
   341  	}
   342  
   343  	for _, test := range testCases {
   344  		actual, err := prepareMonitoringGroup(test.container, test.getContainerPids, true)
   345  		assert.Equal(t, test.expected, actual)
   346  		checkError(t, err, test.err)
   347  	}
   348  }
   349  
   350  func TestGetPids(t *testing.T) {
   351  	pidsPath = mockContainersPids()
   352  	defer os.RemoveAll(pidsPath)
   353  
   354  	var testCases = []struct {
   355  		container string
   356  		expected  []int
   357  		err       string
   358  	}{
   359  		{
   360  			"",
   361  			nil,
   362  			noContainerNameError,
   363  		},
   364  		{
   365  			"container",
   366  			[]int{1, 2, 3},
   367  			"",
   368  		},
   369  		{
   370  			"no_container",
   371  			nil,
   372  			fmt.Sprintf("couldn't obtain pids for \"no_container\" container: lstat %v: no such file or directory", filepath.Join(pidsPath, "no_container")),
   373  		},
   374  	}
   375  
   376  	for _, test := range testCases {
   377  		actual, err := getPids(test.container)
   378  		assert.Equal(t, test.expected, actual)
   379  		checkError(t, err, test.err)
   380  	}
   381  }
   382  
   383  func TestGetAllProcessThreads(t *testing.T) {
   384  	mockProcFs := func() string {
   385  		path, _ := os.MkdirTemp("", "proc")
   386  
   387  		var files = []struct {
   388  			path  string
   389  			touch func(string) error
   390  		}{
   391  			// correct
   392  			{
   393  				filepath.Join(path, "4215", processTask, "4215"),
   394  				touchDir,
   395  			},
   396  			{
   397  				filepath.Join(path, "4215", processTask, "4216"),
   398  				touchDir,
   399  			},
   400  			{
   401  				filepath.Join(path, "4215", processTask, "4217"),
   402  				touchDir,
   403  			},
   404  			{
   405  				filepath.Join(path, "4215", processTask, "4218"),
   406  				touchDir,
   407  			},
   408  			// invalid
   409  			{
   410  				filepath.Join(path, "301", processTask, "301"),
   411  				touchDir,
   412  			},
   413  			{
   414  				filepath.Join(path, "301", processTask, "incorrect"),
   415  				touchDir,
   416  			},
   417  		}
   418  
   419  		for _, file := range files {
   420  			_ = file.touch(file.path)
   421  		}
   422  
   423  		return path
   424  	}
   425  
   426  	mockedProcFs := mockProcFs()
   427  	defer os.RemoveAll(mockedProcFs)
   428  
   429  	var testCases = []struct {
   430  		path     string
   431  		expected []int
   432  		err      string
   433  	}{
   434  		{
   435  			filepath.Join(mockedProcFs, "4215", processTask),
   436  			[]int{4215, 4216, 4217, 4218},
   437  			"",
   438  		},
   439  		{
   440  			filepath.Join(mockedProcFs, "301", processTask),
   441  			nil,
   442  			"couldn't parse \"incorrect\" dir: strconv.Atoi: parsing \"incorrect\": invalid syntax",
   443  		},
   444  	}
   445  
   446  	for _, test := range testCases {
   447  		actual, err := getAllProcessThreads(test.path)
   448  		assert.Equal(t, test.expected, actual)
   449  		checkError(t, err, test.err)
   450  	}
   451  }
   452  
   453  func TestFindGroup(t *testing.T) {
   454  	rootResctrl = mockResctrl()
   455  	defer os.RemoveAll(rootResctrl)
   456  
   457  	var testCases = []struct {
   458  		path         string
   459  		pids         []string
   460  		includeGroup bool
   461  		exclusive    bool
   462  		expected     string
   463  		err          string
   464  	}{
   465  		{
   466  			rootResctrl,
   467  			[]string{"1", "2", "3", "4"},
   468  			true,
   469  			false,
   470  			rootResctrl,
   471  			"",
   472  		},
   473  		{
   474  			rootResctrl,
   475  			[]string{},
   476  			true,
   477  			false,
   478  			"",
   479  			"there are no pids passed",
   480  		},
   481  		{
   482  			rootResctrl,
   483  			[]string{"5", "6"},
   484  			true,
   485  			false,
   486  			filepath.Join(rootResctrl, "m1"),
   487  			"",
   488  		},
   489  		{
   490  			rootResctrl,
   491  			[]string{"11", "12"},
   492  			true,
   493  			false,
   494  			"",
   495  			"",
   496  		},
   497  		{
   498  			filepath.Join(rootResctrl, "m1", monGroupsDirName),
   499  			[]string{"5", "6"},
   500  			false,
   501  			true,
   502  			"",
   503  			"",
   504  		},
   505  		{
   506  			filepath.Join(rootResctrl, "m1", monGroupsDirName),
   507  			[]string{"7", "8"},
   508  			false,
   509  			true,
   510  			filepath.Join(rootResctrl, "m1", monGroupsDirName, "test"),
   511  			"",
   512  		},
   513  		{
   514  			filepath.Join(rootResctrl, "m1", monGroupsDirName),
   515  			[]string{"7"},
   516  			false,
   517  			true,
   518  			"",
   519  			"group should have container pids only",
   520  		},
   521  	}
   522  	for _, test := range testCases {
   523  		actual, err := findGroup(test.path, test.pids, test.includeGroup, test.exclusive)
   524  		assert.Equal(t, test.expected, actual)
   525  		checkError(t, err, test.err)
   526  	}
   527  }
   528  
   529  func TestArePIDsInGroup(t *testing.T) {
   530  	rootResctrl = mockResctrl()
   531  	defer os.RemoveAll(rootResctrl)
   532  
   533  	var testCases = []struct {
   534  		expected  bool
   535  		err       string
   536  		path      string
   537  		pids      []string
   538  		exclusive bool
   539  	}{
   540  		{
   541  			true,
   542  			"",
   543  			rootResctrl,
   544  			[]string{"1", "2"},
   545  			false,
   546  		},
   547  		{
   548  			false,
   549  			"there should be all pids in group",
   550  			rootResctrl,
   551  			[]string{"4", "5"},
   552  			false,
   553  		},
   554  		{
   555  			false,
   556  			"",
   557  			filepath.Join(rootResctrl, "m1"),
   558  			[]string{"1"},
   559  			false,
   560  		},
   561  		{
   562  			false,
   563  			fmt.Sprintf("couldn't read tasks file from %q path: open %s: no such file or directory", filepath.Join(rootResctrl, monitoringGroupDir, tasksFileName), filepath.Join(rootResctrl, monitoringGroupDir, tasksFileName)),
   564  			filepath.Join(rootResctrl, monitoringGroupDir),
   565  			[]string{"1", "2"},
   566  			false,
   567  		},
   568  		{
   569  			false,
   570  			fmt.Sprintf("couldn't obtain pids from %q path: %v", rootResctrl, noPidsPassedError),
   571  			rootResctrl,
   572  			nil,
   573  			false,
   574  		},
   575  	}
   576  
   577  	for _, test := range testCases {
   578  		actual, err := arePIDsInGroup(test.path, test.pids, test.exclusive)
   579  		assert.Equal(t, test.expected, actual)
   580  		checkError(t, err, test.err)
   581  	}
   582  }
   583  
   584  func TestGetStats(t *testing.T) {
   585  	rootResctrl = mockResctrl()
   586  	defer os.RemoveAll(rootResctrl)
   587  
   588  	pidsPath = mockContainersPids()
   589  	defer os.RemoveAll(pidsPath)
   590  
   591  	processPath = mockProcFs()
   592  	defer os.RemoveAll(processPath)
   593  
   594  	enabledCMT, enabledMBM = true, true
   595  
   596  	var testCases = []struct {
   597  		container string
   598  		expected  intelrdt.Stats
   599  		err       string
   600  	}{
   601  		{
   602  			"container",
   603  			intelrdt.Stats{
   604  				MBMStats: &[]intelrdt.MBMNumaNodeStats{
   605  					{
   606  						MBMTotalBytes: 3333,
   607  						MBMLocalBytes: 2222,
   608  					},
   609  					{
   610  						MBMTotalBytes: 3333,
   611  						MBMLocalBytes: 1111,
   612  					},
   613  				},
   614  				CMTStats: &[]intelrdt.CMTNumaNodeStats{
   615  					{
   616  						LLCOccupancy: 1111,
   617  					},
   618  					{
   619  						LLCOccupancy: 3333,
   620  					},
   621  				},
   622  			},
   623  			"",
   624  		},
   625  		{
   626  			"another",
   627  			intelrdt.Stats{
   628  				MBMStats: &[]intelrdt.MBMNumaNodeStats{
   629  					{
   630  						MBMTotalBytes: 3333,
   631  						MBMLocalBytes: 2222,
   632  					},
   633  					{
   634  						MBMTotalBytes: 3333,
   635  						MBMLocalBytes: 1111,
   636  					},
   637  				},
   638  				CMTStats: &[]intelrdt.CMTNumaNodeStats{
   639  					{
   640  						LLCOccupancy: 1111,
   641  					},
   642  					{
   643  						LLCOccupancy: 3333,
   644  					},
   645  				},
   646  			},
   647  			"",
   648  		},
   649  		{
   650  			"/",
   651  			intelrdt.Stats{
   652  				MBMStats: &[]intelrdt.MBMNumaNodeStats{
   653  					{
   654  						MBMTotalBytes: 3333,
   655  						MBMLocalBytes: 2222,
   656  					},
   657  					{
   658  						MBMTotalBytes: 3333,
   659  						MBMLocalBytes: 1111,
   660  					},
   661  				},
   662  				CMTStats: &[]intelrdt.CMTNumaNodeStats{
   663  					{
   664  						LLCOccupancy: 1111,
   665  					},
   666  					{
   667  						LLCOccupancy: 3333,
   668  					},
   669  				},
   670  			},
   671  			"",
   672  		},
   673  	}
   674  
   675  	for _, test := range testCases {
   676  		containerPath, _ := prepareMonitoringGroup(test.container, mockGetContainerPids, true)
   677  		mockResctrlMonData(containerPath)
   678  		actual, err := getIntelRDTStatsFrom(containerPath, "")
   679  		checkError(t, err, test.err)
   680  		assert.Equal(t, test.expected.CMTStats, actual.CMTStats)
   681  		assert.Equal(t, test.expected.MBMStats, actual.MBMStats)
   682  	}
   683  }
   684  
   685  func TestReadTasksFile(t *testing.T) {
   686  	var testCases = []struct {
   687  		tasksFile string
   688  		expected  map[string]struct{}
   689  		err       string
   690  	}{
   691  		{"testing/tasks_two",
   692  			map[string]struct{}{
   693  				"12": {},
   694  				"77": {},
   695  			},
   696  			"",
   697  		},
   698  		{"testing/tasks_one",
   699  			map[string]struct{}{
   700  				"2": {},
   701  			},
   702  			"",
   703  		},
   704  		{"testing/tasks_empty",
   705  			map[string]struct{}{},
   706  			"",
   707  		},
   708  	}
   709  
   710  	for _, test := range testCases {
   711  		actual, err := readTasksFile(test.tasksFile)
   712  		assert.Equal(t, test.expected, actual)
   713  		checkError(t, err, test.err)
   714  	}
   715  }