capping funciton now returns which zones were sucessfully capped and which zones could not be capped. This information is now returned to the caller of the HTTP api.
This commit is contained in:
parent
bcd25c805a
commit
5e4ba5a933
4 changed files with 86 additions and 21 deletions
|
@ -11,3 +11,29 @@ on all worker nodes.
|
|||
--data '{"percentage":75}' \
|
||||
http://localhost:9090/powercap
|
||||
```
|
||||
|
||||
### Payload
|
||||
|
||||
```json
|
||||
{
|
||||
"percentage":75
|
||||
}
|
||||
```
|
||||
|
||||
### Response
|
||||
|
||||
The daemon will respond with a json payload containing zones that were
|
||||
successfully capped as well as the zones that were not capped.
|
||||
|
||||
```json
|
||||
{
|
||||
"cappedZones": null,
|
||||
"failedZones": [
|
||||
"intel-rapl:0",
|
||||
"intel-rapl:1"
|
||||
],
|
||||
"error": "some zones were not able to be powercapped"
|
||||
}
|
||||
```
|
||||
|
||||
Field error will not exist if failed zones is empty.
|
|
@ -15,6 +15,13 @@ type Cap struct {
|
|||
Percentage int
|
||||
}
|
||||
|
||||
// CapResponse is the payload sent with information about the capping call
|
||||
type CapResponse struct {
|
||||
CappedZones []string `json:"cappedZones"`
|
||||
FailedZones []string `json:"failedZones"`
|
||||
Error *string `json:"error"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "Unsupported endpoint %s", html.EscapeString(r.URL.Path))
|
||||
|
@ -26,19 +33,28 @@ func main() {
|
|||
|
||||
// Handler for the powercapping HTTP API endpoint.
|
||||
func powercapEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
var payload Cap
|
||||
var response CapResponse
|
||||
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
err := decoder.Decode(&payload)
|
||||
if err != nil {
|
||||
http.Error(w, "error parsing payload: "+err.Error(), 400)
|
||||
errorMsg := "error parsing payload: " + err.Error()
|
||||
response.Error = &errorMsg
|
||||
json.NewEncoder(w).Encode(response)
|
||||
return
|
||||
}
|
||||
|
||||
err = capNode(powercapDir, payload.Percentage)
|
||||
cappedZones, failedZones, err := capNode(powercapDir, payload.Percentage)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 400)
|
||||
return
|
||||
errorMsg := err.Error()
|
||||
response.Error = &errorMsg
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, "capped node at %d percent", payload.Percentage)
|
||||
response.CappedZones = cappedZones
|
||||
response.FailedZones = failedZones
|
||||
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
|
|
@ -19,19 +19,19 @@ const powerLimitFileLongWindow = "constraint_0_power_limit_uw"
|
|||
// 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
|
||||
func capNode(base string, percentage int) error {
|
||||
func capNode(base string, percentage int) ([]string, []string, error) {
|
||||
|
||||
if percentage <= 0 || percentage > 100 {
|
||||
return fmt.Errorf("cap percentage must be between (0, 100]: %d", percentage)
|
||||
return nil, nil, fmt.Errorf("cap percentage must be between 0 (non-inclusive) and 100 (inclusive): %d", percentage)
|
||||
}
|
||||
|
||||
files, err := ioutil.ReadDir(base)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var capped, failed []string
|
||||
for _, file := range files {
|
||||
|
||||
fields := strings.Split(file.Name(), ":")
|
||||
|
||||
// Fields should be in the form intel-rapl:X where X is the power zone
|
||||
|
@ -43,22 +43,28 @@ func capNode(base string, percentage int) error {
|
|||
if fields[0] == raplPrefixCPU {
|
||||
maxPower, err := maxPower(filepath.Join(base, file.Name(), maxPowerFileLongWindow))
|
||||
if err != nil {
|
||||
failed = append(failed, file.Name())
|
||||
fmt.Println("unable to retreive max power for zone ", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// We use floats to mitigate the possibility of an integer overflows.
|
||||
// We use floats to mitigate the possibility of an integer overflow.
|
||||
powercap := uint64(math.Ceil(float64(maxPower) * (float64(percentage) / 100)))
|
||||
|
||||
err = capZone(filepath.Join(base, file.Name(), powerLimitFileLongWindow), powercap)
|
||||
if err != nil {
|
||||
if err := capZone(filepath.Join(base, file.Name(), powerLimitFileLongWindow), powercap); err != nil {
|
||||
failed = append(failed, file.Name())
|
||||
fmt.Println("unable to write powercap value: ", err)
|
||||
continue
|
||||
}
|
||||
capped = append(capped, file.Name())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
if len(failed) > 0 {
|
||||
return capped, failed, fmt.Errorf("some zones were not able to be powercapped")
|
||||
}
|
||||
|
||||
return capped, nil, nil
|
||||
}
|
||||
|
||||
// maxPower returns the value in float of the maximum watts a power zone can use.
|
||||
|
@ -88,3 +94,17 @@ func capZone(limitFile string, value uint64) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
@ -48,18 +47,25 @@ func TestMain(m *testing.M) {
|
|||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
// TODO(rdelvalle): Add tests where capping fails
|
||||
func TestCapNode(t *testing.T) {
|
||||
err := capNode(raplDir, 95)
|
||||
capped, failed, err := capNode(raplDir, 95)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, capped, 1)
|
||||
assert.Nil(t, failed)
|
||||
|
||||
t.Run("bad-percentage", func(t *testing.T) {
|
||||
err := capNode(raplDir, 1000)
|
||||
capped, failed, err := capNode(raplDir, 1000)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, capped)
|
||||
assert.Nil(t, failed)
|
||||
})
|
||||
|
||||
t.Run("zero-percent", func(t *testing.T) {
|
||||
err := capNode(raplDir, 0)
|
||||
capped, failed, err := capNode(raplDir, 0)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, capped)
|
||||
assert.Nil(t, failed)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -84,10 +90,7 @@ func TestCapZone(t *testing.T) {
|
|||
err := capZone(limitFile, powercap)
|
||||
assert.NoError(t, err)
|
||||
|
||||
newCapBytes, err := ioutil.ReadFile(limitFile)
|
||||
assert.NoError(t, err)
|
||||
|
||||
newCap, err := strconv.ParseUint(strings.TrimSpace(string(newCapBytes)), 10, 64)
|
||||
newCap, err := currentCap(limitFile)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, powercap, newCap)
|
||||
|
||||
|
|
Reference in a new issue