package daemon import ( "context" "fmt" "github.com/moby/moby/api/types/container" "github.com/moby/moby/v2/daemon/internal/filters" "github.com/moby/moby/v2/daemon/server/backend" "github.com/moby/moby/v2/daemon/server/imagebackend" "github.com/pkg/errors" "golang.org/x/sync/errgroup" ) // containerDiskUsage obtains information about container data disk usage // and makes sure that only one calculation is performed at the same time. func (daemon *Daemon) containerDiskUsage(ctx context.Context, verbose bool) (*backend.ContainerDiskUsage, error) { res, _, err := daemon.usageContainers.Do(ctx, verbose, func(ctx context.Context) (*backend.ContainerDiskUsage, error) { // Retrieve container list containers, err := daemon.Containers(ctx, &backend.ContainerListOptions{ Size: true, All: true, }) if err != nil { return nil, fmt.Errorf("failed to retrieve container list: %v", err) } du := &backend.ContainerDiskUsage{ ActiveCount: int64(len(containers)), TotalCount: int64(len(containers)), } for i := range containers { du.TotalSize += containers[i].SizeRw switch containers[i].State { case container.StateRunning, container.StatePaused, container.StateRestarting: // active default: du.Reclaimable += containers[i].SizeRw du.ActiveCount-- } // Remove image manifest descriptor from the result as it should not be included. // https://github.com/moby/moby/pull/49407#discussion_r1954396666 containers[i].ImageManifestDescriptor = nil } if verbose { du.Items = containers } return du, nil }) return res, err } // imageDiskUsage obtains information about image data disk usage from image service // and makes sure that only one calculation is performed at the same time. func (daemon *Daemon) imageDiskUsage(ctx context.Context, verbose bool) (*backend.ImageDiskUsage, error) { du, _, err := daemon.usageImages.Do(ctx, verbose, func(ctx context.Context) (*backend.ImageDiskUsage, error) { // Get all top images with extra attributes images, err := daemon.imageService.Images(ctx, imagebackend.ListOptions{ Filters: filters.NewArgs(), SharedSize: true, }) if err != nil { return nil, errors.Wrap(err, "failed to retrieve image list") } totalSize, _, err := daemon.usageLayer.Do(ctx, struct{}{}, func(ctx context.Context) (int64, error) { return daemon.imageService.ImageDiskUsage(ctx) }) if err != nil { return nil, errors.Wrap(err, "failed to calculate image disk usage") } du := &backend.ImageDiskUsage{ ActiveCount: int64(len(images)), Reclaimable: totalSize, TotalCount: int64(len(images)), TotalSize: totalSize, } for _, i := range images { if i.Containers == 0 { du.ActiveCount-- if i.Size == -1 || i.SharedSize == -1 { continue } du.Reclaimable -= i.Size - i.SharedSize } } if verbose { du.Items = images } return du, nil }) return du, err } // localVolumesSize obtains information about volume disk usage from volumes service // and makes sure that only one size calculation is performed at the same time. func (daemon *Daemon) localVolumesSize(ctx context.Context, verbose bool) (*backend.VolumeDiskUsage, error) { volumes, _, err := daemon.usageVolumes.Do(ctx, verbose, func(ctx context.Context) (*backend.VolumeDiskUsage, error) { volumes, err := daemon.volumes.LocalVolumesSize(ctx) if err != nil { return nil, err } du := &backend.VolumeDiskUsage{ ActiveCount: int64(len(volumes)), TotalCount: int64(len(volumes)), } for _, v := range volumes { if v.UsageData.Size != -1 { du.TotalSize += v.UsageData.Size if v.UsageData.RefCount == 0 { du.Reclaimable += v.UsageData.Size du.ActiveCount-- } } } if verbose { du.Items = volumes } return du, nil }) return volumes, err } // SystemDiskUsage returns information about the daemon data disk usage. // Callers must not mutate contents of the returned fields. func (daemon *Daemon) SystemDiskUsage(ctx context.Context, opts backend.DiskUsageOptions) (*backend.DiskUsage, error) { eg, ctx := errgroup.WithContext(ctx) du := &backend.DiskUsage{} if opts.Containers { eg.Go(func() (err error) { du.Containers, err = daemon.containerDiskUsage(ctx, opts.Verbose) return err }) } if opts.Images { eg.Go(func() (err error) { du.Images, err = daemon.imageDiskUsage(ctx, opts.Verbose) return err }) } if opts.Volumes { eg.Go(func() (err error) { du.Volumes, err = daemon.localVolumesSize(ctx, opts.Verbose) return err }) } if err := eg.Wait(); err != nil { return nil, err } return du, nil }