Merge branch 'fabianlee'
This commit is contained in:
commit
f63f9dbe37
4 changed files with 153 additions and 33 deletions
41
README.md
41
README.md
|
@ -12,8 +12,14 @@ different ways, to help you understand what's going on inside the system.
|
|||
* Set up an alias to run it from the (5.8 MB) docker image:
|
||||
|
||||
```
|
||||
# if docker client using local unix socket
|
||||
alias dockviz="docker run -it --rm -v /var/run/docker.sock:/var/run/docker.sock nate/dockviz"
|
||||
|
||||
# if docker client using tcp
|
||||
alias dockviz="docker run -it --rm -e DOCKER_HOST='tcp://127.0.0.1:2375' nate/dockviz"
|
||||
|
||||
```
|
||||
|
||||
2. Visualize images by running `dockviz images -t`, which has similar output to `docker images -t`.
|
||||
* Image can be visualized as [Graphviz](http://www.graphviz.org), or as a tree or short summary in the terminal. Only Graphviz output has been implemented for containers.
|
||||
* If you would like to visualize outside the container you will have to install [Graphviz](http://www.graphviz.org) first.
|
||||
|
@ -35,7 +41,11 @@ brew update && brew install graphviz
|
|||
Currently, containers are visualized with labelled lines for links. Containers that aren't running are greyed out.
|
||||
|
||||
```
|
||||
# show all containers
|
||||
$ dockviz containers -d | dot -Tpng -o containers.png
|
||||
|
||||
# only show running containers
|
||||
$ dockviz containers -d -r | dot -Tpng -o containers.png
|
||||
```
|
||||
|
||||
![](sample/containers.png "Container")
|
||||
|
@ -46,8 +56,6 @@ Image info is visualized with lines indicating parent images:
|
|||
|
||||
```
|
||||
$ dockviz images -d | dot -Tpng -o images.png
|
||||
OR
|
||||
$ dockviz images --dot | dot -Tpng -o images.png
|
||||
```
|
||||
|
||||
![](sample/images.png "Image")
|
||||
|
@ -197,6 +205,7 @@ image layers when they show up with "<missing>" image Ids.
|
|||
Dockviz supports connecting to the Docker daemon directly. It defaults to `unix:///var/run/docker.sock`, but respects the following as well:
|
||||
|
||||
* The `DOCKER_HOST`, `DOCKER_CERT_PATH`, and `DOCKER_TLS_VERIFY` environment variables, as set up by [boot2docker](http://boot2docker.io/) or [docker-machine](https://docs.docker.com/machine/).
|
||||
|
||||
* Command line arguments (e.g. `--tlscacert`), like those that Docker itself supports.
|
||||
|
||||
Dockviz also supports receiving Docker image or container json data on standard input.
|
||||
|
@ -219,6 +228,30 @@ See the [releases](https://github.com/justone/dockviz/releases) area for binarie
|
|||
# Build
|
||||
|
||||
```bash
|
||||
go get ./...
|
||||
go build
|
||||
# install graphviz in host environment for rendering (Debian example)
|
||||
sudo apt-get install git graphviz -y
|
||||
|
||||
# pull latest code
|
||||
cd $GOPATH
|
||||
git clone https://github.com/nate/dockviz.git
|
||||
|
||||
# force static compilation for go language
|
||||
export CGO_ENABLED=0
|
||||
|
||||
# build go program
|
||||
cd dockviz
|
||||
go get
|
||||
go build -a
|
||||
|
||||
# build docker image
|
||||
docker build --no-cache=true -t "nate/dockviz:1.0" -t "nate/dockviz:latest" .
|
||||
|
||||
# push to docker hub
|
||||
docker login --username=mygituser --password=xxxxxxx
|
||||
docker push nate/dockviz
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
2
cli.go
2
cli.go
|
@ -1,4 +1,4 @@
|
|||
package main // import "github.com/justone/dockviz"
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
|
|
@ -24,6 +24,7 @@ type Container struct {
|
|||
type ContainersCommand struct {
|
||||
Dot bool `short:"d" long:"dot" description:"Show container information as Graphviz dot."`
|
||||
NoTruncate bool `short:"n" long:"no-trunc" description:"Don't truncate the container IDs."`
|
||||
OnlyRunning bool `short:"r" long:"running" description:"Only show running containers, not Exited"`
|
||||
}
|
||||
|
||||
var containersCommand ContainersCommand
|
||||
|
@ -81,7 +82,7 @@ func (x *ContainersCommand) Execute(args []string) error {
|
|||
}
|
||||
|
||||
if containersCommand.Dot {
|
||||
fmt.Printf(jsonContainersToDot(containers))
|
||||
fmt.Printf(jsonContainersToDot(containers, containersCommand.OnlyRunning))
|
||||
} else {
|
||||
return fmt.Errorf("Please specify --dot")
|
||||
}
|
||||
|
@ -115,24 +116,53 @@ func parseContainersJSON(rawJSON []byte) (*[]Container, error) {
|
|||
return &containers, nil
|
||||
}
|
||||
|
||||
func jsonContainersToDot(containers *[]Container) string {
|
||||
func jsonContainersToDot(containers *[]Container,OnlyRunning bool) string {
|
||||
|
||||
var buffer bytes.Buffer
|
||||
buffer.WriteString("digraph docker {\n")
|
||||
|
||||
// build list of all primary container names
|
||||
// this is so we can throw away links to
|
||||
// non-primary container name
|
||||
var PrimaryContainerNames map[string]string
|
||||
PrimaryContainerNames = make(map[string]string)
|
||||
for _, container := range *containers {
|
||||
for _, name := range container.Names {
|
||||
if strings.Count(name, "/") == 1 {
|
||||
PrimaryContainerNames[name[1:]] = name[1:]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// stores ony first value of link to avoid duplicates
|
||||
var LinkMap map[string]string
|
||||
LinkMap = make(map[string]string)
|
||||
|
||||
for _, container := range *containers {
|
||||
if OnlyRunning && strings.HasPrefix(container.Status,"Exit") { continue }
|
||||
|
||||
var containerName string
|
||||
|
||||
//fmt.Printf("container status/Names %s/%s\n",container.Status,container.Names)
|
||||
for _, name := range container.Names {
|
||||
if strings.Count(name, "/") == 1 {
|
||||
containerName = name[1:]
|
||||
}
|
||||
}
|
||||
|
||||
for _, name := range container.Names {
|
||||
nameParts := strings.Split(name, "/")
|
||||
if len(nameParts) > 2 {
|
||||
//fmt.Printf("\t%s to %s\n",containerName,nameParts[1])
|
||||
// source and dest should be primary container names
|
||||
if IsPrimaryContainerName(containerName,PrimaryContainerNames) && IsPrimaryContainerName(nameParts[1],PrimaryContainerNames) {
|
||||
|
||||
// only create link if none exists already
|
||||
if _,ok := LinkMap[containerName + "-" + nameParts[1]]; !ok {
|
||||
LinkMap[containerName + "-" + nameParts[1]] = "exists"
|
||||
buffer.WriteString(fmt.Sprintf(" \"%s\" -> \"%s\" [label = \" %s\" ]\n", containerName, nameParts[1], nameParts[len(nameParts)-1] ))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -144,7 +174,7 @@ func jsonContainersToDot(containers *[]Container) string {
|
|||
containerBackground = "paleturquoise"
|
||||
}
|
||||
|
||||
buffer.WriteString(fmt.Sprintf(" \"%s\" [label=\"%s\\n%s\",shape=box,fillcolor=\"%s\",style=\"filled,rounded\"];\n", containerName, containerName, truncate(container.Id, 12), containerBackground))
|
||||
buffer.WriteString(fmt.Sprintf(" \"%s\" [label=\"%s\\n%s\\n%s\",shape=box,fillcolor=\"%s\",style=\"filled,rounded\"];\n", containerName, container.Image, containerName, truncate(container.Id, 12), containerBackground))
|
||||
}
|
||||
|
||||
buffer.WriteString("}\n")
|
||||
|
@ -152,6 +182,11 @@ func jsonContainersToDot(containers *[]Container) string {
|
|||
return buffer.String()
|
||||
}
|
||||
|
||||
func IsPrimaryContainerName(Name string,PrimaryContainerNames map[string]string) bool {
|
||||
_,ok := PrimaryContainerNames[Name]
|
||||
return ok
|
||||
}
|
||||
|
||||
func init() {
|
||||
parser.AddCommand("containers",
|
||||
"Visualize docker containers.",
|
||||
|
|
72
images.go
72
images.go
|
@ -32,6 +32,7 @@ type ImagesCommand struct {
|
|||
NoTruncate bool `short:"n" long:"no-trunc" description:"Don't truncate the image IDs (only works with tree mode)."`
|
||||
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."`
|
||||
ShowCreatedBy bool `long:"show-created-by" description:"Show the image 'CreatedBy' to help identify layers."`
|
||||
NoHuman bool `short:"c" long:"no-human" description:"Don't humanize the sizes."`
|
||||
}
|
||||
|
||||
|
@ -39,6 +40,7 @@ type DisplayOpts struct {
|
|||
NoTruncate bool
|
||||
Incremental bool
|
||||
NoHuman bool
|
||||
ShowCreatedBy bool
|
||||
}
|
||||
|
||||
var imagesCommand ImagesCommand
|
||||
|
@ -156,16 +158,17 @@ func (x *ImagesCommand) Execute(args []string) error {
|
|||
*images, imagesByParent = filterImages(images, &imagesByParent)
|
||||
}
|
||||
|
||||
if imagesCommand.Tree {
|
||||
dispOpts := DisplayOpts{
|
||||
imagesCommand.NoTruncate,
|
||||
imagesCommand.Incremental,
|
||||
imagesCommand.NoHuman,
|
||||
imagesCommand.ShowCreatedBy,
|
||||
}
|
||||
if imagesCommand.Tree {
|
||||
fmt.Print(jsonToTree(roots, imagesByParent, dispOpts))
|
||||
}
|
||||
if imagesCommand.Dot {
|
||||
fmt.Print(jsonToDot(roots, imagesByParent))
|
||||
fmt.Print(jsonToDot(roots, imagesByParent, dispOpts))
|
||||
}
|
||||
|
||||
} else if imagesCommand.Short {
|
||||
|
@ -282,11 +285,11 @@ func jsonToTree(images []Image, byParent map[string][]Image, dispOpts DisplayOpt
|
|||
return buffer.String()
|
||||
}
|
||||
|
||||
func jsonToDot(roots []Image, byParent map[string][]Image) string {
|
||||
func jsonToDot(roots []Image, byParent map[string][]Image, dispOpts DisplayOpts) string {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
buffer.WriteString("digraph docker {\n")
|
||||
imagesToDot(&buffer, roots, byParent)
|
||||
imagesToDot(&buffer, roots, byParent, dispOpts)
|
||||
buffer.WriteString(" base [style=invisible]\n}\n")
|
||||
|
||||
return buffer.String()
|
||||
|
@ -399,10 +402,12 @@ func PrintTreeNode(buffer *bytes.Buffer, image Image, dispOpts DisplayOpts, pref
|
|||
|
||||
buffer.WriteString(fmt.Sprintf("%s%s %s: %s", prefix, imageID, sizeLabel, sizeStr))
|
||||
if image.RepoTags[0] != "<none>:<none>" {
|
||||
buffer.WriteString(fmt.Sprintf(" Tags: %s\n", strings.Join(image.RepoTags, ", ")))
|
||||
} else {
|
||||
buffer.WriteString(fmt.Sprintf("\n"))
|
||||
buffer.WriteString(fmt.Sprintf(" Tags: %s", strings.Join(image.RepoTags, ", ")))
|
||||
}
|
||||
if dispOpts.ShowCreatedBy {
|
||||
buffer.WriteString(fmt.Sprintf(" (%s)", SanitizeCommand(image.CreatedBy, 100)))
|
||||
}
|
||||
buffer.WriteString(fmt.Sprintf("\n"))
|
||||
}
|
||||
|
||||
func humanSize(raw int64) string {
|
||||
|
@ -453,20 +458,45 @@ func parseImagesJSON(rawJSON []byte) (*[]Image, error) {
|
|||
return &images, nil
|
||||
}
|
||||
|
||||
func imagesToDot(buffer *bytes.Buffer, images []Image, byParent map[string][]Image) {
|
||||
func imagesToDot(buffer *bytes.Buffer, images []Image, byParent map[string][]Image, dispOpts DisplayOpts) {
|
||||
for _, image := range images {
|
||||
|
||||
if image.ParentId == "" {
|
||||
buffer.WriteString(fmt.Sprintf(" base -> \"%s\" [style=invis]\n", truncate(image.Id, 12)))
|
||||
} else {
|
||||
buffer.WriteString(fmt.Sprintf(" \"%s\" -> \"%s\"\n", truncate(image.ParentId, 12), truncate(image.Id, 12)))
|
||||
}
|
||||
|
||||
if image.RepoTags[0] != "<none>:<none>" {
|
||||
buffer.WriteString(fmt.Sprintf(" \"%s\" [label=\"%s\\n%s\",shape=box,fillcolor=\"paleturquoise\",style=\"filled,rounded\"];\n", truncate(image.Id, 12), truncate(stripPrefix(image.OrigId), 12), strings.Join(image.RepoTags, "\\n")))
|
||||
} else {
|
||||
buffer.WriteString(fmt.Sprintf(" \"%s\" [label=\"%s\"]\n", truncate(image.Id, 12), truncate(stripPrefix(image.OrigId), 12)))
|
||||
labelParts := []string{truncate(stripPrefix(image.OrigId), 12)}
|
||||
if dispOpts.ShowCreatedBy {
|
||||
labelParts = append(labelParts, SanitizeCommand(image.CreatedBy, 30))
|
||||
}
|
||||
|
||||
var size int64
|
||||
var sizeLabel string
|
||||
if dispOpts.Incremental {
|
||||
sizeLabel = "Size"
|
||||
size = image.Size
|
||||
} else {
|
||||
sizeLabel = "Virtual Size"
|
||||
size = image.VirtualSize
|
||||
}
|
||||
|
||||
var sizeStr string
|
||||
if dispOpts.NoHuman {
|
||||
sizeStr = strconv.FormatInt(size, 10)
|
||||
} else {
|
||||
sizeStr = humanSize(size)
|
||||
}
|
||||
labelParts = append(labelParts, fmt.Sprintf("%s: %s", sizeLabel, sizeStr))
|
||||
|
||||
buffer.WriteString(fmt.Sprintf(" \"%s\" [label=\"%s\"]\n", truncate(image.Id, 12), strings.Join(labelParts, "\n")))
|
||||
}
|
||||
if subimages, exists := byParent[image.Id]; exists {
|
||||
imagesToDot(buffer, subimages, byParent)
|
||||
imagesToDot(buffer, subimages, byParent, dispOpts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -502,6 +532,28 @@ func jsonToShort(images *[]Image) string {
|
|||
return buffer.String()
|
||||
}
|
||||
|
||||
func SanitizeCommand(CommandStr string, MaxLength int) string {
|
||||
|
||||
temp := CommandStr
|
||||
|
||||
// remove prefixes that don't add meaning
|
||||
if strings.HasPrefix(temp, "/bin/sh -c") {
|
||||
temp = strings.TrimSpace(temp[10:])
|
||||
}
|
||||
if strings.HasPrefix(temp, "#(nop)") {
|
||||
temp = strings.TrimSpace(temp[6:])
|
||||
}
|
||||
|
||||
// remove double and single quotes which make dot format invalid
|
||||
temp = strings.Replace(temp, "\"", " ", -1)
|
||||
temp = strings.Replace(temp, "'", " ", -1)
|
||||
|
||||
// remove double spaces inside
|
||||
temp = strings.Join(strings.Fields(temp), " ")
|
||||
|
||||
return truncate(temp, MaxLength)
|
||||
}
|
||||
|
||||
func init() {
|
||||
parser.AddCommand("images",
|
||||
"Visualize docker images.",
|
||||
|
|
Loading…
Reference in a new issue