Update dependencies
Signed-off-by: Markus Lehtonen <markus.lehtonen@intel.com>
This commit is contained in:
118
vendor/github.com/intel/goresctrl/pkg/rdt/bitmask.go
generated
vendored
Normal file
118
vendor/github.com/intel/goresctrl/pkg/rdt/bitmask.go
generated
vendored
Normal file
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
Copyright 2019 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 (
|
||||
"fmt"
|
||||
"math/bits"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// bitmask represents a generic 64 bit wide bitmask
|
||||
type bitmask uint64
|
||||
|
||||
// MarshalJSON implements the Marshaler interface of "encoding/json"
|
||||
func (b bitmask) MarshalJSON() ([]byte, error) {
|
||||
return []byte(fmt.Sprintf("\"%#x\"", b)), nil
|
||||
}
|
||||
|
||||
// listStr prints the bitmask in human-readable format, similar to e.g. the
|
||||
// cpuset format of the Linux kernel
|
||||
func (b bitmask) listStr() string {
|
||||
str := ""
|
||||
sep := ""
|
||||
|
||||
shift := int(0)
|
||||
lsbOne := b.lsbOne()
|
||||
|
||||
// Process "ranges of ones"
|
||||
for lsbOne != -1 {
|
||||
b >>= uint(lsbOne)
|
||||
|
||||
// Get range lenght from the position of the first zero
|
||||
numOnes := b.lsbZero()
|
||||
|
||||
if numOnes == 1 {
|
||||
str += sep + strconv.Itoa(lsbOne+shift)
|
||||
} else {
|
||||
str += sep + strconv.Itoa(lsbOne+shift) + "-" + strconv.Itoa(lsbOne+numOnes-1+shift)
|
||||
}
|
||||
|
||||
// Shift away the bits that have been processed
|
||||
b >>= uint(numOnes)
|
||||
shift += lsbOne + numOnes
|
||||
|
||||
// Get next bit that is set (if any)
|
||||
lsbOne = b.lsbOne()
|
||||
|
||||
sep = ","
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
// listStrToBitmask parses a string containing a human-readable list of bit
|
||||
// numbers into a bitmask
|
||||
func listStrToBitmask(str string) (bitmask, error) {
|
||||
b := bitmask(0)
|
||||
|
||||
// Empty bitmask
|
||||
if len(str) == 0 {
|
||||
return b, nil
|
||||
}
|
||||
|
||||
ranges := strings.Split(str, ",")
|
||||
for _, ran := range ranges {
|
||||
split := strings.SplitN(ran, "-", 2)
|
||||
|
||||
bitNum, err := strconv.ParseUint(split[0], 10, 6)
|
||||
if err != nil {
|
||||
return b, fmt.Errorf("invalid bitmask %q: %v", str, err)
|
||||
}
|
||||
|
||||
if len(split) == 1 {
|
||||
b |= 1 << bitNum
|
||||
} else {
|
||||
endNum, err := strconv.ParseUint(split[1], 10, 6)
|
||||
if err != nil {
|
||||
return b, fmt.Errorf("invalid bitmask %q: %v", str, err)
|
||||
}
|
||||
if endNum <= bitNum {
|
||||
return b, fmt.Errorf("invalid range %q in bitmask %q", ran, str)
|
||||
}
|
||||
b |= (1<<(endNum-bitNum+1) - 1) << bitNum
|
||||
}
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (b bitmask) lsbOne() int {
|
||||
if b == 0 {
|
||||
return -1
|
||||
}
|
||||
return bits.TrailingZeros64(uint64(b))
|
||||
}
|
||||
|
||||
func (b bitmask) msbOne() int {
|
||||
// Returns -1 for b == 0
|
||||
return 63 - bits.LeadingZeros64(uint64(b))
|
||||
}
|
||||
|
||||
func (b bitmask) lsbZero() int {
|
||||
return bits.TrailingZeros64(^uint64(b))
|
||||
}
|
||||
1193
vendor/github.com/intel/goresctrl/pkg/rdt/config.go
generated
vendored
Normal file
1193
vendor/github.com/intel/goresctrl/pkg/rdt/config.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
341
vendor/github.com/intel/goresctrl/pkg/rdt/info.go
generated
vendored
Normal file
341
vendor/github.com/intel/goresctrl/pkg/rdt/info.go
generated
vendored
Normal file
@@ -0,0 +1,341 @@
|
||||
/*
|
||||
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
|
||||
}
|
||||
74
vendor/github.com/intel/goresctrl/pkg/rdt/kubernetes.go
generated
vendored
Normal file
74
vendor/github.com/intel/goresctrl/pkg/rdt/kubernetes.go
generated
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
Copyright 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 (
|
||||
"fmt"
|
||||
"github.com/intel/goresctrl/pkg/kubernetes"
|
||||
)
|
||||
|
||||
const (
|
||||
// RdtContainerAnnotation is the CRI level container annotation for setting
|
||||
// the RDT class (CLOS) of a container
|
||||
RdtContainerAnnotation = "io.kubernetes.cri.rdt-class"
|
||||
|
||||
// RdtPodAnnotation is a Pod annotation for setting the RDT class (CLOS) of
|
||||
// all containers of the pod
|
||||
RdtPodAnnotation = "rdt.resources.beta.kubernetes.io/pod"
|
||||
|
||||
// RdtPodAnnotationContainerPrefix is prefix for per-container Pod annotation
|
||||
// for setting the RDT class (CLOS) of one container of the pod
|
||||
RdtPodAnnotationContainerPrefix = "rdt.resources.beta.kubernetes.io/container."
|
||||
)
|
||||
|
||||
// ContainerClassFromAnnotations determines the effective RDT class of a
|
||||
// container from the Pod annotations and CRI level container annotations of a
|
||||
// container. Verifies that the class exists in goresctrl configuration and that
|
||||
// it is allowed to be used.
|
||||
func ContainerClassFromAnnotations(containerName string, containerAnnotations, podAnnotations map[string]string) (string, error) {
|
||||
clsName, clsOrigin := kubernetes.ContainerClassFromAnnotations(
|
||||
RdtContainerAnnotation, RdtPodAnnotation, RdtPodAnnotationContainerPrefix,
|
||||
containerName, containerAnnotations, podAnnotations)
|
||||
|
||||
if clsOrigin != kubernetes.ClassOriginNotFound {
|
||||
if rdt == nil {
|
||||
return "", fmt.Errorf("RDT not initialized, class %q not available", clsName)
|
||||
}
|
||||
|
||||
// Verify validity of class name
|
||||
if !IsQualifiedClassName(clsName) {
|
||||
return "", fmt.Errorf("unqualified RDT class name %q", clsName)
|
||||
}
|
||||
|
||||
// If RDT has been initialized we check that the class exists
|
||||
if _, ok := rdt.getClass(clsName); !ok {
|
||||
return "", fmt.Errorf("RDT class %q does not exist in configuration", clsName)
|
||||
}
|
||||
|
||||
// If classes have been configured by goresctrl
|
||||
if clsConf, ok := rdt.conf.Classes[unaliasClassName(clsName)]; ok {
|
||||
// Check that the class is allowed
|
||||
if clsOrigin == kubernetes.ClassOriginPodAnnotation && clsConf.Kubernetes.DenyPodAnnotation {
|
||||
return "", fmt.Errorf("RDT class %q not allowed from Pod annotations", clsName)
|
||||
} else if clsOrigin == kubernetes.ClassOriginContainerAnnotation && clsConf.Kubernetes.DenyContainerAnnotation {
|
||||
return "", fmt.Errorf("RDT class %q not allowed from Container annotation", clsName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return clsName, nil
|
||||
}
|
||||
124
vendor/github.com/intel/goresctrl/pkg/rdt/prometheus.go
generated
vendored
Normal file
124
vendor/github.com/intel/goresctrl/pkg/rdt/prometheus.go
generated
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
Copyright 2020 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 (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
var customLabels []string = []string{}
|
||||
|
||||
// collector implements prometheus.Collector interface
|
||||
type collector struct {
|
||||
descriptors map[string]*prometheus.Desc
|
||||
}
|
||||
|
||||
// NewCollector creates new Prometheus collector of RDT metrics
|
||||
func NewCollector() (prometheus.Collector, error) {
|
||||
c := &collector{descriptors: make(map[string]*prometheus.Desc)}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// RegisterCustomPrometheusLabels registers monitor group annotations to be
|
||||
// exported as Prometheus metrics labels
|
||||
func RegisterCustomPrometheusLabels(names ...string) {
|
||||
Names:
|
||||
for _, n := range names {
|
||||
for _, c := range customLabels {
|
||||
if n == c {
|
||||
break Names
|
||||
}
|
||||
}
|
||||
customLabels = append(customLabels, n)
|
||||
}
|
||||
}
|
||||
|
||||
// Describe method of the prometheus.Collector interface
|
||||
func (c *collector) Describe(ch chan<- *prometheus.Desc) {
|
||||
for resource, features := range GetMonFeatures() {
|
||||
switch resource {
|
||||
case MonResourceL3:
|
||||
for _, f := range features {
|
||||
ch <- c.describeL3(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Collect method of the prometheus.Collector interface
|
||||
func (c collector) Collect(ch chan<- prometheus.Metric) {
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for _, cls := range GetClasses() {
|
||||
for _, monGrp := range cls.GetMonGroups() {
|
||||
wg.Add(1)
|
||||
g := monGrp
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
c.collectGroupMetrics(ch, g)
|
||||
}()
|
||||
}
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func (c *collector) describeL3(feature string) *prometheus.Desc {
|
||||
d, ok := c.descriptors[feature]
|
||||
if !ok {
|
||||
name := "l3_" + feature
|
||||
help := "L3 " + feature
|
||||
|
||||
switch feature {
|
||||
case "llc_occupancy":
|
||||
help = "L3 (LLC) occupancy"
|
||||
case "mbm_local_bytes":
|
||||
help = "bytes transferred to/from local memory through LLC"
|
||||
case "mbm_total_bytes":
|
||||
help = "total bytes transferred to/from memory through LLC"
|
||||
}
|
||||
labels := append([]string{"rdt_class", "rdt_mon_group", "cache_id"}, customLabels...)
|
||||
d = prometheus.NewDesc(name, help, labels, nil)
|
||||
c.descriptors[feature] = d
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
func (c *collector) collectGroupMetrics(ch chan<- prometheus.Metric, mg MonGroup) {
|
||||
allData := mg.GetMonData()
|
||||
|
||||
annotations := mg.GetAnnotations()
|
||||
customLabelValues := make([]string, len(customLabels))
|
||||
for i, name := range customLabels {
|
||||
customLabelValues[i] = annotations[name]
|
||||
}
|
||||
|
||||
for cacheID, data := range allData.L3 {
|
||||
for feature, value := range data {
|
||||
labels := append([]string{mg.Parent().Name(), mg.Name(), fmt.Sprint(cacheID)}, customLabelValues...)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.describeL3(feature),
|
||||
prometheus.CounterValue,
|
||||
float64(value),
|
||||
labels...,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
859
vendor/github.com/intel/goresctrl/pkg/rdt/rdt.go
generated
vendored
Normal file
859
vendor/github.com/intel/goresctrl/pkg/rdt/rdt.go
generated
vendored
Normal file
@@ -0,0 +1,859 @@
|
||||
/*
|
||||
Copyright 2019 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 implements an API for managing Intel® RDT technologies via the
|
||||
// resctrl pseudo-filesystem of the Linux kernel. It provides flexible
|
||||
// configuration with a hierarchical approach for easy management of exclusive
|
||||
// cache allocations.
|
||||
//
|
||||
// Goresctrl supports all available RDT technologies, i.e. L2 and L3 Cache
|
||||
// Allocation (CAT) with Code and Data Prioritization (CDP) and Memory
|
||||
// Bandwidth Allocation (MBA) plus Cache Monitoring (CMT) and Memory Bandwidth
|
||||
// Monitoring (MBM).
|
||||
//
|
||||
// Basic usage example:
|
||||
// rdt.SetLogger(logrus.New())
|
||||
//
|
||||
// if err := rdt.Initialize(""); err != nil {
|
||||
// return fmt.Errorf("RDT not supported: %v", err)
|
||||
// }
|
||||
//
|
||||
// if err := rdt.SetConfigFromFile("/path/to/rdt.conf.yaml", false); err != nil {
|
||||
// return fmt.Errorf("RDT configuration failed: %v", err)
|
||||
// }
|
||||
//
|
||||
// if cls, ok := rdt.GetClass("my-class"); ok {
|
||||
// // Set PIDs 12345 and 12346 to class "my-class"
|
||||
// if err := cls.AddPids("12345", "12346"); err != nil {
|
||||
// return fmt.Errorf("failed to add PIDs to RDT class: %v", err)
|
||||
// }
|
||||
// }
|
||||
package rdt
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
stdlog "log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
grclog "github.com/intel/goresctrl/pkg/log"
|
||||
"github.com/intel/goresctrl/pkg/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
// RootClassName is the name we use in our config for the special class
|
||||
// that configures the "root" resctrl group of the system
|
||||
RootClassName = "system/default"
|
||||
// RootClassAlias is an alternative name for the root class
|
||||
RootClassAlias = ""
|
||||
)
|
||||
|
||||
type control struct {
|
||||
grclog.Logger
|
||||
|
||||
resctrlGroupPrefix string
|
||||
conf config
|
||||
rawConf Config
|
||||
classes map[string]*ctrlGroup
|
||||
}
|
||||
|
||||
var log grclog.Logger = grclog.NewLoggerWrapper(stdlog.New(os.Stderr, "[ rdt ] ", 0))
|
||||
|
||||
var info *resctrlInfo
|
||||
|
||||
var rdt *control
|
||||
|
||||
// Function for removing resctrl groups from the filesystem. This is
|
||||
// configurable because of unit tests.
|
||||
var groupRemoveFunc func(string) error = os.Remove
|
||||
|
||||
// CtrlGroup defines the interface of one goresctrl managed RDT class. It maps
|
||||
// to one CTRL group directory in the goresctrl pseudo-filesystem.
|
||||
type CtrlGroup interface {
|
||||
ResctrlGroup
|
||||
|
||||
// CreateMonGroup creates a new monitoring group under this CtrlGroup.
|
||||
CreateMonGroup(name string, annotations map[string]string) (MonGroup, error)
|
||||
|
||||
// DeleteMonGroup deletes a monitoring group from this CtrlGroup.
|
||||
DeleteMonGroup(name string) error
|
||||
|
||||
// DeleteMonGroups deletes all monitoring groups from this CtrlGroup.
|
||||
DeleteMonGroups() error
|
||||
|
||||
// GetMonGroup returns a specific monitoring group under this CtrlGroup.
|
||||
GetMonGroup(name string) (MonGroup, bool)
|
||||
|
||||
// GetMonGroups returns all monitoring groups under this CtrlGroup.
|
||||
GetMonGroups() []MonGroup
|
||||
}
|
||||
|
||||
// ResctrlGroup is the generic interface for resctrl CTRL and MON groups. It
|
||||
// maps to one CTRL or MON group directory in the goresctrl pseudo-filesystem.
|
||||
type ResctrlGroup interface {
|
||||
// Name returns the name of the group.
|
||||
Name() string
|
||||
|
||||
// GetPids returns the process ids assigned to the group.
|
||||
GetPids() ([]string, error)
|
||||
|
||||
// AddPids assigns the given process ids to the group.
|
||||
AddPids(pids ...string) error
|
||||
|
||||
// GetMonData retrieves the monitoring data of the group.
|
||||
GetMonData() MonData
|
||||
}
|
||||
|
||||
// MonGroup represents the interface to a RDT monitoring group. It maps to one
|
||||
// MON group in the goresctrl filesystem.
|
||||
type MonGroup interface {
|
||||
ResctrlGroup
|
||||
|
||||
// Parent returns the CtrlGroup under which the monitoring group exists.
|
||||
Parent() CtrlGroup
|
||||
|
||||
// GetAnnotations returns the annotations stored to the monitoring group.
|
||||
GetAnnotations() map[string]string
|
||||
}
|
||||
|
||||
// MonData contains monitoring stats of one monitoring group.
|
||||
type MonData struct {
|
||||
L3 MonL3Data
|
||||
}
|
||||
|
||||
// MonL3Data contains L3 monitoring stats of one monitoring group.
|
||||
type MonL3Data map[uint64]MonLeafData
|
||||
|
||||
// MonLeafData represents the raw numerical stats from one RDT monitor data leaf.
|
||||
type MonLeafData map[string]uint64
|
||||
|
||||
// MonResource is the type of RDT monitoring resource.
|
||||
type MonResource string
|
||||
|
||||
const (
|
||||
// MonResourceL3 is the RDT L3 cache monitor resource.
|
||||
MonResourceL3 MonResource = "l3"
|
||||
)
|
||||
|
||||
type ctrlGroup struct {
|
||||
resctrlGroup
|
||||
|
||||
monPrefix string
|
||||
monGroups map[string]*monGroup
|
||||
}
|
||||
|
||||
type monGroup struct {
|
||||
resctrlGroup
|
||||
|
||||
annotations map[string]string
|
||||
}
|
||||
|
||||
type resctrlGroup struct {
|
||||
prefix string
|
||||
name string
|
||||
parent *ctrlGroup // parent for MON groups
|
||||
}
|
||||
|
||||
// SetLogger sets the logger instance to be used by the package. This function
|
||||
// may be called even before Initialize().
|
||||
func SetLogger(l grclog.Logger) {
|
||||
log = l
|
||||
if rdt != nil {
|
||||
rdt.setLogger(l)
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize detects RDT from the system and initializes control interface of
|
||||
// the package.
|
||||
func Initialize(resctrlGroupPrefix string) error {
|
||||
var err error
|
||||
|
||||
info = nil
|
||||
rdt = nil
|
||||
|
||||
// Get info from the resctrl filesystem
|
||||
info, err = getRdtInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r := &control{Logger: log, resctrlGroupPrefix: resctrlGroupPrefix}
|
||||
|
||||
// NOTE: we lose monitoring group annotations (i.e. prometheus metrics
|
||||
// labels) on re-init
|
||||
if r.classes, err = r.classesFromResctrlFs(); err != nil {
|
||||
return fmt.Errorf("failed to initialize classes from resctrl fs: %v", err)
|
||||
}
|
||||
|
||||
if err := r.pruneMonGroups(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rdt = r
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DiscoverClasses discovers existing classes from the resctrl filesystem.
|
||||
// Makes it possible to discover gropus with another prefix than was set with
|
||||
// Initialize(). The original prefix is still used for monitoring groups.
|
||||
func DiscoverClasses(resctrlGroupPrefix string) error {
|
||||
if rdt != nil {
|
||||
return rdt.discoverFromResctrl(resctrlGroupPrefix)
|
||||
}
|
||||
return fmt.Errorf("rdt not initialized")
|
||||
}
|
||||
|
||||
// SetConfig (re-)configures the resctrl filesystem according to the specified
|
||||
// configuration.
|
||||
func SetConfig(c *Config, force bool) error {
|
||||
if rdt != nil {
|
||||
return rdt.setConfig(c, force)
|
||||
}
|
||||
return fmt.Errorf("rdt not initialized")
|
||||
}
|
||||
|
||||
// SetConfigFromData takes configuration as raw data, parses it and
|
||||
// reconfigures the resctrl filesystem.
|
||||
func SetConfigFromData(data []byte, force bool) error {
|
||||
cfg := &Config{}
|
||||
if err := yaml.Unmarshal(data, &cfg); err != nil {
|
||||
return fmt.Errorf("failed to parse configuration data: %v", err)
|
||||
}
|
||||
|
||||
return SetConfig(cfg, force)
|
||||
}
|
||||
|
||||
// SetConfigFromFile reads configuration from the filesystem and reconfigures
|
||||
// the resctrl filesystem.
|
||||
func SetConfigFromFile(path string, force bool) error {
|
||||
data, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read config file: %v", err)
|
||||
}
|
||||
|
||||
if err := SetConfigFromData(data, force); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infof("configuration successfully loaded from %q", path)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetClass returns one RDT class.
|
||||
func GetClass(name string) (CtrlGroup, bool) {
|
||||
if rdt != nil {
|
||||
return rdt.getClass(name)
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// GetClasses returns all available RDT classes.
|
||||
func GetClasses() []CtrlGroup {
|
||||
if rdt != nil {
|
||||
return rdt.getClasses()
|
||||
}
|
||||
return []CtrlGroup{}
|
||||
}
|
||||
|
||||
// MonSupported returns true if RDT monitoring features are available.
|
||||
func MonSupported() bool {
|
||||
if rdt != nil {
|
||||
return rdt.monSupported()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetMonFeatures returns the available monitoring stats of each available
|
||||
// monitoring technology.
|
||||
func GetMonFeatures() map[MonResource][]string {
|
||||
if rdt != nil {
|
||||
return rdt.getMonFeatures()
|
||||
}
|
||||
return map[MonResource][]string{}
|
||||
}
|
||||
|
||||
// IsQualifiedClassName returns true if given string qualifies as a class name
|
||||
func IsQualifiedClassName(name string) bool {
|
||||
// Must be qualified as a file name
|
||||
return name == RootClassName || (len(name) < 4096 && name != "." && name != ".." && !strings.ContainsAny(name, "/\n"))
|
||||
}
|
||||
|
||||
func (c *control) getClass(name string) (CtrlGroup, bool) {
|
||||
cls, ok := c.classes[unaliasClassName(name)]
|
||||
return cls, ok
|
||||
}
|
||||
|
||||
func (c *control) getClasses() []CtrlGroup {
|
||||
ret := make([]CtrlGroup, 0, len(c.classes))
|
||||
|
||||
for _, v := range c.classes {
|
||||
ret = append(ret, v)
|
||||
}
|
||||
sort.Slice(ret, func(i, j int) bool { return ret[i].Name() < ret[j].Name() })
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func (c *control) monSupported() bool {
|
||||
return info.l3mon.Supported()
|
||||
}
|
||||
|
||||
func (c *control) getMonFeatures() map[MonResource][]string {
|
||||
ret := make(map[MonResource][]string)
|
||||
if info.l3mon.Supported() {
|
||||
ret[MonResourceL3] = append([]string{}, info.l3mon.monFeatures...)
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func (c *control) setLogger(l grclog.Logger) {
|
||||
c.Logger = l
|
||||
}
|
||||
|
||||
func (c *control) setConfig(newConfig *Config, force bool) error {
|
||||
c.Infof("configuration update")
|
||||
|
||||
conf, err := (*newConfig).resolve()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid configuration: %v", err)
|
||||
}
|
||||
|
||||
err = c.configureResctrl(conf, force)
|
||||
if err != nil {
|
||||
return fmt.Errorf("resctrl configuration failed: %v", err)
|
||||
}
|
||||
|
||||
c.conf = conf
|
||||
// TODO: we'd better create a deep copy
|
||||
c.rawConf = *newConfig
|
||||
c.Infof("configuration finished")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *control) configureResctrl(conf config, force bool) error {
|
||||
grclog.DebugBlock(c, "applying resolved config:", " ", "%s", utils.DumpJSON(conf))
|
||||
|
||||
// Remove stale resctrl groups
|
||||
classesFromFs, err := c.classesFromResctrlFs()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for name, cls := range classesFromFs {
|
||||
if _, ok := conf.Classes[cls.name]; !isRootClass(cls.name) && !ok {
|
||||
if !force {
|
||||
tasks, err := cls.GetPids()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get resctrl group tasks: %v", err)
|
||||
}
|
||||
if len(tasks) > 0 {
|
||||
return fmt.Errorf("refusing to remove non-empty resctrl group %q", cls.relPath(""))
|
||||
}
|
||||
}
|
||||
log.Debugf("removing existing resctrl group %q", cls.relPath(""))
|
||||
err = groupRemoveFunc(cls.path(""))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to remove resctrl group %q: %v", cls.relPath(""), err)
|
||||
}
|
||||
|
||||
delete(c.classes, name)
|
||||
}
|
||||
}
|
||||
|
||||
for name, cls := range c.classes {
|
||||
if _, ok := conf.Classes[cls.name]; !ok || cls.prefix != c.resctrlGroupPrefix {
|
||||
if !isRootClass(cls.name) {
|
||||
log.Debugf("dropping stale class %q (%q)", name, cls.path(""))
|
||||
delete(c.classes, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := c.classes[RootClassName]; !ok {
|
||||
log.Warnf("root class missing from runtime data, re-adding...")
|
||||
c.classes[RootClassName] = classesFromFs[RootClassName]
|
||||
}
|
||||
|
||||
// Try to apply given configuration
|
||||
for name, class := range conf.Classes {
|
||||
if _, ok := c.classes[name]; !ok {
|
||||
cg, err := newCtrlGroup(c.resctrlGroupPrefix, c.resctrlGroupPrefix, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.classes[name] = cg
|
||||
}
|
||||
partition := conf.Partitions[class.Partition]
|
||||
if err := c.classes[name].configure(name, class, partition, conf.Options); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.pruneMonGroups(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *control) discoverFromResctrl(prefix string) error {
|
||||
c.Debugf("running class discovery from resctrl filesystem using prefix %q", prefix)
|
||||
|
||||
classesFromFs, err := c.classesFromResctrlFsPrefix(prefix)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Drop stale classes
|
||||
for name, cls := range c.classes {
|
||||
if _, ok := classesFromFs[cls.name]; !ok || cls.prefix != prefix {
|
||||
if !isRootClass(cls.name) {
|
||||
log.Debugf("dropping stale class %q (%q)", name, cls.path(""))
|
||||
delete(c.classes, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for name, cls := range classesFromFs {
|
||||
if _, ok := c.classes[name]; !ok {
|
||||
c.classes[name] = cls
|
||||
log.Debugf("adding discovered class %q (%q)", name, cls.path(""))
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.pruneMonGroups(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *control) classesFromResctrlFs() (map[string]*ctrlGroup, error) {
|
||||
return c.classesFromResctrlFsPrefix(c.resctrlGroupPrefix)
|
||||
}
|
||||
|
||||
func (c *control) classesFromResctrlFsPrefix(prefix string) (map[string]*ctrlGroup, error) {
|
||||
names := []string{RootClassName}
|
||||
if g, err := resctrlGroupsFromFs(prefix, info.resctrlPath); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
for _, n := range g {
|
||||
if prefix != c.resctrlGroupPrefix &&
|
||||
strings.HasPrefix(n, c.resctrlGroupPrefix) &&
|
||||
strings.HasPrefix(c.resctrlGroupPrefix, prefix) {
|
||||
// Skip groups in the standard namespace
|
||||
continue
|
||||
}
|
||||
names = append(names, n[len(prefix):])
|
||||
}
|
||||
}
|
||||
|
||||
classes := make(map[string]*ctrlGroup, len(names)+1)
|
||||
for _, name := range names {
|
||||
g, err := newCtrlGroup(prefix, c.resctrlGroupPrefix, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
classes[name] = g
|
||||
}
|
||||
|
||||
return classes, nil
|
||||
}
|
||||
|
||||
func (c *control) pruneMonGroups() error {
|
||||
for name, cls := range c.classes {
|
||||
if err := cls.pruneMonGroups(); err != nil {
|
||||
return fmt.Errorf("failed to prune stale monitoring groups of %q: %v", name, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *control) readRdtFile(rdtPath string) ([]byte, error) {
|
||||
return ioutil.ReadFile(filepath.Join(info.resctrlPath, rdtPath))
|
||||
}
|
||||
|
||||
func (c *control) writeRdtFile(rdtPath string, data []byte) error {
|
||||
if err := ioutil.WriteFile(filepath.Join(info.resctrlPath, rdtPath), data, 0644); err != nil {
|
||||
return c.cmdError(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *control) cmdError(origErr error) error {
|
||||
errData, readErr := c.readRdtFile(filepath.Join("info", "last_cmd_status"))
|
||||
if readErr != nil {
|
||||
return origErr
|
||||
}
|
||||
cmdStatus := strings.TrimSpace(string(errData))
|
||||
if len(cmdStatus) > 0 && cmdStatus != "ok" {
|
||||
return fmt.Errorf("%s", cmdStatus)
|
||||
}
|
||||
return origErr
|
||||
}
|
||||
|
||||
func newCtrlGroup(prefix, monPrefix, name string) (*ctrlGroup, error) {
|
||||
cg := &ctrlGroup{
|
||||
resctrlGroup: resctrlGroup{prefix: prefix, name: name},
|
||||
monPrefix: monPrefix,
|
||||
}
|
||||
|
||||
if err := os.Mkdir(cg.path(""), 0755); err != nil && !os.IsExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var err error
|
||||
cg.monGroups, err = cg.monGroupsFromResctrlFs()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error when retrieving existing monitor groups: %v", err)
|
||||
}
|
||||
|
||||
return cg, nil
|
||||
}
|
||||
|
||||
func (c *ctrlGroup) CreateMonGroup(name string, annotations map[string]string) (MonGroup, error) {
|
||||
if mg, ok := c.monGroups[name]; ok {
|
||||
return mg, nil
|
||||
}
|
||||
|
||||
log.Debugf("creating monitoring group %s/%s", c.name, name)
|
||||
mg, err := newMonGroup(c.monPrefix, name, c, annotations)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create new monitoring group %q: %v", name, err)
|
||||
}
|
||||
|
||||
c.monGroups[name] = mg
|
||||
|
||||
return mg, err
|
||||
}
|
||||
|
||||
func (c *ctrlGroup) DeleteMonGroup(name string) error {
|
||||
mg, ok := c.monGroups[name]
|
||||
if !ok {
|
||||
log.Warnf("trying to delete non-existent mon group %s/%s", c.name, name)
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Debugf("deleting monitoring group %s/%s", c.name, name)
|
||||
if err := groupRemoveFunc(mg.path("")); err != nil {
|
||||
return fmt.Errorf("failed to remove monitoring group %q: %v", mg.relPath(""), err)
|
||||
}
|
||||
|
||||
delete(c.monGroups, name)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ctrlGroup) DeleteMonGroups() error {
|
||||
for name := range c.monGroups {
|
||||
if err := c.DeleteMonGroup(name); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ctrlGroup) GetMonGroup(name string) (MonGroup, bool) {
|
||||
mg, ok := c.monGroups[name]
|
||||
return mg, ok
|
||||
}
|
||||
|
||||
func (c *ctrlGroup) GetMonGroups() []MonGroup {
|
||||
ret := make([]MonGroup, 0, len(c.monGroups))
|
||||
|
||||
for _, v := range c.monGroups {
|
||||
ret = append(ret, v)
|
||||
}
|
||||
sort.Slice(ret, func(i, j int) bool { return ret[i].Name() < ret[j].Name() })
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func (c *ctrlGroup) configure(name string, class *classConfig,
|
||||
partition *partitionConfig, options Options) error {
|
||||
schemata := ""
|
||||
|
||||
// Handle cache allocation
|
||||
for _, lvl := range []cacheLevel{L2, L3} {
|
||||
switch {
|
||||
case info.cat[lvl].unified.Supported():
|
||||
schema, err := class.CATSchema[lvl].toStr(catSchemaTypeUnified, partition.CAT[lvl])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
schemata += schema
|
||||
case info.cat[lvl].data.Supported() || info.cat[lvl].code.Supported():
|
||||
schema, err := class.CATSchema[lvl].toStr(catSchemaTypeCode, partition.CAT[lvl])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
schemata += schema
|
||||
|
||||
schema, err = class.CATSchema[lvl].toStr(catSchemaTypeData, partition.CAT[lvl])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
schemata += schema
|
||||
default:
|
||||
if class.CATSchema[lvl].Alloc != nil && !options.cat(lvl).Optional {
|
||||
return fmt.Errorf("%s cache allocation for %q specified in configuration but not supported by system", lvl, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle memory bandwidth allocation
|
||||
switch {
|
||||
case info.mb.Supported():
|
||||
schemata += class.MBSchema.toStr(partition.MB)
|
||||
default:
|
||||
if class.MBSchema != nil && !options.MB.Optional {
|
||||
return fmt.Errorf("memory bandwidth allocation for %q specified in configuration but not supported by system", name)
|
||||
}
|
||||
}
|
||||
|
||||
if len(schemata) > 0 {
|
||||
log.Debugf("writing schemata %q to %q", schemata, c.relPath(""))
|
||||
if err := rdt.writeRdtFile(c.relPath("schemata"), []byte(schemata)); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
log.Debugf("empty schemata")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ctrlGroup) monGroupsFromResctrlFs() (map[string]*monGroup, error) {
|
||||
names, err := resctrlGroupsFromFs(c.monPrefix, c.path("mon_groups"))
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
grps := make(map[string]*monGroup, len(names))
|
||||
for _, name := range names {
|
||||
name = name[len(c.monPrefix):]
|
||||
mg, err := newMonGroup(c.monPrefix, name, c, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
grps[name] = mg
|
||||
}
|
||||
return grps, nil
|
||||
}
|
||||
|
||||
// Remove empty monitoring groups
|
||||
func (c *ctrlGroup) pruneMonGroups() error {
|
||||
for name, mg := range c.monGroups {
|
||||
pids, err := mg.GetPids()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get pids for monitoring group %q: %v", mg.relPath(""), err)
|
||||
}
|
||||
if len(pids) == 0 {
|
||||
if err := c.DeleteMonGroup(name); err != nil {
|
||||
return fmt.Errorf("failed to remove monitoring group %q: %v", mg.relPath(""), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *resctrlGroup) Name() string {
|
||||
return r.name
|
||||
}
|
||||
|
||||
func (r *resctrlGroup) GetPids() ([]string, error) {
|
||||
data, err := rdt.readRdtFile(r.relPath("tasks"))
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
split := strings.Split(strings.TrimSpace(string(data)), "\n")
|
||||
if len(split[0]) > 0 {
|
||||
return split, nil
|
||||
}
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
func (r *resctrlGroup) AddPids(pids ...string) error {
|
||||
f, err := os.OpenFile(r.path("tasks"), os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
for _, pid := range pids {
|
||||
if _, err := f.WriteString(pid + "\n"); err != nil {
|
||||
if errors.Is(err, syscall.ESRCH) {
|
||||
log.Debugf("no task %s", pid)
|
||||
} else {
|
||||
return fmt.Errorf("failed to assign processes %v to class %q: %v", pids, r.name, rdt.cmdError(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *resctrlGroup) GetMonData() MonData {
|
||||
m := MonData{}
|
||||
|
||||
if info.l3mon.Supported() {
|
||||
l3, err := r.getMonL3Data()
|
||||
if err != nil {
|
||||
log.Warnf("failed to retrieve L3 monitoring data: %v", err)
|
||||
} else {
|
||||
m.L3 = l3
|
||||
}
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func (r *resctrlGroup) getMonL3Data() (MonL3Data, error) {
|
||||
files, err := ioutil.ReadDir(r.path("mon_data"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m := MonL3Data{}
|
||||
for _, file := range files {
|
||||
name := file.Name()
|
||||
if strings.HasPrefix(name, "mon_L3_") {
|
||||
// Parse cache id from the dirname
|
||||
id, err := strconv.ParseUint(strings.TrimPrefix(name, "mon_L3_"), 10, 32)
|
||||
if err != nil {
|
||||
// Just print a warning, we try to retrieve as much info as possible
|
||||
log.Warnf("error parsing L3 monitor data directory name %q: %v", name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
data, err := r.getMonLeafData(filepath.Join("mon_data", name))
|
||||
if err != nil {
|
||||
log.Warnf("failed to read monitor data: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
m[id] = data
|
||||
}
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (r *resctrlGroup) getMonLeafData(path string) (MonLeafData, error) {
|
||||
files, err := ioutil.ReadDir(r.path(path))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m := make(MonLeafData, len(files))
|
||||
|
||||
for _, file := range files {
|
||||
name := file.Name()
|
||||
|
||||
// We expect that all the files in the dir are regular files
|
||||
val, err := readFileUint64(r.path(path, name))
|
||||
if err != nil {
|
||||
// Just print a warning, we want to retrieve as much info as possible
|
||||
log.Warnf("error reading data file: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
m[name] = val
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (r *resctrlGroup) relPath(elem ...string) string {
|
||||
if r.parent == nil {
|
||||
if r.name == RootClassName {
|
||||
return filepath.Join(elem...)
|
||||
}
|
||||
return filepath.Join(append([]string{r.prefix + r.name}, elem...)...)
|
||||
}
|
||||
// Parent is only intended for MON groups - non-root CTRL groups are considered
|
||||
// as peers to the root CTRL group (as they are in HW) and do not have a parent
|
||||
return r.parent.relPath(append([]string{"mon_groups", r.prefix + r.name}, elem...)...)
|
||||
}
|
||||
|
||||
func (r *resctrlGroup) path(elem ...string) string {
|
||||
return filepath.Join(info.resctrlPath, r.relPath(elem...))
|
||||
}
|
||||
|
||||
func newMonGroup(prefix string, name string, parent *ctrlGroup, annotations map[string]string) (*monGroup, error) {
|
||||
mg := &monGroup{
|
||||
resctrlGroup: resctrlGroup{prefix: prefix, name: name, parent: parent},
|
||||
annotations: make(map[string]string, len(annotations))}
|
||||
|
||||
if err := os.Mkdir(mg.path(""), 0755); err != nil && !os.IsExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
for k, v := range annotations {
|
||||
mg.annotations[k] = v
|
||||
}
|
||||
|
||||
return mg, nil
|
||||
}
|
||||
|
||||
func (m *monGroup) Parent() CtrlGroup {
|
||||
return m.parent
|
||||
}
|
||||
|
||||
func (m *monGroup) GetAnnotations() map[string]string {
|
||||
a := make(map[string]string, len(m.annotations))
|
||||
for k, v := range m.annotations {
|
||||
a[k] = v
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func resctrlGroupsFromFs(prefix string, path string) ([]string, error) {
|
||||
files, err := ioutil.ReadDir(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
grps := make([]string, 0, len(files))
|
||||
for _, file := range files {
|
||||
filename := file.Name()
|
||||
if strings.HasPrefix(filename, prefix) {
|
||||
if s, err := os.Stat(filepath.Join(path, filename, "tasks")); err == nil && !s.IsDir() {
|
||||
grps = append(grps, filename)
|
||||
}
|
||||
}
|
||||
}
|
||||
return grps, nil
|
||||
}
|
||||
|
||||
func isRootClass(name string) bool {
|
||||
return name == RootClassName || name == RootClassAlias
|
||||
}
|
||||
|
||||
func unaliasClassName(name string) string {
|
||||
if isRootClass(name) {
|
||||
return RootClassName
|
||||
}
|
||||
return name
|
||||
}
|
||||
Reference in New Issue
Block a user