diff --git a/render/detailed_node.go b/render/detailed_node.go index 2f329228a..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,13 +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) } -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 @@ -74,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 { @@ -95,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), ""}) - } - if rate, ok := rate(n.EdgeMetadata.EgressPacketCount); ok { - rows = append(rows, Row{"Egress packet rate", fmt.Sprintf("%.0f", rate), "packets/sec"}) - } - if rate, ok := rate(n.EdgeMetadata.IngressPacketCount); ok { - rows = append(rows, Row{"Ingress packet rate", fmt.Sprintf("%.0f", rate), "packets/sec"}) - } - if rate, ok := rate(n.EdgeMetadata.EgressByteCount); ok { - s, unit := shortenByteRate(rate) - rows = append(rows, Row{"Egress byte rate", s, unit}) - } - if rate, ok := rate(n.EdgeMetadata.IngressByteCount); ok { - s, unit := shortenByteRate(rate) - rows = append(rows, Row{"Ingress byte rate", s, unit}) - } - 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}) } } @@ -194,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. @@ -215,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 20b6f97cc..11742d62e 100644 --- a/render/detailed_node_test.go +++ b/render/detailed_node_test.go @@ -19,8 +19,8 @@ func TestOriginTable(t *testing.T) { Numeric: false, Rank: 2, Rows: []render.Row{ - {"Name", "apache", ""}, - {"PID", test.ServerPID, ""}, + {"Name", "apache", "", false}, + {"PID", test.ServerPID, "", false}, }, }, test.ServerHostNodeID: { @@ -28,9 +28,9 @@ func TestOriginTable(t *testing.T) { Numeric: false, Rank: 1, Rows: []render.Row{ - {"Host name", test.ServerHostName, ""}, - {"Load", "0.01 0.01 0.01", ""}, - {"Operating system", "Linux", ""}, + {"Host name", test.ServerHostName, "", false}, + {"Load", "0.01 0.01 0.01", "", false}, + {"Operating system", "Linux", "", false}, }, }, } { @@ -54,18 +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: "", - }, - }, - }, { Title: "Origin Host", Numeric: false, @@ -89,19 +77,26 @@ func TestMakeDetailedHostNode(t *testing.T) { }, }, { - 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: true, }, { Key: "10.10.10.20", ValueMajor: "192.168.1.1", ValueMinor: "", + Expandable: true, }, }, }, @@ -121,23 +116,14 @@ 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"}, - {"Egress byte rate", "1.0", "KBps"}, - }, - }, { Title: "Origin Container", Numeric: false, Rank: 3, Rows: []render.Row{ - {"ID", test.ServerContainerID, ""}, - {"Name", "server", ""}, - {"Image ID", test.ServerContainerImageID, ""}, + {"ID", test.ServerContainerID, "", false}, + {"Name", "server", "", false}, + {"Image ID", test.ServerContainerImageID, "", false}, }, }, { @@ -145,8 +131,8 @@ func TestMakeDetailedContainerNode(t *testing.T) { Numeric: false, Rank: 2, Rows: []render.Row{ - {"Name", "apache", ""}, - {"PID", test.ServerPID, ""}, + {"Name", "apache", "", false}, + {"PID", test.ServerPID, "", false}, }, }, { @@ -154,45 +140,54 @@ func TestMakeDetailedContainerNode(t *testing.T) { Numeric: false, Rank: 1, Rows: []render.Row{ - {"Host name", test.ServerHostName, ""}, - {"Load", "0.01 0.01 0.01", ""}, - {"Operating system", "Linux", ""}, + {"Host name", test.ServerHostName, "", false}, + {"Load", "0.01 0.01 0.01", "", false}, + {"Operating system", "Linux", "", false}, }, }, { - Title: "Connection Details", - Numeric: false, + Title: "Connections", + Numeric: true, + Rank: 0, Rows: []render.Row{ - {"Client", "Server", ""}, + {"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), "", + true, }, { fmt.Sprintf("%s:%s", test.UnknownClient1IP, test.ClientPort54020), fmt.Sprintf("%s:%s", test.ServerIP, test.ServerPort), "", + true, }, { fmt.Sprintf("%s:%s", test.UnknownClient3IP, test.ClientPort54020), fmt.Sprintf("%s:%s", test.ServerIP, test.ServerPort), "", + true, }, { fmt.Sprintf("%s:%s", test.ClientIP, test.ClientPort54001), fmt.Sprintf("%s:%s", test.ServerIP, test.ServerPort), "", + true, }, { fmt.Sprintf("%s:%s", test.ClientIP, test.ClientPort54002), fmt.Sprintf("%s:%s", test.ServerIP, test.ServerPort), "", + true, }, { fmt.Sprintf("%s:%s", test.RandomClientIP, test.ClientPort12345), fmt.Sprintf("%s:%s", test.ServerIP, test.ServerPort), "", + true, }, }, },