From c78f9d812cbd1a846c9bc105fb74da5c5ac4a4d3 Mon Sep 17 00:00:00 2001 From: Alfonso Acosta Date: Thu, 20 Aug 2015 15:26:23 +0000 Subject: [PATCH 1/2] Add Expandable attribute to detailed-pane rows --- render/detailed_node.go | 11 +++++---- render/detailed_node_test.go | 44 +++++++++++++++++++++++------------- 2 files changed, 34 insertions(+), 21 deletions(-) diff --git a/render/detailed_node.go b/render/detailed_node.go index 2f329228a..a2df21083 100644 --- a/render/detailed_node.go +++ b/render/detailed_node.go @@ -46,6 +46,7 @@ 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 @@ -99,21 +100,21 @@ func MakeDetailedNode(r report.Report, n RenderableNode) DetailedNode { { rows := []Row{} if n.EdgeMetadata.MaxConnCountTCP != nil { - rows = append(rows, Row{"TCP connections", strconv.FormatUint(*n.EdgeMetadata.MaxConnCountTCP, 10), ""}) + 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"}) + 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"}) + 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}) + 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}) + rows = append(rows, Row{"Ingress byte rate", s, unit, false}) } if len(rows) > 0 { tables = append(tables, Table{"Connections", true, connectionsRank, rows}) diff --git a/render/detailed_node_test.go b/render/detailed_node_test.go index 20b6f97cc..e3c6969e9 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}, }, }, } { @@ -63,6 +63,7 @@ func TestMakeDetailedHostNode(t *testing.T) { Key: "TCP connections", ValueMajor: "3", ValueMinor: "", + Expandable: false, }, }, }, @@ -75,16 +76,19 @@ 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, }, }, }, @@ -97,11 +101,13 @@ func TestMakeDetailedHostNode(t *testing.T) { Key: "Client", ValueMajor: "Server", ValueMinor: "", + Expandable: false, }, { Key: "10.10.10.20", ValueMajor: "192.168.1.1", ValueMinor: "", + Expandable: false, }, }, }, @@ -126,8 +132,8 @@ func TestMakeDetailedContainerNode(t *testing.T) { Numeric: true, Rank: 100, Rows: []render.Row{ - {"Egress packet rate", "105", "packets/sec"}, - {"Egress byte rate", "1.0", "KBps"}, + {"Egress packet rate", "105", "packets/sec", false}, + {"Egress byte rate", "1.0", "KBps", false}, }, }, { @@ -135,9 +141,9 @@ func TestMakeDetailedContainerNode(t *testing.T) { 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 +151,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 +160,51 @@ 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, Rows: []render.Row{ - {"Client", "Server", ""}, + {"Client", "Server", "", false}, { fmt.Sprintf("%s:%s", test.UnknownClient1IP, test.ClientPort54010), fmt.Sprintf("%s:%s", test.ServerIP, test.ServerPort), "", + false, }, { fmt.Sprintf("%s:%s", test.UnknownClient1IP, test.ClientPort54020), fmt.Sprintf("%s:%s", test.ServerIP, test.ServerPort), "", + false, }, { fmt.Sprintf("%s:%s", test.UnknownClient3IP, test.ClientPort54020), fmt.Sprintf("%s:%s", test.ServerIP, test.ServerPort), "", + false, }, { fmt.Sprintf("%s:%s", test.ClientIP, test.ClientPort54001), fmt.Sprintf("%s:%s", test.ServerIP, test.ServerPort), "", + false, }, { fmt.Sprintf("%s:%s", test.ClientIP, test.ClientPort54002), fmt.Sprintf("%s:%s", test.ServerIP, test.ServerPort), "", + false, }, { fmt.Sprintf("%s:%s", test.RandomClientIP, test.ClientPort12345), fmt.Sprintf("%s:%s", test.ServerIP, test.ServerPort), "", + false, }, }, }, From 37307c26b912eaab3e3a04898bf52d71f2681b6b Mon Sep 17 00:00:00 2001 From: Alfonso Acosta Date: Thu, 20 Aug 2015 15:59:13 +0000 Subject: [PATCH 2/2] Merge 'Connections' and 'Connection Details' in node details pane * Now the Connections table appears at the end of the pane * Connection details are expandable (still not implemented in the UI but backwards compatible). --- render/detailed_node.go | 126 +++++++++++++++++------------------ render/detailed_node_test.go | 59 ++++++---------- 2 files changed, 81 insertions(+), 104 deletions(-) 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, }, }, },