github.com/intel/goresctrl@v0.5.0/pkg/rdt/info.go (about)

     1  /*
     2  Copyright 2019-2021 Intel Corporation
     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 rdt
    18  
    19  import (
    20  	"bufio"
    21  	"fmt"
    22  	"os"
    23  	"path/filepath"
    24  	"sort"
    25  	"strconv"
    26  	"strings"
    27  )
    28  
    29  // resctrlInfo contains information about the RDT support in the system
    30  type resctrlInfo struct {
    31  	resctrlPath      string
    32  	resctrlMountOpts map[string]struct{}
    33  	numClosids       uint64
    34  	cat              map[cacheLevel]catInfoAll
    35  	l3mon            l3MonInfo
    36  	mb               mbInfo
    37  }
    38  
    39  type cacheLevel string
    40  
    41  const (
    42  	L2 cacheLevel = "L2"
    43  	L3 cacheLevel = "L3"
    44  )
    45  
    46  type catInfoAll struct {
    47  	cacheIds []uint64
    48  	unified  catInfo
    49  	code     catInfo
    50  	data     catInfo
    51  }
    52  
    53  type catInfo struct {
    54  	cbmMask       bitmask
    55  	minCbmBits    uint64
    56  	shareableBits bitmask
    57  }
    58  
    59  type l3MonInfo struct {
    60  	numRmids    uint64
    61  	monFeatures []string
    62  }
    63  
    64  type mbInfo struct {
    65  	cacheIds      []uint64
    66  	bandwidthGran uint64
    67  	delayLinear   uint64
    68  	minBandwidth  uint64
    69  	mbpsEnabled   bool // true if MBA_MBps is enabled
    70  }
    71  
    72  var mountInfoPath string = "/proc/mounts"
    73  
    74  // getInfo is a helper method for a "unified API" for getting L3 information
    75  func (i catInfoAll) getInfo() catInfo {
    76  	switch {
    77  	case i.code.Supported():
    78  		return i.code
    79  	case i.data.Supported():
    80  		return i.data
    81  	}
    82  	return i.unified
    83  }
    84  
    85  func (i catInfoAll) cbmMask() bitmask {
    86  	mask := i.getInfo().cbmMask
    87  	if mask != 0 {
    88  		return mask
    89  	}
    90  	return bitmask(^uint64(0))
    91  }
    92  
    93  func (i catInfoAll) minCbmBits() uint64 {
    94  	return i.getInfo().minCbmBits
    95  }
    96  
    97  func getRdtInfo() (*resctrlInfo, error) {
    98  	var err error
    99  	info := &resctrlInfo{cat: make(map[cacheLevel]catInfoAll)}
   100  
   101  	info.resctrlPath, info.resctrlMountOpts, err = getResctrlMountInfo()
   102  	if err != nil {
   103  		return info, fmt.Errorf("failed to detect resctrl mount point: %v", err)
   104  	}
   105  	log.Infof("detected resctrl filesystem at %q", info.resctrlPath)
   106  
   107  	// Check that RDT is available
   108  	infopath := filepath.Join(info.resctrlPath, "info")
   109  	if _, err := os.Stat(infopath); err != nil {
   110  		return info, fmt.Errorf("failed to read RDT info from %q: %v", infopath, err)
   111  	}
   112  
   113  	// Check CAT feature available
   114  	for _, cl := range []cacheLevel{L2, L3} {
   115  		cat := catInfoAll{}
   116  		catFeatures := map[string]*catInfo{
   117  			"":     &cat.unified,
   118  			"CODE": &cat.code,
   119  			"DATA": &cat.data,
   120  		}
   121  		for suffix, i := range catFeatures {
   122  			dir := string(cl) + suffix
   123  			subpath := filepath.Join(infopath, dir)
   124  			if _, err = os.Stat(subpath); err == nil {
   125  				*i, info.numClosids, err = getCatInfo(subpath)
   126  				if err != nil {
   127  					return info, fmt.Errorf("failed to get %s info from %q: %v", dir, subpath, err)
   128  				}
   129  			}
   130  		}
   131  		if cat.getInfo().Supported() {
   132  			cat.cacheIds, err = getCacheIds(info.resctrlPath, string(cl))
   133  			if err != nil {
   134  				return info, fmt.Errorf("failed to get %s CAT cache IDs: %v", cl, err)
   135  			}
   136  		}
   137  		info.cat[cl] = cat
   138  	}
   139  
   140  	// Check MON features available
   141  	subpath := filepath.Join(infopath, "L3_MON")
   142  	if _, err = os.Stat(subpath); err == nil {
   143  		info.l3mon, err = getL3MonInfo(subpath)
   144  		if err != nil {
   145  			return info, fmt.Errorf("failed to get L3_MON info from %q: %v", subpath, err)
   146  		}
   147  	}
   148  
   149  	// Check MBA feature available
   150  	subpath = filepath.Join(infopath, "MB")
   151  	if _, err = os.Stat(subpath); err == nil {
   152  		info.mb, info.numClosids, err = getMBInfo(subpath)
   153  		if err != nil {
   154  			return info, fmt.Errorf("failed to get MBA info from %q: %v", subpath, err)
   155  		}
   156  
   157  		info.mb.cacheIds, err = getCacheIds(info.resctrlPath, "MB")
   158  		if err != nil {
   159  			return info, fmt.Errorf("failed to get MBA cache IDs: %v", err)
   160  		}
   161  	}
   162  
   163  	return info, nil
   164  }
   165  
   166  func getCatInfo(basepath string) (catInfo, uint64, error) {
   167  	var err error
   168  	var numClosids uint64
   169  	info := catInfo{}
   170  
   171  	info.cbmMask, err = readFileBitmask(filepath.Join(basepath, "cbm_mask"))
   172  	if err != nil {
   173  		return info, numClosids, err
   174  	}
   175  	info.minCbmBits, err = readFileUint64(filepath.Join(basepath, "min_cbm_bits"))
   176  	if err != nil {
   177  		return info, numClosids, err
   178  	}
   179  	info.shareableBits, err = readFileBitmask(filepath.Join(basepath, "shareable_bits"))
   180  	if err != nil {
   181  		return info, numClosids, err
   182  	}
   183  	numClosids, err = readFileUint64(filepath.Join(basepath, "num_closids"))
   184  	if err != nil {
   185  		return info, numClosids, err
   186  	}
   187  
   188  	return info, numClosids, nil
   189  }
   190  
   191  // Supported returns true if L3 cache allocation has is supported and enabled in the system
   192  func (i catInfo) Supported() bool {
   193  	return i.cbmMask != 0
   194  }
   195  
   196  func getL3MonInfo(basepath string) (l3MonInfo, error) {
   197  	var err error
   198  	info := l3MonInfo{}
   199  
   200  	info.numRmids, err = readFileUint64(filepath.Join(basepath, "num_rmids"))
   201  	if err != nil {
   202  		return info, err
   203  	}
   204  
   205  	lines, err := readFileString(filepath.Join(basepath, "mon_features"))
   206  	if err != nil {
   207  		return info, err
   208  	}
   209  	info.monFeatures = strings.Split(lines, "\n")
   210  	sort.Strings(info.monFeatures)
   211  
   212  	return info, nil
   213  }
   214  
   215  // Supported returns true if L3 monitoring is supported and enabled in the system
   216  func (i l3MonInfo) Supported() bool {
   217  	return i.numRmids != 0 && len(i.monFeatures) > 0
   218  }
   219  
   220  func getMBInfo(basepath string) (mbInfo, uint64, error) {
   221  	var err error
   222  	var numClosids uint64
   223  	info := mbInfo{}
   224  
   225  	info.bandwidthGran, err = readFileUint64(filepath.Join(basepath, "bandwidth_gran"))
   226  	if err != nil {
   227  		return info, numClosids, err
   228  	}
   229  	info.delayLinear, err = readFileUint64(filepath.Join(basepath, "delay_linear"))
   230  	if err != nil {
   231  		return info, numClosids, err
   232  	}
   233  	info.minBandwidth, err = readFileUint64(filepath.Join(basepath, "min_bandwidth"))
   234  	if err != nil {
   235  		return info, numClosids, err
   236  	}
   237  	numClosids, err = readFileUint64(filepath.Join(basepath, "num_closids"))
   238  	if err != nil {
   239  		return info, numClosids, err
   240  	}
   241  
   242  	// Detect MBps mode directly from mount options as it's not visible in MB
   243  	// info directory
   244  	_, mountOpts, err := getResctrlMountInfo()
   245  	if err != nil {
   246  		return info, numClosids, fmt.Errorf("failed to get resctrl mount options: %v", err)
   247  	}
   248  	if _, ok := mountOpts["mba_MBps"]; ok {
   249  		info.mbpsEnabled = true
   250  	}
   251  
   252  	return info, numClosids, nil
   253  }
   254  
   255  // Supported returns true if memory bandwidth allocation has is supported and enabled in the system
   256  func (i mbInfo) Supported() bool {
   257  	return i.minBandwidth != 0
   258  }
   259  
   260  func getCacheIds(basepath string, prefix string) ([]uint64, error) {
   261  	var ids []uint64
   262  
   263  	// Parse cache IDs from the root schemata
   264  	data, err := readFileString(filepath.Join(basepath, "schemata"))
   265  	if err != nil {
   266  		return ids, fmt.Errorf("failed to read root schemata: %v", err)
   267  	}
   268  
   269  	for _, line := range strings.Split(data, "\n") {
   270  		trimmed := strings.TrimSpace(line)
   271  		lineSplit := strings.SplitN(trimmed, ":", 2)
   272  
   273  		// Find line with given resource prefix
   274  		if len(lineSplit) == 2 && strings.HasPrefix(lineSplit[0], prefix) {
   275  			schema := strings.Split(lineSplit[1], ";")
   276  			ids = make([]uint64, len(schema))
   277  
   278  			// Get individual cache configurations from the schema
   279  			for idx, definition := range schema {
   280  				split := strings.Split(definition, "=")
   281  				if len(split) != 2 {
   282  					return ids, fmt.Errorf("looks like an invalid schema %q", trimmed)
   283  				}
   284  				ids[idx], err = strconv.ParseUint(split[0], 10, 64)
   285  				if err != nil {
   286  					return ids, fmt.Errorf("failed to parse cache id in %q: %v", trimmed, err)
   287  				}
   288  			}
   289  			return ids, nil
   290  		}
   291  	}
   292  	return ids, fmt.Errorf("no %s resources in root schemata", prefix)
   293  }
   294  
   295  func getResctrlMountInfo() (string, map[string]struct{}, error) {
   296  	mountOptions := map[string]struct{}{}
   297  
   298  	f, err := os.Open(mountInfoPath)
   299  	if err != nil {
   300  		return "", mountOptions, err
   301  	}
   302  	defer f.Close()
   303  
   304  	s := bufio.NewScanner(f)
   305  	for s.Scan() {
   306  		split := strings.Split(s.Text(), " ")
   307  		if len(split) > 3 && split[2] == "resctrl" {
   308  			opts := strings.Split(split[3], ",")
   309  			for _, opt := range opts {
   310  				mountOptions[opt] = struct{}{}
   311  			}
   312  			return split[1], mountOptions, nil
   313  		}
   314  	}
   315  	return "", mountOptions, fmt.Errorf("resctrl not found in " + mountInfoPath)
   316  }
   317  
   318  func readFileUint64(path string) (uint64, error) {
   319  	data, err := readFileString(path)
   320  	if err != nil {
   321  		return 0, err
   322  	}
   323  
   324  	return strconv.ParseUint(data, 10, 64)
   325  }
   326  
   327  func readFileBitmask(path string) (bitmask, error) {
   328  	data, err := readFileString(path)
   329  	if err != nil {
   330  		return 0, err
   331  	}
   332  
   333  	value, err := strconv.ParseUint(data, 16, 64)
   334  	return bitmask(value), err
   335  }
   336  
   337  func readFileString(path string) (string, error) {
   338  	data, err := os.ReadFile(path)
   339  	return strings.TrimSpace(string(data)), err
   340  }