2020-01-04 13:23:10 -08:00
package main
import (
"fmt"
"io/ioutil"
"math"
2020-01-04 18:51:41 -08:00
"os"
2020-01-04 13:23:10 -08:00
"path/filepath"
"strconv"
"strings"
)
const raplPrefixCPU = "intel-rapl"
2020-01-17 09:33:43 -08:00
// constraint_0 is usually the longer window while constraint_1 is usually the shorter window
const maxPowerFileLongWindow = "constraint_0_max_power_uw"
const powerLimitFileLongWindow = "constraint_0_power_limit_uw"
2020-01-04 13:23:10 -08:00
// capNode uses pseudo files made available by the Linux kernel
// in order to capNode CPU power. More information is available at:
// https://www.kernel.org/doc/html/latest/power/powercap/powercap.html
2020-01-18 20:11:44 -08:00
func capNode ( base string , percentage int ) ( [ ] string , [ ] string , error ) {
2020-01-04 13:23:10 -08:00
if percentage <= 0 || percentage > 100 {
2020-01-18 20:11:44 -08:00
return nil , nil , fmt . Errorf ( "cap percentage must be between 0 (non-inclusive) and 100 (inclusive): %d" , percentage )
2020-01-04 13:23:10 -08:00
}
files , err := ioutil . ReadDir ( base )
if err != nil {
2020-01-18 20:11:44 -08:00
return nil , nil , err
2020-01-04 13:23:10 -08:00
}
2020-01-18 20:11:44 -08:00
var capped , failed [ ] string
2020-01-04 13:23:10 -08:00
for _ , file := range files {
fields := strings . Split ( file . Name ( ) , ":" )
// Fields should be in the form intel-rapl:X where X is the power zone
// We ignore sub-zones which follow the form intel-rapl:X:Y
if len ( fields ) != 2 {
continue
}
if fields [ 0 ] == raplPrefixCPU {
2020-01-17 09:33:43 -08:00
maxPower , err := maxPower ( filepath . Join ( base , file . Name ( ) , maxPowerFileLongWindow ) )
2020-01-04 13:23:10 -08:00
if err != nil {
2020-01-18 20:11:44 -08:00
failed = append ( failed , file . Name ( ) )
2020-01-04 13:23:10 -08:00
fmt . Println ( "unable to retreive max power for zone " , err )
continue
}
2020-01-18 20:11:44 -08:00
// We use floats to mitigate the possibility of an integer overflow.
2020-01-04 17:42:59 -08:00
powercap := uint64 ( math . Ceil ( float64 ( maxPower ) * ( float64 ( percentage ) / 100 ) ) )
2020-01-04 13:23:10 -08:00
2020-01-18 20:11:44 -08:00
if err := capZone ( filepath . Join ( base , file . Name ( ) , powerLimitFileLongWindow ) , powercap ) ; err != nil {
failed = append ( failed , file . Name ( ) )
2020-01-04 13:23:10 -08:00
fmt . Println ( "unable to write powercap value: " , err )
continue
}
2020-01-18 20:11:44 -08:00
capped = append ( capped , file . Name ( ) )
2020-01-04 13:23:10 -08:00
}
}
2020-01-18 20:11:44 -08:00
if len ( failed ) > 0 {
return capped , failed , fmt . Errorf ( "some zones were not able to be powercapped" )
}
return capped , nil , nil
2020-01-04 13:23:10 -08:00
}
2020-01-17 09:43:34 -08:00
// maxPower returns the value in float of the maximum watts a power zone can use.
2020-01-04 18:11:43 -08:00
func maxPower ( maxFile string ) ( uint64 , error ) {
maxPower , err := ioutil . ReadFile ( maxFile )
2020-01-04 13:23:10 -08:00
if err != nil {
2020-01-04 18:51:41 -08:00
return 0 , err
2020-01-04 13:23:10 -08:00
}
maxPoweruW , err := strconv . ParseUint ( strings . TrimSpace ( string ( maxPower ) ) , 10 , 64 )
if err != nil {
2020-01-04 18:51:41 -08:00
return 0 , err
2020-01-04 13:23:10 -08:00
}
return maxPoweruW , nil
}
// capZone caps a power zone to a specific amount of watts specified by value
2020-01-04 18:11:43 -08:00
func capZone ( limitFile string , value uint64 ) error {
2020-01-04 18:51:41 -08:00
if _ , err := os . Stat ( limitFile ) ; os . IsNotExist ( err ) {
return err
}
2020-01-04 18:11:43 -08:00
err := ioutil . WriteFile ( limitFile , [ ] byte ( strconv . FormatUint ( value , 10 ) ) , 0644 )
2020-01-04 13:23:10 -08:00
if err != nil {
return err
}
return nil
}
2020-01-18 20:11:44 -08:00
func currentCap ( limit string ) ( uint64 , error ) {
powercap , err := ioutil . ReadFile ( limit )
if err != nil {
return 0 , err
}
powercapuW , err := strconv . ParseUint ( strings . TrimSpace ( string ( powercap ) ) , 10 , 64 )
if err != nil {
return 0 , err
}
return powercapuW , nil
}