Display summary of reclaimed ACI resources (CPU/mem) in `docker prune`

Signed-off-by: Guillaume Tardif <guillaume.tardif@docker.com>
This commit is contained in:
Guillaume Tardif 2020-10-15 12:49:43 +02:00 committed by Ulysses Souza
parent 7f4bfd16dc
commit 50a2ae1100
6 changed files with 83 additions and 56 deletions

View File

@ -380,7 +380,7 @@ func (s serviceConfigAciHelper) getResourceRequestsLimits() (*containerinstance.
return s.Deploy != nil && s.Deploy.Resources.Reservations != nil && s.Deploy.Resources.Reservations.NanoCPUs != ""
}
if hasMemoryRequest() {
memRequest = bytesToGb(s.Deploy.Resources.Reservations.MemoryBytes)
memRequest = BytesToGB(float64(s.Deploy.Resources.Reservations.MemoryBytes))
}
if hasCPURequest() {
@ -393,7 +393,7 @@ func (s serviceConfigAciHelper) getResourceRequestsLimits() (*containerinstance.
cpuLimit := cpuRequest
if s.Deploy != nil && s.Deploy.Resources.Limits != nil {
if s.Deploy.Resources.Limits.MemoryBytes != 0 {
memLimit = bytesToGb(s.Deploy.Resources.Limits.MemoryBytes)
memLimit = BytesToGB(float64(s.Deploy.Resources.Limits.MemoryBytes))
if !hasMemoryRequest() {
memRequest = memLimit
}
@ -438,8 +438,9 @@ func getEnvVariables(composeEnv types.MappingWithEquals) *[]containerinstance.En
return &result
}
func bytesToGb(b types.UnitBytes) float64 {
f := float64(b) / 1024 / 1024 / 1024 // from bytes to gigabytes
// BytesToGB convert bytes To GB
func BytesToGB(b float64) float64 {
f := b / 1024 / 1024 / 1024 // from bytes to gigabytes
return math.Round(f*100) / 100
}
@ -472,6 +473,47 @@ func fqdn(group containerinstance.ContainerGroup, region string) string {
// ContainerGroupToContainer composes a Container from an ACI container definition
func ContainerGroupToContainer(containerID string, cg containerinstance.ContainerGroup, cc containerinstance.Container, region string) containers.Container {
command := ""
if cc.Command != nil {
command = strings.Join(*cc.Command, " ")
}
status := GetStatus(cc, cg)
platform := string(cg.OsType)
var envVars map[string]string = nil
if cc.EnvironmentVariables != nil && len(*cc.EnvironmentVariables) != 0 {
envVars = map[string]string{}
for _, envVar := range *cc.EnvironmentVariables {
envVars[*envVar.Name] = *envVar.Value
}
}
hostConfig := ToHostConfig(cc, cg)
config := &containers.RuntimeConfig{
FQDN: fqdn(cg, region),
Env: envVars,
}
c := containers.Container{
ID: containerID,
Status: status,
Image: to.String(cc.Image),
Command: command,
CPUTime: 0,
MemoryUsage: 0,
PidsCurrent: 0,
PidsLimit: 0,
Ports: ToPorts(cg.IPAddress, *cc.Ports),
Platform: platform,
Config: config,
HostConfig: hostConfig,
}
return c
}
// ToHostConfig convert an ACI container to host config value
func ToHostConfig(cc containerinstance.Container, cg containerinstance.ContainerGroup) *containers.HostConfig {
memLimits := uint64(0)
memRequest := uint64(0)
cpuLimit := 0.
@ -494,27 +536,6 @@ func ContainerGroupToContainer(containerID string, cg containerinstance.Containe
}
}
}
command := ""
if cc.Command != nil {
command = strings.Join(*cc.Command, " ")
}
status := GetStatus(cc, cg)
platform := string(cg.OsType)
var envVars map[string]string = nil
if cc.EnvironmentVariables != nil && len(*cc.EnvironmentVariables) != 0 {
envVars = map[string]string{}
for _, envVar := range *cc.EnvironmentVariables {
envVars[*envVar.Name] = *envVar.Value
}
}
config := &containers.RuntimeConfig{
FQDN: fqdn(cg, region),
Env: envVars,
}
hostConfig := &containers.HostConfig{
CPULimit: cpuLimit,
CPUReservation: cpuReservation,
@ -522,22 +543,7 @@ func ContainerGroupToContainer(containerID string, cg containerinstance.Containe
MemoryReservation: memRequest,
RestartPolicy: toContainerRestartPolicy(cg.RestartPolicy),
}
c := containers.Container{
ID: containerID,
Status: status,
Image: to.String(cc.Image),
Command: command,
CPUTime: 0,
MemoryUsage: 0,
PidsCurrent: 0,
PidsLimit: 0,
Ports: ToPorts(cg.IPAddress, *cc.Ports),
Platform: platform,
Config: config,
HostConfig: hostConfig,
}
return c
return hostConfig
}
// GetStatus returns status for the specified container

View File

@ -18,6 +18,7 @@ package aci
import (
"context"
"fmt"
"github.com/hashicorp/go-multierror"
@ -30,18 +31,28 @@ type aciResourceService struct {
aciContext store.AciContext
}
func (cs *aciResourceService) Prune(ctx context.Context, request resources.PruneRequest) ([]string, error) {
func (cs *aciResourceService) Prune(ctx context.Context, request resources.PruneRequest) (resources.PruneResult, error) {
res, err := getACIContainerGroups(ctx, cs.aciContext.SubscriptionID, cs.aciContext.ResourceGroup)
result := resources.PruneResult{}
if err != nil {
return nil, err
return result, err
}
multierr := &multierror.Error{}
deleted := []string{}
cpus := 0.
mem := 0.
for _, containerGroup := range res {
if !request.Force && convert.GetGroupStatus(containerGroup) == "Node "+convert.StatusRunning {
continue
}
for _, container := range *containerGroup.Containers {
hostConfig := convert.ToHostConfig(container, containerGroup)
cpus += hostConfig.CPUReservation
mem += convert.BytesToGB(float64(hostConfig.MemoryReservation))
}
if !request.DryRun {
_, err := deleteACIContainerGroup(ctx, cs.aciContext, *containerGroup.Name)
multierr = multierror.Append(multierr, err)
@ -50,5 +61,7 @@ func (cs *aciResourceService) Prune(ctx context.Context, request resources.Prune
deleted = append(deleted, *containerGroup.Name)
}
}
return deleted, multierr.ErrorOrNil()
result.DeletedIDs = deleted
result.Summary = fmt.Sprintf("Total CPUs reclaimed: %.2f, total memory reclaimed: %.2f GB", cpus, mem)
return result, multierr.ErrorOrNil()
}

View File

@ -27,6 +27,6 @@ type resourceService struct {
}
// Prune prune resources
func (c *resourceService) Prune(ctx context.Context, request resources.PruneRequest) ([]string, error) {
return nil, errdefs.ErrNotImplemented
func (c *resourceService) Prune(ctx context.Context, request resources.PruneRequest) (resources.PruneResult, error) {
return resources.PruneResult{}, errdefs.ErrNotImplemented
}

View File

@ -26,8 +26,14 @@ type PruneRequest struct {
DryRun bool
}
// PruneResult info on what has been pruned
type PruneResult struct {
DeletedIDs []string
Summary string
}
// Service interacts with the underlying container backend
type Service interface {
// Prune prune resources
Prune(ctx context.Context, request PruneRequest) ([]string, error)
Prune(ctx context.Context, request PruneRequest) (PruneResult, error)
}

View File

@ -56,14 +56,17 @@ func runPrune(ctx context.Context, opts pruneOpts) error {
return errors.Wrap(err, "cannot connect to backend")
}
ids, err := c.ResourceService().Prune(ctx, resources.PruneRequest{Force: opts.force, DryRun: opts.dryRun})
result, err := c.ResourceService().Prune(ctx, resources.PruneRequest{Force: opts.force, DryRun: opts.dryRun})
if opts.dryRun {
fmt.Println("resources that would be deleted:")
fmt.Println("Resources that would be deleted:")
} else {
fmt.Println("deleted resources:")
fmt.Println("Deleted resources:")
}
for _, id := range ids {
for _, id := range result.DeletedIDs {
fmt.Println(id)
}
if result.Summary != "" {
fmt.Println(result.Summary)
}
return err
}

View File

@ -490,21 +490,20 @@ func TestContainerRunAttached(t *testing.T) {
t.Run("prune dry run", func(t *testing.T) {
res := c.RunDockerCmd("prune", "--dry-run")
fmt.Println("prune output:")
assert.Equal(t, "resources that would be deleted:\n", res.Stdout())
assert.Equal(t, "Resources that would be deleted:\nTotal CPUs reclaimed: 0.00, total memory reclaimed: 0.00 GB\n", res.Stdout())
res = c.RunDockerCmd("prune", "--dry-run", "--force")
assert.Equal(t, "resources that would be deleted:\n"+container+"\n", res.Stdout())
assert.Equal(t, "Resources that would be deleted:\n"+container+"\nTotal CPUs reclaimed: 0.10, total memory reclaimed: 0.10 GB\n", res.Stdout())
})
t.Run("prune", func(t *testing.T) {
res := c.RunDockerCmd("prune")
assert.Equal(t, "deleted resources:\n", res.Stdout())
assert.Equal(t, "Deleted resources:\nTotal CPUs reclaimed: 0.00, total memory reclaimed: 0.00 GB\n", res.Stdout())
res = c.RunDockerCmd("ps")
l := lines(res.Stdout())
assert.Equal(t, 2, len(l))
res = c.RunDockerCmd("prune", "--force")
assert.Equal(t, "deleted resources:\n"+container+"\n", res.Stdout())
assert.Equal(t, "Deleted resources:\n"+container+"\nTotal CPUs reclaimed: 0.10, total memory reclaimed: 0.10 GB\n", res.Stdout())
res = c.RunDockerCmd("ps", "--all")
l = lines(res.Stdout())