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 }