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 }