mirror of
https://github.com/weaveworks/scope.git
synced 2026-03-03 02:00:43 +00:00
Merge pull request #387 from weaveworks/origin-host-labels
Improve titles and context of tables in the details pane
This commit is contained in:
@@ -64,23 +64,27 @@ func (r sortableRows) Less(i, j int) bool {
|
||||
}
|
||||
}
|
||||
|
||||
type tables []Table
|
||||
type sortableTables []Table
|
||||
|
||||
func (t tables) Len() int { return len(t) }
|
||||
func (t tables) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
|
||||
func (t tables) Less(i, j int) bool { return t[i].Rank > t[j].Rank }
|
||||
func (t sortableTables) Len() int { return len(t) }
|
||||
func (t sortableTables) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
|
||||
func (t sortableTables) 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{}
|
||||
tables := sortableTables{}
|
||||
|
||||
// Figure out if multiple hosts/containers are referenced by the renderableNode
|
||||
multiContainer, multiHost := getRenderingContext(r, n)
|
||||
|
||||
// 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 {
|
||||
if table, ok := OriginTable(r, id, multiHost, multiContainer); ok {
|
||||
tables = append(tables, table)
|
||||
} else if _, ok := r.Endpoint.NodeMetadatas[id]; ok {
|
||||
connections = append(connections, connectionDetailsRows(r.Endpoint, id)...)
|
||||
@@ -89,7 +93,9 @@ func MakeDetailedNode(r report.Report, n RenderableNode) DetailedNode {
|
||||
}
|
||||
}
|
||||
|
||||
addConnectionsTable(&tables, connections, r, n)
|
||||
if table, ok := connectionsTable(connections, r, n); ok {
|
||||
tables = append(tables, table)
|
||||
}
|
||||
|
||||
// Sort tables by rank
|
||||
sort.Sort(tables)
|
||||
@@ -103,7 +109,29 @@ func MakeDetailedNode(r report.Report, n RenderableNode) DetailedNode {
|
||||
}
|
||||
}
|
||||
|
||||
func addConnectionsTable(tables *tables, connections []Row, r report.Report, n RenderableNode) {
|
||||
func getRenderingContext(r report.Report, n RenderableNode) (multiContainer bool, multiHost bool) {
|
||||
originHosts := make(map[string]struct{})
|
||||
originContainers := make(map[string]struct{})
|
||||
for _, id := range n.Origins {
|
||||
for _, topology := range r.Topologies() {
|
||||
if nmd, ok := topology.NodeMetadatas[id]; ok {
|
||||
originHosts[report.ExtractHostID(nmd)] = struct{}{}
|
||||
if id, ok := nmd.Metadata[docker.ContainerID]; ok {
|
||||
originContainers[id] = struct{}{}
|
||||
}
|
||||
}
|
||||
// Return early if possible
|
||||
multiHost = len(originHosts) > 1
|
||||
multiContainer = len(originContainers) > 1
|
||||
if multiHost && multiContainer {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func connectionsTable(connections []Row, r report.Report, n RenderableNode) (Table, bool) {
|
||||
sec := r.Window.Seconds()
|
||||
rate := func(u *uint64) (float64, bool) {
|
||||
if u == nil {
|
||||
@@ -149,18 +177,19 @@ func addConnectionsTable(tables *tables, connections []Row, r report.Report, n R
|
||||
rows = append(rows, connections...)
|
||||
}
|
||||
if len(rows) > 0 {
|
||||
*tables = append(*tables, Table{"Connections", true, connectionsRank, rows})
|
||||
return Table{"Connections", true, connectionsRank, rows}, true
|
||||
}
|
||||
return Table{}, false
|
||||
}
|
||||
|
||||
// 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.Report, originID string) (Table, bool) {
|
||||
func OriginTable(r report.Report, originID string, addHostTags bool, addContainerTags bool) (Table, bool) {
|
||||
if nmd, ok := r.Process.NodeMetadatas[originID]; ok {
|
||||
return processOriginTable(nmd)
|
||||
return processOriginTable(nmd, addHostTags, addContainerTags)
|
||||
}
|
||||
if nmd, ok := r.Container.NodeMetadatas[originID]; ok {
|
||||
return containerOriginTable(nmd)
|
||||
return containerOriginTable(nmd, addHostTags)
|
||||
}
|
||||
if nmd, ok := r.ContainerImage.NodeMetadatas[originID]; ok {
|
||||
return containerImageOriginTable(nmd)
|
||||
@@ -224,11 +253,9 @@ func connectionDetailsRows(topology report.Topology, originID string) []Row {
|
||||
return rows
|
||||
}
|
||||
|
||||
func processOriginTable(nmd report.NodeMetadata) (Table, bool) {
|
||||
func processOriginTable(nmd report.NodeMetadata, addHostTag bool, addContainerTag bool) (Table, bool) {
|
||||
rows := []Row{}
|
||||
for _, tuple := range []struct{ key, human string }{
|
||||
{process.Comm, "Name"},
|
||||
{process.PID, "PID"},
|
||||
{process.PPID, "Parent PID"},
|
||||
{process.Cmdline, "Command"},
|
||||
{process.Threads, "# Threads"},
|
||||
@@ -238,19 +265,37 @@ func processOriginTable(nmd report.NodeMetadata) (Table, bool) {
|
||||
}
|
||||
}
|
||||
|
||||
if containerID, ok := nmd.Metadata[docker.ContainerID]; ok && addContainerTag {
|
||||
rows = append([]Row{{Key: "Container ID", ValueMajor: containerID}}, rows...)
|
||||
}
|
||||
|
||||
if addHostTag {
|
||||
rows = append([]Row{{Key: "Host", ValueMajor: report.ExtractHostID(nmd)}}, rows...)
|
||||
}
|
||||
|
||||
var (
|
||||
title = "Process"
|
||||
name, commFound = nmd.Metadata[process.Comm]
|
||||
pid, pidFound = nmd.Metadata[process.PID]
|
||||
)
|
||||
if commFound {
|
||||
title += ` "` + name + `"`
|
||||
}
|
||||
if pidFound {
|
||||
title += " (" + pid + ")"
|
||||
}
|
||||
return Table{
|
||||
Title: "Origin Process",
|
||||
Title: title,
|
||||
Numeric: false,
|
||||
Rows: rows,
|
||||
Rank: processRank,
|
||||
}, len(rows) > 0
|
||||
}, len(rows) > 0 || commFound || pidFound
|
||||
}
|
||||
|
||||
func containerOriginTable(nmd report.NodeMetadata) (Table, bool) {
|
||||
func containerOriginTable(nmd report.NodeMetadata, addHostTag bool) (Table, bool) {
|
||||
rows := []Row{}
|
||||
for _, tuple := range []struct{ key, human string }{
|
||||
{docker.ContainerID, "ID"},
|
||||
{docker.ContainerName, "Name"},
|
||||
{docker.ImageID, "Image ID"},
|
||||
{docker.ContainerPorts, "Ports"},
|
||||
{docker.ContainerCreated, "Created"},
|
||||
@@ -268,37 +313,54 @@ func containerOriginTable(nmd report.NodeMetadata) (Table, bool) {
|
||||
rows = append(rows, Row{Key: "Memory Usage (MB):", ValueMajor: memoryStr, ValueMinor: ""})
|
||||
}
|
||||
}
|
||||
if addHostTag {
|
||||
rows = append([]Row{{Key: "Host", ValueMajor: report.ExtractHostID(nmd)}}, rows...)
|
||||
}
|
||||
|
||||
var (
|
||||
title = "Container"
|
||||
name, nameFound = nmd.Metadata[docker.ContainerName]
|
||||
)
|
||||
if nameFound {
|
||||
title += ` "` + name + `"`
|
||||
}
|
||||
|
||||
return Table{
|
||||
Title: "Origin Container",
|
||||
Title: title,
|
||||
Numeric: false,
|
||||
Rows: rows,
|
||||
Rank: containerRank,
|
||||
}, len(rows) > 0
|
||||
}, len(rows) > 0 || nameFound
|
||||
}
|
||||
|
||||
func containerImageOriginTable(nmd report.NodeMetadata) (Table, bool) {
|
||||
rows := []Row{}
|
||||
for _, tuple := range []struct{ key, human string }{
|
||||
{docker.ImageID, "Image ID"},
|
||||
{docker.ImageName, "Image name"},
|
||||
} {
|
||||
if val, ok := nmd.Metadata[tuple.key]; ok {
|
||||
rows = append(rows, Row{Key: tuple.human, ValueMajor: val, ValueMinor: ""})
|
||||
}
|
||||
}
|
||||
title := "Container Image"
|
||||
var (
|
||||
nameFound bool
|
||||
name string
|
||||
)
|
||||
if name, nameFound = nmd.Metadata[docker.ImageName]; nameFound {
|
||||
title += ` "` + name + `"`
|
||||
}
|
||||
return Table{
|
||||
Title: "Origin Container Image",
|
||||
Title: title,
|
||||
Numeric: false,
|
||||
Rows: rows,
|
||||
Rank: containerImageRank,
|
||||
}, len(rows) > 0
|
||||
}, len(rows) > 0 || nameFound
|
||||
}
|
||||
|
||||
func hostOriginTable(nmd report.NodeMetadata) (Table, bool) {
|
||||
rows := []Row{}
|
||||
for _, tuple := range []struct{ key, human string }{
|
||||
{host.HostName, "Host name"},
|
||||
{host.Load, "Load"},
|
||||
{host.OS, "Operating system"},
|
||||
{host.KernelVersion, "Kernel version"},
|
||||
@@ -309,10 +371,18 @@ func hostOriginTable(nmd report.NodeMetadata) (Table, bool) {
|
||||
}
|
||||
}
|
||||
|
||||
title := "Host"
|
||||
var (
|
||||
name string
|
||||
foundName bool
|
||||
)
|
||||
if name, foundName = nmd.Metadata[host.HostName]; foundName {
|
||||
title += ` "` + name + `"`
|
||||
}
|
||||
return Table{
|
||||
Title: "Origin Host",
|
||||
Title: title,
|
||||
Numeric: false,
|
||||
Rows: rows,
|
||||
Rank: hostRank,
|
||||
}, len(rows) > 0
|
||||
}, len(rows) > 0 || foundName
|
||||
}
|
||||
|
||||
@@ -10,31 +10,26 @@ import (
|
||||
)
|
||||
|
||||
func TestOriginTable(t *testing.T) {
|
||||
if _, ok := render.OriginTable(test.Report, "not-found"); ok {
|
||||
if _, ok := render.OriginTable(test.Report, "not-found", false, false); ok {
|
||||
t.Errorf("unknown origin ID gave unexpected success")
|
||||
}
|
||||
for originID, want := range map[string]render.Table{
|
||||
test.ServerProcessNodeID: {
|
||||
Title: "Origin Process",
|
||||
Numeric: false,
|
||||
Rank: 2,
|
||||
Rows: []render.Row{
|
||||
{"Name", "apache", "", false},
|
||||
{"PID", test.ServerPID, "", false},
|
||||
},
|
||||
},
|
||||
for originID, want := range map[string]render.Table{test.ServerProcessNodeID: {
|
||||
Title: fmt.Sprintf(`Process "apache" (%s)`, test.ServerPID),
|
||||
Numeric: false,
|
||||
Rank: 2,
|
||||
Rows: []render.Row{},
|
||||
},
|
||||
test.ServerHostNodeID: {
|
||||
Title: "Origin Host",
|
||||
Title: fmt.Sprintf("Host %q", test.ServerHostName),
|
||||
Numeric: false,
|
||||
Rank: 1,
|
||||
Rows: []render.Row{
|
||||
{"Host name", test.ServerHostName, "", false},
|
||||
{"Load", "0.01 0.01 0.01", "", false},
|
||||
{"Operating system", "Linux", "", false},
|
||||
},
|
||||
},
|
||||
} {
|
||||
have, ok := render.OriginTable(test.Report, originID)
|
||||
have, ok := render.OriginTable(test.Report, originID, false, false)
|
||||
if !ok {
|
||||
t.Errorf("%q: not OK", originID)
|
||||
continue
|
||||
@@ -43,6 +38,39 @@ func TestOriginTable(t *testing.T) {
|
||||
t.Errorf("%q: %s", originID, test.Diff(want, have))
|
||||
}
|
||||
}
|
||||
|
||||
// Test host/container tags
|
||||
for originID, want := range map[string]render.Table{
|
||||
test.ServerProcessNodeID: {
|
||||
Title: fmt.Sprintf(`Process "apache" (%s)`, test.ServerPID),
|
||||
Numeric: false,
|
||||
Rank: 2,
|
||||
Rows: []render.Row{
|
||||
{"Host", test.ServerHostID, "", false},
|
||||
{"Container ID", test.ServerContainerID, "", false},
|
||||
},
|
||||
},
|
||||
test.ServerContainerNodeID: {
|
||||
Title: `Container "server"`,
|
||||
Numeric: false,
|
||||
Rank: 3,
|
||||
Rows: []render.Row{
|
||||
{"Host", test.ServerHostID, "", false},
|
||||
{"ID", test.ServerContainerID, "", false},
|
||||
{"Image ID", test.ServerContainerImageID, "", false},
|
||||
},
|
||||
},
|
||||
} {
|
||||
have, ok := render.OriginTable(test.Report, originID, true, true)
|
||||
if !ok {
|
||||
t.Errorf("%q: not OK", originID)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(want, have) {
|
||||
t.Errorf("%q: %s", originID, test.Diff(want, have))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestMakeDetailedHostNode(t *testing.T) {
|
||||
@@ -55,15 +83,10 @@ func TestMakeDetailedHostNode(t *testing.T) {
|
||||
Pseudo: false,
|
||||
Tables: []render.Table{
|
||||
{
|
||||
Title: "Origin Host",
|
||||
Title: fmt.Sprintf("Host %q", test.ClientHostName),
|
||||
Numeric: false,
|
||||
Rank: 1,
|
||||
Rows: []render.Row{
|
||||
{
|
||||
Key: "Host name",
|
||||
ValueMajor: "client.hostname.com",
|
||||
ValueMinor: "",
|
||||
},
|
||||
{
|
||||
Key: "Load",
|
||||
ValueMajor: "0.01 0.01 0.01",
|
||||
@@ -117,30 +140,25 @@ func TestMakeDetailedContainerNode(t *testing.T) {
|
||||
Pseudo: false,
|
||||
Tables: []render.Table{
|
||||
{
|
||||
Title: "Origin Container",
|
||||
Title: `Container "server"`,
|
||||
Numeric: false,
|
||||
Rank: 3,
|
||||
Rows: []render.Row{
|
||||
{"ID", test.ServerContainerID, "", false},
|
||||
{"Name", "server", "", false},
|
||||
{"Image ID", test.ServerContainerImageID, "", false},
|
||||
},
|
||||
},
|
||||
{
|
||||
Title: "Origin Process",
|
||||
Title: fmt.Sprintf(`Process "apache" (%s)`, test.ServerPID),
|
||||
Numeric: false,
|
||||
Rank: 2,
|
||||
Rows: []render.Row{
|
||||
{"Name", "apache", "", false},
|
||||
{"PID", test.ServerPID, "", false},
|
||||
},
|
||||
Rows: []render.Row{},
|
||||
},
|
||||
{
|
||||
Title: "Origin Host",
|
||||
Title: fmt.Sprintf("Host %q", test.ServerHostName),
|
||||
Numeric: false,
|
||||
Rank: 1,
|
||||
Rows: []render.Row{
|
||||
{"Host name", test.ServerHostName, "", false},
|
||||
{"Load", "0.01 0.01 0.01", "", false},
|
||||
{"Operating system", "Linux", "", false},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user