342 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			342 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
/*
 | 
						|
Copyright 2019-2021 Intel Corporation
 | 
						|
 | 
						|
Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
you may not use this file except in compliance with the License.
 | 
						|
You may obtain a copy of the License at
 | 
						|
 | 
						|
    http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
 | 
						|
Unless required by applicable law or agreed to in writing, software
 | 
						|
distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
See the License for the specific language governing permissions and
 | 
						|
limitations under the License.
 | 
						|
*/
 | 
						|
 | 
						|
package rdt
 | 
						|
 | 
						|
import (
 | 
						|
	"bufio"
 | 
						|
	"fmt"
 | 
						|
	"io/ioutil"
 | 
						|
	"os"
 | 
						|
	"path/filepath"
 | 
						|
	"sort"
 | 
						|
	"strconv"
 | 
						|
	"strings"
 | 
						|
)
 | 
						|
 | 
						|
// resctrlInfo contains information about the RDT support in the system
 | 
						|
type resctrlInfo struct {
 | 
						|
	resctrlPath      string
 | 
						|
	resctrlMountOpts map[string]struct{}
 | 
						|
	numClosids       uint64
 | 
						|
	cat              map[cacheLevel]catInfoAll
 | 
						|
	l3mon            l3MonInfo
 | 
						|
	mb               mbInfo
 | 
						|
}
 | 
						|
 | 
						|
type cacheLevel string
 | 
						|
 | 
						|
const (
 | 
						|
	L2 cacheLevel = "L2"
 | 
						|
	L3 cacheLevel = "L3"
 | 
						|
)
 | 
						|
 | 
						|
type catInfoAll struct {
 | 
						|
	cacheIds []uint64
 | 
						|
	unified  catInfo
 | 
						|
	code     catInfo
 | 
						|
	data     catInfo
 | 
						|
}
 | 
						|
 | 
						|
type catInfo struct {
 | 
						|
	cbmMask       bitmask
 | 
						|
	minCbmBits    uint64
 | 
						|
	shareableBits bitmask
 | 
						|
}
 | 
						|
 | 
						|
type l3MonInfo struct {
 | 
						|
	numRmids    uint64
 | 
						|
	monFeatures []string
 | 
						|
}
 | 
						|
 | 
						|
type mbInfo struct {
 | 
						|
	cacheIds      []uint64
 | 
						|
	bandwidthGran uint64
 | 
						|
	delayLinear   uint64
 | 
						|
	minBandwidth  uint64
 | 
						|
	mbpsEnabled   bool // true if MBA_MBps is enabled
 | 
						|
}
 | 
						|
 | 
						|
var mountInfoPath string = "/proc/mounts"
 | 
						|
 | 
						|
// getInfo is a helper method for a "unified API" for getting L3 information
 | 
						|
func (i catInfoAll) getInfo() catInfo {
 | 
						|
	switch {
 | 
						|
	case i.code.Supported():
 | 
						|
		return i.code
 | 
						|
	case i.data.Supported():
 | 
						|
		return i.data
 | 
						|
	}
 | 
						|
	return i.unified
 | 
						|
}
 | 
						|
 | 
						|
func (i catInfoAll) cbmMask() bitmask {
 | 
						|
	mask := i.getInfo().cbmMask
 | 
						|
	if mask != 0 {
 | 
						|
		return mask
 | 
						|
	}
 | 
						|
	return bitmask(^uint64(0))
 | 
						|
}
 | 
						|
 | 
						|
func (i catInfoAll) minCbmBits() uint64 {
 | 
						|
	return i.getInfo().minCbmBits
 | 
						|
}
 | 
						|
 | 
						|
