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