Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(acpi): Fixed how kepler searches for ACPI in hwmon #1375

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pkg/sensors/platform/power.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,11 @@ func InitPowerImpl() {
powerImpl = &source.PowerHMC{}
} else if redfish := source.NewRedfishClient(); redfish != nil && redfish.IsSystemCollectionSupported() {
powerImpl = redfish
} else if acpi := source.NewACPIPowerMeter(); acpi != nil && acpi.CollectEnergy {
} else if acpi := source.NewACPIPowerMeter(); acpi != nil && acpi.IsInitialized {
powerImpl = acpi
}

klog.V(1).Infof("using %s to obtain power", powerImpl.GetName())
klog.V(1).Infof("using %s to obtain platform power", powerImpl.GetName())
}

func GetSourceName() string {
Expand Down
87 changes: 41 additions & 46 deletions pkg/sensors/platform/source/acpi.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@

import (
"fmt"
"io/fs"
"os"
"path/filepath"
"runtime"
Expand All @@ -27,14 +26,14 @@
"time"

"github.com/sustainable-computing-io/kepler/pkg/config"
"github.com/sustainable-computing-io/kepler/pkg/utils"
"k8s.io/klog/v2"
)

const (
freqPathDir = "/sys/devices/system/cpu/cpufreq/"
freqPath = "/sys/devices/system/cpu/cpufreq/policy%d/scaling_cur_freq"
hwmonPowerPath = "/sys/class/hwmon/hwmon2/device/"
acpiPowerPath = "/sys/devices/LNXSYSTM:00"
hwmonRoot = "/sys/class/hwmon"
acpiPowerFilePrefix = "power"
acpiPowerFileSuffix = "_average"
poolingInterval = 3000 * time.Millisecond // in seconds
Expand All @@ -48,53 +47,55 @@
// Advanced Configuration and Power Interface (APCI) makes the system hardware sensor status
// information available to the operating system via hwmon in sysfs.
type ACPI struct {
CollectEnergy bool
IsInitialized bool
powerPath string
}

func NewACPIPowerMeter() *ACPI {

Check failure on line 54 in pkg/sensors/platform/source/acpi.go

View workflow job for this annotation

GitHub Actions / golang / lint

unnecessary leading newline (whitespace)
acpi := &ACPI{powerPath: hwmonPowerPath}
if acpi.IsHWMONCollectionSupported() {
acpi.CollectEnergy = true
klog.V(5).Infof("Using the HWMON power meter path: %s\n", acpi.powerPath)
} else {
// if the acpi power_average file is not in the hwmon path, try to find the acpi path
acpi.powerPath = findACPIPowerPath()
if acpi.powerPath != "" {
acpi.CollectEnergy = true
klog.V(5).Infof("Using the ACPI power meter path: %s\n", acpi.powerPath)
} else {
klog.Infoln("Could not find any ACPI power meter path. Is it a VM?")
}

path, err := detecthwmonACPIPath()
if err != nil {
klog.V(0).ErrorS(err, "initialization of ACPI power meter failed.")
return nil
}
klog.V(0).Infof("acpi power source initialized with path: %q", path)

return acpi
return &ACPI{powerPath: path, IsInitialized: true}
}

func findACPIPowerPath() string {
var powerPath string
err := filepath.WalkDir(acpiPowerPath, func(path string, info fs.DirEntry, err error) error {
/*
detecthwmonACPIPath looks for an entry in /sys/class/hwmon which has an attribute
Copy link
Contributor

@rootfs rootfs Apr 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add a doc source and sample path too?

"name" set to "power_meter" and a subsystem named "acpi"
*/
func detecthwmonACPIPath() (string, error) {
d, err := os.ReadDir(hwmonRoot)
if err != nil {
return "", fmt.Errorf("could not read %s", hwmonRoot)
}
for _, ent := range d {
var name []byte
devicePath, err := utils.Realpath(filepath.Join(hwmonRoot, ent.Name(), "device"))
if err != nil {
return err
return "", fmt.Errorf("error occurred in reading hwmon device %w", err)
}
if info.IsDir() && (info.Name() == "power" ||
strings.Contains(info.Name(), "INTL") ||
strings.Contains(info.Name(), "PNP") ||
strings.Contains(info.Name(), "input") ||
strings.Contains(info.Name(), "device:") ||
strings.Contains(info.Name(), "wakeup")) {
return filepath.SkipDir
name, err = os.ReadFile(filepath.Join(hwmonRoot, ent.Name(), "name"))
if err != nil {
name, err = os.ReadFile(filepath.Join(devicePath, "name"))
if err != nil {
return "", fmt.Errorf("error occurred in reading file %w", err)
}
}
if !info.IsDir() && strings.Contains(info.Name(), "_average") {
powerPath = path[:(len(path) - len(info.Name()))]
strname := strings.Trim(string(name), "\n ")
ssname, err := utils.Realpath(filepath.Join(devicePath, "subsystem"))
if err != nil {
return "", fmt.Errorf("error occurred in reading hwmon device %w", err)
}
ssname = filepath.Base(ssname)
if strname == "power_meter" && ssname == "acpi" {
return devicePath, nil
}
return nil
})
if err != nil {
klog.V(3).Infof("Could not find any ACPI power meter path: %v\n", err)
return ""
}
return powerPath
return "", fmt.Errorf("could not find acpi power meter in hwmon")
}

func (ACPI) GetName() string {
Expand Down Expand Up @@ -143,22 +144,16 @@
}

func (a *ACPI) IsSystemCollectionSupported() bool {
return a.CollectEnergy
}

func (a *ACPI) IsHWMONCollectionSupported() bool {
// we do not use fmt.Sprintf because it is expensive in the performance standpoint
file := a.powerPath + acpiPowerFilePrefix + "1" + acpiPowerFileSuffix
_, err := os.ReadFile(file)
return err == nil
return a.IsInitialized
}

// GetEnergyFromHost returns the accumulated energy consumption
func (a *ACPI) GetAbsEnergyFromPlatform() (map[string]float64, error) {
power := map[string]float64{}

// TODO: the files in acpi power meter device does not depend on number of CPUs. The below loop will run only once
for i := int32(1); i <= numCPUS; i++ {
path := a.powerPath + acpiPowerFilePrefix + strconv.Itoa(int(i)) + acpiPowerFileSuffix
path := a.powerPath + "/" + acpiPowerFilePrefix + strconv.Itoa(int(i)) + acpiPowerFileSuffix
data, err := os.ReadFile(path)
if err != nil {
break
Expand Down
15 changes: 15 additions & 0 deletions pkg/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/binary"
"fmt"
"os"
"path/filepath"
"strings"
"unsafe"
)
Expand Down Expand Up @@ -69,3 +70,17 @@ func GetPathFromPID(searchPath string, pid uint64) (string, error) {
}
return "", fmt.Errorf("could not find cgroup description entry for pid %d", pid)
}

func Realpath(path string) (string, error) {
absPath, err := filepath.Abs(path)
if err != nil {
return "", err
}

resolvedPath, err := filepath.EvalSymlinks(absPath)
if err != nil {
return "", err
}

return resolvedPath, nil
}
Loading