func getRdtInfo() (*resctrlInfo, error) {
 | 
						|
	var err error
 | 
						|
	info := &resctrlInfo{cat: make(map[cacheLevel]catInfoAll)}
 | 
						|
 | 
						|
	info.resctrlPath, info.resctrlMountOpts, err = getResctrlMountInfo()
 | 
						|
	if err != nil {
 | 
						|
		return info, fmt.Errorf("failed to detect resctrl mount point: %v", err)
 | 
						|
	}
 | 
						|
	log.Infof("detected resctrl filesystem at %q", info.resctrlPath)
 | 
						|
 | 
						|
	// Check that RDT is available
 | 
						|
	infopath := filepath.Join(info.resctrlPath, "info")
 | 
						|
	if _, err := os.Stat(infopath); err != nil {
 | 
						|
		return info, fmt.Errorf("failed to read RDT info from %q: %v", infopath, err)
 | 
						|
	}
 | 
						|
 | 
						|
	// Check CAT feature available
 | 
						|
	for _, cl := range []cacheLevel{L2, L3} {
 | 
						|
		cat := catInfoAll{}
 | 
						|
		catFeatures := map[string]*catInfo{
 | 
						|
			"":     &cat.unified,
 | 
						|
			"CODE": &cat.code,
 | 
						|
			"DATA": &cat.data,
 | 
						|
		}
 | 
						|
		for suffix, i := range catFeatures {
 | 
						|
			dir := string(cl) + suffix
 | 
						|
			subpath := filepath.Join(infopath, dir)
 | 
						|
			if _, err = os.Stat(subpath); err == nil {
 | 
						|
				*i, info.numClosids, err = getCatInfo(subpath)
 | 
						|
				if err != nil {
 | 
						|
					return info, fmt.Errorf("failed to get %s info from %q: %v", dir, subpath, err)
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if cat.getInfo().Supported() {
 | 
						|
			cat.cacheIds, err = getCacheIds(info.resctrlPath, string(cl))
 | 
						|
			if err != nil {
 | 
						|
				return info, fmt.Errorf("failed to get %s CAT cache IDs: %v", cl, err)
 | 
						|
			}
 | 
						|
		}
 | 
						|
		info.cat[cl] = cat
 | 
						|
	}
 | 
						|
 | 
						|
	// Check MON features available
 | 
						|
	subpath := filepath.Join(infopath, "L3_MON")
 | 
						|
	if _, err = os.Stat(subpath); err == nil {
 | 
						|
		info.l3mon, err = getL3MonInfo(subpath)
 | 
						|
		if err != nil {
 | 
						|
			return info, fmt.Errorf("failed to get L3_MON info from %q: %v", subpath, err)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Check MBA feature available
 | 
						|
	subpath = filepath.Join(infopath, "MB")
 | 
						|
	if _, err = os.Stat(subpath); err == nil {
 | 
						|
		info.mb, info.numClosids, err = getMBInfo(subpath)
 | 
						|
		if err != nil {
 | 
						|
			return info, fmt.Errorf("failed to get MBA info from %q: %v", subpath, err)
 | 
						|
		}
 | 
						|
 | 
						|
		info.mb.cacheIds, err = getCacheIds(info.resctrlPath, "MB")
 | 
						|
		if err != nil {
 | 
						|
			return info, fmt.Errorf("failed to get MBA cache IDs: %v", err)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return info, nil
 | 
						|
}
 | 
						|
 | 
						|
func getCatInfo(basepath string) (catInfo, uint64, error) {
 | 
						|
	var err error
 | 
						|
	var numClosids uint64
 | 
						|
	info := catInfo{}
 | 
						|
 | 
						|
	info.cbmMask, err = readFileBitmask(filepath.Join(basepath, "cbm_mask"))
 | 
						|
	if err != nil {
 | 
						|
		return info, numClosids, err
 | 
						|
	}
 | 
						|
	info.minCbmBits, err = readFileUint64(filepath.Join(basepath, "min_cbm_bits"))
 | 
						|
	if err != nil {
 | 
						|
		return info, numClosids, err
 | 
						|
	}
 | 
						|
	info.shareableBits, err = readFileBitmask(filepath.Join(basepath, "shareable_bits"))
 | 
						|
	if err != nil {
 | 
						|
		return info, numClosids, err
 | 
						|
	}
 | 
						|
	numClosids, err = readFileUint64(filepath.Join(basepath, "num_closids"))
 | 
						|
	if err != nil {
 | 
						|
		return info, numClosids, err
 | 
						|
	}
 | 
						|
 | 
						|
	return info, numClosids, nil
 | 
						|
}
 | 
						|
 | 
						|
// Supported returns true if L3 cache allocation has is supported and enabled in the system
 | 
						|
func (i catInfo) Supported() bool {
 | 
						|
	return i.cbmMask != 0
 | 
						|
}
 | 
						|
 | 
						|
func getL3MonInfo(basepath string) (l3MonInfo, error) {
 | 
						|
	var err error
 | 
						|
	info := l3MonInfo{}
 | 
						|
 | 
						|
	info.numRmids, err = readFileUint64(filepath.Join(basepath, "num_rmids"))
 | 
						|
	if err != nil {
 | 
						|
		return info, err
 | 
						|
	}
 | 
						|
 | 
						|
	lines, err := readFileString(filepath.Join(basepath, "mon_features"))
 | 
						|
	if err != nil {
 | 
						|
		return info, err
 | 
						|
	}
 | 
						|
	info.monFeatures = strings.Split(lines, "\n")
 | 
						|
	sort.Strings(info.monFeatures)
 | 
						|
 | 
						|
	return info, nil
 | 
						|
}
 | 
						|
 | 
						|
// Supported returns true if L3 monitoring is supported and enabled in the system
 | 
						|
func (i l3MonInfo) Supported() bool {
 | 
						|
	return i.numRmids != 0 && len(i.monFeatures) > 0
 | 
						|
}
 | 
						|
 | 
						|
func getMBInfo(basepath string) (mbInfo, uint64, error) {
 | 
						|
	var err error
 | 
						|
	var numClosids uint64
 | 
						|
	info := mbInfo{}
 | 
						|
 | 
						|
	info.bandwidthGran, err = readFileUint64(filepath.Join(basepath, "bandwidth_gran"))
 | 
						|
	if err != nil {
 | 
						|
		return info, numClosids, err
 | 
						|
	}
 | 
						|
	info.delayLinear, err = readFileUint64(filepath.Join(basepath, "delay_linear"))
 | 
						|
	if err != nil {
 | 
						|
		return info, numClosids, err
 | 
						|
	}
 | 
						|
	info.minBandwidth, err = readFileUint64(filepath.Join(basepath, "min_bandwidth"))
 | 
						|
	if err != nil {
 | 
						|
		return info, numClosids, err
 | 
						|
	}
 | 
						|
	numClosids, err = readFileUint64(filepath.Join(basepath, "num_closids"))
 | 
						|
	if err != nil {
 | 
						|
		return info, numClosids, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Detect MBps mode directly from mount options as it's not visible in MB
 | 
						|
	// info directory
 | 
						|
	_, mountOpts, err := getResctrlMountInfo()
 | 
						|
	if err != nil {
 | 
						|
		return info, numClosids, fmt.Errorf("failed to get resctrl mount options: %v", err)
 | 
						|
	}
 | 
						|
	if _, ok := mountOpts["mba_MBps"]; ok {
 | 
						|
		info.mbpsEnabled = true
 | 
						|
	}
 | 
						|
 | 
						|
	return info, numClosids, nil
 | 
						|
}
 | 
						|
 | 
						|
// Supported returns true if memory bandwidth allocation has is supported and enabled in the system
 | 
						|
func (i mbInfo) Supported() bool {
 | 
						|
	return i.minBandwidth != 0
 | 
						|
}
 | 
						|
 | 
						|
func getCacheIds(basepath string, prefix string) ([]uint64, error) {
 | 
						|
	var ids []uint64
 | 
						|
 | 
						|
	// Parse cache IDs from the root schemata
 | 
						|
	data, err := readFileString(filepath.Join(basepath, "schemata"))
 | 
						|
	if err != nil {
 | 
						|
		return ids, fmt.Errorf("failed to read root schemata: %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	for _, line := range strings.Split(data, "\n") {
 | 
						|
		trimmed := strings.TrimSpace(line)
 | 
						|
		lineSplit := strings.SplitN(trimmed, ":", 2)
 | 
						|
 | 
						|
		// Find line with given resource prefix
 | 
						|
		if len(lineSplit) == 2 && strings.HasPrefix(lineSplit[0], prefix) {
 | 
						|
			schema := strings.Split(lineSplit[1], ";")
 | 
						|
			ids = make([]uint64, len(schema))
 | 
						|
 | 
						|
			// Get individual cache configurations from the schema
 | 
						|
			for idx, definition := range schema {
 | 
						|
				split := strings.Split(definition, "=")
 | 
						|
				if len(split) != 2 {
 | 
						|
					return ids, fmt.Errorf("looks like an invalid schema %q", trimmed)
 | 
						|
				}
 | 
						|
				ids[idx], err = strconv.ParseUint(split[0], 10, 64)
 | 
						|
				if err != nil {
 | 
						|
					return ids, fmt.Errorf("failed to parse cache id in %q: %v", trimmed, err)
 | 
						|
				}
 | 
						|
			}
 | 
						|
			return ids, nil
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return ids, fmt.Errorf("no %s resources in root schemata", prefix)
 | 
						|
}
 | 
						|
 | 
						|
func getResctrlMountInfo() (string, map[string]struct{}, error) {
 | 
						|
	mountOptions := map[string]struct{}{}
 | 
						|
 | 
						|
	f, err := os.Open(mountInfoPath)
 | 
						|
	if err != nil {
 | 
						|
		return "", mountOptions, err
 | 
						|
	}
 | 
						|
	defer f.Close()
 | 
						|
 | 
						|
	s := bufio.NewScanner(f)
 | 
						|
	for s.Scan() {
 | 
						|
		split := strings.Split(s.Text(), " ")
 | 
						|
		if len(split) > 3 && split[2] == "resctrl" {
 | 
						|
			opts := strings.Split(split[3], ",")
 | 
						|
			for _, opt := range opts {
 | 
						|
				mountOptions[opt] = struct{}{}
 | 
						|
			}
 | 
						|
			return split[1], mountOptions, nil
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return "", mountOptions, fmt.Errorf("resctrl not found in " + mountInfoPath)
 | 
						|
}
 | 
						|
 | 
						|
func readFileUint64(path string) (uint64, error) {
 | 
						|
	data, err := readFileString(path)
 | 
						|
	if err != nil {
 | 
						|
		return 0, err
 | 
						|
	}
 | 
						|
 | 
						|
	return strconv.ParseUint(data, 10, 64)
 | 
						|
}
 | 
						|
 | 
						|
func readFileBitmask(path string) (bitmask, error) {
 | 
						|
	data, err := readFileString(path)
 | 
						|
	if err != nil {
 | 
						|
		return 0, err
 | 
						|
	}
 | 
						|
 | 
						|
	value, err := strconv.ParseUint(data, 16, 64)
 | 
						|
	return bitmask(value), err
 | 
						|
}
 | 
						|
 | 
						|
func readFileString(path string) (string, error) {
 | 
						|
	data, err := ioutil.ReadFile(path)
 | 
						|
	return strings.TrimSpace(string(data)), err
 | 
						|
}
 |