diff --git a/README.md b/README.md index d6e30e4..a8b8561 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,9 @@ $ dockviz images -t └─cb12405ee8fa Virtual Size: 98.5 MB └─316b678ddf48 Virtual Size: 169.4 MB Tags: ubuntu:13.04, ubuntu:raring ``` + +Only showing labelled images: + ``` $ dockviz images -t -l └─511136ea3c5a Virtual Size: 0.0 B @@ -104,6 +107,41 @@ $ dockviz images -t -l └─316b678ddf48 Virtual Size: 169.4 MB Tags: ubuntu:13.04, ubuntu:raring ``` +Showing incremental size rather than cumulative: + +``` +$ dockviz images -t -i +└─511136ea3c5a Virtual Size: 0.0 B + ├─f10ebce2c0e1 Virtual Size: 103.7 MB + │ └─82cdea7ab5b5 Virtual Size: 255.5 KB + │ └─5dbd9cb5a02f Virtual Size: 1.9 KB + │ └─74fe38d11401 Virtual Size: 105.7 MB Tags: ubuntu:12.04, ubuntu:precise + ├─ef519c9ee91a Virtual Size: 100.9 MB + │ └─07302703becc Virtual Size: 251.0 KB + │ └─cf8dc907452c Virtual Size: 1.9 KB + │ └─a7cf8ae4e998 Virtual Size: 70.1 MB Tags: ubuntu:12.10, ubuntu:quantal + │ ├─e18d8001204e Virtual Size: 29.4 KB + │ │ └─d0525208a46c Virtual Size: 71.0 B + │ │ └─59dac4bae93b Virtual Size: 71.2 MB + │ │ └─89541b3b35f2 Virtual Size: 269.3 MB + │ │ └─7dac4e98548e Virtual Size: 0.0 B + │ │ └─341d0cc3fac8 Virtual Size: 0.0 B + │ │ └─2f96171d2098 Virtual Size: 0.0 B + │ │ └─67b8b7262a67 Virtual Size: 1.9 MB + │ │ └─0fe9a2bc50fe Virtual Size: 656.0 B + │ │ └─8c32832f07ba Virtual Size: 383.0 B + │ │ └─cc4e1358bc80 Virtual Size: 0.0 B + │ │ └─5c0d04fba9df Virtual Size: 0.0 B Tags: nate/mongodb:latest + │ └─398d592f2009 Virtual Size: 70.9 MB + │ └─0cd8e7f50270 Virtual Size: 1.4 MB + │ └─594b6f8e6f92 Virtual Size: 0.0 B + │ └─f832a63e87a4 Virtual Size: 0.0 B Tags: redis:latest + └─02dae1c13f51 Virtual Size: 98.3 MB + └─e7206bfc66aa Virtual Size: 190.0 KB + └─cb12405ee8fa Virtual Size: 1.9 KB + └─316b678ddf48 Virtual Size: 70.8 MB Tags: ubuntu:13.04, ubuntu:raring +``` + # Running Dockviz supports connecting to the Docker daemon directly. It defaults to `unix:///var/run/docker.sock`, but respects the following as well: diff --git a/images.go b/images.go index 2f885dc..9b71eaf 100644 --- a/images.go +++ b/images.go @@ -25,6 +25,7 @@ type ImagesCommand struct { Tree bool `short:"t" long:"tree" description:"Show image information as tree. You can add a start image id or name -t/--tree [id/name]"` Short bool `short:"s" long:"short" description:"Show short summary of images (repo name and list of tags)."` NoTruncate bool `short:"n" long:"no-trunc" description:"Don't truncate the image IDs."` + Incremental bool `short:"i" long:"incremental" description:"Display image size as incremental rather than cumulative."` OnlyLabelled bool `short:"l" long:"only-labelled" description:"Print only labelled images/containers."` } @@ -110,7 +111,7 @@ func (x *ImagesCommand) Execute(args []string) error { } if imagesCommand.Tree { - fmt.Print(jsonToTree(roots, imagesByParent, imagesCommand.NoTruncate)) + fmt.Print(jsonToTree(roots, imagesByParent, imagesCommand.NoTruncate, imagesCommand.Incremental)) } if imagesCommand.Dot { fmt.Print(jsonToDot(roots, imagesByParent)) @@ -163,10 +164,10 @@ IMAGES: return startImage, nil } -func jsonToTree(images []Image, byParent map[string][]Image, noTrunc bool) string { +func jsonToTree(images []Image, byParent map[string][]Image, noTrunc bool, incremental bool) string { var buffer bytes.Buffer - jsonToText(&buffer, images, byParent, noTrunc, "") + jsonToText(&buffer, images, byParent, noTrunc, incremental, "") return buffer.String() } @@ -235,33 +236,33 @@ func filterImages(images *[]Image, byParent *map[string][]Image) (filteredImages return filteredImages, filteredChildren } -func jsonToText(buffer *bytes.Buffer, images []Image, byParent map[string][]Image, noTrunc bool, prefix string) { +func jsonToText(buffer *bytes.Buffer, images []Image, byParent map[string][]Image, noTrunc bool, incremental bool, prefix string) { var length = len(images) if length > 1 { for index, image := range images { var nextPrefix string = "" if index+1 == length { - PrintTreeNode(buffer, image, noTrunc, prefix+"└─") + PrintTreeNode(buffer, image, noTrunc, incremental, prefix+"└─") nextPrefix = " " } else { - PrintTreeNode(buffer, image, noTrunc, prefix+"├─") + PrintTreeNode(buffer, image, noTrunc, incremental, prefix+"├─") nextPrefix = "│ " } if subimages, exists := byParent[image.Id]; exists { - jsonToText(buffer, subimages, byParent, noTrunc, prefix+nextPrefix) + jsonToText(buffer, subimages, byParent, noTrunc, incremental, prefix+nextPrefix) } } } else { for _, image := range images { - PrintTreeNode(buffer, image, noTrunc, prefix+"└─") + PrintTreeNode(buffer, image, noTrunc, incremental, prefix+"└─") if subimages, exists := byParent[image.Id]; exists { - jsonToText(buffer, subimages, byParent, noTrunc, prefix+" ") + jsonToText(buffer, subimages, byParent, noTrunc, incremental, prefix+" ") } } } } -func PrintTreeNode(buffer *bytes.Buffer, image Image, noTrunc bool, prefix string) { +func PrintTreeNode(buffer *bytes.Buffer, image Image, noTrunc bool, incremental bool, prefix string) { var imageID string if noTrunc { imageID = image.Id @@ -269,7 +270,14 @@ func PrintTreeNode(buffer *bytes.Buffer, image Image, noTrunc bool, prefix strin imageID = truncate(image.Id) } - buffer.WriteString(fmt.Sprintf("%s%s Virtual Size: %s", prefix, imageID, humanSize(image.VirtualSize))) + var size int64 + if incremental { + size = image.Size + } else { + size = image.VirtualSize + } + + buffer.WriteString(fmt.Sprintf("%s%s Virtual Size: %s", prefix, imageID, humanSize(size))) if image.RepoTags[0] != ":" { buffer.WriteString(fmt.Sprintf(" Tags: %s\n", strings.Join(image.RepoTags, ", "))) } else { diff --git a/images_test.go b/images_test.go index a38afb7..ed2fde8 100644 --- a/images_test.go +++ b/images_test.go @@ -19,6 +19,7 @@ type TreeTest struct { json string startImage string noTrunc bool + incr bool regexps []string } @@ -78,23 +79,36 @@ func Test_Dot(t *testing.T) { } func Test_Tree(t *testing.T) { - treeJSON := `[ { "VirtualSize": 662553464, "Size": 0, "RepoTags": [ "foo:latest" ], "ParentId": "735f5db5626147582d2ae3f2c87be8e5e697c088574c5faaf8d4d1bccab99470", "Id": "c87be8e5e697c735f5db5626147582d2ae3f2088574c5faaf8d4d1bccab99470", "Created": 1386142123 }, { "VirtualSize": 682553464, "Size": 0, "RepoTags": [ ":" ], "ParentId": "4c1208b690c68af3476b437e7bc2bcc460f062bda2094d2d8f21a7e70368d358", "Id": "626147582d2ae3735f5db5f2c87be8e5e697c088574c5faaf8d4d1bccab99470", "Created": 1386142123 }, { "VirtualSize": 712553464, "Size": 0, "RepoTags": [ "base:latest" ], "ParentId": "626147582d2ae3735f5db5f2c87be8e5e697c088574c5faaf8d4d1bccab99470", "Id": "574c5faaf8d4d1bccab994626147582d2ae3735f5db5f2c87be8e5e697c08870", "Created": 1386142123 }, { "VirtualSize": 752553464, "Size": 0, "RepoTags": [ ":" ], "ParentId": "574c5faaf8d4d1bccab994626147582d2ae3735f5db5f2c87be8e5e697c08870", "Id": "aaf8d4d1bccab994574c5f626147582d2ae3735f5db5f2c87be8e5e697c08870", "Created": 1386142123 }, { "VirtualSize": 662553464, "Size": 0, "RepoTags": [ ":" ], "ParentId": "4c1208b690c68af3476b437e7bc2bcc460f062bda2094d2d8f21a7e70368d358", "Id": "735f5db5626147582d2ae3f2c87be8e5e697c088574c5faaf8d4d1bccab99470", "Created": 1386142123 }, { "VirtualSize": 662553464, "Size": 662553464, "RepoTags": [ ":" ], "ParentId": "", "Id": "4c1208b690c68af3476b437e7bc2bcc460f062bda2094d2d8f21a7e70368d358", "Created": 1386114144 } ]` + treeJSON := `[{"VirtualSize":674553464,"Size":2000000,"RepoTags":["foo:latest"],"ParentId":"735f5db5626147582d2ae3f2c87be8e5e697c088574c5faaf8d4d1bccab99470","Id":"c87be8e5e697c735f5db5626147582d2ae3f2088574c5faaf8d4d1bccab99470","Created":1386142123},{"VirtualSize":682553464,"Size":20000000,"RepoTags":[":"],"ParentId":"4c1208b690c68af3476b437e7bc2bcc460f062bda2094d2d8f21a7e70368d358","Id":"626147582d2ae3735f5db5f2c87be8e5e697c088574c5faaf8d4d1bccab99470","Created":1386142123},{"VirtualSize":712553464,"Size":30000000,"RepoTags":["base:latest"],"ParentId":"626147582d2ae3735f5db5f2c87be8e5e697c088574c5faaf8d4d1bccab99470","Id":"574c5faaf8d4d1bccab994626147582d2ae3735f5db5f2c87be8e5e697c08870","Created":1386142123},{"VirtualSize":752553464,"Size":40000000,"RepoTags":[":"],"ParentId":"574c5faaf8d4d1bccab994626147582d2ae3735f5db5f2c87be8e5e697c08870","Id":"aaf8d4d1bccab994574c5f626147582d2ae3735f5db5f2c87be8e5e697c08870","Created":1386142123},{"VirtualSize":672553464,"Size":10000000,"RepoTags":[":"],"ParentId":"4c1208b690c68af3476b437e7bc2bcc460f062bda2094d2d8f21a7e70368d358","Id":"735f5db5626147582d2ae3f2c87be8e5e697c088574c5faaf8d4d1bccab99470","Created":1386142123},{"VirtualSize":662553464,"Size":662553464,"RepoTags":[":"],"ParentId":"","Id":"4c1208b690c68af3476b437e7bc2bcc460f062bda2094d2d8f21a7e70368d358","Created":1386114144}]` treeTests := []TreeTest{ TreeTest{ json: treeJSON, startImage: "", noTrunc: false, + incr: false, regexps: []string{ - `(?m)└─4c1208b690c6`, - `(?m) └─735f5db56261`, - `(?m) └─c87be8e5e697`, + `(?m)└─4c1208b690c6 Virtual Size: 662.6 MB`, + `(?m) └─735f5db56261 Virtual Size: 672.6 MB`, + `(?m) └─c87be8e5e697 Virtual Size: 674.6 MB Tags: foo:latest`, + }, + }, + TreeTest{ + json: treeJSON, + startImage: "", + noTrunc: false, + incr: true, + regexps: []string{ + `(?m)└─4c1208b690c6 Virtual Size: 662.6 MB`, + `(?m) └─735f5db56261 Virtual Size: 10.0 MB`, + `(?m) └─c87be8e5e697 Virtual Size: 2.0 MB Tags: foo:latest`, }, }, TreeTest{ json: treeJSON, startImage: "626147582d2a", noTrunc: false, + incr: false, regexps: []string{ `(?m)└─626147582d2a`, `(?m) └─574c5faaf8d4`, @@ -105,6 +119,7 @@ func Test_Tree(t *testing.T) { json: treeJSON, startImage: "base:latest", noTrunc: true, + incr: false, regexps: []string{ `(?m)└─574c5faaf8d4d1bccab994626147582d2ae3735f5db5f2c87be8e5e697c08870`, `(?m) └─aaf8d4d1bccab994574c5f626147582d2ae3735f5db5f2c87be8e5e697c08870`, @@ -123,7 +138,7 @@ func Test_Tree(t *testing.T) { } else { roots = collectRoots(im) } - result := jsonToTree(roots, byParent, treeTest.noTrunc) + result := jsonToTree(roots, byParent, treeTest.noTrunc, treeTest.incr) for _, regexp := range compileRegexps(t, treeTest.regexps) { if !regexp.MatchString(result) {