mirror of
https://github.com/weaveworks/scope.git
synced 2026-07-28 09:41:57 +00:00
Merge pull request #242 from tomwilkie/228-more-moves
Move RenderableNode and DetailedNode into render/
This commit is contained in:
@@ -3,7 +3,7 @@ package main
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/weaveworks/scope/report"
|
||||
"github.com/weaveworks/scope/render"
|
||||
)
|
||||
|
||||
// APITopologyDesc is returned in a list by the /api/topology handler.
|
||||
@@ -52,7 +52,7 @@ func makeTopologyList(rep Reporter) func(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
}
|
||||
|
||||
func stats(r report.RenderableNodes) *topologyStats {
|
||||
func stats(r render.RenderableNodes) *topologyStats {
|
||||
var (
|
||||
nodes int
|
||||
realNodes int
|
||||
|
||||
@@ -18,12 +18,12 @@ const (
|
||||
|
||||
// APITopology is returned by the /api/topology/{name} handler.
|
||||
type APITopology struct {
|
||||
Nodes report.RenderableNodes `json:"nodes"`
|
||||
Nodes render.RenderableNodes `json:"nodes"`
|
||||
}
|
||||
|
||||
// APINode is returned by the /api/topology/{name}/{id} handler.
|
||||
type APINode struct {
|
||||
Node report.DetailedNode `json:"node"`
|
||||
Node render.DetailedNode `json:"node"`
|
||||
}
|
||||
|
||||
// APIEdge is returned by the /api/topology/*/*/* handlers.
|
||||
@@ -67,7 +67,7 @@ func handleNode(rep Reporter, t topologyView, w http.ResponseWriter, r *http.Req
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
respondWith(w, http.StatusOK, APINode{Node: report.MakeDetailedNode(rpt, node)})
|
||||
respondWith(w, http.StatusOK, APINode{Node: render.MakeDetailedNode(rpt, node)})
|
||||
}
|
||||
|
||||
// Individual edges.
|
||||
@@ -112,7 +112,7 @@ func handleWebsocket(
|
||||
}(conn)
|
||||
|
||||
var (
|
||||
previousTopo report.RenderableNodes
|
||||
previousTopo render.RenderableNodes
|
||||
tick = time.Tick(loop)
|
||||
)
|
||||
for {
|
||||
|
||||
@@ -56,7 +56,7 @@ func handleHTML(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "</body></html>\n")
|
||||
}
|
||||
|
||||
func dot(w io.Writer, m map[string]report.RenderableNode) {
|
||||
func dot(w io.Writer, m map[string]render.RenderableNode) {
|
||||
fmt.Fprintf(w, "digraph G {\n")
|
||||
fmt.Fprintf(w, "\tgraph [ overlap=false ];\n")
|
||||
fmt.Fprintf(w, "\tnode [ shape=circle, style=filled ];\n")
|
||||
|
||||
@@ -1,23 +1,50 @@
|
||||
package report
|
||||
package render
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
"github.com/weaveworks/scope/report"
|
||||
)
|
||||
|
||||
// DetailedNode is the data type that's yielded to the JavaScript layer when
|
||||
// we want deep information about an individual node.
|
||||
type DetailedNode struct {
|
||||
ID string `json:"id"`
|
||||
LabelMajor string `json:"label_major"`
|
||||
LabelMinor string `json:"label_minor,omitempty"`
|
||||
Pseudo bool `json:"pseudo,omitempty"`
|
||||
Tables []Table `json:"tables"`
|
||||
}
|
||||
|
||||
// Table is a dataset associated with a node. It will be displayed in the
|
||||
// detail panel when a user clicks on a node.
|
||||
type Table struct {
|
||||
Title string `json:"title"` // e.g. Bandwidth
|
||||
Numeric bool `json:"numeric"` // should the major column be right-aligned?
|
||||
Rows []Row `json:"rows"`
|
||||
}
|
||||
|
||||
// Row is a single entry in a Table dataset.
|
||||
type Row struct {
|
||||
Key string `json:"key"` // e.g. Ingress
|
||||
ValueMajor string `json:"value_major"` // e.g. 25
|
||||
ValueMinor string `json:"value_minor,omitempty"` // e.g. KB/s
|
||||
}
|
||||
|
||||
// MakeDetailedNode transforms a renderable node to a detailed node. It uses
|
||||
// aggregate metadata, plus the set of origin node IDs, to produce tables.
|
||||
func MakeDetailedNode(r Report, n RenderableNode) DetailedNode {
|
||||
func MakeDetailedNode(r report.Report, n RenderableNode) DetailedNode {
|
||||
tables := []Table{}
|
||||
{
|
||||
rows := []Row{}
|
||||
if val, ok := n.Metadata[KeyMaxConnCountTCP]; ok {
|
||||
if val, ok := n.Metadata[report.KeyMaxConnCountTCP]; ok {
|
||||
rows = append(rows, Row{"TCP connections", strconv.FormatInt(int64(val), 10), ""})
|
||||
}
|
||||
if val, ok := n.Metadata[KeyBytesIngress]; ok {
|
||||
if val, ok := n.Metadata[report.KeyBytesIngress]; ok {
|
||||
rows = append(rows, Row{"Bytes ingress", strconv.FormatInt(int64(val), 10), ""})
|
||||
}
|
||||
if val, ok := n.Metadata[KeyBytesEgress]; ok {
|
||||
if val, ok := n.Metadata[report.KeyBytesEgress]; ok {
|
||||
rows = append(rows, Row{"Bytes egress", strconv.FormatInt(int64(val), 10), ""})
|
||||
}
|
||||
if len(rows) > 0 {
|
||||
@@ -53,7 +80,7 @@ outer:
|
||||
|
||||
// OriginTable produces a table (to be consumed directly by the UI) based on
|
||||
// an origin ID, which is (optimistically) a node ID in one of our topologies.
|
||||
func OriginTable(r Report, originID string) (Table, bool) {
|
||||
func OriginTable(r report.Report, originID string) (Table, bool) {
|
||||
if nmd, ok := r.Endpoint.NodeMetadatas[originID]; ok {
|
||||
return endpointOriginTable(nmd)
|
||||
}
|
||||
@@ -72,7 +99,7 @@ func OriginTable(r Report, originID string) (Table, bool) {
|
||||
return Table{}, false
|
||||
}
|
||||
|
||||
func endpointOriginTable(nmd NodeMetadata) (Table, bool) {
|
||||
func endpointOriginTable(nmd report.NodeMetadata) (Table, bool) {
|
||||
rows := []Row{}
|
||||
for _, tuple := range []struct{ key, human string }{
|
||||
{"endpoint", "Endpoint"},
|
||||
@@ -91,7 +118,7 @@ func endpointOriginTable(nmd NodeMetadata) (Table, bool) {
|
||||
}, len(rows) > 0
|
||||
}
|
||||
|
||||
func addressOriginTable(nmd NodeMetadata) (Table, bool) {
|
||||
func addressOriginTable(nmd report.NodeMetadata) (Table, bool) {
|
||||
rows := []Row{}
|
||||
if val, ok := nmd["address"]; ok {
|
||||
rows = append(rows, Row{"Address", val, ""})
|
||||
@@ -106,7 +133,7 @@ func addressOriginTable(nmd NodeMetadata) (Table, bool) {
|
||||
}, len(rows) > 0
|
||||
}
|
||||
|
||||
func processOriginTable(nmd NodeMetadata) (Table, bool) {
|
||||
func processOriginTable(nmd report.NodeMetadata) (Table, bool) {
|
||||
rows := []Row{}
|
||||
if val, ok := nmd["comm"]; ok {
|
||||
rows = append(rows, Row{"Name (comm)", val, ""})
|
||||
@@ -124,7 +151,7 @@ func processOriginTable(nmd NodeMetadata) (Table, bool) {
|
||||
}, len(rows) > 0
|
||||
}
|
||||
|
||||
func containerOriginTable(nmd NodeMetadata) (Table, bool) {
|
||||
func containerOriginTable(nmd report.NodeMetadata) (Table, bool) {
|
||||
rows := []Row{}
|
||||
for _, tuple := range []struct{ key, human string }{
|
||||
{"docker_container_id", "Container ID"},
|
||||
@@ -143,7 +170,7 @@ func containerOriginTable(nmd NodeMetadata) (Table, bool) {
|
||||
}, len(rows) > 0
|
||||
}
|
||||
|
||||
func hostOriginTable(nmd NodeMetadata) (Table, bool) {
|
||||
func hostOriginTable(nmd report.NodeMetadata) (Table, bool) {
|
||||
rows := []Row{}
|
||||
if val, ok := nmd["host_name"]; ok {
|
||||
rows = append(rows, Row{"Host name", val, ""})
|
||||
@@ -1,9 +1,10 @@
|
||||
package report_test
|
||||
package render_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/weaveworks/scope/render"
|
||||
"github.com/weaveworks/scope/report"
|
||||
)
|
||||
|
||||
@@ -12,26 +13,30 @@ func TestMakeDetailedNode(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestOriginTable(t *testing.T) {
|
||||
if _, ok := report.OriginTable(reportFixture, "not-found"); ok {
|
||||
if _, ok := render.OriginTable(rpt, "not-found"); ok {
|
||||
t.Errorf("unknown origin ID gave unexpected success")
|
||||
}
|
||||
for originID, want := range map[string]report.Table{
|
||||
client54001EndpointNodeID: {
|
||||
for originID, want := range map[string]render.Table{
|
||||
client54001NodeID: {
|
||||
Title: "Origin Endpoint",
|
||||
Numeric: false,
|
||||
Rows: []report.Row{{"Host name", clientHostName, ""}},
|
||||
Rows: []render.Row{
|
||||
{"Host name", clientHostName, ""},
|
||||
{"PID", "10001", ""},
|
||||
{"Process name", "curl", ""},
|
||||
},
|
||||
},
|
||||
clientAddressNodeID: {
|
||||
Title: "Origin Address",
|
||||
Numeric: false,
|
||||
Rows: []report.Row{
|
||||
Rows: []render.Row{
|
||||
{"Host name", clientHostName, ""},
|
||||
},
|
||||
},
|
||||
report.MakeProcessNodeID(clientHostID, "4242"): {
|
||||
Title: "Origin Process",
|
||||
Numeric: false,
|
||||
Rows: []report.Row{
|
||||
Rows: []render.Row{
|
||||
{"Name (comm)", "curl", ""},
|
||||
{"PID", "4242", ""},
|
||||
},
|
||||
@@ -39,14 +44,14 @@ func TestOriginTable(t *testing.T) {
|
||||
serverHostNodeID: {
|
||||
Title: "Origin Host",
|
||||
Numeric: false,
|
||||
Rows: []report.Row{
|
||||
Rows: []render.Row{
|
||||
{"Host name", serverHostName, ""},
|
||||
{"Load", "0.01 0.01 0.01", ""},
|
||||
{"Operating system", "Linux", ""},
|
||||
},
|
||||
},
|
||||
} {
|
||||
have, ok := report.OriginTable(reportFixture, originID)
|
||||
have, ok := render.OriginTable(rpt, originID)
|
||||
if !ok {
|
||||
t.Errorf("%q: not OK", originID)
|
||||
continue
|
||||
@@ -9,8 +9,8 @@ import (
|
||||
|
||||
const humanTheInternet = "the Internet"
|
||||
|
||||
func newRenderableNode(id, major, minor, rank string) report.RenderableNode {
|
||||
return report.RenderableNode{
|
||||
func newRenderableNode(id, major, minor, rank string) RenderableNode {
|
||||
return RenderableNode{
|
||||
ID: id,
|
||||
LabelMajor: major,
|
||||
LabelMinor: minor,
|
||||
@@ -20,8 +20,8 @@ func newRenderableNode(id, major, minor, rank string) report.RenderableNode {
|
||||
}
|
||||
}
|
||||
|
||||
func newPseudoNode(id, major, minor string) report.RenderableNode {
|
||||
return report.RenderableNode{
|
||||
func newPseudoNode(id, major, minor string) RenderableNode {
|
||||
return RenderableNode{
|
||||
ID: id,
|
||||
LabelMajor: major,
|
||||
LabelMinor: minor,
|
||||
@@ -41,18 +41,18 @@ func newPseudoNode(id, major, minor string) report.RenderableNode {
|
||||
//
|
||||
// If the final output parameter is false, the node shall be omitted from the
|
||||
// rendered topology.
|
||||
type MapFunc func(report.NodeMetadata) (report.RenderableNode, bool)
|
||||
type MapFunc func(report.NodeMetadata) (RenderableNode, bool)
|
||||
|
||||
// PseudoFunc creates RenderableNode representing pseudo nodes given the dstNodeID.
|
||||
// The srcNode renderable node is essentially from MapFunc, representing one of
|
||||
// the rendered nodes this pseudo node refers to. srcNodeID and dstNodeID are
|
||||
// node IDs prior to mapping.
|
||||
type PseudoFunc func(srcNodeID string, srcNode report.RenderableNode, dstNodeID string) (report.RenderableNode, bool)
|
||||
type PseudoFunc func(srcNodeID string, srcNode RenderableNode, dstNodeID string) (RenderableNode, bool)
|
||||
|
||||
// ProcessPID takes a node NodeMetadata from topology, and returns a
|
||||
// representation with the ID based on the process PID and the labels based on
|
||||
// the process name.
|
||||
func ProcessPID(m report.NodeMetadata) (report.RenderableNode, bool) {
|
||||
func ProcessPID(m report.NodeMetadata) (RenderableNode, bool) {
|
||||
var (
|
||||
identifier = fmt.Sprintf("%s:%s:%s", "pid", m["domain"], m["pid"])
|
||||
minor = fmt.Sprintf("%s (%s)", m["domain"], m["pid"])
|
||||
@@ -65,7 +65,7 @@ func ProcessPID(m report.NodeMetadata) (report.RenderableNode, bool) {
|
||||
// ProcessName takes a node NodeMetadata from a topology, and returns a
|
||||
// representation with the ID based on the process name (grouping all
|
||||
// processes with the same name together).
|
||||
func ProcessName(m report.NodeMetadata) (report.RenderableNode, bool) {
|
||||
func ProcessName(m report.NodeMetadata) (RenderableNode, bool) {
|
||||
show := m["pid"] != "" && m["name"] != ""
|
||||
return newRenderableNode(m["name"], m["name"], "", m["name"]), show
|
||||
}
|
||||
@@ -74,7 +74,7 @@ func ProcessName(m report.NodeMetadata) (report.RenderableNode, bool) {
|
||||
// in. We consider container and image IDs to be globally unique, and so don't
|
||||
// scope them further by e.g. host. If no container metadata is found, nodes are
|
||||
// grouped into the Uncontained node.
|
||||
func MapEndpoint2Container(m report.NodeMetadata) (report.RenderableNode, bool) {
|
||||
func MapEndpoint2Container(m report.NodeMetadata) (RenderableNode, bool) {
|
||||
var id, major, minor, rank string
|
||||
if m["docker_container_id"] == "" {
|
||||
id, major, minor, rank = "uncontained", "Uncontained", "", "uncontained"
|
||||
@@ -86,7 +86,7 @@ func MapEndpoint2Container(m report.NodeMetadata) (report.RenderableNode, bool)
|
||||
}
|
||||
|
||||
// MapContainerIdentity maps container topology node to container mapped nodes.
|
||||
func MapContainerIdentity(m report.NodeMetadata) (report.RenderableNode, bool) {
|
||||
func MapContainerIdentity(m report.NodeMetadata) (RenderableNode, bool) {
|
||||
var id, major, minor, rank string
|
||||
if m["docker_container_id"] == "" {
|
||||
id, major, minor, rank = "uncontained", "Uncontained", "", "uncontained"
|
||||
@@ -100,7 +100,7 @@ func MapContainerIdentity(m report.NodeMetadata) (report.RenderableNode, bool) {
|
||||
// ProcessContainerImage maps topology nodes to the container images they run
|
||||
// on. If no container metadata is found, nodes are grouped into the
|
||||
// Uncontained node.
|
||||
func ProcessContainerImage(m report.NodeMetadata) (report.RenderableNode, bool) {
|
||||
func ProcessContainerImage(m report.NodeMetadata) (RenderableNode, bool) {
|
||||
var id, major, minor, rank string
|
||||
if m["docker_image_id"] == "" {
|
||||
id, major, minor, rank = "uncontained", "Uncontained", "", "uncontained"
|
||||
@@ -114,7 +114,7 @@ func ProcessContainerImage(m report.NodeMetadata) (report.RenderableNode, bool)
|
||||
// NetworkHostname takes a node NodeMetadata and returns a representation
|
||||
// based on the hostname. Major label is the hostname, the minor label is the
|
||||
// domain, if any.
|
||||
func NetworkHostname(m report.NodeMetadata) (report.RenderableNode, bool) {
|
||||
func NetworkHostname(m report.NodeMetadata) (RenderableNode, bool) {
|
||||
var (
|
||||
name = m["name"]
|
||||
domain = ""
|
||||
@@ -130,7 +130,7 @@ func NetworkHostname(m report.NodeMetadata) (report.RenderableNode, bool) {
|
||||
|
||||
// GenericPseudoNode contains heuristics for building sensible pseudo nodes.
|
||||
// It should go away.
|
||||
func GenericPseudoNode(src string, srcMapped report.RenderableNode, dst string) (report.RenderableNode, bool) {
|
||||
func GenericPseudoNode(src string, srcMapped RenderableNode, dst string) (RenderableNode, bool) {
|
||||
var maj, min, outputID string
|
||||
|
||||
if dst == report.TheInternet {
|
||||
@@ -151,7 +151,7 @@ func GenericPseudoNode(src string, srcMapped report.RenderableNode, dst string)
|
||||
|
||||
// GenericGroupedPseudoNode contains heuristics for building sensible pseudo nodes.
|
||||
// It should go away.
|
||||
func GenericGroupedPseudoNode(src string, srcMapped report.RenderableNode, dst string) (report.RenderableNode, bool) {
|
||||
func GenericGroupedPseudoNode(src string, srcMapped RenderableNode, dst string) (RenderableNode, bool) {
|
||||
var maj, min, outputID string
|
||||
|
||||
if dst == report.TheInternet {
|
||||
@@ -169,11 +169,11 @@ func GenericGroupedPseudoNode(src string, srcMapped report.RenderableNode, dst s
|
||||
}
|
||||
|
||||
// InternetOnlyPseudoNode never creates a pseudo node, unless it's the Internet.
|
||||
func InternetOnlyPseudoNode(_ string, _ report.RenderableNode, dst string) (report.RenderableNode, bool) {
|
||||
func InternetOnlyPseudoNode(_ string, _ RenderableNode, dst string) (RenderableNode, bool) {
|
||||
if dst == report.TheInternet {
|
||||
return newPseudoNode(report.TheInternet, humanTheInternet, ""), true
|
||||
}
|
||||
return report.RenderableNode{}, false
|
||||
return RenderableNode{}, false
|
||||
}
|
||||
|
||||
// trySplitAddr is basically ParseArbitraryNodeID, since its callsites
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
package render
|
||||
package render_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/weaveworks/scope/render"
|
||||
"github.com/weaveworks/scope/report"
|
||||
)
|
||||
|
||||
func TestUngroupedMapping(t *testing.T) {
|
||||
for i, c := range []struct {
|
||||
f MapFunc
|
||||
f render.MapFunc
|
||||
id string
|
||||
meta report.NodeMetadata
|
||||
wantOK bool
|
||||
wantID, wantMajor, wantMinor, wantRank string
|
||||
}{
|
||||
{
|
||||
f: NetworkHostname,
|
||||
f: render.NetworkHostname,
|
||||
id: report.MakeAddressNodeID("", "1.2.3.4"),
|
||||
meta: report.NodeMetadata{
|
||||
"name": "my.host",
|
||||
@@ -28,7 +29,7 @@ func TestUngroupedMapping(t *testing.T) {
|
||||
wantRank: "my",
|
||||
},
|
||||
{
|
||||
f: NetworkHostname,
|
||||
f: render.NetworkHostname,
|
||||
id: report.MakeAddressNodeID("", "1.2.3.4"),
|
||||
meta: report.NodeMetadata{
|
||||
"name": "localhost",
|
||||
@@ -40,7 +41,7 @@ func TestUngroupedMapping(t *testing.T) {
|
||||
wantRank: "localhost",
|
||||
},
|
||||
{
|
||||
f: ProcessPID,
|
||||
f: render.ProcessPID,
|
||||
id: "not-used-beta",
|
||||
meta: report.NodeMetadata{
|
||||
"pid": "42",
|
||||
@@ -54,7 +55,7 @@ func TestUngroupedMapping(t *testing.T) {
|
||||
wantRank: "42",
|
||||
},
|
||||
{
|
||||
f: MapEndpoint2Container,
|
||||
f: render.MapEndpoint2Container,
|
||||
id: "foo-id",
|
||||
meta: report.NodeMetadata{
|
||||
"pid": "42",
|
||||
@@ -68,7 +69,7 @@ func TestUngroupedMapping(t *testing.T) {
|
||||
wantRank: "uncontained",
|
||||
},
|
||||
{
|
||||
f: MapEndpoint2Container,
|
||||
f: render.MapEndpoint2Container,
|
||||
id: "bar-id",
|
||||
meta: report.NodeMetadata{
|
||||
"pid": "42",
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
// Renderer is something that can render a report to a set of RenderableNodes
|
||||
type Renderer interface {
|
||||
Render(report.Report) report.RenderableNodes
|
||||
Render(report.Report) RenderableNodes
|
||||
AggregateMetadata(rpt report.Report, localID, remoteID string) report.AggregateMetadata
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@ type Renderer interface {
|
||||
type Reduce []Renderer
|
||||
|
||||
// Render produces a set of RenderableNodes given a Report
|
||||
func (r Reduce) Render(rpt report.Report) report.RenderableNodes {
|
||||
result := report.RenderableNodes{}
|
||||
func (r Reduce) Render(rpt report.Report) RenderableNodes {
|
||||
result := RenderableNodes{}
|
||||
for _, renderer := range r {
|
||||
result.Merge(renderer.Render(rpt))
|
||||
}
|
||||
@@ -43,18 +43,18 @@ type Map struct {
|
||||
}
|
||||
|
||||
// Render produces a set of RenderableNodes given a Report
|
||||
func (m Map) Render(rpt report.Report) report.RenderableNodes {
|
||||
return renderTopology(m.Selector(rpt), m.Mapper, m.Pseudo)
|
||||
func (m Map) Render(rpt report.Report) RenderableNodes {
|
||||
return Topology(m.Selector(rpt), m.Mapper, m.Pseudo)
|
||||
}
|
||||
|
||||
// RenderBy transforms a given Topology into a set of RenderableNodes, which
|
||||
// Topology transforms a given Topology into a set of RenderableNodes, which
|
||||
// the UI will render collectively as a graph. Note that a RenderableNode will
|
||||
// always be rendered with other nodes, and therefore contains limited detail.
|
||||
//
|
||||
// RenderBy takes a a MapFunc, which defines how to group and label nodes. Npdes
|
||||
// with the same mapped IDs will be merged.
|
||||
func renderTopology(t report.Topology, mapFunc MapFunc, pseudoFunc PseudoFunc) report.RenderableNodes {
|
||||
nodes := report.RenderableNodes{}
|
||||
func Topology(t report.Topology, mapFunc MapFunc, pseudoFunc PseudoFunc) RenderableNodes {
|
||||
nodes := RenderableNodes{}
|
||||
|
||||
// Build a set of RenderableNodes for all non-pseudo probes, and an
|
||||
// addressID to nodeID lookup map. Multiple addressIDs can map to the same
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package render
|
||||
package render_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/weaveworks/scope/render"
|
||||
"github.com/weaveworks/scope/report"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
@@ -15,11 +16,11 @@ func init() {
|
||||
}
|
||||
|
||||
type mockRenderer struct {
|
||||
report.RenderableNodes
|
||||
render.RenderableNodes
|
||||
aggregateMetadata report.AggregateMetadata
|
||||
}
|
||||
|
||||
func (m mockRenderer) Render(rpt report.Report) report.RenderableNodes {
|
||||
func (m mockRenderer) Render(rpt report.Report) render.RenderableNodes {
|
||||
return m.RenderableNodes
|
||||
}
|
||||
func (m mockRenderer) AggregateMetadata(rpt report.Report, localID, remoteID string) report.AggregateMetadata {
|
||||
@@ -27,12 +28,12 @@ func (m mockRenderer) AggregateMetadata(rpt report.Report, localID, remoteID str
|
||||
}
|
||||
|
||||
func TestReduceRender(t *testing.T) {
|
||||
renderer := Reduce([]Renderer{
|
||||
mockRenderer{RenderableNodes: report.RenderableNodes{"foo": {ID: "foo"}}},
|
||||
mockRenderer{RenderableNodes: report.RenderableNodes{"bar": {ID: "bar"}}},
|
||||
renderer := render.Reduce([]render.Renderer{
|
||||
mockRenderer{RenderableNodes: render.RenderableNodes{"foo": {ID: "foo"}}},
|
||||
mockRenderer{RenderableNodes: render.RenderableNodes{"bar": {ID: "bar"}}},
|
||||
})
|
||||
|
||||
want := report.RenderableNodes{"foo": {ID: "foo"}, "bar": {ID: "bar"}}
|
||||
want := render.RenderableNodes{"foo": {ID: "foo"}, "bar": {ID: "bar"}}
|
||||
have := renderer.Render(report.MakeReport())
|
||||
|
||||
if !reflect.DeepEqual(want, have) {
|
||||
@@ -41,7 +42,7 @@ func TestReduceRender(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestReduceEdge(t *testing.T) {
|
||||
renderer := Reduce([]Renderer{
|
||||
renderer := render.Reduce([]render.Renderer{
|
||||
mockRenderer{aggregateMetadata: report.AggregateMetadata{"foo": 1}},
|
||||
mockRenderer{aggregateMetadata: report.AggregateMetadata{"bar": 2}},
|
||||
})
|
||||
@@ -60,42 +61,46 @@ var (
|
||||
randomHostID = "random.hostname.com"
|
||||
unknownHostID = ""
|
||||
|
||||
clientHostName = clientHostID
|
||||
serverHostName = serverHostID
|
||||
|
||||
clientHostNodeID = report.MakeHostNodeID(clientHostID)
|
||||
serverHostNodeID = report.MakeHostNodeID(serverHostID)
|
||||
randomHostNodeID = report.MakeHostNodeID(randomHostID)
|
||||
|
||||
client54001 = report.MakeEndpointNodeID(clientHostID, "10.10.10.20", "54001") // curl (1)
|
||||
client54002 = report.MakeEndpointNodeID(clientHostID, "10.10.10.20", "54002") // curl (2)
|
||||
unknownClient1 = report.MakeEndpointNodeID(serverHostID, "10.10.10.10", "54010") // we want to ensure two unknown clients, connnected
|
||||
unknownClient2 = report.MakeEndpointNodeID(serverHostID, "10.10.10.10", "54020") // to the same server, are deduped.
|
||||
unknownClient3 = report.MakeEndpointNodeID(serverHostID, "10.10.10.11", "54020") // Check this one isn't deduped
|
||||
server80 = report.MakeEndpointNodeID(serverHostID, "192.168.1.1", "80") // apache
|
||||
client54001NodeID = report.MakeEndpointNodeID(clientHostID, "10.10.10.20", "54001") // curl (1)
|
||||
client54002NodeID = report.MakeEndpointNodeID(clientHostID, "10.10.10.20", "54002") // curl (2)
|
||||
unknownClient1 = report.MakeEndpointNodeID(serverHostID, "10.10.10.10", "54010") // we want to ensure two unknown clients, connnected
|
||||
unknownClient2 = report.MakeEndpointNodeID(serverHostID, "10.10.10.10", "54020") // to the same server, are deduped.
|
||||
unknownClient3 = report.MakeEndpointNodeID(serverHostID, "10.10.10.11", "54020") // Check this one isn't deduped
|
||||
server80 = report.MakeEndpointNodeID(serverHostID, "192.168.1.1", "80") // apache
|
||||
|
||||
clientIP = report.MakeAddressNodeID(clientHostID, "10.10.10.20")
|
||||
serverIP = report.MakeAddressNodeID(serverHostID, "192.168.1.1")
|
||||
randomIP = report.MakeAddressNodeID(randomHostID, "172.16.11.9") // only in Address topology
|
||||
unknownIP = report.MakeAddressNodeID(unknownHostID, "10.10.10.10")
|
||||
clientAddressNodeID = report.MakeAddressNodeID(clientHostID, "10.10.10.20")
|
||||
serverAddressNodeID = report.MakeAddressNodeID(serverHostID, "192.168.1.1")
|
||||
randomAddressNodeID = report.MakeAddressNodeID(randomHostID, "172.16.11.9") // only in Address topology
|
||||
unknownAddressNodeID = report.MakeAddressNodeID(unknownHostID, "10.10.10.10")
|
||||
)
|
||||
|
||||
var (
|
||||
rpt = report.Report{
|
||||
Endpoint: report.Topology{
|
||||
Adjacency: report.Adjacency{
|
||||
report.MakeAdjacencyID(client54001): report.MakeIDList(server80),
|
||||
report.MakeAdjacencyID(client54002): report.MakeIDList(server80),
|
||||
report.MakeAdjacencyID(server80): report.MakeIDList(client54001, client54002, unknownClient1, unknownClient2, unknownClient3),
|
||||
report.MakeAdjacencyID(client54001NodeID): report.MakeIDList(server80),
|
||||
report.MakeAdjacencyID(client54002NodeID): report.MakeIDList(server80),
|
||||
report.MakeAdjacencyID(server80): report.MakeIDList(client54001NodeID, client54002NodeID, unknownClient1, unknownClient2, unknownClient3),
|
||||
},
|
||||
NodeMetadatas: report.NodeMetadatas{
|
||||
// NodeMetadata is arbitrary. We're free to put only precisely what we
|
||||
// care to test into the fixture. Just be sure to include the bits
|
||||
// that the mapping funcs extract :)
|
||||
client54001: report.NodeMetadata{
|
||||
client54001NodeID: report.NodeMetadata{
|
||||
"name": "curl",
|
||||
"domain": "client-54001-domain",
|
||||
"pid": "10001",
|
||||
report.HostNodeID: clientHostNodeID,
|
||||
"host_name": clientHostName,
|
||||
},
|
||||
client54002: report.NodeMetadata{
|
||||
client54002NodeID: report.NodeMetadata{
|
||||
"name": "curl", // should be same as above!
|
||||
"domain": "client-54002-domain", // may be different than above
|
||||
"pid": "10001", // should be same as above!
|
||||
@@ -109,23 +114,23 @@ var (
|
||||
},
|
||||
},
|
||||
EdgeMetadatas: report.EdgeMetadatas{
|
||||
report.MakeEdgeID(client54001, server80): report.EdgeMetadata{
|
||||
report.MakeEdgeID(client54001NodeID, server80): report.EdgeMetadata{
|
||||
WithBytes: true,
|
||||
BytesIngress: 100,
|
||||
BytesEgress: 10,
|
||||
},
|
||||
report.MakeEdgeID(client54002, server80): report.EdgeMetadata{
|
||||
report.MakeEdgeID(client54002NodeID, server80): report.EdgeMetadata{
|
||||
WithBytes: true,
|
||||
BytesIngress: 200,
|
||||
BytesEgress: 20,
|
||||
},
|
||||
|
||||
report.MakeEdgeID(server80, client54001): report.EdgeMetadata{
|
||||
report.MakeEdgeID(server80, client54001NodeID): report.EdgeMetadata{
|
||||
WithBytes: true,
|
||||
BytesIngress: 10,
|
||||
BytesEgress: 100,
|
||||
},
|
||||
report.MakeEdgeID(server80, client54002): report.EdgeMetadata{
|
||||
report.MakeEdgeID(server80, client54002NodeID): report.EdgeMetadata{
|
||||
WithBytes: true,
|
||||
BytesIngress: 20,
|
||||
BytesEgress: 200,
|
||||
@@ -147,50 +152,84 @@ var (
|
||||
},
|
||||
},
|
||||
},
|
||||
Process: report.Topology{
|
||||
Adjacency: report.Adjacency{},
|
||||
NodeMetadatas: report.NodeMetadatas{
|
||||
report.MakeProcessNodeID(clientHostID, "4242"): report.NodeMetadata{
|
||||
"host_name": "client.host.com",
|
||||
"pid": "4242",
|
||||
"comm": "curl",
|
||||
"docker_container_id": "a1b2c3d4e5",
|
||||
"docker_container_name": "fixture-container",
|
||||
"docker_image_id": "0000000000",
|
||||
"docker_image_name": "fixture/container:latest",
|
||||
},
|
||||
report.MakeProcessNodeID(serverHostID, "215"): report.NodeMetadata{
|
||||
"pid": "215",
|
||||
"process_name": "apache",
|
||||
},
|
||||
|
||||
"no-container": report.NodeMetadata{},
|
||||
},
|
||||
EdgeMetadatas: report.EdgeMetadatas{},
|
||||
},
|
||||
Address: report.Topology{
|
||||
Adjacency: report.Adjacency{
|
||||
report.MakeAdjacencyID(clientIP): report.MakeIDList(serverIP),
|
||||
report.MakeAdjacencyID(randomIP): report.MakeIDList(serverIP),
|
||||
report.MakeAdjacencyID(serverIP): report.MakeIDList(clientIP, unknownIP), // no backlink to random
|
||||
report.MakeAdjacencyID(clientAddressNodeID): report.MakeIDList(serverAddressNodeID),
|
||||
report.MakeAdjacencyID(randomAddressNodeID): report.MakeIDList(serverAddressNodeID),
|
||||
report.MakeAdjacencyID(serverAddressNodeID): report.MakeIDList(clientAddressNodeID, unknownAddressNodeID), // no backlink to random
|
||||
},
|
||||
NodeMetadatas: report.NodeMetadatas{
|
||||
clientIP: report.NodeMetadata{
|
||||
clientAddressNodeID: report.NodeMetadata{
|
||||
"name": "client.hostname.com", // hostname
|
||||
"host_name": "client.hostname.com",
|
||||
report.HostNodeID: clientHostNodeID,
|
||||
},
|
||||
randomIP: report.NodeMetadata{
|
||||
randomAddressNodeID: report.NodeMetadata{
|
||||
"name": "random.hostname.com", // hostname
|
||||
report.HostNodeID: randomHostNodeID,
|
||||
},
|
||||
serverIP: report.NodeMetadata{
|
||||
serverAddressNodeID: report.NodeMetadata{
|
||||
"name": "server.hostname.com", // hostname
|
||||
report.HostNodeID: serverHostNodeID,
|
||||
},
|
||||
},
|
||||
EdgeMetadatas: report.EdgeMetadatas{
|
||||
report.MakeEdgeID(clientIP, serverIP): report.EdgeMetadata{
|
||||
report.MakeEdgeID(clientAddressNodeID, serverAddressNodeID): report.EdgeMetadata{
|
||||
WithConnCountTCP: true,
|
||||
MaxConnCountTCP: 3,
|
||||
},
|
||||
report.MakeEdgeID(randomIP, serverIP): report.EdgeMetadata{
|
||||
report.MakeEdgeID(randomAddressNodeID, serverAddressNodeID): report.EdgeMetadata{
|
||||
WithConnCountTCP: true,
|
||||
MaxConnCountTCP: 20, // dangling connections, weird but possible
|
||||
},
|
||||
report.MakeEdgeID(serverIP, clientIP): report.EdgeMetadata{
|
||||
report.MakeEdgeID(serverAddressNodeID, clientAddressNodeID): report.EdgeMetadata{
|
||||
WithConnCountTCP: true,
|
||||
MaxConnCountTCP: 3,
|
||||
},
|
||||
report.MakeEdgeID(serverIP, unknownIP): report.EdgeMetadata{
|
||||
report.MakeEdgeID(serverAddressNodeID, unknownAddressNodeID): report.EdgeMetadata{
|
||||
WithConnCountTCP: true,
|
||||
MaxConnCountTCP: 7,
|
||||
},
|
||||
},
|
||||
},
|
||||
Host: report.Topology{
|
||||
Adjacency: report.Adjacency{},
|
||||
NodeMetadatas: report.NodeMetadatas{
|
||||
serverHostNodeID: report.NodeMetadata{
|
||||
"host_name": serverHostName,
|
||||
"local_networks": "10.10.10.0/24",
|
||||
"os": "Linux",
|
||||
"load": "0.01 0.01 0.01",
|
||||
},
|
||||
},
|
||||
EdgeMetadatas: report.EdgeMetadatas{},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func TestRenderByEndpointPID(t *testing.T) {
|
||||
want := report.RenderableNodes{
|
||||
want := render.RenderableNodes{
|
||||
"pid:client-54001-domain:10001": {
|
||||
ID: "pid:client-54001-domain:10001",
|
||||
LabelMajor: "curl",
|
||||
@@ -248,7 +287,7 @@ func TestRenderByEndpointPID(t *testing.T) {
|
||||
Metadata: report.AggregateMetadata{},
|
||||
},
|
||||
}
|
||||
have := renderTopology(rpt.Endpoint, ProcessPID, GenericPseudoNode)
|
||||
have := render.Topology(rpt.Endpoint, render.ProcessPID, render.GenericPseudoNode)
|
||||
if !reflect.DeepEqual(want, have) {
|
||||
t.Error("\n" + diff(want, have))
|
||||
}
|
||||
@@ -258,7 +297,7 @@ func TestRenderByEndpointPIDGrouped(t *testing.T) {
|
||||
// For grouped, I've somewhat arbitrarily chosen to squash together all
|
||||
// processes with the same name by removing the PID and domain (host)
|
||||
// dimensions from the ID. That could be changed.
|
||||
want := report.RenderableNodes{
|
||||
want := render.RenderableNodes{
|
||||
"curl": {
|
||||
ID: "curl",
|
||||
LabelMajor: "curl",
|
||||
@@ -302,14 +341,14 @@ func TestRenderByEndpointPIDGrouped(t *testing.T) {
|
||||
Metadata: report.AggregateMetadata{},
|
||||
},
|
||||
}
|
||||
have := renderTopology(rpt.Endpoint, ProcessName, GenericGroupedPseudoNode)
|
||||
have := render.Topology(rpt.Endpoint, render.ProcessName, render.GenericGroupedPseudoNode)
|
||||
if !reflect.DeepEqual(want, have) {
|
||||
t.Error("\n" + diff(want, have))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderByNetworkHostname(t *testing.T) {
|
||||
want := report.RenderableNodes{
|
||||
want := render.RenderableNodes{
|
||||
"host:client.hostname.com": {
|
||||
ID: "host:client.hostname.com",
|
||||
LabelMajor: "client", // before first .
|
||||
@@ -357,7 +396,7 @@ func TestRenderByNetworkHostname(t *testing.T) {
|
||||
Metadata: report.AggregateMetadata{},
|
||||
},
|
||||
}
|
||||
have := renderTopology(rpt.Address, NetworkHostname, GenericPseudoNode)
|
||||
have := render.Topology(rpt.Address, render.NetworkHostname, render.GenericPseudoNode)
|
||||
if !reflect.DeepEqual(want, have) {
|
||||
t.Error("\n" + diff(want, have))
|
||||
}
|
||||
|
||||
58
render/renderable_node.go
Normal file
58
render/renderable_node.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package render
|
||||
|
||||
import (
|
||||
"github.com/weaveworks/scope/report"
|
||||
)
|
||||
|
||||
// RenderableNode is the data type that's yielded to the JavaScript layer as
|
||||
// an element of a topology. It should contain information that's relevant
|
||||
// to rendering a node when there are many nodes visible at once.
|
||||
type RenderableNode struct {
|
||||
ID string `json:"id"` //
|
||||
LabelMajor string `json:"label_major"` // e.g. "process", human-readable
|
||||
LabelMinor string `json:"label_minor,omitempty"` // e.g. "hostname", human-readable, optional
|
||||
Rank string `json:"rank"` // to help the layout engine
|
||||
Pseudo bool `json:"pseudo,omitempty"` // sort-of a placeholder node, for rendering purposes
|
||||
Adjacency report.IDList `json:"adjacency,omitempty"` // Node IDs (in the same topology domain)
|
||||
Origins report.IDList `json:"origins,omitempty"` // Core node IDs that contributed information
|
||||
Metadata report.AggregateMetadata `json:"metadata"` // Numeric sums
|
||||
}
|
||||
|
||||
// RenderableNodes is a set of RenderableNodes
|
||||
type RenderableNodes map[string]RenderableNode
|
||||
|
||||
// Merge merges two sets of RenderableNodes
|
||||
func (rns RenderableNodes) Merge(other RenderableNodes) {
|
||||
for key, value := range other {
|
||||
if existing, ok := rns[key]; ok {
|
||||
existing.Merge(value)
|
||||
rns[key] = existing
|
||||
} else {
|
||||
rns[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Merge merges in another RenderableNode
|
||||
func (rn *RenderableNode) Merge(other RenderableNode) {
|
||||
if rn.LabelMajor == "" {
|
||||
rn.LabelMajor = other.LabelMajor
|
||||
}
|
||||
|
||||
if rn.LabelMinor == "" {
|
||||
rn.LabelMinor = other.LabelMinor
|
||||
}
|
||||
|
||||
if rn.Rank == "" {
|
||||
rn.Rank = other.Rank
|
||||
}
|
||||
|
||||
if rn.Pseudo != other.Pseudo {
|
||||
panic(rn.ID)
|
||||
}
|
||||
|
||||
rn.Adjacency = rn.Adjacency.Add(other.Adjacency...)
|
||||
rn.Origins = rn.Origins.Add(other.Origins...)
|
||||
|
||||
rn.Metadata.Merge(other.Metadata)
|
||||
}
|
||||
@@ -2,20 +2,18 @@ package render
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/weaveworks/scope/report"
|
||||
)
|
||||
|
||||
// Diff is returned by TopoDiff. It represents the changes between two
|
||||
// RenderableNode maps.
|
||||
type Diff struct {
|
||||
Add []report.RenderableNode `json:"add"`
|
||||
Update []report.RenderableNode `json:"update"`
|
||||
Remove []string `json:"remove"`
|
||||
Add []RenderableNode `json:"add"`
|
||||
Update []RenderableNode `json:"update"`
|
||||
Remove []string `json:"remove"`
|
||||
}
|
||||
|
||||
// TopoDiff gives you the diff to get from A to B.
|
||||
func TopoDiff(a, b report.RenderableNodes) Diff {
|
||||
func TopoDiff(a, b RenderableNodes) Diff {
|
||||
diff := Diff{}
|
||||
|
||||
notSeen := map[string]struct{}{}
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
package render
|
||||
package render_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/weaveworks/scope/report"
|
||||
"github.com/weaveworks/scope/render"
|
||||
)
|
||||
|
||||
// ByID is a sort interface for a RenderableNode slice.
|
||||
type ByID []report.RenderableNode
|
||||
type ByID []render.RenderableNode
|
||||
|
||||
func (r ByID) Len() int { return len(r) }
|
||||
func (r ByID) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
|
||||
func (r ByID) Less(i, j int) bool { return r[i].ID < r[j].ID }
|
||||
|
||||
func TestTopoDiff(t *testing.T) {
|
||||
nodea := report.RenderableNode{
|
||||
nodea := render.RenderableNode{
|
||||
ID: "nodea",
|
||||
LabelMajor: "Node A",
|
||||
LabelMinor: "'ts an a",
|
||||
@@ -30,14 +30,14 @@ func TestTopoDiff(t *testing.T) {
|
||||
"nodeb",
|
||||
"nodeq", // not the same anymore
|
||||
}
|
||||
nodeb := report.RenderableNode{
|
||||
nodeb := render.RenderableNode{
|
||||
ID: "nodeb",
|
||||
LabelMajor: "Node B",
|
||||
}
|
||||
|
||||
// Helper to make RenderableNode maps.
|
||||
nodes := func(ns ...report.RenderableNode) report.RenderableNodes {
|
||||
r := report.RenderableNodes{}
|
||||
nodes := func(ns ...render.RenderableNode) render.RenderableNodes {
|
||||
r := render.RenderableNodes{}
|
||||
for _, n := range ns {
|
||||
r[n.ID] = n
|
||||
}
|
||||
@@ -46,40 +46,40 @@ func TestTopoDiff(t *testing.T) {
|
||||
|
||||
for _, c := range []struct {
|
||||
label string
|
||||
have, want Diff
|
||||
have, want render.Diff
|
||||
}{
|
||||
{
|
||||
label: "basecase: empty -> something",
|
||||
have: TopoDiff(nodes(), nodes(nodea, nodeb)),
|
||||
want: Diff{
|
||||
Add: []report.RenderableNode{nodea, nodeb},
|
||||
have: render.TopoDiff(nodes(), nodes(nodea, nodeb)),
|
||||
want: render.Diff{
|
||||
Add: []render.RenderableNode{nodea, nodeb},
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "basecase: something -> empty",
|
||||
have: TopoDiff(nodes(nodea, nodeb), nodes()),
|
||||
want: Diff{
|
||||
have: render.TopoDiff(nodes(nodea, nodeb), nodes()),
|
||||
want: render.Diff{
|
||||
Remove: []string{"nodea", "nodeb"},
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "add and remove",
|
||||
have: TopoDiff(nodes(nodea), nodes(nodeb)),
|
||||
want: Diff{
|
||||
Add: []report.RenderableNode{nodeb},
|
||||
have: render.TopoDiff(nodes(nodea), nodes(nodeb)),
|
||||
want: render.Diff{
|
||||
Add: []render.RenderableNode{nodeb},
|
||||
Remove: []string{"nodea"},
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "no change",
|
||||
have: TopoDiff(nodes(nodea), nodes(nodea)),
|
||||
want: Diff{},
|
||||
have: render.TopoDiff(nodes(nodea), nodes(nodea)),
|
||||
want: render.Diff{},
|
||||
},
|
||||
{
|
||||
label: "change a single node",
|
||||
have: TopoDiff(nodes(nodea), nodes(nodeap)),
|
||||
want: Diff{
|
||||
Update: []report.RenderableNode{nodeap},
|
||||
have: render.TopoDiff(nodes(nodea), nodes(nodeap)),
|
||||
want: render.Diff{
|
||||
Update: []render.RenderableNode{nodeap},
|
||||
},
|
||||
},
|
||||
} {
|
||||
|
||||
@@ -79,39 +79,3 @@ func (m *EdgeMetadata) Flatten(other EdgeMetadata) {
|
||||
m.MaxConnCountTCP += other.MaxConnCountTCP
|
||||
}
|
||||
}
|
||||
|
||||
// Merge merges two sets of RenderableNodes
|
||||
func (rns RenderableNodes) Merge(other RenderableNodes) {
|
||||
for key, value := range other {
|
||||
if existing, ok := rns[key]; ok {
|
||||
existing.Merge(value)
|
||||
rns[key] = existing
|
||||
} else {
|
||||
rns[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Merge merges in another RenderableNode
|
||||
func (rn *RenderableNode) Merge(other RenderableNode) {
|
||||
if rn.LabelMajor == "" {
|
||||
rn.LabelMajor = other.LabelMajor
|
||||
}
|
||||
|
||||
if rn.LabelMinor == "" {
|
||||
rn.LabelMinor = other.LabelMinor
|
||||
}
|
||||
|
||||
if rn.Rank == "" {
|
||||
rn.Rank = other.Rank
|
||||
}
|
||||
|
||||
if rn.Pseudo != other.Pseudo {
|
||||
panic(rn.ID)
|
||||
}
|
||||
|
||||
rn.Adjacency = rn.Adjacency.Add(other.Adjacency...)
|
||||
rn.Origins = rn.Origins.Add(other.Origins...)
|
||||
|
||||
rn.Metadata.Merge(other.Metadata)
|
||||
}
|
||||
|
||||
@@ -45,48 +45,6 @@ const (
|
||||
HostNodeID = "host_node_id"
|
||||
)
|
||||
|
||||
// RenderableNode is the data type that's yielded to the JavaScript layer as
|
||||
// an element of a topology. It should contain information that's relevant
|
||||
// to rendering a node when there are many nodes visible at once.
|
||||
type RenderableNode struct {
|
||||
ID string `json:"id"` //
|
||||
LabelMajor string `json:"label_major"` // e.g. "process", human-readable
|
||||
LabelMinor string `json:"label_minor,omitempty"` // e.g. "hostname", human-readable, optional
|
||||
Rank string `json:"rank"` // to help the layout engine
|
||||
Pseudo bool `json:"pseudo,omitempty"` // sort-of a placeholder node, for rendering purposes
|
||||
Adjacency IDList `json:"adjacency,omitempty"` // Node IDs (in the same topology domain)
|
||||
Origins IDList `json:"origins,omitempty"` // Core node IDs that contributed information
|
||||
Metadata AggregateMetadata `json:"metadata"` // Numeric sums
|
||||
}
|
||||
|
||||
// RenderableNodes is a set of RenderableNodes
|
||||
type RenderableNodes map[string]RenderableNode
|
||||
|
||||
// DetailedNode is the data type that's yielded to the JavaScript layer when
|
||||
// we want deep information about an individual node.
|
||||
type DetailedNode struct {
|
||||
ID string `json:"id"`
|
||||
LabelMajor string `json:"label_major"`
|
||||
LabelMinor string `json:"label_minor,omitempty"`
|
||||
Pseudo bool `json:"pseudo,omitempty"`
|
||||
Tables []Table `json:"tables"`
|
||||
}
|
||||
|
||||
// Table is a dataset associated with a node. It will be displayed in the
|
||||
// detail panel when a user clicks on a node.
|
||||
type Table struct {
|
||||
Title string `json:"title"` // e.g. Bandwidth
|
||||
Numeric bool `json:"numeric"` // should the major column be right-aligned?
|
||||
Rows []Row `json:"rows"`
|
||||
}
|
||||
|
||||
// Row is a single entry in a Table dataset.
|
||||
type Row struct {
|
||||
Key string `json:"key"` // e.g. Ingress
|
||||
ValueMajor string `json:"value_major"` // e.g. 25
|
||||
ValueMinor string `json:"value_minor,omitempty"` // e.g. KB/s
|
||||
}
|
||||
|
||||
// TopologySelector selects a single topology from a report.
|
||||
type TopologySelector func(r Report) Topology
|
||||
|
||||
|
||||
Reference in New Issue
Block a user