diff --git a/render/detailed_node.go b/render/detailed_node.go index a2df21083..2d7e9f34b 100644 --- a/render/detailed_node.go +++ b/render/detailed_node.go @@ -13,13 +13,11 @@ import ( const ( mb = 1 << 20 - connectionsRank = 100 containerImageRank = 4 containerRank = 3 processRank = 2 hostRank = 1 - endpointRank = 0 // this is the least important table, so sort to bottom - addressRank = 0 // also least important; never merged with endpoints + connectionsRank = 0 // keep connections at the bottom until they are expandable in the UI ) // DetailedNode is the data type that's yielded to the JavaScript layer when @@ -46,14 +44,14 @@ 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 - Expandable bool `json:expandable,omitempty` // Whether it can be expanded (hidden by default) + Expandable bool `json:"expandable,omitempty"` // Whether it can be expanded (hidden by default) } -type rows []Row +type sortableRows []Row -func (r rows) Len() int { return len(r) } -func (r rows) Swap(i, j int) { r[i], r[j] = r[j], r[i] } -func (r rows) Less(i, j int) bool { +func (r sortableRows) Len() int { return len(r) } +func (r sortableRows) Swap(i, j int) { r[i], r[j] = r[j], r[i] } +func (r sortableRows) Less(i, j int) bool { switch { case r[i].Key != r[j].Key: return r[i].Key < r[j].Key @@ -75,6 +73,37 @@ func (t tables) Less(i, j int) bool { return t[i].Rank > t[j].Rank } // 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.Report, n RenderableNode) DetailedNode { + tables := tables{} + // RenderableNode may be the result of merge operation(s), and so may have + // multiple origins. The ultimate goal here is to generate tables to view + // in the UI, so we skip the intermediate representations, but we could + // add them later. + connections := []Row{} + for _, id := range n.Origins { + if table, ok := OriginTable(r, id); ok { + tables = append(tables, table) + } else if _, ok := r.Endpoint.NodeMetadatas[id]; ok { + connections = append(connections, connectionDetailsRows(r.Endpoint, id)...) + } else if _, ok := r.Address.NodeMetadatas[id]; ok { + connections = append(connections, connectionDetailsRows(r.Address, id)...) + } + } + + addConnectionsTable(&tables, connections, r, n) + + // Sort tables by rank + sort.Sort(tables) + + return DetailedNode{ + ID: n.ID, + LabelMajor: n.LabelMajor, + LabelMinor: n.LabelMinor, + Pseudo: n.Pseudo, + Tables: tables, + } +} + +func addConnectionsTable(tables *tables, connections []Row, r report.Report, n RenderableNode) { sec := r.Window.Seconds() rate := func(u *uint64) (float64, bool) { if u == nil { @@ -96,59 +125,31 @@ func MakeDetailedNode(r report.Report, n RenderableNode) DetailedNode { } } - tables := tables{} - { - rows := []Row{} - if n.EdgeMetadata.MaxConnCountTCP != nil { - rows = append(rows, Row{"TCP connections", strconv.FormatUint(*n.EdgeMetadata.MaxConnCountTCP, 10), "", false}) - } - if rate, ok := rate(n.EdgeMetadata.EgressPacketCount); ok { - rows = append(rows, Row{"Egress packet rate", fmt.Sprintf("%.0f", rate), "packets/sec", false}) - } - if rate, ok := rate(n.EdgeMetadata.IngressPacketCount); ok { - rows = append(rows, Row{"Ingress packet rate", fmt.Sprintf("%.0f", rate), "packets/sec", false}) - } - if rate, ok := rate(n.EdgeMetadata.EgressByteCount); ok { - s, unit := shortenByteRate(rate) - rows = append(rows, Row{"Egress byte rate", s, unit, false}) - } - if rate, ok := rate(n.EdgeMetadata.IngressByteCount); ok { - s, unit := shortenByteRate(rate) - rows = append(rows, Row{"Ingress byte rate", s, unit, false}) - } - if len(rows) > 0 { - tables = append(tables, Table{"Connections", true, connectionsRank, rows}) - } + rows := []Row{} + if n.EdgeMetadata.MaxConnCountTCP != nil { + rows = append(rows, Row{"TCP connections", strconv.FormatUint(*n.EdgeMetadata.MaxConnCountTCP, 10), "", false}) } - - // RenderableNode may be the result of merge operation(s), and so may have - // multiple origins. The ultimate goal here is to generate tables to view - // in the UI, so we skip the intermediate representations, but we could - // add them later. - connections := []Row{} - for _, id := range n.Origins { - if table, ok := OriginTable(r, id); ok { - tables = append(tables, table) - } else if _, ok := r.Endpoint.NodeMetadatas[id]; ok { - connections = append(connections, connectionDetailsRows(r.Endpoint, id)...) - } else if _, ok := r.Address.NodeMetadatas[id]; ok { - connections = append(connections, connectionDetailsRows(r.Address, id)...) - } + if rate, ok := rate(n.EdgeMetadata.EgressPacketCount); ok { + rows = append(rows, Row{"Egress packet rate", fmt.Sprintf("%.0f", rate), "packets/sec", false}) + } + if rate, ok := rate(n.EdgeMetadata.IngressPacketCount); ok { + rows = append(rows, Row{"Ingress packet rate", fmt.Sprintf("%.0f", rate), "packets/sec", false}) + } + if rate, ok := rate(n.EdgeMetadata.EgressByteCount); ok { + s, unit := shortenByteRate(rate) + rows = append(rows, Row{"Egress byte rate", s, unit, false}) + } + if rate, ok := rate(n.EdgeMetadata.IngressByteCount); ok { + s, unit := shortenByteRate(rate) + rows = append(rows, Row{"Ingress byte rate", s, unit, false}) } if len(connections) > 0 { - sort.Sort(rows(connections)) - tables = append(tables, connectionDetailsTable(connections)) + sort.Sort(sortableRows(connections)) + rows = append(rows, Row{Key: "Client", ValueMajor: "Server", Expandable: true}) + rows = append(rows, connections...) } - - // Sort tables by rank - sort.Sort(tables) - - return DetailedNode{ - ID: n.ID, - LabelMajor: n.LabelMajor, - LabelMinor: n.LabelMinor, - Pseudo: n.Pseudo, - Tables: tables, + if len(rows) > 0 { + *tables = append(*tables, Table{"Connections", true, connectionsRank, rows}) } } @@ -195,6 +196,7 @@ func connectionDetailsRows(topology report.Topology, originID string) []Row { rows = append(rows, Row{ Key: local, ValueMajor: remote, + Expandable: true, }) } // Next, scan the topology for incoming connections to this node. @@ -216,20 +218,12 @@ func connectionDetailsRows(topology report.Topology, originID string) []Row { rows = append(rows, Row{ Key: remote, ValueMajor: local, + Expandable: true, }) } return rows } -func connectionDetailsTable(connectionRows []Row) Table { - return Table{ - Title: "Connection Details", - Numeric: false, - Rows: append([]Row{{Key: "Client", ValueMajor: "Server"}}, connectionRows...), - Rank: endpointRank, - } -} - func processOriginTable(nmd report.NodeMetadata) (Table, bool) { rows := []Row{} for _, tuple := range []struct{ key, human string }{ diff --git a/render/detailed_node_test.go b/render/detailed_node_test.go index e3c6969e9..11742d62e 100644 --- a/render/detailed_node_test.go +++ b/render/detailed_node_test.go @@ -54,19 +54,6 @@ func TestMakeDetailedHostNode(t *testing.T) { LabelMinor: "hostname.com", Pseudo: false, Tables: []render.Table{ - { - Title: "Connections", - Numeric: true, - Rank: 100, - Rows: []render.Row{ - { - Key: "TCP connections", - ValueMajor: "3", - ValueMinor: "", - Expandable: false, - }, - }, - }, { Title: "Origin Host", Numeric: false, @@ -76,38 +63,40 @@ func TestMakeDetailedHostNode(t *testing.T) { Key: "Host name", ValueMajor: "client.hostname.com", ValueMinor: "", - Expandable: false, }, { Key: "Load", ValueMajor: "0.01 0.01 0.01", ValueMinor: "", - Expandable: false, }, { Key: "Operating system", ValueMajor: "Linux", ValueMinor: "", - Expandable: false, }, }, }, { - Title: "Connection Details", - Numeric: false, + Title: "Connections", + Numeric: true, Rank: 0, Rows: []render.Row{ + { + Key: "TCP connections", + ValueMajor: "3", + ValueMinor: "", + }, { Key: "Client", ValueMajor: "Server", ValueMinor: "", - Expandable: false, + Expandable: true, }, { Key: "10.10.10.20", ValueMajor: "192.168.1.1", ValueMinor: "", - Expandable: false, + Expandable: true, }, }, }, @@ -127,15 +116,6 @@ func TestMakeDetailedContainerNode(t *testing.T) { LabelMinor: test.ServerHostName, Pseudo: false, Tables: []render.Table{ - { - Title: "Connections", - Numeric: true, - Rank: 100, - Rows: []render.Row{ - {"Egress packet rate", "105", "packets/sec", false}, - {"Egress byte rate", "1.0", "KBps", false}, - }, - }, { Title: "Origin Container", Numeric: false, @@ -166,45 +146,48 @@ func TestMakeDetailedContainerNode(t *testing.T) { }, }, { - Title: "Connection Details", - Numeric: false, + Title: "Connections", + Numeric: true, + Rank: 0, Rows: []render.Row{ - {"Client", "Server", "", false}, + {"Egress packet rate", "105", "packets/sec", false}, + {"Egress byte rate", "1.0", "KBps", false}, + {"Client", "Server", "", true}, { fmt.Sprintf("%s:%s", test.UnknownClient1IP, test.ClientPort54010), fmt.Sprintf("%s:%s", test.ServerIP, test.ServerPort), "", - false, + true, }, { fmt.Sprintf("%s:%s", test.UnknownClient1IP, test.ClientPort54020), fmt.Sprintf("%s:%s", test.ServerIP, test.ServerPort), "", - false, + true, }, { fmt.Sprintf("%s:%s", test.UnknownClient3IP, test.ClientPort54020), fmt.Sprintf("%s:%s", test.ServerIP, test.ServerPort), "", - false, + true, }, { fmt.Sprintf("%s:%s", test.ClientIP, test.ClientPort54001), fmt.Sprintf("%s:%s", test.ServerIP, test.ServerPort), "", - false, + true, }, { fmt.Sprintf("%s:%s", test.ClientIP, test.ClientPort54002), fmt.Sprintf("%s:%s", test.ServerIP, test.ServerPort), "", - false, + true, }, { fmt.Sprintf("%s:%s", test.RandomClientIP, test.ClientPort12345), fmt.Sprintf("%s:%s", test.ServerIP, test.ServerPort), "", - false, + true, }, }, },