2016-08-23 19:37:37 -04:00
|
|
|
package formatter
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"fmt"
|
|
|
|
"strings"
|
|
|
|
"text/template"
|
|
|
|
|
|
|
|
"github.com/docker/distribution/reference"
|
|
|
|
"github.com/docker/docker/api/types"
|
|
|
|
units "github.com/docker/go-units"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2018-08-31 18:14:36 -04:00
|
|
|
defaultDiskUsageImageTableFormat = "table {{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.CreatedSince}}\t{{.VirtualSize}}\t{{.SharedSize}}\t{{.UniqueSize}}\t{{.Containers}}"
|
|
|
|
defaultDiskUsageContainerTableFormat = "table {{.ID}}\t{{.Image}}\t{{.Command}}\t{{.LocalVolumes}}\t{{.Size}}\t{{.RunningFor}}\t{{.Status}}\t{{.Names}}"
|
|
|
|
defaultDiskUsageVolumeTableFormat = "table {{.Name}}\t{{.Links}}\t{{.Size}}"
|
|
|
|
defaultDiskUsageTableFormat = "table {{.Type}}\t{{.TotalCount}}\t{{.Active}}\t{{.Size}}\t{{.Reclaimable}}"
|
|
|
|
defaultDiskUsageBuildCacheTableFormat = "table {{.ID}}\t{{.Type}}\t{{.Size}}\t{{.CreatedSince}}\t{{.LastUsedSince}}\t{{.UsageCount}}\t{{.Shared}}"
|
2016-08-23 19:37:37 -04:00
|
|
|
|
|
|
|
typeHeader = "TYPE"
|
|
|
|
totalHeader = "TOTAL"
|
|
|
|
activeHeader = "ACTIVE"
|
|
|
|
reclaimableHeader = "RECLAIMABLE"
|
|
|
|
containersHeader = "CONTAINERS"
|
|
|
|
sharedSizeHeader = "SHARED SIZE"
|
2018-08-31 18:14:36 -04:00
|
|
|
uniqueSizeHeader = "UNIQUE SIZE"
|
2016-08-23 19:37:37 -04:00
|
|
|
)
|
|
|
|
|
2017-01-17 11:26:37 -05:00
|
|
|
// DiskUsageContext contains disk usage specific information required by the formatter, encapsulate a Context struct.
|
2016-08-23 19:37:37 -04:00
|
|
|
type DiskUsageContext struct {
|
|
|
|
Context
|
2017-05-15 17:14:31 -04:00
|
|
|
Verbose bool
|
|
|
|
LayersSize int64
|
|
|
|
Images []*types.ImageSummary
|
|
|
|
Containers []*types.Container
|
|
|
|
Volumes []*types.Volume
|
2018-05-18 14:51:35 -04:00
|
|
|
BuildCache []*types.BuildCache
|
2017-05-15 17:14:31 -04:00
|
|
|
BuilderSize int64
|
2016-08-23 19:37:37 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func (ctx *DiskUsageContext) startSubsection(format string) (*template.Template, error) {
|
|
|
|
ctx.buffer = bytes.NewBufferString("")
|
|
|
|
ctx.header = ""
|
|
|
|
ctx.Format = Format(format)
|
|
|
|
ctx.preFormat()
|
|
|
|
|
|
|
|
return ctx.parseFormat()
|
|
|
|
}
|
|
|
|
|
2017-03-02 07:05:48 -05:00
|
|
|
// NewDiskUsageFormat returns a format for rendering an DiskUsageContext
|
|
|
|
func NewDiskUsageFormat(source string) Format {
|
|
|
|
switch source {
|
|
|
|
case TableFormatKey:
|
|
|
|
format := defaultDiskUsageTableFormat
|
|
|
|
return Format(format)
|
|
|
|
case RawFormatKey:
|
|
|
|
format := `type: {{.Type}}
|
|
|
|
total: {{.TotalCount}}
|
|
|
|
active: {{.Active}}
|
|
|
|
size: {{.Size}}
|
|
|
|
reclaimable: {{.Reclaimable}}
|
|
|
|
`
|
|
|
|
return Format(format)
|
|
|
|
}
|
|
|
|
return Format(source)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ctx *DiskUsageContext) Write() (err error) {
|
2017-05-09 17:24:40 -04:00
|
|
|
if ctx.Verbose {
|
|
|
|
return ctx.verboseWrite()
|
|
|
|
}
|
|
|
|
ctx.buffer = bytes.NewBufferString("")
|
|
|
|
ctx.preFormat()
|
|
|
|
|
|
|
|
tmpl, err := ctx.parseFormat()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2016-08-23 19:37:37 -04:00
|
|
|
|
2017-05-09 17:24:40 -04:00
|
|
|
err = ctx.contextFormat(tmpl, &diskUsageImagesContext{
|
|
|
|
totalSize: ctx.LayersSize,
|
|
|
|
images: ctx.Images,
|
|
|
|
})
|
|
|
|
if err != nil {
|
2017-03-02 07:05:48 -05:00
|
|
|
return err
|
2016-08-23 19:37:37 -04:00
|
|
|
}
|
2017-05-09 17:24:40 -04:00
|
|
|
err = ctx.contextFormat(tmpl, &diskUsageContainersContext{
|
|
|
|
containers: ctx.Containers,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = ctx.contextFormat(tmpl, &diskUsageVolumesContext{
|
|
|
|
volumes: ctx.Volumes,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-05-15 17:14:31 -04:00
|
|
|
err = ctx.contextFormat(tmpl, &diskUsageBuilderContext{
|
|
|
|
builderSize: ctx.BuilderSize,
|
2018-05-18 14:51:35 -04:00
|
|
|
buildCache: ctx.BuildCache,
|
2017-05-15 17:14:31 -04:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-05-09 17:24:40 -04:00
|
|
|
diskUsageContainersCtx := diskUsageContainersContext{containers: []*types.Container{}}
|
|
|
|
diskUsageContainersCtx.header = map[string]string{
|
|
|
|
"Type": typeHeader,
|
|
|
|
"TotalCount": totalHeader,
|
|
|
|
"Active": activeHeader,
|
|
|
|
"Size": sizeHeader,
|
|
|
|
"Reclaimable": reclaimableHeader,
|
|
|
|
}
|
|
|
|
ctx.postFormat(tmpl, &diskUsageContainersCtx)
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
2016-08-23 19:37:37 -04:00
|
|
|
|
2018-08-31 18:14:36 -04:00
|
|
|
// nolint: gocyclo
|
2017-10-12 11:44:03 -04:00
|
|
|
func (ctx *DiskUsageContext) verboseWrite() error {
|
2016-08-23 19:37:37 -04:00
|
|
|
// First images
|
|
|
|
tmpl, err := ctx.startSubsection(defaultDiskUsageImageTableFormat)
|
|
|
|
if err != nil {
|
2017-10-12 11:44:03 -04:00
|
|
|
return err
|
2016-08-23 19:37:37 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
ctx.Output.Write([]byte("Images space usage:\n\n"))
|
|
|
|
for _, i := range ctx.Images {
|
|
|
|
repo := "<none>"
|
|
|
|
tag := "<none>"
|
|
|
|
if len(i.RepoTags) > 0 && !isDangling(*i) {
|
|
|
|
// Only show the first tag
|
2017-01-25 19:54:18 -05:00
|
|
|
ref, err := reference.ParseNormalizedNamed(i.RepoTags[0])
|
2016-08-23 19:37:37 -04:00
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if nt, ok := ref.(reference.NamedTagged); ok {
|
2017-01-25 19:54:18 -05:00
|
|
|
repo = reference.FamiliarName(ref)
|
2016-08-23 19:37:37 -04:00
|
|
|
tag = nt.Tag()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-12 11:44:03 -04:00
|
|
|
err := ctx.contextFormat(tmpl, &imageContext{
|
2016-08-23 19:37:37 -04:00
|
|
|
repo: repo,
|
|
|
|
tag: tag,
|
|
|
|
trunc: true,
|
|
|
|
i: *i,
|
|
|
|
})
|
|
|
|
if err != nil {
|
2017-10-12 11:44:03 -04:00
|
|
|
return err
|
2016-08-23 19:37:37 -04:00
|
|
|
}
|
|
|
|
}
|
2017-02-03 19:48:46 -05:00
|
|
|
ctx.postFormat(tmpl, newImageContext())
|
2016-08-23 19:37:37 -04:00
|
|
|
|
|
|
|
// Now containers
|
|
|
|
ctx.Output.Write([]byte("\nContainers space usage:\n\n"))
|
|
|
|
tmpl, err = ctx.startSubsection(defaultDiskUsageContainerTableFormat)
|
|
|
|
if err != nil {
|
2017-10-12 11:44:03 -04:00
|
|
|
return err
|
2016-08-23 19:37:37 -04:00
|
|
|
}
|
|
|
|
for _, c := range ctx.Containers {
|
|
|
|
// Don't display the virtual size
|
|
|
|
c.SizeRootFs = 0
|
2017-10-12 11:44:03 -04:00
|
|
|
err := ctx.contextFormat(tmpl, &containerContext{trunc: true, c: *c})
|
2016-08-23 19:37:37 -04:00
|
|
|
if err != nil {
|
2017-10-12 11:44:03 -04:00
|
|
|
return err
|
2016-08-23 19:37:37 -04:00
|
|
|
}
|
|
|
|
}
|
2017-02-03 19:48:46 -05:00
|
|
|
ctx.postFormat(tmpl, newContainerContext())
|
2016-08-23 19:37:37 -04:00
|
|
|
|
|
|
|
// And volumes
|
|
|
|
ctx.Output.Write([]byte("\nLocal Volumes space usage:\n\n"))
|
|
|
|
tmpl, err = ctx.startSubsection(defaultDiskUsageVolumeTableFormat)
|
|
|
|
if err != nil {
|
2017-10-12 11:44:03 -04:00
|
|
|
return err
|
2016-08-23 19:37:37 -04:00
|
|
|
}
|
|
|
|
for _, v := range ctx.Volumes {
|
2017-10-12 11:44:03 -04:00
|
|
|
if err := ctx.contextFormat(tmpl, &volumeContext{v: *v}); err != nil {
|
|
|
|
return err
|
2016-08-23 19:37:37 -04:00
|
|
|
}
|
|
|
|
}
|
2017-02-03 19:48:46 -05:00
|
|
|
ctx.postFormat(tmpl, newVolumeContext())
|
2017-05-15 17:14:31 -04:00
|
|
|
|
|
|
|
// And build cache
|
|
|
|
fmt.Fprintf(ctx.Output, "\nBuild cache usage: %s\n\n", units.HumanSize(float64(ctx.BuilderSize)))
|
2018-05-18 14:51:35 -04:00
|
|
|
|
2018-08-31 18:14:36 -04:00
|
|
|
tmpl, err = ctx.startSubsection(defaultDiskUsageBuildCacheTableFormat)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
buildCacheSort(ctx.BuildCache)
|
2018-05-18 14:51:35 -04:00
|
|
|
for _, v := range ctx.BuildCache {
|
2018-08-31 18:14:36 -04:00
|
|
|
if err := ctx.contextFormat(tmpl, &buildCacheContext{v: v, trunc: true}); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-05-18 14:51:35 -04:00
|
|
|
}
|
2018-08-31 18:14:36 -04:00
|
|
|
ctx.postFormat(tmpl, newBuildCacheContext())
|
2018-05-18 14:51:35 -04:00
|
|
|
|
2017-10-12 11:44:03 -04:00
|
|
|
return nil
|
2016-08-23 19:37:37 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
type diskUsageImagesContext struct {
|
|
|
|
HeaderContext
|
|
|
|
totalSize int64
|
2016-10-03 15:17:39 -04:00
|
|
|
images []*types.ImageSummary
|
2016-08-23 19:37:37 -04:00
|
|
|
}
|
|
|
|
|
2017-01-19 04:50:28 -05:00
|
|
|
func (c *diskUsageImagesContext) MarshalJSON() ([]byte, error) {
|
|
|
|
return marshalJSON(c)
|
|
|
|
}
|
|
|
|
|
2016-08-23 19:37:37 -04:00
|
|
|
func (c *diskUsageImagesContext) Type() string {
|
|
|
|
return "Images"
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *diskUsageImagesContext) TotalCount() string {
|
|
|
|
return fmt.Sprintf("%d", len(c.images))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *diskUsageImagesContext) Active() string {
|
|
|
|
used := 0
|
|
|
|
for _, i := range c.images {
|
|
|
|
if i.Containers > 0 {
|
|
|
|
used++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return fmt.Sprintf("%d", used)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *diskUsageImagesContext) Size() string {
|
|
|
|
return units.HumanSize(float64(c.totalSize))
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *diskUsageImagesContext) Reclaimable() string {
|
|
|
|
var used int64
|
|
|
|
|
|
|
|
for _, i := range c.images {
|
|
|
|
if i.Containers != 0 {
|
2017-01-23 16:52:33 -05:00
|
|
|
if i.VirtualSize == -1 || i.SharedSize == -1 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
used += i.VirtualSize - i.SharedSize
|
2016-08-23 19:37:37 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
reclaimable := c.totalSize - used
|
|
|
|
if c.totalSize > 0 {
|
|
|
|
return fmt.Sprintf("%s (%v%%)", units.HumanSize(float64(reclaimable)), (reclaimable*100)/c.totalSize)
|
|
|
|
}
|
2017-07-11 14:19:02 -04:00
|
|
|
return units.HumanSize(float64(reclaimable))
|
2016-08-23 19:37:37 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
type diskUsageContainersContext struct {
|
|
|
|
HeaderContext
|
|
|
|
containers []*types.Container
|
|
|
|
}
|
|
|
|
|
2017-01-19 04:50:28 -05:00
|
|
|
func (c *diskUsageContainersContext) MarshalJSON() ([]byte, error) {
|
|
|
|
return marshalJSON(c)
|
|
|
|
}
|
|
|
|
|
2016-08-23 19:37:37 -04:00
|
|
|
func (c *diskUsageContainersContext) Type() string {
|
|
|
|
return "Containers"
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *diskUsageContainersContext) TotalCount() string {
|
|
|
|
return fmt.Sprintf("%d", len(c.containers))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *diskUsageContainersContext) isActive(container types.Container) bool {
|
|
|
|
return strings.Contains(container.State, "running") ||
|
|
|
|
strings.Contains(container.State, "paused") ||
|
|
|
|
strings.Contains(container.State, "restarting")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *diskUsageContainersContext) Active() string {
|
|
|
|
used := 0
|
|
|
|
for _, container := range c.containers {
|
|
|
|
if c.isActive(*container) {
|
|
|
|
used++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return fmt.Sprintf("%d", used)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *diskUsageContainersContext) Size() string {
|
|
|
|
var size int64
|
|
|
|
|
|
|
|
for _, container := range c.containers {
|
|
|
|
size += container.SizeRw
|
|
|
|
}
|
|
|
|
|
|
|
|
return units.HumanSize(float64(size))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *diskUsageContainersContext) Reclaimable() string {
|
|
|
|
var reclaimable int64
|
|
|
|
var totalSize int64
|
|
|
|
|
|
|
|
for _, container := range c.containers {
|
|
|
|
if !c.isActive(*container) {
|
|
|
|
reclaimable += container.SizeRw
|
|
|
|
}
|
|
|
|
totalSize += container.SizeRw
|
|
|
|
}
|
|
|
|
|
|
|
|
if totalSize > 0 {
|
|
|
|
return fmt.Sprintf("%s (%v%%)", units.HumanSize(float64(reclaimable)), (reclaimable*100)/totalSize)
|
|
|
|
}
|
|
|
|
|
2017-07-11 14:19:02 -04:00
|
|
|
return units.HumanSize(float64(reclaimable))
|
2016-08-23 19:37:37 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
type diskUsageVolumesContext struct {
|
|
|
|
HeaderContext
|
|
|
|
volumes []*types.Volume
|
|
|
|
}
|
|
|
|
|
2017-01-19 04:50:28 -05:00
|
|
|
func (c *diskUsageVolumesContext) MarshalJSON() ([]byte, error) {
|
|
|
|
return marshalJSON(c)
|
|
|
|
}
|
|
|
|
|
2016-08-23 19:37:37 -04:00
|
|
|
func (c *diskUsageVolumesContext) Type() string {
|
|
|
|
return "Local Volumes"
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *diskUsageVolumesContext) TotalCount() string {
|
|
|
|
return fmt.Sprintf("%d", len(c.volumes))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *diskUsageVolumesContext) Active() string {
|
|
|
|
|
|
|
|
used := 0
|
|
|
|
for _, v := range c.volumes {
|
2016-10-11 14:49:26 -04:00
|
|
|
if v.UsageData.RefCount > 0 {
|
2016-08-23 19:37:37 -04:00
|
|
|
used++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return fmt.Sprintf("%d", used)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *diskUsageVolumesContext) Size() string {
|
|
|
|
var size int64
|
|
|
|
|
|
|
|
for _, v := range c.volumes {
|
2016-10-11 14:49:26 -04:00
|
|
|
if v.UsageData.Size != -1 {
|
|
|
|
size += v.UsageData.Size
|
2016-08-23 19:37:37 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return units.HumanSize(float64(size))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *diskUsageVolumesContext) Reclaimable() string {
|
|
|
|
var reclaimable int64
|
|
|
|
var totalSize int64
|
|
|
|
|
|
|
|
for _, v := range c.volumes {
|
2016-10-11 14:49:26 -04:00
|
|
|
if v.UsageData.Size != -1 {
|
|
|
|
if v.UsageData.RefCount == 0 {
|
|
|
|
reclaimable += v.UsageData.Size
|
2016-08-23 19:37:37 -04:00
|
|
|
}
|
2016-10-11 14:49:26 -04:00
|
|
|
totalSize += v.UsageData.Size
|
2016-08-23 19:37:37 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if totalSize > 0 {
|
|
|
|
return fmt.Sprintf("%s (%v%%)", units.HumanSize(float64(reclaimable)), (reclaimable*100)/totalSize)
|
|
|
|
}
|
|
|
|
|
2017-07-11 14:19:02 -04:00
|
|
|
return units.HumanSize(float64(reclaimable))
|
2016-08-23 19:37:37 -04:00
|
|
|
}
|
2017-05-15 17:14:31 -04:00
|
|
|
|
|
|
|
type diskUsageBuilderContext struct {
|
|
|
|
HeaderContext
|
|
|
|
builderSize int64
|
2018-05-18 14:51:35 -04:00
|
|
|
buildCache []*types.BuildCache
|
2017-05-15 17:14:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *diskUsageBuilderContext) MarshalJSON() ([]byte, error) {
|
|
|
|
return marshalJSON(c)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *diskUsageBuilderContext) Type() string {
|
|
|
|
return "Build Cache"
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *diskUsageBuilderContext) TotalCount() string {
|
2018-05-18 14:51:35 -04:00
|
|
|
return fmt.Sprintf("%d", len(c.buildCache))
|
2017-05-15 17:14:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *diskUsageBuilderContext) Active() string {
|
2018-05-18 14:51:35 -04:00
|
|
|
numActive := 0
|
|
|
|
for _, bc := range c.buildCache {
|
|
|
|
if bc.InUse {
|
|
|
|
numActive++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return fmt.Sprintf("%d", numActive)
|
2017-05-15 17:14:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *diskUsageBuilderContext) Size() string {
|
|
|
|
return units.HumanSize(float64(c.builderSize))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *diskUsageBuilderContext) Reclaimable() string {
|
2018-06-13 18:35:15 -04:00
|
|
|
var inUseBytes int64
|
2018-05-18 14:51:35 -04:00
|
|
|
for _, bc := range c.buildCache {
|
2018-08-29 21:56:17 -04:00
|
|
|
if bc.InUse && !bc.Shared {
|
2018-05-18 14:51:35 -04:00
|
|
|
inUseBytes += bc.Size
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return units.HumanSize(float64(c.builderSize - inUseBytes))
|
2017-05-15 17:14:31 -04:00
|
|
|
}
|