Expose some more information on containers.

This commit is contained in:
Tom Wilkie
2015-06-19 12:54:39 +00:00
parent 49dae07cca
commit c793e86d5b
6 changed files with 92 additions and 48 deletions

View File

@@ -13,6 +13,7 @@ import (
"strconv"
"strings"
"sync"
"time"
docker "github.com/fsouza/go-dockerclient"
@@ -20,8 +21,12 @@ 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"
@@ -59,7 +64,7 @@ type ClientConn interface {
Close() error
}
// Container represents a docker container
// Container represents a Docker container
type Container interface {
ID() string
Image() string
@@ -163,7 +168,6 @@ func (c *container) StartGatheringStats() error {
return nil
}
// called whilst holding t.Lock()
func (c *container) StopGatheringStats() {
c.Lock()
defer c.Unlock()
@@ -178,15 +182,36 @@ func (c *container) StopGatheringStats() {
return
}
// called whilst holding t.RLock()
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, ", ")
}
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 {

View File

@@ -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

View File

@@ -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: ""})

View File

@@ -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", ""},
},
},
{

View File

@@ -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
}

View File

@@ -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,
},
},
},