use linux perf tool for amd power usage,
add support for intel DC ssds
This commit is contained in:
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user