github.com/xeptore/docker-cli@v20.10.14+incompatible/cli/command/formatter/disk_usage.go (about)

     1  package formatter
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"strings"
     7  	"text/template"
     8  
     9  	"github.com/docker/distribution/reference"
    10  	"github.com/docker/docker/api/types"
    11  	units "github.com/docker/go-units"
    12  )
    13  
    14  const (
    15  	defaultDiskUsageImageTableFormat      = "table {{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.CreatedSince}}\t{{.VirtualSize}}\t{{.SharedSize}}\t{{.UniqueSize}}\t{{.Containers}}"
    16  	defaultDiskUsageContainerTableFormat  = "table {{.ID}}\t{{.Image}}\t{{.Command}}\t{{.LocalVolumes}}\t{{.Size}}\t{{.RunningFor}}\t{{.Status}}\t{{.Names}}"
    17  	defaultDiskUsageVolumeTableFormat     = "table {{.Name}}\t{{.Links}}\t{{.Size}}"
    18  	defaultDiskUsageBuildCacheTableFormat = "table {{.ID}}\t{{.CacheType}}\t{{.Size}}\t{{.CreatedSince}}\t{{.LastUsedSince}}\t{{.UsageCount}}\t{{.Shared}}"
    19  	defaultDiskUsageTableFormat           = "table {{.Type}}\t{{.TotalCount}}\t{{.Active}}\t{{.Size}}\t{{.Reclaimable}}"
    20  
    21  	typeHeader        = "TYPE"
    22  	totalHeader       = "TOTAL"
    23  	activeHeader      = "ACTIVE"
    24  	reclaimableHeader = "RECLAIMABLE"
    25  	containersHeader  = "CONTAINERS"
    26  	sharedSizeHeader  = "SHARED SIZE"
    27  	uniqueSizeHeader  = "UNIQUE SIZE"
    28  )
    29  
    30  // DiskUsageContext contains disk usage specific information required by the formatter, encapsulate a Context struct.
    31  type DiskUsageContext struct {
    32  	Context
    33  	Verbose     bool
    34  	LayersSize  int64
    35  	Images      []*types.ImageSummary
    36  	Containers  []*types.Container
    37  	Volumes     []*types.Volume
    38  	BuildCache  []*types.BuildCache
    39  	BuilderSize int64
    40  }
    41  
    42  func (ctx *DiskUsageContext) startSubsection(format string) (*template.Template, error) {
    43  	ctx.buffer = bytes.NewBufferString("")
    44  	ctx.header = ""
    45  	ctx.Format = Format(format)
    46  	ctx.preFormat()
    47  
    48  	return ctx.parseFormat()
    49  }
    50  
    51  // NewDiskUsageFormat returns a format for rendering an DiskUsageContext
    52  func NewDiskUsageFormat(source string, verbose bool) Format {
    53  	switch {
    54  	case verbose && source == RawFormatKey:
    55  		format := `{{range .Images}}type: Image
    56  ` + NewImageFormat(source, false, true) + `
    57  {{end -}}
    58  {{range .Containers}}type: Container
    59  ` + NewContainerFormat(source, false, true) + `
    60  {{end -}}
    61  {{range .Volumes}}type: Volume
    62  ` + NewVolumeFormat(source, false) + `
    63  {{end -}}
    64  {{range .BuildCache}}type: Build Cache
    65  ` + NewBuildCacheFormat(source, false) + `
    66  {{end -}}`
    67  		return format
    68  	case !verbose && source == TableFormatKey:
    69  		return Format(defaultDiskUsageTableFormat)
    70  	case !verbose && source == RawFormatKey:
    71  		format := `type: {{.Type}}
    72  total: {{.TotalCount}}
    73  active: {{.Active}}
    74  size: {{.Size}}
    75  reclaimable: {{.Reclaimable}}
    76  `
    77  		return Format(format)
    78  	default:
    79  		return Format(source)
    80  	}
    81  }
    82  
    83  func (ctx *DiskUsageContext) Write() (err error) {
    84  	if ctx.Verbose {
    85  		return ctx.verboseWrite()
    86  	}
    87  	ctx.buffer = bytes.NewBufferString("")
    88  	ctx.preFormat()
    89  
    90  	tmpl, err := ctx.parseFormat()
    91  	if err != nil {
    92  		return err
    93  	}
    94  
    95  	err = ctx.contextFormat(tmpl, &diskUsageImagesContext{
    96  		totalSize: ctx.LayersSize,
    97  		images:    ctx.Images,
    98  	})
    99  	if err != nil {
   100  		return err
   101  	}
   102  	err = ctx.contextFormat(tmpl, &diskUsageContainersContext{
   103  		containers: ctx.Containers,
   104  	})
   105  	if err != nil {
   106  		return err
   107  	}
   108  
   109  	err = ctx.contextFormat(tmpl, &diskUsageVolumesContext{
   110  		volumes: ctx.Volumes,
   111  	})
   112  	if err != nil {
   113  		return err
   114  	}
   115  
   116  	err = ctx.contextFormat(tmpl, &diskUsageBuilderContext{
   117  		builderSize: ctx.BuilderSize,
   118  		buildCache:  ctx.BuildCache,
   119  	})
   120  	if err != nil {
   121  		return err
   122  	}
   123  
   124  	diskUsageContainersCtx := diskUsageContainersContext{containers: []*types.Container{}}
   125  	diskUsageContainersCtx.Header = SubHeaderContext{
   126  		"Type":        typeHeader,
   127  		"TotalCount":  totalHeader,
   128  		"Active":      activeHeader,
   129  		"Size":        SizeHeader,
   130  		"Reclaimable": reclaimableHeader,
   131  	}
   132  	ctx.postFormat(tmpl, &diskUsageContainersCtx)
   133  
   134  	return err
   135  }
   136  
   137  type diskUsageContext struct {
   138  	Images     []*imageContext
   139  	Containers []*ContainerContext
   140  	Volumes    []*volumeContext
   141  	BuildCache []*buildCacheContext
   142  }
   143  
   144  func (ctx *DiskUsageContext) verboseWrite() error {
   145  	duc := &diskUsageContext{
   146  		Images:     make([]*imageContext, 0, len(ctx.Images)),
   147  		Containers: make([]*ContainerContext, 0, len(ctx.Containers)),
   148  		Volumes:    make([]*volumeContext, 0, len(ctx.Volumes)),
   149  		BuildCache: make([]*buildCacheContext, 0, len(ctx.BuildCache)),
   150  	}
   151  	trunc := ctx.Format.IsTable()
   152  
   153  	// First images
   154  	for _, i := range ctx.Images {
   155  		repo := "<none>"
   156  		tag := "<none>"
   157  		if len(i.RepoTags) > 0 && !isDangling(*i) {
   158  			// Only show the first tag
   159  			ref, err := reference.ParseNormalizedNamed(i.RepoTags[0])
   160  			if err != nil {
   161  				continue
   162  			}
   163  			if nt, ok := ref.(reference.NamedTagged); ok {
   164  				repo = reference.FamiliarName(ref)
   165  				tag = nt.Tag()
   166  			}
   167  		}
   168  
   169  		duc.Images = append(duc.Images, &imageContext{
   170  			repo:  repo,
   171  			tag:   tag,
   172  			trunc: trunc,
   173  			i:     *i,
   174  		})
   175  	}
   176  
   177  	// Now containers
   178  	for _, c := range ctx.Containers {
   179  		// Don't display the virtual size
   180  		c.SizeRootFs = 0
   181  		duc.Containers = append(duc.Containers, &ContainerContext{trunc: trunc, c: *c})
   182  	}
   183  
   184  	// And volumes
   185  	for _, v := range ctx.Volumes {
   186  		duc.Volumes = append(duc.Volumes, &volumeContext{v: *v})
   187  	}
   188  
   189  	// And build cache
   190  	buildCacheSort(ctx.BuildCache)
   191  	for _, v := range ctx.BuildCache {
   192  		duc.BuildCache = append(duc.BuildCache, &buildCacheContext{v: v, trunc: trunc})
   193  	}
   194  
   195  	if ctx.Format == TableFormatKey {
   196  		return ctx.verboseWriteTable(duc)
   197  	}
   198  
   199  	ctx.preFormat()
   200  	tmpl, err := ctx.parseFormat()
   201  	if err != nil {
   202  		return err
   203  	}
   204  	return tmpl.Execute(ctx.Output, duc)
   205  }
   206  
   207  func (ctx *DiskUsageContext) verboseWriteTable(duc *diskUsageContext) error {
   208  	tmpl, err := ctx.startSubsection(defaultDiskUsageImageTableFormat)
   209  	if err != nil {
   210  		return err
   211  	}
   212  	ctx.Output.Write([]byte("Images space usage:\n\n"))
   213  	for _, img := range duc.Images {
   214  		if err := ctx.contextFormat(tmpl, img); err != nil {
   215  			return err
   216  		}
   217  	}
   218  	ctx.postFormat(tmpl, newImageContext())
   219  
   220  	tmpl, err = ctx.startSubsection(defaultDiskUsageContainerTableFormat)
   221  	if err != nil {
   222  		return err
   223  	}
   224  	ctx.Output.Write([]byte("\nContainers space usage:\n\n"))
   225  	for _, c := range duc.Containers {
   226  		if err := ctx.contextFormat(tmpl, c); err != nil {
   227  			return err
   228  		}
   229  	}
   230  	ctx.postFormat(tmpl, NewContainerContext())
   231  
   232  	tmpl, err = ctx.startSubsection(defaultDiskUsageVolumeTableFormat)
   233  	if err != nil {
   234  		return err
   235  	}
   236  	ctx.Output.Write([]byte("\nLocal Volumes space usage:\n\n"))
   237  	for _, v := range duc.Volumes {
   238  		if err := ctx.contextFormat(tmpl, v); err != nil {
   239  			return err
   240  		}
   241  	}
   242  	ctx.postFormat(tmpl, newVolumeContext())
   243  
   244  	tmpl, err = ctx.startSubsection(defaultDiskUsageBuildCacheTableFormat)
   245  	if err != nil {
   246  		return err
   247  	}
   248  	fmt.Fprintf(ctx.Output, "\nBuild cache usage: %s\n\n", units.HumanSize(float64(ctx.BuilderSize)))
   249  	for _, v := range duc.BuildCache {
   250  		if err := ctx.contextFormat(tmpl, v); err != nil {
   251  			return err
   252  		}
   253  	}
   254  	ctx.postFormat(tmpl, newBuildCacheContext())
   255  
   256  	return nil
   257  }
   258  
   259  type diskUsageImagesContext struct {
   260  	HeaderContext
   261  	totalSize int64
   262  	images    []*types.ImageSummary
   263  }
   264  
   265  func (c *diskUsageImagesContext) MarshalJSON() ([]byte, error) {
   266  	return MarshalJSON(c)
   267  }
   268  
   269  func (c *diskUsageImagesContext) Type() string {
   270  	return "Images"
   271  }
   272  
   273  func (c *diskUsageImagesContext) TotalCount() string {
   274  	return fmt.Sprintf("%d", len(c.images))
   275  }
   276  
   277  func (c *diskUsageImagesContext) Active() string {
   278  	used := 0
   279  	for _, i := range c.images {
   280  		if i.Containers > 0 {
   281  			used++
   282  		}
   283  	}
   284  
   285  	return fmt.Sprintf("%d", used)
   286  }
   287  
   288  func (c *diskUsageImagesContext) Size() string {
   289  	return units.HumanSize(float64(c.totalSize))
   290  
   291  }
   292  
   293  func (c *diskUsageImagesContext) Reclaimable() string {
   294  	var used int64
   295  
   296  	for _, i := range c.images {
   297  		if i.Containers != 0 {
   298  			if i.VirtualSize == -1 || i.SharedSize == -1 {
   299  				continue
   300  			}
   301  			used += i.VirtualSize - i.SharedSize
   302  		}
   303  	}
   304  
   305  	reclaimable := c.totalSize - used
   306  	if c.totalSize > 0 {
   307  		return fmt.Sprintf("%s (%v%%)", units.HumanSize(float64(reclaimable)), (reclaimable*100)/c.totalSize)
   308  	}
   309  	return units.HumanSize(float64(reclaimable))
   310  }
   311  
   312  type diskUsageContainersContext struct {
   313  	HeaderContext
   314  	containers []*types.Container
   315  }
   316  
   317  func (c *diskUsageContainersContext) MarshalJSON() ([]byte, error) {
   318  	return MarshalJSON(c)
   319  }
   320  
   321  func (c *diskUsageContainersContext) Type() string {
   322  	return "Containers"
   323  }
   324  
   325  func (c *diskUsageContainersContext) TotalCount() string {
   326  	return fmt.Sprintf("%d", len(c.containers))
   327  }
   328  
   329  func (c *diskUsageContainersContext) isActive(container types.Container) bool {
   330  	return strings.Contains(container.State, "running") ||
   331  		strings.Contains(container.State, "paused") ||
   332  		strings.Contains(container.State, "restarting")
   333  }
   334  
   335  func (c *diskUsageContainersContext) Active() string {
   336  	used := 0
   337  	for _, container := range c.containers {
   338  		if c.isActive(*container) {
   339  			used++
   340  		}
   341  	}
   342  
   343  	return fmt.Sprintf("%d", used)
   344  }
   345  
   346  func (c *diskUsageContainersContext) Size() string {
   347  	var size int64
   348  
   349  	for _, container := range c.containers {
   350  		size += container.SizeRw
   351  	}
   352  
   353  	return units.HumanSize(float64(size))
   354  }
   355  
   356  func (c *diskUsageContainersContext) Reclaimable() string {
   357  	var reclaimable int64
   358  	var totalSize int64
   359  
   360  	for _, container := range c.containers {
   361  		if !c.isActive(*container) {
   362  			reclaimable += container.SizeRw
   363  		}
   364  		totalSize += container.SizeRw
   365  	}
   366  
   367  	if totalSize > 0 {
   368  		return fmt.Sprintf("%s (%v%%)", units.HumanSize(float64(reclaimable)), (reclaimable*100)/totalSize)
   369  	}
   370  
   371  	return units.HumanSize(float64(reclaimable))
   372  }
   373  
   374  type diskUsageVolumesContext struct {
   375  	HeaderContext
   376  	volumes []*types.Volume
   377  }
   378  
   379  func (c *diskUsageVolumesContext) MarshalJSON() ([]byte, error) {
   380  	return MarshalJSON(c)
   381  }
   382  
   383  func (c *diskUsageVolumesContext) Type() string {
   384  	return "Local Volumes"
   385  }
   386  
   387  func (c *diskUsageVolumesContext) TotalCount() string {
   388  	return fmt.Sprintf("%d", len(c.volumes))
   389  }
   390  
   391  func (c *diskUsageVolumesContext) Active() string {
   392  
   393  	used := 0
   394  	for _, v := range c.volumes {
   395  		if v.UsageData.RefCount > 0 {
   396  			used++
   397  		}
   398  	}
   399  
   400  	return fmt.Sprintf("%d", used)
   401  }
   402  
   403  func (c *diskUsageVolumesContext) Size() string {
   404  	var size int64
   405  
   406  	for _, v := range c.volumes {
   407  		if v.UsageData.Size != -1 {
   408  			size += v.UsageData.Size
   409  		}
   410  	}
   411  
   412  	return units.HumanSize(float64(size))
   413  }
   414  
   415  func (c *diskUsageVolumesContext) Reclaimable() string {
   416  	var reclaimable int64
   417  	var totalSize int64
   418  
   419  	for _, v := range c.volumes {
   420  		if v.UsageData.Size != -1 {
   421  			if v.UsageData.RefCount == 0 {
   422  				reclaimable += v.UsageData.Size
   423  			}
   424  			totalSize += v.UsageData.Size
   425  		}
   426  	}
   427  
   428  	if totalSize > 0 {
   429  		return fmt.Sprintf("%s (%v%%)", units.HumanSize(float64(reclaimable)), (reclaimable*100)/totalSize)
   430  	}
   431  
   432  	return units.HumanSize(float64(reclaimable))
   433  }
   434  
   435  type diskUsageBuilderContext struct {
   436  	HeaderContext
   437  	builderSize int64
   438  	buildCache  []*types.BuildCache
   439  }
   440  
   441  func (c *diskUsageBuilderContext) MarshalJSON() ([]byte, error) {
   442  	return MarshalJSON(c)
   443  }
   444  
   445  func (c *diskUsageBuilderContext) Type() string {
   446  	return "Build Cache"
   447  }
   448  
   449  func (c *diskUsageBuilderContext) TotalCount() string {
   450  	return fmt.Sprintf("%d", len(c.buildCache))
   451  }
   452  
   453  func (c *diskUsageBuilderContext) Active() string {
   454  	numActive := 0
   455  	for _, bc := range c.buildCache {
   456  		if bc.InUse {
   457  			numActive++
   458  		}
   459  	}
   460  	return fmt.Sprintf("%d", numActive)
   461  }
   462  
   463  func (c *diskUsageBuilderContext) Size() string {
   464  	return units.HumanSize(float64(c.builderSize))
   465  }
   466  
   467  func (c *diskUsageBuilderContext) Reclaimable() string {
   468  	var inUseBytes int64
   469  	for _, bc := range c.buildCache {
   470  		if bc.InUse && !bc.Shared {
   471  			inUseBytes += bc.Size
   472  		}
   473  	}
   474  
   475  	return units.HumanSize(float64(c.builderSize - inUseBytes))
   476  }