use linux perf tool for amd power usage,
add support for intel DC ssds
This commit is contained in:
@@ -414,17 +414,16 @@ func GetDiskSMART(devicePath string) (*DiskSMART, error) {
|
|||||||
|
|
||||||
for _, attr := range smartData.ATAMetrics.Table {
|
for _, attr := range smartData.ATAMetrics.Table {
|
||||||
switch attr.ID {
|
switch attr.ID {
|
||||||
|
// 245: Intel DC
|
||||||
case 231, 177, 233, 169, 230, 173:
|
case 231, 177, 233, 169, 230, 173:
|
||||||
smart.HealthRemaining = attr.Value
|
smart.HealthRemaining = attr.Value
|
||||||
smart.WearoutPercent = max(100-attr.Value, 0)
|
smart.WearoutPercent = max(100-attr.Value, 0)
|
||||||
smart.HasWearData = true
|
smart.HasWearData = true
|
||||||
|
case 202, 245:
|
||||||
case 202:
|
|
||||||
smart.WearoutPercent = attr.Value
|
smart.WearoutPercent = attr.Value
|
||||||
smart.HealthRemaining = max(100-attr.Value, 0)
|
smart.HealthRemaining = max(100-attr.Value, 0)
|
||||||
smart.HasWearData = true
|
smart.HasWearData = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if smart.HasWearData {
|
if smart.HasWearData {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,146 @@
|
|||||||
|
package drivers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"finally-a-monolithic-linux-hw-monitor/app/common"
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PerfEvent struct {
|
||||||
|
EventName string
|
||||||
|
Label string
|
||||||
|
}
|
||||||
|
|
||||||
|
type PerfDriver struct {
|
||||||
|
events []PerfEvent
|
||||||
|
interval time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPerfDriver() (*PerfDriver, error) {
|
||||||
|
if _, err := exec.LookPath("perf"); err != nil {
|
||||||
|
return nil, fmt.Errorf("'perf' command not found: install linux-perf package")
|
||||||
|
}
|
||||||
|
|
||||||
|
r := &PerfDriver{
|
||||||
|
events: []PerfEvent{},
|
||||||
|
interval: 1 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.detectEvents(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PerfDriver) detectEvents() error {
|
||||||
|
cmd := exec.Command("perf", "list")
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to execute 'perf list': %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
perfList := string(out)
|
||||||
|
|
||||||
|
if strings.Contains(perfList, "power/energy-pkg/") {
|
||||||
|
r.events = append(r.events, PerfEvent{EventName: "power/energy-pkg/", Label: "Package"})
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(perfList, "power_core/energy-core/") {
|
||||||
|
r.events = append(r.events, PerfEvent{EventName: "power_core/energy-core/", Label: "Cores"})
|
||||||
|
} else if strings.Contains(perfList, "power/energy-cores/") {
|
||||||
|
r.events = append(r.events, PerfEvent{EventName: "power/energy-cores/", Label: "Cores"})
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(perfList, "power/energy-ram/") {
|
||||||
|
r.events = append(r.events, PerfEvent{EventName: "power/energy-ram/", Label: "DRAM"})
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(r.events) == 0 {
|
||||||
|
return fmt.Errorf("no compatible RAPL perf events detected on this system")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PerfDriver) Render(term common.Terminal) error {
|
||||||
|
p, err := r.Read()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
power := p.([]PowerSensor)
|
||||||
|
term.Subheader("Power (perf)")
|
||||||
|
|
||||||
|
for _, sensor := range power {
|
||||||
|
term.Print(fmt.Sprintf("%-16s | %.2f W", sensor.Name, sensor.Watts))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PerfDriver) Read() (any, error) {
|
||||||
|
if len(r.events) == 0 {
|
||||||
|
return []PowerSensor{}, fmt.Errorf("no perf events configured")
|
||||||
|
}
|
||||||
|
|
||||||
|
args := []string{"stat"}
|
||||||
|
for _, event := range r.events {
|
||||||
|
args = append(args, "-e", event.EventName)
|
||||||
|
}
|
||||||
|
intervalSec := fmt.Sprintf("%.1f", r.interval.Seconds())
|
||||||
|
args = append(args, "-a", "sleep", intervalSec)
|
||||||
|
|
||||||
|
// perf outputs statistics to stderr, so CombinedOutput captures it
|
||||||
|
cmd := exec.Command("perf", args...)
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return []PowerSensor{}, fmt.Errorf("perf stat execution failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsePerfOutput(string(out), r.events)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parsePerfOutput(output string, events []PerfEvent) ([]PowerSensor, error) {
|
||||||
|
sensors := []PowerSensor{}
|
||||||
|
lines := strings.Split(output, "\n")
|
||||||
|
|
||||||
|
// Parse actual elapsed time from perf output (e.g., "1.001234567 seconds time elapsed")
|
||||||
|
elapsedSeconds := 1.0
|
||||||
|
for _, line := range lines {
|
||||||
|
if strings.Contains(line, "seconds time elapsed") {
|
||||||
|
fields := strings.Fields(line)
|
||||||
|
if len(fields) > 0 {
|
||||||
|
if val, err := strconv.ParseFloat(fields[0], 64); err == nil && val > 0 {
|
||||||
|
elapsedSeconds = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract Joules for each event and calculate Watts = Joules / Seconds
|
||||||
|
for _, event := range events {
|
||||||
|
for _, line := range lines {
|
||||||
|
if strings.Contains(line, event.EventName) {
|
||||||
|
fields := strings.Fields(line)
|
||||||
|
if len(fields) > 0 {
|
||||||
|
// Strip commas from numbers formatted like "1,234.56"
|
||||||
|
cleanVal := strings.ReplaceAll(fields[0], ",", "")
|
||||||
|
if joules, err := strconv.ParseFloat(cleanVal, 64); err == nil {
|
||||||
|
watts := joules / elapsedSeconds
|
||||||
|
sensors = append(sensors, PowerSensor{
|
||||||
|
Name: event.Label,
|
||||||
|
Watts: watts,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sensors, nil
|
||||||
|
}
|
||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const milliJoulePerJoule = float64(1000000)
|
const microJoulePerJoule = float64(1000000)
|
||||||
|
|
||||||
type EnergyState struct {
|
type EnergyState struct {
|
||||||
lastEnergy uint64
|
lastEnergy uint64
|
||||||
@@ -36,7 +36,7 @@ func (r *RaplDriver) Render(term common.Terminal) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
power := p.([]PowerSensor)
|
power := p.([]PowerSensor)
|
||||||
term.Subheader("Power /sys/class/powercap/intel-rapl")
|
term.Subheader("Power (rapl)")
|
||||||
|
|
||||||
for _, sensor := range power {
|
for _, sensor := range power {
|
||||||
// \033[K clears from cursor to end of line (prevents lingering chars)
|
// \033[K clears from cursor to end of line (prevents lingering chars)
|
||||||
@@ -127,7 +127,7 @@ func (r *RaplDriver) calculateEnergyDelta(inputs []string, out *[]PowerSensor) {
|
|||||||
// the value will be off for that one measurement, then become correct again
|
// the value will be off for that one measurement, then become correct again
|
||||||
energyDiff := float64(microJoules - prevState.lastEnergy)
|
energyDiff := float64(microJoules - prevState.lastEnergy)
|
||||||
|
|
||||||
watts := (energyDiff / milliJoulePerJoule) / timeDiff
|
watts := (energyDiff / microJoulePerJoule) / timeDiff
|
||||||
|
|
||||||
r.energyState[path] = EnergyState{
|
r.energyState[path] = EnergyState{
|
||||||
lastEnergy: microJoules,
|
lastEnergy: microJoules,
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ func NewAMDDrivers() ([]common.Driver, error) {
|
|||||||
}
|
}
|
||||||
drivers = append(drivers, temperatureDriver)
|
drivers = append(drivers, temperatureDriver)
|
||||||
|
|
||||||
powerDriver, err := driver.NewRaplDriver()
|
powerDriver, err := driver.NewPerfDriver()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not initialize hwmon temperature driver")
|
return nil, fmt.Errorf("could not initialize hwmon temperature driver")
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user