mirror of
https://github.com/docker/compose.git
synced 2025-07-24 22:24:41 +02:00
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:
parent
7f4bfd16dc
commit
50a2ae1100
@ -380,7 +380,7 @@ func (s serviceConfigAciHelper) getResourceRequestsLimits() (*containerinstance.
|
|||||||
return s.Deploy != nil && s.Deploy.Resources.Reservations != nil && s.Deploy.Resources.Reservations.NanoCPUs != ""
|
return s.Deploy != nil && s.Deploy.Resources.Reservations != nil && s.Deploy.Resources.Reservations.NanoCPUs != ""
|
||||||
}
|
}
|
||||||
if hasMemoryRequest() {
|
if hasMemoryRequest() {
|
||||||
memRequest = bytesToGb(s.Deploy.Resources.Reservations.MemoryBytes)
|
memRequest = BytesToGB(float64(s.Deploy.Resources.Reservations.MemoryBytes))
|
||||||
}
|
}
|
||||||
|
|
||||||
if hasCPURequest() {
|
if hasCPURequest() {
|
||||||
@ -393,7 +393,7 @@ func (s serviceConfigAciHelper) getResourceRequestsLimits() (*containerinstance.
|
|||||||
cpuLimit := cpuRequest
|
cpuLimit := cpuRequest
|
||||||
if s.Deploy != nil && s.Deploy.Resources.Limits != nil {
|
if s.Deploy != nil && s.Deploy.Resources.Limits != nil {
|
||||||
if s.Deploy.Resources.Limits.MemoryBytes != 0 {
|
if s.Deploy.Resources.Limits.MemoryBytes != 0 {
|
||||||
memLimit = bytesToGb(s.Deploy.Resources.Limits.MemoryBytes)
|
memLimit = BytesToGB(float64(s.Deploy.Resources.Limits.MemoryBytes))
|
||||||
if !hasMemoryRequest() {
|
if !hasMemoryRequest() {
|
||||||
memRequest = memLimit
|
memRequest = memLimit
|
||||||
}
|
}
|
||||||
@ -438,8 +438,9 @@ func getEnvVariables(composeEnv types.MappingWithEquals) *[]containerinstance.En
|
|||||||
return &result
|
return &result
|
||||||
}
|
}
|
||||||
|
|
||||||
func bytesToGb(b types.UnitBytes) float64 {
|
// BytesToGB convert bytes To GB
|
||||||
f := float64(b) / 1024 / 1024 / 1024 // from bytes to gigabytes
|
func BytesToGB(b float64) float64 {
|
||||||
|
f := b / 1024 / 1024 / 1024 // from bytes to gigabytes
|
||||||
return math.Round(f*100) / 100
|
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
|
// ContainerGroupToContainer composes a Container from an ACI container definition
|
||||||
func ContainerGroupToContainer(containerID string, cg containerinstance.ContainerGroup, cc containerinstance.Container, region string) containers.Container {
|
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)
|
memLimits := uint64(0)
|
||||||
memRequest := uint64(0)
|
memRequest := uint64(0)
|
||||||
cpuLimit := 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{
|
hostConfig := &containers.HostConfig{
|
||||||
CPULimit: cpuLimit,
|
CPULimit: cpuLimit,
|
||||||
CPUReservation: cpuReservation,
|
CPUReservation: cpuReservation,
|
||||||
@ -522,22 +543,7 @@ func ContainerGroupToContainer(containerID string, cg containerinstance.Containe
|
|||||||
MemoryReservation: memRequest,
|
MemoryReservation: memRequest,
|
||||||
RestartPolicy: toContainerRestartPolicy(cg.RestartPolicy),
|
RestartPolicy: toContainerRestartPolicy(cg.RestartPolicy),
|
||||||
}
|
}
|
||||||
c := containers.Container{
|
return hostConfig
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetStatus returns status for the specified container
|
// GetStatus returns status for the specified container
|
||||||
|
@ -18,6 +18,7 @@ package aci
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/hashicorp/go-multierror"
|
"github.com/hashicorp/go-multierror"
|
||||||
|
|
||||||
@ -30,18 +31,28 @@ type aciResourceService struct {
|
|||||||
aciContext store.AciContext
|
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)
|
res, err := getACIContainerGroups(ctx, cs.aciContext.SubscriptionID, cs.aciContext.ResourceGroup)
|
||||||
|
result := resources.PruneResult{}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return result, err
|
||||||
}
|
}
|
||||||
multierr := &multierror.Error{}
|
multierr := &multierror.Error{}
|
||||||
deleted := []string{}
|
deleted := []string{}
|
||||||
|
cpus := 0.
|
||||||
|
mem := 0.
|
||||||
|
|
||||||
for _, containerGroup := range res {
|
for _, containerGroup := range res {
|
||||||
if !request.Force && convert.GetGroupStatus(containerGroup) == "Node "+convert.StatusRunning {
|
if !request.Force && convert.GetGroupStatus(containerGroup) == "Node "+convert.StatusRunning {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, container := range *containerGroup.Containers {
|
||||||
|
hostConfig := convert.ToHostConfig(container, containerGroup)
|
||||||
|
cpus += hostConfig.CPUReservation
|
||||||
|
mem += convert.BytesToGB(float64(hostConfig.MemoryReservation))
|
||||||
|
}
|
||||||
|
|
||||||
if !request.DryRun {
|
if !request.DryRun {
|
||||||
_, err := deleteACIContainerGroup(ctx, cs.aciContext, *containerGroup.Name)
|
_, err := deleteACIContainerGroup(ctx, cs.aciContext, *containerGroup.Name)
|
||||||
multierr = multierror.Append(multierr, err)
|
multierr = multierror.Append(multierr, err)
|
||||||
@ -50,5 +61,7 @@ func (cs *aciResourceService) Prune(ctx context.Context, request resources.Prune
|
|||||||
deleted = append(deleted, *containerGroup.Name)
|
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()
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,6 @@ type resourceService struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Prune prune resources
|
// Prune prune resources
|
||||||
func (c *resourceService) Prune(ctx context.Context, request resources.PruneRequest) ([]string, error) {
|
func (c *resourceService) Prune(ctx context.Context, request resources.PruneRequest) (resources.PruneResult, error) {
|
||||||
return nil, errdefs.ErrNotImplemented
|
return resources.PruneResult{}, errdefs.ErrNotImplemented
|
||||||
}
|
}
|
||||||
|
@ -26,8 +26,14 @@ type PruneRequest struct {
|
|||||||
DryRun bool
|
DryRun bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PruneResult info on what has been pruned
|
||||||
|
type PruneResult struct {
|
||||||
|
DeletedIDs []string
|
||||||
|
Summary string
|
||||||
|
}
|
||||||
|
|
||||||
// Service interacts with the underlying container backend
|
// Service interacts with the underlying container backend
|
||||||
type Service interface {
|
type Service interface {
|
||||||
// Prune prune resources
|
// Prune prune resources
|
||||||
Prune(ctx context.Context, request PruneRequest) ([]string, error)
|
Prune(ctx context.Context, request PruneRequest) (PruneResult, error)
|
||||||
}
|
}
|
||||||
|
@ -56,14 +56,17 @@ func runPrune(ctx context.Context, opts pruneOpts) error {
|
|||||||
return errors.Wrap(err, "cannot connect to backend")
|
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 {
|
if opts.dryRun {
|
||||||
fmt.Println("resources that would be deleted:")
|
fmt.Println("Resources that would be deleted:")
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("deleted resources:")
|
fmt.Println("Deleted resources:")
|
||||||
}
|
}
|
||||||
for _, id := range ids {
|
for _, id := range result.DeletedIDs {
|
||||||
fmt.Println(id)
|
fmt.Println(id)
|
||||||
}
|
}
|
||||||
|
if result.Summary != "" {
|
||||||
|
fmt.Println(result.Summary)
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -490,21 +490,20 @@ func TestContainerRunAttached(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("prune dry run", func(t *testing.T) {
|
t.Run("prune dry run", func(t *testing.T) {
|
||||||
res := c.RunDockerCmd("prune", "--dry-run")
|
res := c.RunDockerCmd("prune", "--dry-run")
|
||||||
fmt.Println("prune output:")
|
assert.Equal(t, "Resources that would be deleted:\nTotal CPUs reclaimed: 0.00, total memory reclaimed: 0.00 GB\n", res.Stdout())
|
||||||
assert.Equal(t, "resources that would be deleted:\n", res.Stdout())
|
|
||||||
res = c.RunDockerCmd("prune", "--dry-run", "--force")
|
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) {
|
t.Run("prune", func(t *testing.T) {
|
||||||
res := c.RunDockerCmd("prune")
|
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")
|
res = c.RunDockerCmd("ps")
|
||||||
l := lines(res.Stdout())
|
l := lines(res.Stdout())
|
||||||
assert.Equal(t, 2, len(l))
|
assert.Equal(t, 2, len(l))
|
||||||
|
|
||||||
res = c.RunDockerCmd("prune", "--force")
|
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")
|
res = c.RunDockerCmd("ps", "--all")
|
||||||
l = lines(res.Stdout())
|
l = lines(res.Stdout())
|
||||||
|
Loading…
x
Reference in New Issue
Block a user