Files
weave-scope/render/process.go
Matthias Radestock 15b605f804 make process-by-name topology show something (again)
...when 'Hide Unconnected' is selected, which is the default.

Here's the problem...

The connectedness filter looks for `is_connected` marks in the Latest
map of the nodes. The mark is added by ColorConnected, which is
invoked by ProcessRenderer. That in turn is the base renderer for
ProcessNameRenderer, which is what the process-by-name view
renders. However, the process2Names mapping does not propagate any
metadata, hence there are no `is_connected` marks on the result
nodes. Consequently they are all filtered out.

The problem was introduced in #3009, when I added the ability for
users to chose whether to show or hide unconnected
processes (previously unconnected processes were always hidden) -
looks like I failed to check that the process-by-name view was working
with the new filter. Oops.

The fix is to move the ColorConnected call from ProcessRenderer to the
higher-level renderers - ProcessWithContainerNameRenderer (which is
what the 'Processes' view renders) and ProcessNameRenderer.

This has the beneficial side effect of improving performance for other
renderers which invoke ProcessRenderer, none of which need
connectedness-coloring.

Fixes #3205
2018-06-03 07:01:40 +01:00

140 lines
4.2 KiB
Go

package render
import (
"github.com/weaveworks/scope/probe/docker"
"github.com/weaveworks/scope/probe/endpoint"
"github.com/weaveworks/scope/probe/process"
"github.com/weaveworks/scope/report"
)
// Constants are used in the tests.
const (
InboundMajor = "The Internet"
OutboundMajor = "The Internet"
InboundMinor = "Inbound connections"
OutboundMinor = "Outbound connections"
)
func renderProcesses(rpt report.Report) bool {
return len(rpt.Process.Nodes) >= 1
}
// ProcessRenderer is a Renderer which produces a renderable process
// graph by merging the endpoint graph and the process topology.
var ProcessRenderer = Memoise(endpoints2Processes{})
// processWithContainerNameRenderer is a Renderer which produces a process
// graph enriched with container names where appropriate
type processWithContainerNameRenderer struct {
Renderer
}
func (r processWithContainerNameRenderer) Render(rpt report.Report) Nodes {
processes := r.Renderer.Render(rpt)
containers := SelectContainer.Render(rpt)
outputs := make(report.Nodes, len(processes.Nodes))
for id, p := range processes.Nodes {
outputs[id] = p
containerID, ok := p.Latest.Lookup(docker.ContainerID)
if !ok {
continue
}
container, ok := containers.Nodes[report.MakeContainerNodeID(containerID)]
if !ok {
continue
}
propagateLatest(docker.ContainerName, container, p)
outputs[id] = p
}
return Nodes{Nodes: outputs, Filtered: processes.Filtered}
}
// ProcessWithContainerNameRenderer is a Renderer which produces a
// process graph enriched with container names where appropriate.
//
// It also colors connected nodes, so we can apply a filter to
// show/hide unconnected nodes depending on user choice.
//
// not memoised
var ProcessWithContainerNameRenderer = ColorConnected(processWithContainerNameRenderer{ProcessRenderer})
// ProcessNameRenderer is a Renderer which produces a renderable
// process name graph by munging the progess graph.
//
// It also colors connected nodes, so we can apply a filter to
// show/hide unconnected nodes depending on user choice.
//
// not memoised
var ProcessNameRenderer = ColorConnected(CustomRenderer{RenderFunc: processes2Names, Renderer: ProcessRenderer})
// endpoints2Processes joins the endpoint topology to the process
// topology, matching on hostID and pid.
type endpoints2Processes struct {
}
func (e endpoints2Processes) Render(rpt report.Report) Nodes {
if len(rpt.Process.Nodes) == 0 {
return Nodes{}
}
endpoints := SelectEndpoint.Render(rpt).Nodes
return MapEndpoints(
func(n report.Node) string {
pid, ok := n.Latest.Lookup(process.PID)
if !ok {
return ""
}
if hasMoreThanOneConnection(n, endpoints) {
return ""
}
hostID := report.ExtractHostID(n)
if hostID == "" {
return ""
}
return report.MakeProcessNodeID(hostID, pid)
}, report.Process).Render(rpt)
}
// When there is more than one connection originating from a source
// endpoint, we cannot be sure that its pid is associated with all of
// them, since the source endpoint may have been re-used by a
// different process. See #2665. It is better to drop such an endpoint
// than risk rendering bogus connections. Aliased connections - when
// all the remote endpoints represent the same logical endpoint, due
// to NATing - are fine though.
func hasMoreThanOneConnection(n report.Node, endpoints report.Nodes) bool {
if len(n.Adjacency) < 2 {
return false
}
firstRealEndpointID := ""
for _, endpointID := range n.Adjacency {
if ep, ok := endpoints[endpointID]; ok {
if copyID, _, ok := ep.Latest.LookupEntry(endpoint.CopyOf); ok {
endpointID = copyID
}
}
if firstRealEndpointID == "" {
firstRealEndpointID = endpointID
} else if firstRealEndpointID != endpointID {
return true
}
}
return false
}
var processNameTopology = MakeGroupNodeTopology(report.Process, process.Name)
// processes2Names maps process Nodes to Nodes for each process name.
func processes2Names(processes Nodes) Nodes {
ret := newJoinResults(nil)
for _, n := range processes.Nodes {
if n.Topology == Pseudo {
ret.passThrough(n)
} else if name, ok := n.Latest.Lookup(process.Name); ok {
ret.addChildAndChildren(n, name, processNameTopology)
}
}
return ret.result(processes)
}