From 43a29db73cc375d9590abbe34fffff3b195d56c5 Mon Sep 17 00:00:00 2001 From: Paul Bellamy Date: Mon, 18 Apr 2016 12:40:50 +0100 Subject: [PATCH 1/4] Add explicit group node summariser instead of doing it in the other summaires --- render/detailed/summary.go | 127 +++++++++++++++++--------------- render/detailed/summary_test.go | 14 ++++ render/expected/expected.go | 27 ++++--- render/id.go | 5 ++ render/mapping.go | 4 +- 5 files changed, 103 insertions(+), 74 deletions(-) diff --git a/render/detailed/summary.go b/render/detailed/summary.go index 3883db99b..1023f322a 100644 --- a/render/detailed/summary.go +++ b/render/detailed/summary.go @@ -29,6 +29,18 @@ const ( MarathonAppIDEnv = "MARATHON_APP_ID" ) +var ( + shapesByTopology = map[string]string{ + report.Host: Circle, + report.Process: Square, + report.Pod: Heptagon, + report.Service: Heptagon, + report.Container: Hexagon, + report.ContainerImage: Hexagon, + render.Pseudo: Circle, + } +) + // NodeSummaryGroup is a topology-typed group of children for a Node. type NodeSummaryGroup struct { ID string `json:"id"` @@ -89,6 +101,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), n) + } return NodeSummary{}, false } @@ -127,9 +142,13 @@ func (n NodeSummary) Copy() NodeSummary { } func baseNodeSummary(r report.Report, n report.Node) NodeSummary { + shape, ok := shapesByTopology[n.Topology] + if !ok { + shape = Circle + } return NodeSummary{ ID: n.ID, - Shape: Circle, + Shape: shape, Linkable: true, Metadata: NodeMetadata(r, n), Metrics: NodeMetrics(r, n), @@ -142,23 +161,23 @@ 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 = 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.LabelMinor = report.ExtractHostID(n) base.Shape = Square base.Stack = true - base.LabelMinor = report.ExtractHostID(n) return base, true } @@ -174,6 +193,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 = Circle return base, true } @@ -183,25 +203,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 +221,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 +239,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 +248,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 +261,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 +276,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 +303,38 @@ 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, 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 + + if count, ok := n.Counters.Lookup(parts[1]); ok { + base.LabelMinor = fmt.Sprintf("%d %s", count, parts[1]) + if count != 1 { + if strings.HasSuffix(parts[1], "s") { + base.LabelMinor += "es" + } else { + base.LabelMinor += "s" + } } } - base.Shape = Circle + if base.Shape, ok = shapesByTopology[parts[1]]; !ok { + base.Shape = Circle + } + base.Stack = true return base, true } diff --git a/render/detailed/summary_test.go b/render/detailed/summary_test.go index d562c9a79..93e544e67 100644 --- a/render/detailed/summary_test.go +++ b/render/detailed/summary_test.go @@ -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) diff --git a/render/expected/expected.go b/render/expected/expected.go index 5f70ff9a5..64f6ac561 100644 --- a/render/expected/expected.go +++ b/render/expected/expected.go @@ -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], diff --git a/render/id.go b/render/id.go index 0ed8d4997..32309fdd9 100644 --- a/render/id.go +++ b/render/id.go @@ -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}, ":") +} diff --git a/render/mapping.go b/render/mapping.go index c1c246d41..2e9ff6cd5 100644 --- a/render/mapping.go +++ b/render/mapping.go @@ -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. From 4ad1ae80dfe2b5e0c4ab813b518ceb53ee22ade0 Mon Sep 17 00:00:00 2001 From: Paul Bellamy Date: Wed, 20 Apr 2016 10:29:42 +0100 Subject: [PATCH 2/4] move shapes determination to the topology --- render/detailed/summary.go | 47 ++++++++++++-------------------------- report/report.go | 19 ++++++++++----- report/topology.go | 24 +++++++++++++++++++ 3 files changed, 52 insertions(+), 38 deletions(-) diff --git a/render/detailed/summary.go b/render/detailed/summary.go index 1023f322a..0892d014f 100644 --- a/render/detailed/summary.go +++ b/render/detailed/summary.go @@ -15,12 +15,6 @@ import ( // Shapes that are allowed const ( - Circle = "circle" - Square = "square" - Heptagon = "heptagon" - Hexagon = "hexagon" - Cloud = "cloud" - ImageNameNone = "" // Keys we use to render container names @@ -29,18 +23,6 @@ const ( MarathonAppIDEnv = "MARATHON_APP_ID" ) -var ( - shapesByTopology = map[string]string{ - report.Host: Circle, - report.Process: Square, - report.Pod: Heptagon, - report.Service: Heptagon, - report.Container: Hexagon, - report.ContainerImage: Hexagon, - render.Pseudo: Circle, - } -) - // NodeSummaryGroup is a topology-typed group of children for a Node. type NodeSummaryGroup struct { ID string `json:"id"` @@ -102,7 +84,7 @@ func MakeNodeSummary(r report.Report, n report.Node) (NodeSummary, bool) { return renderer(baseNodeSummary(r, n), n) } if strings.HasPrefix(n.Topology, "group:") { - return groupNodeSummary(baseNodeSummary(r, n), n) + return groupNodeSummary(baseNodeSummary(r, n), r, n) } return NodeSummary{}, false } @@ -142,13 +124,9 @@ func (n NodeSummary) Copy() NodeSummary { } func baseNodeSummary(r report.Report, n report.Node) NodeSummary { - shape, ok := shapesByTopology[n.Topology] - if !ok { - shape = Circle - } return NodeSummary{ ID: n.ID, - Shape: shape, + Shape: topologyShape(r, n.Topology), Linkable: true, Metadata: NodeMetadata(r, n), Metrics: NodeMetrics(r, n), @@ -168,7 +146,7 @@ func pseudoNodeSummary(base NodeSummary, n report.Node) (NodeSummary, bool) { }[n.ID]; ok { base.Label = template.Label base.LabelMinor = template.LabelMinor - base.Shape = Cloud + base.Shape = report.Cloud return base, true } @@ -176,7 +154,7 @@ func pseudoNodeSummary(base NodeSummary, n report.Node) (NodeSummary, bool) { if strings.HasPrefix(n.ID, render.MakePseudoNodeID(render.UncontainedID)) { base.Label = render.UncontainedMajor base.LabelMinor = report.ExtractHostID(n) - base.Shape = Square + base.Shape = report.Square base.Stack = true return base, true } @@ -184,7 +162,7 @@ func pseudoNodeSummary(base NodeSummary, n report.Node) (NodeSummary, bool) { // 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 @@ -193,7 +171,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 = Circle + base.Shape = report.Circle return base, true } @@ -308,7 +286,7 @@ func hostNodeSummary(base NodeSummary, n report.Node) (NodeSummary, bool) { // groupNodeSummary renders the summary for a group node. n.Topology is // expected to be of the form: group:container:hostname -func groupNodeSummary(base NodeSummary, n report.Node) (NodeSummary, bool) { +func groupNodeSummary(base NodeSummary, r report.Report, n report.Node) (NodeSummary, bool) { parts := strings.Split(n.Topology, ":") if len(parts) != 3 { return NodeSummary{}, false @@ -331,9 +309,7 @@ func groupNodeSummary(base NodeSummary, n report.Node) (NodeSummary, bool) { } } - if base.Shape, ok = shapesByTopology[parts[1]]; !ok { - base.Shape = Circle - } + base.Shape = topologyShape(r, parts[1]) base.Stack = true return base, true } @@ -397,3 +373,10 @@ func getRenderableContainerName(nmd report.Node) string { } return "" } + +func topologyShape(r report.Report, topology string) string { + if t, ok := r.Topology(topology); ok && t.Shape != "" { + return t.Shape + } + return report.Circle +} diff --git a/report/report.go b/report/report.go index d2f8b47bb..3475efa0f 100644 --- a/report/report.go +++ b/report/report.go @@ -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" ) @@ -97,12 +104,12 @@ type Report struct { func MakeReport() Report { return Report{ Endpoint: MakeTopology(), - Process: MakeTopology(), - Container: MakeTopology(), - ContainerImage: MakeTopology(), - Host: MakeTopology(), - Pod: MakeTopology(), - Service: MakeTopology(), + Process: MakeTopology().WithShape(Square), + Container: MakeTopology().WithShape(Hexagon), + ContainerImage: MakeTopology().WithShape(Hexagon), + Host: MakeTopology().WithShape(Circle), + Pod: MakeTopology().WithShape(Heptagon), + Service: MakeTopology().WithShape(Heptagon), Overlay: MakeTopology(), Sampling: Sampling{}, Window: 0, diff --git a/report/topology.go b/report/topology.go index da783e3b7..f648d9351 100644 --- a/report/topology.go +++ b/report/topology.go @@ -10,6 +10,7 @@ import ( // EdgeMetadatas and Nodes respectively. Edges are directional, and embedded // in the Node struct. type Topology struct { + Shape string `json:"shape,omitempty"` Nodes `json:"nodes"` Controls `json:"controls,omitempty"` MetadataTemplates `json:"metadata_templates,omitempty"` @@ -20,6 +21,7 @@ type Topology struct { // MakeTopology gives you a Topology. func MakeTopology() Topology { return Topology{ + Shape: Circle, Nodes: map[string]Node{}, Controls: Controls{}, } @@ -29,6 +31,7 @@ func MakeTopology() Topology { // returning a new topology. func (t Topology) WithMetadataTemplates(other MetadataTemplates) Topology { return Topology{ + Shape: t.Shape, Nodes: t.Nodes.Copy(), Controls: t.Controls.Copy(), MetadataTemplates: t.MetadataTemplates.Merge(other), @@ -41,6 +44,7 @@ func (t Topology) WithMetadataTemplates(other MetadataTemplates) Topology { // returning a new topology. func (t Topology) WithMetricTemplates(other MetricTemplates) Topology { return Topology{ + Shape: t.Shape, Nodes: t.Nodes.Copy(), Controls: t.Controls.Copy(), MetadataTemplates: t.MetadataTemplates.Copy(), @@ -53,6 +57,7 @@ func (t Topology) WithMetricTemplates(other MetricTemplates) Topology { // returning a new topology. func (t Topology) WithTableTemplates(other TableTemplates) Topology { return Topology{ + Shape: t.Shape, Nodes: t.Nodes.Copy(), Controls: t.Controls.Copy(), MetadataTemplates: t.MetadataTemplates.Copy(), @@ -61,6 +66,18 @@ func (t Topology) WithTableTemplates(other TableTemplates) Topology { } } +// WithShape sets the shape nodes of this topology, returning a new topology. +func (t Topology) WithShape(shape string) Topology { + return Topology{ + Shape: shape, + 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. @@ -77,6 +94,7 @@ func (t Topology) AddNode(node Node) Topology { // Copy returns a value copy of the Topology. func (t Topology) Copy() Topology { return Topology{ + Shape: t.Shape, Nodes: t.Nodes.Copy(), Controls: t.Controls.Copy(), MetadataTemplates: t.MetadataTemplates.Copy(), @@ -88,7 +106,13 @@ 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 + // A circle is the lowliest shape. + if shape == Circle { + shape = other.Shape + } return Topology{ + Shape: t.Shape, Nodes: t.Nodes.Merge(other.Nodes), Controls: t.Controls.Merge(other.Controls), MetadataTemplates: t.MetadataTemplates.Merge(other.MetadataTemplates), From 4bd383221906574bbda750d906bccf822803ea06 Mon Sep 17 00:00:00 2001 From: Paul Bellamy Date: Wed, 20 Apr 2016 10:56:57 +0100 Subject: [PATCH 3/4] move counting sublabel definition to the topologies --- render/detailed/summary.go | 24 +++++++---------- report/metadata_template.go | 8 +----- report/report.go | 46 +++++++++++++++++++++++--------- report/table.go | 8 ++---- report/topology.go | 48 ++++++++++++++++++++++++++++++---- test/fixture/report_fixture.go | 12 ++++----- 6 files changed, 94 insertions(+), 52 deletions(-) diff --git a/render/detailed/summary.go b/render/detailed/summary.go index 0892d014f..7779cc1e2 100644 --- a/render/detailed/summary.go +++ b/render/detailed/summary.go @@ -124,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: topologyShape(r, n.Topology), + Shape: t.GetShape(), Linkable: true, Metadata: NodeMetadata(r, n), Metrics: NodeMetrics(r, n), @@ -298,18 +299,18 @@ func groupNodeSummary(base NodeSummary, r report.Report, n report.Node) (NodeSum } base.Label, base.Rank = label, label - if count, ok := n.Counters.Lookup(parts[1]); ok { - base.LabelMinor = fmt.Sprintf("%d %s", count, parts[1]) - if count != 1 { - if strings.HasSuffix(parts[1], "s") { - base.LabelMinor += "es" + 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 += "s" + base.LabelMinor = fmt.Sprintf("%d %s", count, t.LabelPlural) } } } - base.Shape = topologyShape(r, parts[1]) + base.Shape = t.GetShape() base.Stack = true return base, true } @@ -373,10 +374,3 @@ func getRenderableContainerName(nmd report.Node) string { } return "" } - -func topologyShape(r report.Report, topology string) string { - if t, ok := r.Topology(topology); ok && t.Shape != "" { - return t.Shape - } - return report.Circle -} diff --git a/report/metadata_template.go b/report/metadata_template.go index 3091ba730..16d99af31 100644 --- a/report/metadata_template.go +++ b/report/metadata_template.go @@ -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 diff --git a/report/report.go b/report/report.go index 3475efa0f..7488f6087 100644 --- a/report/report.go +++ b/report/report.go @@ -103,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().WithShape(Square), - Container: MakeTopology().WithShape(Hexagon), - ContainerImage: MakeTopology().WithShape(Hexagon), - Host: MakeTopology().WithShape(Circle), - Pod: MakeTopology().WithShape(Heptagon), - Service: MakeTopology().WithShape(Heptagon), - 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{}, } } diff --git a/report/table.go b/report/table.go index 806a1b23c..25052640b 100644 --- a/report/table.go +++ b/report/table.go @@ -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 - diff --git a/report/topology.go b/report/topology.go index f648d9351..989cdfbd2 100644 --- a/report/topology.go +++ b/report/topology.go @@ -11,6 +11,8 @@ import ( // 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"` @@ -21,7 +23,6 @@ type Topology struct { // MakeTopology gives you a Topology. func MakeTopology() Topology { return Topology{ - Shape: Circle, Nodes: map[string]Node{}, Controls: Controls{}, } @@ -32,6 +33,8 @@ func MakeTopology() 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), @@ -45,6 +48,8 @@ func (t Topology) WithMetadataTemplates(other MetadataTemplates) 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(), @@ -58,6 +63,8 @@ func (t Topology) WithMetricTemplates(other MetricTemplates) 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(), @@ -66,10 +73,26 @@ func (t Topology) WithTableTemplates(other TableTemplates) Topology { } } -// WithShape sets the shape nodes of this topology, returning a new 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(), @@ -91,10 +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(), @@ -107,12 +140,17 @@ func (t Topology) Copy() Topology { // The original is not modified. func (t Topology) Merge(other Topology) Topology { shape := t.Shape - // A circle is the lowliest shape. - if shape == Circle { + if shape == "" { shape = other.Shape } + label, labelPlural := t.Label, t.LabelPlural + if label == "" { + label, labelPlural = other.Label, other.LabelPlural + } return Topology{ - Shape: t.Shape, + Shape: shape, + Label: label, + LabelPlural: labelPlural, Nodes: t.Nodes.Merge(other.Nodes), Controls: t.Controls.Merge(other.Controls), MetadataTemplates: t.MetadataTemplates.Merge(other.MetadataTemplates), diff --git a/test/fixture/report_fixture.go b/test/fixture/report_fixture.go index 5a9a9e33f..eff141875 100644 --- a/test/fixture/report_fixture.go +++ b/test/fixture/report_fixture.go @@ -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, From 99c611f594f981504b4e01074db4459a8579a139 Mon Sep 17 00:00:00 2001 From: Paul Bellamy Date: Wed, 20 Apr 2016 13:24:32 +0100 Subject: [PATCH 4/4] Fixing up tests for shape and sublabel moving to topologies --- probe/appclient/app_client_internal_test.go | 8 ++++++++ probe/probe_internal_test.go | 16 ++++++++-------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/probe/appclient/app_client_internal_test.go b/probe/appclient/app_client_internal_test.go index 7a99ea9cd..575f0e670 100644 --- a/probe/appclient/app_client_internal_test.go +++ b/probe/appclient/app_client_internal_test.go @@ -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 diff --git a/probe/probe_internal_test.go b/probe/probe_internal_test.go index ac1d8c7f4..666d625fb 100644 --- a/probe/probe_internal_test.go +++ b/probe/probe_internal_test.go @@ -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()