Merge pull request #1327 from weaveworks/1213-group-summariser

Add explicit group node summariser instead of doing it in the other summaries
This commit is contained in:
Paul Bellamy
2016-04-20 16:11:56 +01:00
12 changed files with 215 additions and 122 deletions

View File

@@ -68,6 +68,14 @@ func TestAppClientPublish(t *testing.T) {
// marshalling->unmarshaling is not idempotent due to `json:"omitempty"`
// tags, transforming empty slices into nils. So, we make DeepEqual
// happy by setting empty `json:"omitempty"` entries to nil
rpt.Endpoint = report.MakeTopology()
rpt.Process = report.MakeTopology()
rpt.Container = report.MakeTopology()
rpt.ContainerImage = report.MakeTopology()
rpt.Pod = report.MakeTopology()
rpt.Service = report.MakeTopology()
rpt.Host = report.MakeTopology()
rpt.Overlay = report.MakeTopology()
rpt.Endpoint.Controls = nil
rpt.Process.Controls = nil
rpt.Container.Controls = nil

View File

@@ -80,14 +80,6 @@ func TestProbe(t *testing.T) {
want := report.MakeReport()
node := report.MakeNodeWith("a", map[string]string{"b": "c"})
node.Metrics = nil // omitempty
want.Endpoint.AddNode(node)
want.Probes[probeID] = report.Probe{
ID: probeID,
LastSeen: now,
}
pub := mockPublisher{make(chan report.Report)}
// omitempty
want.Endpoint.Controls = nil
want.Process.Controls = nil
@@ -98,6 +90,14 @@ func TestProbe(t *testing.T) {
want.Host.Controls = nil
want.Overlay.Controls = nil
want.Endpoint.AddNode(node)
want.Probes[probeID] = report.Probe{
ID: probeID,
LastSeen: now,
}
pub := mockPublisher{make(chan report.Report)}
p := New(probeID, 10*time.Millisecond, 100*time.Millisecond, pub)
p.AddReporter(mockReporter{want})
p.Start()

View File

@@ -15,12 +15,6 @@ import (
// Shapes that are allowed
const (
Circle = "circle"
Square = "square"
Heptagon = "heptagon"
Hexagon = "hexagon"
Cloud = "cloud"
ImageNameNone = "<none>"
// Keys we use to render container names
@@ -89,6 +83,9 @@ func MakeNodeSummary(r report.Report, n report.Node) (NodeSummary, bool) {
if renderer, ok := renderers[n.Topology]; ok {
return renderer(baseNodeSummary(r, n), n)
}
if strings.HasPrefix(n.Topology, "group:") {
return groupNodeSummary(baseNodeSummary(r, n), r, n)
}
return NodeSummary{}, false
}
@@ -127,9 +124,10 @@ func (n NodeSummary) Copy() NodeSummary {
}
func baseNodeSummary(r report.Report, n report.Node) NodeSummary {
t, _ := r.Topology(n.Topology)
return NodeSummary{
ID: n.ID,
Shape: Circle,
Shape: t.GetShape(),
Linkable: true,
Metadata: NodeMetadata(r, n),
Metrics: NodeMetrics(r, n),
@@ -142,30 +140,30 @@ func pseudoNodeSummary(base NodeSummary, n report.Node) (NodeSummary, bool) {
base.Pseudo = true
base.Rank = n.ID
if template, ok := map[string]struct{ Label, LabelMinor, Shape string }{
render.TheInternetID: {render.InboundMajor, "", Cloud},
render.IncomingInternetID: {render.InboundMajor, render.InboundMinor, Cloud},
render.OutgoingInternetID: {render.OutboundMajor, render.OutboundMinor, Cloud},
if template, ok := map[string]struct{ Label, LabelMinor string }{
render.TheInternetID: {render.InboundMajor, ""},
render.IncomingInternetID: {render.InboundMajor, render.InboundMinor},
render.OutgoingInternetID: {render.OutboundMajor, render.OutboundMinor},
}[n.ID]; ok {
base.Label = template.Label
base.LabelMinor = template.LabelMinor
base.Shape = template.Shape
base.Shape = report.Cloud
return base, true
}
// try rendering it as an uncontained node
if strings.HasPrefix(n.ID, render.MakePseudoNodeID(render.UncontainedID)) {
base.Label = render.UncontainedMajor
base.Shape = Square
base.Stack = true
base.LabelMinor = report.ExtractHostID(n)
base.Shape = report.Square
base.Stack = true
return base, true
}
// try rendering it as an unmanaged node
if strings.HasPrefix(n.ID, render.MakePseudoNodeID(render.UnmanagedID)) {
base.Label = render.UnmanagedMajor
base.Shape = Square
base.Shape = report.Square
base.Stack = true
base.LabelMinor = report.ExtractHostID(n)
return base, true
@@ -174,6 +172,7 @@ func pseudoNodeSummary(base NodeSummary, n report.Node) (NodeSummary, bool) {
// try rendering it as an endpoint
if addr, ok := n.Latest.Lookup(endpoint.Addr); ok {
base.Label = addr
base.Shape = report.Circle
return base, true
}
@@ -183,25 +182,15 @@ func pseudoNodeSummary(base NodeSummary, n report.Node) (NodeSummary, bool) {
func processNodeSummary(base NodeSummary, n report.Node) (NodeSummary, bool) {
base.Label, _ = n.Latest.Lookup(process.Name)
base.Rank, _ = n.Latest.Lookup(process.Name)
base.Shape = Square
if p, ok := n.Counters.Lookup(report.Process); ok {
base.Stack = true
if p == 1 {
base.LabelMinor = fmt.Sprintf("%d process", p)
} else {
base.LabelMinor = fmt.Sprintf("%d processes", p)
}
pid, ok := n.Latest.Lookup(process.PID)
if !ok {
return NodeSummary{}, false
}
if containerName, ok := n.Latest.Lookup(docker.ContainerName); ok {
base.LabelMinor = fmt.Sprintf("%s (%s:%s)", report.ExtractHostID(n), containerName, pid)
} else {
pid, ok := n.Latest.Lookup(process.PID)
if !ok {
return NodeSummary{}, false
}
if containerName, ok := n.Latest.Lookup(docker.ContainerName); ok {
base.LabelMinor = fmt.Sprintf("%s (%s:%s)", report.ExtractHostID(n), containerName, pid)
} else {
base.LabelMinor = fmt.Sprintf("%s (%s)", report.ExtractHostID(n), pid)
}
base.LabelMinor = fmt.Sprintf("%s (%s)", report.ExtractHostID(n), pid)
}
_, isConnected := n.Latest.Lookup(render.IsConnected)
@@ -211,23 +200,12 @@ func processNodeSummary(base NodeSummary, n report.Node) (NodeSummary, bool) {
func containerNodeSummary(base NodeSummary, n report.Node) (NodeSummary, bool) {
base.Label = getRenderableContainerName(n)
if c, ok := n.Counters.Lookup(report.Container); ok {
base.Stack = true
if c == 1 {
base.LabelMinor = fmt.Sprintf("%d container", c)
} else {
base.LabelMinor = fmt.Sprintf("%d containers", c)
}
} else {
base.LabelMinor = report.ExtractHostID(n)
}
base.LabelMinor = report.ExtractHostID(n)
if imageName, ok := n.Latest.Lookup(docker.ImageName); ok {
base.Rank = render.ImageNameWithoutVersion(imageName)
}
base.Shape = Hexagon
return base, true
}
@@ -240,7 +218,6 @@ func containerImageNodeSummary(base NodeSummary, n report.Node) (NodeSummary, bo
imageNameWithoutVersion := render.ImageNameWithoutVersion(imageName)
base.Label = imageNameWithoutVersion
base.Rank = imageNameWithoutVersion
base.Shape = Hexagon
base.Stack = true
if base.Label == ImageNameNone {
@@ -250,13 +227,7 @@ func containerImageNodeSummary(base NodeSummary, n report.Node) (NodeSummary, bo
}
}
if i, ok := n.Counters.Lookup(report.ContainerImage); ok {
if i == 1 {
base.LabelMinor = fmt.Sprintf("%d image", i)
} else {
base.LabelMinor = fmt.Sprintf("%d images", i)
}
} else if c, ok := n.Counters.Lookup(report.Container); ok {
if c, ok := n.Counters.Lookup(report.Container); ok {
if c == 1 {
base.LabelMinor = fmt.Sprintf("%d container", c)
} else {
@@ -269,16 +240,8 @@ func containerImageNodeSummary(base NodeSummary, n report.Node) (NodeSummary, bo
func podNodeSummary(base NodeSummary, n report.Node) (NodeSummary, bool) {
base.Label, _ = n.Latest.Lookup(kubernetes.PodName)
base.Rank, _ = n.Latest.Lookup(kubernetes.PodID)
base.Shape = Heptagon
if p, ok := n.Counters.Lookup(report.Pod); ok {
base.Stack = true
if p == 1 {
base.LabelMinor = fmt.Sprintf("%d pod", p)
} else {
base.LabelMinor = fmt.Sprintf("%d pods", p)
}
} else if c, ok := n.Counters.Lookup(report.Container); ok {
if c, ok := n.Counters.Lookup(report.Container); ok {
if c == 1 {
base.LabelMinor = fmt.Sprintf("%d container", c)
} else {
@@ -292,7 +255,6 @@ func podNodeSummary(base NodeSummary, n report.Node) (NodeSummary, bool) {
func serviceNodeSummary(base NodeSummary, n report.Node) (NodeSummary, bool) {
base.Label, _ = n.Latest.Lookup(kubernetes.ServiceName)
base.Rank, _ = n.Latest.Lookup(kubernetes.ServiceID)
base.Shape = Heptagon
base.Stack = true
// Services are always just a group of pods, so there's no counting multiple
@@ -320,16 +282,36 @@ func hostNodeSummary(base NodeSummary, n report.Node) (NodeSummary, bool) {
base.Label = hostname
}
if h, ok := n.Counters.Lookup(report.Host); ok {
base.Stack = true
if h == 1 {
base.LabelMinor = fmt.Sprintf("%d host", h)
} else {
base.LabelMinor = fmt.Sprintf("%d hosts", h)
return base, true
}
// groupNodeSummary renders the summary for a group node. n.Topology is
// expected to be of the form: group:container:hostname
func groupNodeSummary(base NodeSummary, r report.Report, n report.Node) (NodeSummary, bool) {
parts := strings.Split(n.Topology, ":")
if len(parts) != 3 {
return NodeSummary{}, false
}
label, ok := n.Latest.Lookup(parts[2])
if !ok {
return NodeSummary{}, false
}
base.Label, base.Rank = label, label
t, ok := r.Topology(parts[1])
if ok && t.Label != "" {
if count, ok := n.Counters.Lookup(parts[1]); ok {
if count == 1 {
base.LabelMinor = fmt.Sprintf("%d %s", count, t.Label)
} else {
base.LabelMinor = fmt.Sprintf("%d %s", count, t.LabelPlural)
}
}
}
base.Shape = Circle
base.Shape = t.GetShape()
base.Stack = true
return base, true
}

View File

@@ -168,6 +168,20 @@ func TestMakeNodeSummary(t *testing.T) {
Adjacency: report.MakeIDList(fixture.ServerHostNodeID),
},
},
{
name: "group node rendering",
input: expected.RenderedProcessNames[fixture.ServerName],
ok: true,
want: detailed.NodeSummary{
ID: "apache",
Label: "apache",
LabelMinor: "1 process",
Rank: "apache",
Shape: "square",
Stack: true,
Linkable: true,
},
},
}
for _, testcase := range testcases {
have, ok := detailed.MakeNodeSummary(fixture.Report, testcase.input)

View File

@@ -27,14 +27,15 @@ var (
return n
}
}
pseudo = node(render.Pseudo)
endpoint = node(report.Endpoint)
processNode = node(report.Process)
container = node(report.Container)
containerImage = node(report.ContainerImage)
pod = node(report.Pod)
service = node(report.Service)
hostNode = node(report.Host)
pseudo = node(render.Pseudo)
endpoint = node(report.Endpoint)
processNode = node(report.Process)
processNameNode = node(render.MakeGroupNodeTopology(report.Process, process.Name))
container = node(report.Container)
containerImage = node(report.ContainerImage)
pod = node(report.Pod)
service = node(report.Service)
hostNode = node(report.Host)
UnknownPseudoNode1ID = render.MakePseudoNodeID(fixture.UnknownClient1IP)
UnknownPseudoNode2ID = render.MakePseudoNodeID(fixture.UnknownClient3IP)
@@ -111,7 +112,9 @@ var (
}
RenderedProcessNames = report.Nodes{
fixture.Client1Name: processNode(fixture.Client1Name, fixture.ServerName).
fixture.Client1Name: processNameNode(fixture.Client1Name, fixture.ServerName).
WithLatests(map[string]string{process.Name: fixture.Client1Name}).
WithCounters(map[string]int{report.Process: 2}).
WithChildren(report.MakeNodeSet(
RenderedEndpoints[fixture.Client54001NodeID],
RenderedEndpoints[fixture.Client54002NodeID],
@@ -119,13 +122,15 @@ var (
RenderedProcesses[fixture.ClientProcess2NodeID],
)),
fixture.ServerName: processNode(fixture.ServerName).
fixture.ServerName: processNameNode(fixture.ServerName).
WithLatests(map[string]string{process.Name: fixture.ServerName}).
WithCounters(map[string]int{report.Process: 1}).
WithChildren(report.MakeNodeSet(
RenderedEndpoints[fixture.Server80NodeID],
RenderedProcesses[fixture.ServerProcessNodeID],
)),
fixture.NonContainerName: processNode(fixture.NonContainerName, render.OutgoingInternetID).
fixture.NonContainerName: processNameNode(fixture.NonContainerName, render.OutgoingInternetID).
WithChildren(report.MakeNodeSet(
RenderedEndpoints[fixture.NonContainerNodeID],
RenderedProcesses[fixture.NonContainerProcessNodeID],

View File

@@ -8,3 +8,8 @@ import (
func MakePseudoNodeID(parts ...string) string {
return strings.Join(append([]string{"pseudo"}, parts...), ":")
}
// MakeGroupNodeTopology joins the parts of a group topology into the topology of a group node
func MakeGroupNodeTopology(originalTopology, key string) string {
return strings.Join([]string{"group", originalTopology, key}, ":")
}

View File

@@ -265,7 +265,7 @@ func MapProcess2Name(n report.Node, _ report.Networks) report.Nodes {
return report.Nodes{}
}
node := NewDerivedNode(name, n).WithTopology(report.Process)
node := NewDerivedNode(name, n).WithTopology(MakeGroupNodeTopology(n.Topology, process.Name))
node.Latest = node.Latest.Set(process.Name, timestamp, name)
node.Counters = node.Counters.Add(n.Topology, 1)
return report.Nodes{name: node}
@@ -461,7 +461,7 @@ func MapContainer2Hostname(n report.Node, _ report.Networks) report.Nodes {
return report.Nodes{}
}
node := NewDerivedNode(id, n)
node := NewDerivedNode(id, n).WithTopology(MakeGroupNodeTopology(n.Topology, docker.ContainerHostname))
node.Latest = node.Latest.
Set(docker.ContainerHostname, timestamp, id).
Delete(docker.ContainerName) // TODO(paulbellamy): total hack to render these by hostname instead.

View File

@@ -30,13 +30,7 @@ type MetadataTemplate struct {
// Copy returns a value-copy of the template
func (t MetadataTemplate) Copy() MetadataTemplate {
return MetadataTemplate{
ID: t.ID,
Label: t.Label,
Truncate: t.Truncate,
Priority: t.Priority,
From: t.From,
}
return t
}
// MetadataRows returns the rows for a node

View File

@@ -20,6 +20,13 @@ const (
Host = "host"
Overlay = "overlay"
// Shapes used for different nodes
Circle = "circle"
Square = "square"
Heptagon = "heptagon"
Hexagon = "hexagon"
Cloud = "cloud"
// Used when counting the number of containers
ContainersKey = "containers"
)
@@ -96,19 +103,39 @@ type Report struct {
// MakeReport makes a clean report, ready to Merge() other reports into.
func MakeReport() Report {
return Report{
Endpoint: MakeTopology(),
Process: MakeTopology(),
Container: MakeTopology(),
ContainerImage: MakeTopology(),
Host: MakeTopology(),
Pod: MakeTopology(),
Service: MakeTopology(),
Overlay: MakeTopology(),
Sampling: Sampling{},
Window: 0,
Plugins: xfer.MakePluginSpecs(),
ID: fmt.Sprintf("%d", rand.Int63()),
Probes: Probes{},
Endpoint: MakeTopology(),
Process: MakeTopology().
WithShape(Square).
WithLabel("process", "processes"),
Container: MakeTopology().
WithShape(Hexagon).
WithLabel("container", "containers"),
ContainerImage: MakeTopology().
WithShape(Hexagon).
WithLabel("image", "images"),
Host: MakeTopology().
WithShape(Circle).
WithLabel("host", "hosts"),
Pod: MakeTopology().
WithShape(Heptagon).
WithLabel("pod", "pods"),
Service: MakeTopology().
WithShape(Heptagon).
WithLabel("service", "services"),
Overlay: MakeTopology(),
Sampling: Sampling{},
Window: 0,
Plugins: xfer.MakePluginSpecs(),
ID: fmt.Sprintf("%d", rand.Int63()),
Probes: Probes{},
}
}

View File

@@ -60,13 +60,9 @@ type TableTemplate struct {
Prefix string `json:"prefix"`
}
// Copy returns a copy of the TableTemplate
// Copy returns a value-copy of the TableTemplate
func (t TableTemplate) Copy() TableTemplate {
return TableTemplate{
ID: t.ID,
Label: t.Label,
Prefix: t.Prefix,
}
return t
}
// Merge other into t, returning a fresh copy. Does fieldwise max -

View File

@@ -10,6 +10,9 @@ import (
// EdgeMetadatas and Nodes respectively. Edges are directional, and embedded
// in the Node struct.
type Topology struct {
Shape string `json:"shape,omitempty"`
Label string `json:"label,omitempty"`
LabelPlural string `json:"label_plural,omitempty"`
Nodes `json:"nodes"`
Controls `json:"controls,omitempty"`
MetadataTemplates `json:"metadata_templates,omitempty"`
@@ -29,6 +32,9 @@ func MakeTopology() Topology {
// returning a new topology.
func (t Topology) WithMetadataTemplates(other MetadataTemplates) Topology {
return Topology{
Shape: t.Shape,
Label: t.Label,
LabelPlural: t.LabelPlural,
Nodes: t.Nodes.Copy(),
Controls: t.Controls.Copy(),
MetadataTemplates: t.MetadataTemplates.Merge(other),
@@ -41,6 +47,9 @@ func (t Topology) WithMetadataTemplates(other MetadataTemplates) Topology {
// returning a new topology.
func (t Topology) WithMetricTemplates(other MetricTemplates) Topology {
return Topology{
Shape: t.Shape,
Label: t.Label,
LabelPlural: t.LabelPlural,
Nodes: t.Nodes.Copy(),
Controls: t.Controls.Copy(),
MetadataTemplates: t.MetadataTemplates.Copy(),
@@ -53,6 +62,9 @@ func (t Topology) WithMetricTemplates(other MetricTemplates) Topology {
// returning a new topology.
func (t Topology) WithTableTemplates(other TableTemplates) Topology {
return Topology{
Shape: t.Shape,
Label: t.Label,
LabelPlural: t.LabelPlural,
Nodes: t.Nodes.Copy(),
Controls: t.Controls.Copy(),
MetadataTemplates: t.MetadataTemplates.Copy(),
@@ -61,6 +73,34 @@ func (t Topology) WithTableTemplates(other TableTemplates) Topology {
}
}
// WithShape sets the shape of nodes from this topology, returning a new topology.
func (t Topology) WithShape(shape string) Topology {
return Topology{
Shape: shape,
Label: t.Label,
LabelPlural: t.LabelPlural,
Nodes: t.Nodes.Copy(),
Controls: t.Controls.Copy(),
MetadataTemplates: t.MetadataTemplates.Copy(),
MetricTemplates: t.MetricTemplates.Copy(),
TableTemplates: t.TableTemplates.Copy(),
}
}
// WithLabel sets the label terminology of this topology, returning a new topology.
func (t Topology) WithLabel(label, labelPlural string) Topology {
return Topology{
Shape: t.Shape,
Label: label,
LabelPlural: labelPlural,
Nodes: t.Nodes.Copy(),
Controls: t.Controls.Copy(),
MetadataTemplates: t.MetadataTemplates.Copy(),
MetricTemplates: t.MetricTemplates.Copy(),
TableTemplates: t.TableTemplates.Copy(),
}
}
// AddNode adds node to the topology under key nodeID; if a
// node already exists for this key, nmd is merged with that node.
// The same topology is returned to enable chaining.
@@ -74,9 +114,20 @@ func (t Topology) AddNode(node Node) Topology {
return t
}
// GetShape returns the current topology shape, or the default if there isn't one.
func (t Topology) GetShape() string {
if t.Shape == "" {
return Circle
}
return t.Shape
}
// Copy returns a value copy of the Topology.
func (t Topology) Copy() Topology {
return Topology{
Shape: t.Shape,
Label: t.Label,
LabelPlural: t.LabelPlural,
Nodes: t.Nodes.Copy(),
Controls: t.Controls.Copy(),
MetadataTemplates: t.MetadataTemplates.Copy(),
@@ -88,7 +139,18 @@ func (t Topology) Copy() Topology {
// Merge merges the other object into this one, and returns the result object.
// The original is not modified.
func (t Topology) Merge(other Topology) Topology {
shape := t.Shape
if shape == "" {
shape = other.Shape
}
label, labelPlural := t.Label, t.LabelPlural
if label == "" {
label, labelPlural = other.Label, other.LabelPlural
}
return Topology{
Shape: shape,
Label: label,
LabelPlural: labelPlural,
Nodes: t.Nodes.Merge(other.Nodes),
Controls: t.Controls.Merge(other.Controls),
MetadataTemplates: t.MetadataTemplates.Merge(other.MetadataTemplates),

View File

@@ -248,7 +248,7 @@ var (
},
MetadataTemplates: process.MetadataTemplates,
MetricTemplates: process.MetricTemplates,
},
}.WithShape(report.Square).WithLabel("process", "processes"),
Container: report.Topology{
Nodes: report.Nodes{
ClientContainerNodeID: report.MakeNodeWith(
@@ -299,7 +299,7 @@ var (
},
MetadataTemplates: docker.ContainerMetadataTemplates,
MetricTemplates: docker.ContainerMetricTemplates,
},
}.WithShape(report.Hexagon).WithLabel("container", "containers"),
ContainerImage: report.Topology{
Nodes: report.Nodes{
ClientContainerImageNodeID: report.MakeNodeWith(ClientContainerImageNodeID, map[string]string{
@@ -322,7 +322,7 @@ var (
).WithTopology(report.ContainerImage),
},
MetadataTemplates: docker.ContainerImageMetadataTemplates,
},
}.WithShape(report.Hexagon).WithLabel("image", "images"),
Host: report.Topology{
Nodes: report.Nodes{
ClientHostNodeID: report.MakeNodeWith(
@@ -356,7 +356,7 @@ var (
},
MetadataTemplates: host.MetadataTemplates,
MetricTemplates: host.MetricTemplates,
},
}.WithShape(report.Circle).WithLabel("host", "hosts"),
Pod: report.Topology{
Nodes: report.Nodes{
ClientPodNodeID: report.MakeNodeWith(
@@ -388,7 +388,7 @@ var (
),
},
MetadataTemplates: kubernetes.PodMetadataTemplates,
},
}.WithShape(report.Heptagon).WithLabel("pod", "pods"),
Service: report.Topology{
Nodes: report.Nodes{
ServiceNodeID: report.MakeNodeWith(
@@ -400,7 +400,7 @@ var (
}).
WithTopology(report.Service),
},
},
}.WithShape(report.Heptagon).WithLabel("service", "services"),
Sampling: report.Sampling{
Count: 1024,
Total: 4096,