From 478739ccf97f620f7b18ff3200298816fa2b8d08 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Fri, 19 Jun 2015 12:54:39 +0000 Subject: [PATCH] Expose some more information on containers. --- probe/docker/container.go | 34 ++++++++++++++++++++++++--- probe/docker/reporter.go | 5 ++-- render/detailed_node.go | 28 ++++++++++++++++++---- render/detailed_node_test.go | 6 ++--- render/mapping.go | 17 +++++++------- render/topologies_test.go | 45 ++++++++++++++++++------------------ 6 files changed, 91 insertions(+), 44 deletions(-) diff --git a/probe/docker/container.go b/probe/docker/container.go index bb8a260176..361aebb19e 100644 --- a/probe/docker/container.go +++ b/probe/docker/container.go @@ -13,6 +13,7 @@ import ( "strconv" "strings" "sync" + "time" docker "github.com/fsouza/go-dockerclient" @@ -22,6 +23,11 @@ import ( // These constants are keys used in node metadata // TODO: use these constants in report/{mapping.go, detailed_node.go} - pending some circular references const ( + ContainerName = "docker_container_name" + ContainerCommand = "docker_container_command" + ContainerPorts = "docker_container_ports" + ContainerCreated = "docker_container_created" + NetworkRxDropped = "network_rx_dropped" NetworkRxBytes = "network_rx_bytes" NetworkRxErrors = "network_rx_errors" @@ -178,15 +184,37 @@ func (c *container) StopGatheringStats() { return } +func (c *container) ports() string { + if c.container.NetworkSettings == nil { + return "" + } + + ports := []string{} + for port, bindings := range c.container.NetworkSettings.Ports { + if len(bindings) == 0 { + ports = append(ports, fmt.Sprintf("%s", port)) + continue + } + for _, b := range bindings { + ports = append(ports, fmt.Sprintf("%s:%s->%s", b.HostIP, b.HostPort, port)) + } + } + + return strings.Join(ports, ", ") +} + // called whilst holding t.RLock() func (c *container) GetNodeMetadata() report.NodeMetadata { c.RLock() defer c.RUnlock() result := report.NodeMetadata{ - ContainerID: c.ID(), - ContainerName: strings.TrimPrefix(c.container.Name, "/"), - ImageID: c.container.Image, + ContainerID: c.ID(), + ContainerName: strings.TrimPrefix(c.container.Name, "/"), + ContainerPorts: c.ports(), + ContainerCreated: c.container.Created.Format(time.RFC822), + ContainerCommand: c.container.Path + " " + strings.Join(c.container.Args, " "), + ImageID: c.container.Image, } if c.latestStats == nil { diff --git a/probe/docker/reporter.go b/probe/docker/reporter.go index ac5ea20726..9d9036bdd3 100644 --- a/probe/docker/reporter.go +++ b/probe/docker/reporter.go @@ -8,9 +8,8 @@ import ( // Keys for use in NodeMetadata const ( - ContainerName = "docker_container_name" - ImageID = "docker_image_id" - ImageName = "docker_image_name" + ImageID = "docker_image_id" + ImageName = "docker_image_name" ) // Reporter generate Reports containing Container and ContainerImage topologies diff --git a/render/detailed_node.go b/render/detailed_node.go index 46fb737bf8..fcb362b037 100644 --- a/render/detailed_node.go +++ b/render/detailed_node.go @@ -1,12 +1,18 @@ package render import ( + "fmt" "reflect" "strconv" + "github.com/weaveworks/scope/probe/docker" "github.com/weaveworks/scope/report" ) +const ( + mb = 1 << 20 +) + // DetailedNode is the data type that's yielded to the JavaScript layer when // we want deep information about an individual node. type DetailedNode struct { @@ -152,14 +158,26 @@ func processOriginTable(nmd report.NodeMetadata) (Table, bool) { func containerOriginTable(nmd report.NodeMetadata) (Table, bool) { rows := []Row{} for _, tuple := range []struct{ key, human string }{ - {"docker_container_id", "Container ID"}, - {"docker_container_name", "Container name"}, - {"docker_image_id", "Container image ID"}, + {docker.ContainerID, "ID"}, + {docker.ContainerName, "Name"}, + {docker.ImageID, "Image ID"}, + {docker.ContainerPorts, "Ports"}, + {docker.ContainerCreated, "Created"}, + {docker.ContainerCommand, "Command"}, } { if val, ok := nmd[tuple.key]; ok { rows = append(rows, Row{Key: tuple.human, ValueMajor: val, ValueMinor: ""}) } } + + if val, ok := nmd[docker.MemoryUsage]; ok { + memory, err := strconv.ParseFloat(val, 64) + if err == nil { + memoryStr := fmt.Sprintf("%0.2f", memory/float64(mb)) + rows = append(rows, Row{Key: "Memory Usage (MB):", ValueMajor: memoryStr, ValueMinor: ""}) + } + } + return Table{ Title: "Origin Container", Numeric: false, @@ -170,8 +188,8 @@ func containerOriginTable(nmd report.NodeMetadata) (Table, bool) { func containerImageOriginTable(nmd report.NodeMetadata) (Table, bool) { rows := []Row{} for _, tuple := range []struct{ key, human string }{ - {"docker_image_id", "Container image ID"}, - {"docker_image_name", "Container image name"}, + {docker.ImageID, "Image ID"}, + {docker.ImageName, "Image name"}, } { if val, ok := nmd[tuple.key]; ok { rows = append(rows, Row{Key: tuple.human, ValueMajor: val, ValueMinor: ""}) diff --git a/render/detailed_node_test.go b/render/detailed_node_test.go index 19f03296c0..eb2a3c6172 100644 --- a/render/detailed_node_test.go +++ b/render/detailed_node_test.go @@ -94,9 +94,9 @@ func TestMakeDetailedNode(t *testing.T) { Title: "Origin Container", Numeric: false, Rows: []render.Row{ - {"Container ID", "5e4d3c2b1a", ""}, - {"Container name", "server", ""}, - {"Container image ID", "imageid456", ""}, + {"ID", "5e4d3c2b1a", ""}, + {"Name", "server", ""}, + {"Image ID", "imageid456", ""}, }, }, { diff --git a/render/mapping.go b/render/mapping.go index dd38d17040..bc0a59dd3f 100644 --- a/render/mapping.go +++ b/render/mapping.go @@ -5,6 +5,7 @@ import ( "net" "strings" + "github.com/weaveworks/scope/probe/docker" "github.com/weaveworks/scope/report" ) @@ -80,10 +81,10 @@ func MapProcessIdentity(m report.NodeMetadata) (RenderableNode, bool) { // nodes, we can safely assume the presences of certain keys. func MapContainerIdentity(m report.NodeMetadata) (RenderableNode, bool) { var ( - id = m["docker_container_id"] - major = m["docker_container_name"] + id = m[docker.ContainerID] + major = m[docker.ContainerName] minor = report.ExtractHostID(m) - rank = m["docker_image_id"] + rank = m[docker.ImageID] ) return NewRenderableNode(id, major, minor, rank, m), true @@ -94,9 +95,9 @@ func MapContainerIdentity(m report.NodeMetadata) (RenderableNode, bool) { // topology nodes, we can safely assume the presences of certain keys. func MapContainerImageIdentity(m report.NodeMetadata) (RenderableNode, bool) { var ( - id = m["docker_image_id"] - major = m["docker_image_name"] - rank = m["docker_image_id"] + id = m[docker.ImageID] + major = m[docker.ImageName] + rank = m[docker.ImageID] ) return NewRenderableNode(id, major, "", rank, m), true @@ -181,7 +182,7 @@ func MapProcess2Container(n RenderableNode) (RenderableNode, bool) { // Otherwise, if the process is not in a container, group it // into an "Uncontained" node - id, ok := n.NodeMetadata["docker_container_id"] + id, ok := n.NodeMetadata[docker.ContainerID] if !ok || n.Pseudo { return newDerivedPseudoNode(UncontainedID, UncontainedMajor, n), true } @@ -231,7 +232,7 @@ func MapContainer2ContainerImage(n RenderableNode) (RenderableNode, bool) { // Otherwise, if the process is not in a container, group it // into an "Uncontained" node - id, ok := n.NodeMetadata["docker_image_id"] + id, ok := n.NodeMetadata[docker.ImageID] if !ok || n.Pseudo { return newDerivedPseudoNode(UncontainedID, UncontainedMajor, n), true } diff --git a/render/topologies_test.go b/render/topologies_test.go index 920b409d12..15a3038cbe 100644 --- a/render/topologies_test.go +++ b/render/topologies_test.go @@ -5,6 +5,7 @@ import ( "reflect" "testing" + "github.com/weaveworks/scope/probe/docker" "github.com/weaveworks/scope/render" "github.com/weaveworks/scope/report" "github.com/weaveworks/scope/test" @@ -160,16 +161,16 @@ var ( Adjacency: report.Adjacency{}, NodeMetadatas: report.NodeMetadatas{ clientProcessNodeID: report.NodeMetadata{ - "pid": clientPID, - "comm": "curl", - "docker_container_id": clientContainerID, - report.HostNodeID: clientHostNodeID, + "pid": clientPID, + "comm": "curl", + docker.ContainerID: clientContainerID, + report.HostNodeID: clientHostNodeID, }, serverProcessNodeID: report.NodeMetadata{ - "pid": serverPID, - "comm": "apache", - "docker_container_id": serverContainerID, - report.HostNodeID: serverHostNodeID, + "pid": serverPID, + "comm": "apache", + docker.ContainerID: serverContainerID, + report.HostNodeID: serverHostNodeID, }, nonContainerProcessNodeID: report.NodeMetadata{ "pid": nonContainerPID, @@ -182,30 +183,30 @@ var ( Container: report.Topology{ NodeMetadatas: report.NodeMetadatas{ clientContainerNodeID: report.NodeMetadata{ - "docker_container_id": clientContainerID, - "docker_container_name": "client", - "docker_image_id": clientContainerImageID, - report.HostNodeID: clientHostNodeID, + docker.ContainerID: clientContainerID, + docker.ContainerName: "client", + docker.ImageID: clientContainerImageID, + report.HostNodeID: clientHostNodeID, }, serverContainerNodeID: report.NodeMetadata{ - "docker_container_id": serverContainerID, - "docker_container_name": "server", - "docker_image_id": serverContainerImageID, - report.HostNodeID: serverHostNodeID, + docker.ContainerID: serverContainerID, + docker.ContainerName: "server", + docker.ImageID: serverContainerImageID, + report.HostNodeID: serverHostNodeID, }, }, }, ContainerImage: report.Topology{ NodeMetadatas: report.NodeMetadatas{ clientContainerImageNodeID: report.NodeMetadata{ - "docker_image_id": clientContainerImageID, - "docker_image_name": "client_image", - report.HostNodeID: clientHostNodeID, + docker.ImageID: clientContainerImageID, + docker.ImageName: "client_image", + report.HostNodeID: clientHostNodeID, }, serverContainerImageNodeID: report.NodeMetadata{ - "docker_image_id": serverContainerImageID, - "docker_image_name": "server_image", - report.HostNodeID: serverHostNodeID, + docker.ImageID: serverContainerImageID, + docker.ImageName: "server_image", + report.HostNodeID: serverHostNodeID, }, }, },