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 CPUPercent for windows and add functional test #1089

Merged
merged 2 commits into from
Nov 17, 2017
Merged
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
6 changes: 3 additions & 3 deletions agent/api/task_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1277,7 +1277,7 @@ func TestSetConfigHostconfigBasedOnAPIVersion(t *testing.T) {
Containers: []*Container{
{
Name: "c1",
CPU: uint(100),
CPU: uint(10),
Memory: uint(50),
},
},
Expand All @@ -1290,7 +1290,7 @@ func TestSetConfigHostconfigBasedOnAPIVersion(t *testing.T) {
assert.Nil(t, cerr)

assert.Equal(t, int64(50*1024*1024), config.Memory)
assert.Equal(t, int64(100), config.CPUShares)
assert.Equal(t, int64(10), config.CPUShares)
assert.Empty(t, hostconfig.CPUShares)
assert.Empty(t, hostconfig.Memory)

Expand All @@ -1300,7 +1300,7 @@ func TestSetConfigHostconfigBasedOnAPIVersion(t *testing.T) {
config, cerr = testTask.DockerConfig(testTask.Containers[0], dockerclient.Version_1_18)
assert.Nil(t, err)
assert.Equal(t, int64(50*1024*1024), hostconfig.Memory)
assert.Equal(t, int64(100), hostconfig.CPUShares)
assert.Equal(t, int64(10), hostconfig.CPUShares)
assert.Empty(t, config.CPUShares)
assert.Empty(t, config.Memory)
}
12 changes: 10 additions & 2 deletions agent/api/task_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,12 @@ import (
const (
//memorySwappinessDefault is the expected default value for this platform
memorySwappinessDefault = -1
// cpuSharesPerCore represents the cpu shares of a cpu core in docker
cpuSharesPerCore = 1024
percentageFactor = 100
)

var cpuShareScaleFactor = runtime.NumCPU() * 1024
var cpuShareScaleFactor = runtime.NumCPU() * cpuSharesPerCore

// adjustForPlatform makes Windows-specific changes to the task after unmarshal
func (task *Task) adjustForPlatform(cfg *config.Config) {
Expand Down Expand Up @@ -62,7 +65,12 @@ func getCanonicalPath(path string) string {
// passed to Docker API.
func (task *Task) platformHostConfigOverride(hostConfig *docker.HostConfig) error {
task.overrideDefaultMemorySwappiness(hostConfig)
hostConfig.CPUPercent = hostConfig.CPUShares / int64(cpuShareScaleFactor)
// Convert the CPUShares to CPUPercent
hostConfig.CPUPercent = hostConfig.CPUShares * percentageFactor / int64(cpuShareScaleFactor)
if hostConfig.CPUPercent != 0 {
// Only unset the CPUShares if the CPUPercent has valid value
hostConfig.CPUShares = 0
}
return nil
}

Expand Down
10 changes: 8 additions & 2 deletions agent/api/task_windows_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,11 +112,17 @@ func TestWindowsPlatformHostConfigOverride(t *testing.T) {

task := &Task{}

hostConfig := &docker.HostConfig{CPUShares: 1024}
hostConfig := &docker.HostConfig{CPUShares: int64(1 * cpuSharesPerCore)}

task.platformHostConfigOverride(hostConfig)
assert.Equal(t, int64(1024)/int64(cpuShareScaleFactor), hostConfig.CPUPercent)
assert.Equal(t, int64(1*cpuSharesPerCore*percentageFactor)/int64(cpuShareScaleFactor), hostConfig.CPUPercent)
assert.Equal(t, int64(0), hostConfig.CPUShares)
assert.EqualValues(t, expectedMemorySwappinessDefault, hostConfig.MemorySwappiness)

hostConfig = &docker.HostConfig{CPUShares: 10}
task.platformHostConfigOverride(hostConfig)
assert.Equal(t, int64(0), hostConfig.CPUPercent)
assert.Equal(t, int64(10), hostConfig.CPUShares)
}

func TestWindowsMemorySwappinessOption(t *testing.T) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
{
"family": "ecsftest-telemetry-windows",
"family": "ecsftest-windows-telemetry",
"containerDefinitions": [{
"image": "microsoft/iis:latest",
"name": "http_server",
"cpu": 100,
"memory": 500,
"command": ["powershell", "-c", "New-Item -Path C:\\inetpub\\wwwroot\\index.html -Type file -Value '<html> <head> <title>Amazon ECS Sample App</title> <style>body {margin-top: 40px; background-color: #333;} </style> </head><body> <div style=color:white;text-align:center> <h1>Amazon ECS Sample App</h1> <h2>Congratulations!</h2> <p>Your application is now running on a container in Amazon ECS.</p>'; C:\\ServiceMonitor.exe w3svc"]
"image": "amazon/amazon-ecs-windows-cpupercent-test:make",
"name": "windows-cpu-percent",
"cpu": $$$$CPUSHARE$$$$,
"memory": 500
}]
}
12 changes: 6 additions & 6 deletions agent/functional_tests/tests/functionaltests_unix_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -383,11 +383,11 @@ func TestTelemetry(t *testing.T) {
time.Sleep(waitMetricsInCloudwatchDuration)

cwclient := cloudwatch.New(session.New(), aws.NewConfig().WithRegion(*ECS.Config.Region))
err = VerifyMetrics(cwclient, params, true)
_, err = VerifyMetrics(cwclient, params, true)
assert.NoError(t, err, "Before task running, verify metrics for CPU utilization failed")

params.MetricName = aws.String("MemoryUtilization")
err = VerifyMetrics(cwclient, params, true)
_, err = VerifyMetrics(cwclient, params, true)
assert.NoError(t, err, "Before task running, verify metrics for memory utilization failed")

testTask, err := agent.StartTask(t, "telemetry")
Expand All @@ -400,11 +400,11 @@ func TestTelemetry(t *testing.T) {
params.EndTime = aws.Time(RoundTimeUp(time.Now(), time.Minute).UTC())
params.StartTime = aws.Time((*params.EndTime).Add(-waitMetricsInCloudwatchDuration).UTC())
params.MetricName = aws.String("CPUUtilization")
err = VerifyMetrics(cwclient, params, false)
_, err = VerifyMetrics(cwclient, params, false)
assert.NoError(t, err, "Task is running, verify metrics for CPU utilization failed")

params.MetricName = aws.String("MemoryUtilization")
err = VerifyMetrics(cwclient, params, false)
_, err = VerifyMetrics(cwclient, params, false)
assert.NoError(t, err, "Task is running, verify metrics for memory utilization failed")

err = testTask.Stop()
Expand All @@ -417,11 +417,11 @@ func TestTelemetry(t *testing.T) {
params.EndTime = aws.Time(RoundTimeUp(time.Now(), time.Minute).UTC())
params.StartTime = aws.Time((*params.EndTime).Add(-waitMetricsInCloudwatchDuration).UTC())
params.MetricName = aws.String("CPUUtilization")
err = VerifyMetrics(cwclient, params, true)
_, err = VerifyMetrics(cwclient, params, true)
assert.NoError(t, err, "Task stopped: verify metrics for CPU utilization failed")

params.MetricName = aws.String("MemoryUtilization")
err = VerifyMetrics(cwclient, params, true)
_, err = VerifyMetrics(cwclient, params, true)
assert.NoError(t, err, "Task stopped, verify metrics for memory utilization failed")
}

Expand Down
25 changes: 18 additions & 7 deletions agent/functional_tests/tests/functionaltests_windows_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ package functional_tests
import (
"fmt"
"os"
"runtime"
"strconv"
"strings"
"testing"
"time"
Expand All @@ -41,6 +43,7 @@ const (
logDriverTaskDefinition = "logdriver-jsonfile-windows"
cleanupTaskDefinition = "cleanup-windows"
networkModeTaskDefinition = "network-mode-windows"
cpuSharesPerCore = 1024
)

// TestAWSLogsDriver verifies that container logs are sent to Amazon CloudWatch Logs with awslogs as the log driver
Expand Down Expand Up @@ -266,14 +269,20 @@ func TestTelemetry(t *testing.T) {
time.Sleep(waitMetricsInCloudwatchDuration)

cwclient := cloudwatch.New(session.New(), aws.NewConfig().WithRegion(*ECS.Config.Region))
err = VerifyMetrics(cwclient, params, true)
_, err = VerifyMetrics(cwclient, params, true)
assert.NoError(t, err, "Before task running, verify metrics for CPU utilization failed")

params.MetricName = aws.String("MemoryUtilization")
err = VerifyMetrics(cwclient, params, true)
_, err = VerifyMetrics(cwclient, params, true)
assert.NoError(t, err, "Before task running, verify metrics for memory utilization failed")

testTask, err := agent.StartTask(t, "telemetry-windows")
cpuNum := runtime.NumCPU()

tdOverrides := make(map[string]string)
// Set the container cpu percentage 25%
tdOverrides["$$$$CPUSHARE$$$$"] = strconv.Itoa(int(float64(cpuNum*cpuSharesPerCore) * 0.25))

testTask, err := agent.StartTaskWithTaskDefinitionOverrides(t, "telemetry-windows", tdOverrides)
require.NoError(t, err, "Failed to start telemetry task")
// Wait for the task to run and the agent to send back metrics
err = testTask.WaitRunning(waitTaskStateChangeDuration)
Expand All @@ -283,11 +292,13 @@ func TestTelemetry(t *testing.T) {
params.EndTime = aws.Time(RoundTimeUp(time.Now(), time.Minute).UTC())
params.StartTime = aws.Time((*params.EndTime).Add(-waitMetricsInCloudwatchDuration).UTC())
params.MetricName = aws.String("CPUUtilization")
err = VerifyMetrics(cwclient, params, false)
metrics, err := VerifyMetrics(cwclient, params, false)
assert.NoError(t, err, "Task is running, verify metrics for CPU utilization failed")
// Also verify the cpu usage is around 25%
assert.InDelta(t, 0.25, *metrics.Average, 0.05)

params.MetricName = aws.String("MemoryUtilization")
err = VerifyMetrics(cwclient, params, false)
_, err = VerifyMetrics(cwclient, params, false)
assert.NoError(t, err, "Task is running, verify metrics for memory utilization failed")

err = testTask.Stop()
Expand All @@ -300,10 +311,10 @@ func TestTelemetry(t *testing.T) {
params.EndTime = aws.Time(RoundTimeUp(time.Now(), time.Minute).UTC())
params.StartTime = aws.Time((*params.EndTime).Add(-waitMetricsInCloudwatchDuration).UTC())
params.MetricName = aws.String("CPUUtilization")
err = VerifyMetrics(cwclient, params, true)
_, err = VerifyMetrics(cwclient, params, true)
assert.NoError(t, err, "Task stopped: verify metrics for CPU utilization failed")

params.MetricName = aws.String("MemoryUtilization")
err = VerifyMetrics(cwclient, params, true)
_, err = VerifyMetrics(cwclient, params, true)
assert.NoError(t, err, "Task stopped, verify metrics for memory utilization failed")
}
16 changes: 8 additions & 8 deletions agent/functional_tests/util/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,36 +266,36 @@ func DeleteCluster(t *testing.T, clusterName string) {

// VerifyMetrics whether the response is as expected
// the expected value can be 0 or positive
func VerifyMetrics(cwclient *cloudwatch.CloudWatch, params *cloudwatch.GetMetricStatisticsInput, idleCluster bool) error {
func VerifyMetrics(cwclient *cloudwatch.CloudWatch, params *cloudwatch.GetMetricStatisticsInput, idleCluster bool) (*cloudwatch.Datapoint, error) {
resp, err := cwclient.GetMetricStatistics(params)
if err != nil {
return fmt.Errorf("Error getting metrics of cluster: %v", err)
return nil, fmt.Errorf("Error getting metrics of cluster: %v", err)
}

if resp == nil || resp.Datapoints == nil {
return fmt.Errorf("Cloudwatch get metrics failed, returned null")
return nil, fmt.Errorf("Cloudwatch get metrics failed, returned null")
}
metricsCount := len(resp.Datapoints)
if metricsCount == 0 {
return fmt.Errorf("No datapoints returned")
return nil, fmt.Errorf("No datapoints returned")
}

datapoint := resp.Datapoints[metricsCount-1]
// Samplecount is always expected to be "1" for cluster metrics
if *datapoint.SampleCount != 1.0 {
return fmt.Errorf("Incorrect SampleCount %f, expected 1", *datapoint.SampleCount)
return nil, fmt.Errorf("Incorrect SampleCount %f, expected 1", *datapoint.SampleCount)
}

if idleCluster {
if *datapoint.Average != 0.0 {
return fmt.Errorf("non-zero utilization for idle cluster")
return nil, fmt.Errorf("non-zero utilization for idle cluster")
}
} else {
if *datapoint.Average == 0.0 {
return fmt.Errorf("utilization is zero for non-idle cluster")
return nil, fmt.Errorf("utilization is zero for non-idle cluster")
}
}
return nil
return datapoint, nil
}

// ResolveTaskDockerID determines the Docker ID for a container within a given
Expand Down
14 changes: 14 additions & 0 deletions misc/windows-cpupercent/build.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You may
# not use this file except in compliance with the License. A copy of the
# License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is distributed
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
# express or implied. See the License for the specific language governing
# permissions and limitations under the License.

docker build -t "amazon/amazon-ecs-windows-cpupercent-test:make" -f "${PSScriptRoot}/windows.dockerfile" ${PSScriptRoot}
37 changes: 37 additions & 0 deletions misc/windows-cpupercent/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"). You may
// not use this file except in compliance with the License. A copy of the
// License is located at
//
// http://aws.amazon.com/apache2.0/
//
// or in the "license" file accompanying this file. This file is distributed
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
// express or implied. See the License for the specific language governing
// permissions and limitations under the License.

package main
Copy link
Contributor

Choose a reason for hiding this comment

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

This needs the license header.


import (
"crypto/md5"
"flag"
"fmt"
)

func main() {
concurrency := flag.Int("concurrency", 1, "amount of concurrency")
flag.Parse()
neverdie := make(chan struct{})

fmt.Printf("Hogging CPU with concurrency %d\n", *concurrency)
for i := 0; i < *concurrency; i++ {
go func() {
md5hash := md5.New()
for {
md5hash.Write([]byte{0})
}
}()
}
<-neverdie
}
20 changes: 20 additions & 0 deletions misc/windows-cpupercent/windows.dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You may
# not use this file except in compliance with the License. A copy of the
# License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is distributed
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
# express or implied. See the License for the specific language governing
# permissions and limitations under the License.
FROM golang:nanoserver

WORKDIR /gopath
COPY main.go .

RUN go build -o cpuhog main.go
ENTRYPOINT ["./cpuhog"]
CMD [ "-concurrency", "1000" ]
1 change: 1 addition & 0 deletions scripts/run-functional-tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

Invoke-Expression "${PSScriptRoot}\..\misc\windows-iam\Setup_Iam.ps1"
Invoke-Expression "${PSScriptRoot}\..\misc\windows-listen80\Setup_Listen80.ps1"
Invoke-Expression "${PSScriptRoot}\..\misc\windows-cpupercent\build.ps1"

# Run the tests
$cwd = (pwd).Path
Expand Down