diff --git a/docs/get-started/features.md b/docs/get-started/features.md index cdd6bb9d3..61e0a6f8c 100644 --- a/docs/get-started/features.md +++ b/docs/get-started/features.md @@ -61,7 +61,10 @@ The **cpu** feature source supports the following labels: | cpuid | <cpuid flag> | CPU capability is supported | hardware_multithreading | | Hardware multithreading, such as Intel HTT, enabled (number of logical CPUs is greater than physical CPUs) | power | sst_bf.enabled | Intel SST-BF ([Intel Speed Select Technology][intel-sst] - Base frequency) enabled -| [pstate][intel-pstate] | turbo | Set to 'true' if turbo frequencies are enabled in Intel pstate driver, set to 'false' if they have been disabled. +| [pstate][intel-pstate] | status | The status of the Intel pstate driver when in use and enabled, either 'active' or 'passive'. +| | turbo | Set to 'true' if turbo frequencies are enabled in Intel pstate driver, set to 'false' if they have been disabled. +| | scaling_governor | The value of the Intel pstate scaling_governor when in use, either 'powersave' or 'performance'. +| cstate | enabled | Set to 'true' if cstates are set in the intel_idle driver, otherwise set to 'false'. | [rdt][intel-rdt] | RDTMON | Intel RDT Monitoring Technology | | RDTCMT | Intel Cache Monitoring (CMT) | | RDTMBM | Intel Memory Bandwidth Monitoring (MBM) diff --git a/source/cpu/cpu.go b/source/cpu/cpu.go index 1bc97ba62..22680d815 100644 --- a/source/cpu/cpu.go +++ b/source/cpu/cpu.go @@ -144,6 +144,14 @@ func (s *Source) Discover() (source.Features, error) { features["rdt."+f] = true } + // Detect cstate configuration + cstate, err := detectCstate() + if err != nil { + klog.Errorf("failed to detect cstate: %v", err) + } else { + features["cstate.enabled"] = cstate + } + return features, nil } diff --git a/source/cpu/cstate.go b/source/cpu/cstate.go new file mode 100644 index 000000000..a446925f5 --- /dev/null +++ b/source/cpu/cstate.go @@ -0,0 +1,51 @@ +/* +Copyright 2021 The Kubernetes Authors. + +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 cpu + +import ( + "fmt" + "io/ioutil" + "strconv" + "strings" + + "sigs.k8s.io/node-feature-discovery/source" +) + +// Discover if c-states are enabled +func detectCstate() (bool, error) { + // When the intel_idle driver is in use (default), check setting of max_cstates + driver, err := ioutil.ReadFile(source.SysfsDir.Path("devices/system/cpu/cpuidle/current_driver")) + if err != nil { + return false, fmt.Errorf("cannot get driver for cpuidle: %s", err.Error()) + } + + if strings.TrimSpace(string(driver)) != "intel_idle" { + // Currently only checking intel_idle driver for cstates + return false, fmt.Errorf("intel_idle driver is not in use: %s", string(driver)) + } + + data, err := ioutil.ReadFile(source.SysfsDir.Path("module/intel_idle/parameters/max_cstate")) + if err != nil { + return false, fmt.Errorf("cannot determine cstate from max_cstates: %s", err.Error()) + } + cstates, err := strconv.Atoi(strings.TrimSpace(string(data))) + if err != nil { + return false, fmt.Errorf("non-integer value of cstates: %s", err.Error()) + } + + return cstates > 0, nil +} diff --git a/source/cpu/pstate.go b/source/cpu/pstate.go index 9b8c1e023..c040df564 100644 --- a/source/cpu/pstate.go +++ b/source/cpu/pstate.go @@ -20,6 +20,9 @@ import ( "fmt" "io/ioutil" "runtime" + "strings" + + "k8s.io/klog/v2" "sigs.k8s.io/node-feature-discovery/source" ) @@ -32,14 +35,72 @@ func detectPstate() (map[string]string, error) { return nil, nil } - // Only looking for turbo boost for now... + // Get global pstate status + data, err := ioutil.ReadFile(source.SysfsDir.Path("devices/system/cpu/intel_pstate/status")) + if err != nil { + return nil, fmt.Errorf("could not read pstate status: %s", err.Error()) + } + status := strings.TrimSpace(string(data)) + if status == "off" { + // No need to check other pstate features + klog.Infof("intel_pstate driver is not in use") + return nil, nil + } + features := map[string]string{"status": status} + + // Check turbo boost bytes, err := ioutil.ReadFile(source.SysfsDir.Path("devices/system/cpu/intel_pstate/no_turbo")) if err != nil { - return nil, fmt.Errorf("can't detect whether turbo boost is enabled: %s", err.Error()) + klog.Errorf("can't detect whether turbo boost is enabled: %s", err.Error()) + } else { + features["turbo"] = "false" + if bytes[0] == byte('0') { + features["turbo"] = "true" + } } - features := map[string]string{"turbo": "false"} - if bytes[0] == byte('0') { - features["turbo"] = "true" + + if status != "active" { + // Don't check other features which depend on active state + return features, nil + } + + // Determine scaling governor that is being used + policies, err := ioutil.ReadDir(source.SysfsDir.Path("devices/system/cpu/cpufreq")) + if err != nil { + klog.Errorf("failed to read cpufreq directory: %s", err.Error()) + return features, nil + } + + scaling := "" + for _, policy := range policies { + // Ensure at least one cpu is using this policy + cpus, err := ioutil.ReadFile(source.SysfsDir.Path("devices/system/cpu/cpufreq", policy.Name(), "affected_cpus")) + if err != nil { + klog.Errorf("could not read cpufreq policy %s affected_cpus", policy.Name()) + continue + } + if strings.TrimSpace(string(cpus)) == "" { + klog.Infof("policy %s has no associated cpus", policy.Name()) + continue + } + + data, err := ioutil.ReadFile(source.SysfsDir.Path("devices/system/cpu/cpufreq", policy.Name(), "scaling_governor")) + if err != nil { + klog.Errorf("could not read cpufreq policy %s scaling_governor", policy.Name()) + continue + } + policy_scaling := strings.TrimSpace(string(data)) + // Check that all of the policies have the same scaling governor, if not don't set feature + if scaling != "" && scaling != policy_scaling { + klog.Infof("scaling_governor for policy %s doesn't match prior policy", policy.Name()) + scaling = "" + break + } + scaling = policy_scaling + } + + if scaling != "" { + features["scaling_governor"] = scaling } return features, nil