mirror of
https://github.com/weaveworks/scope.git
synced 2026-03-03 02:00:43 +00:00
We used to ignore source endpoints that are associated with multiple destination endpoints, which is a partial workaround for our inability to correctly represent two connections from the same source ip/port but different processes, or the same destination ip/port but different processes. See #2665. However, that condition is too coarse. In particular, we end up ignoring endpoints that are connected to NATed destinations, since the latter are represented by two (or more) endpoints. The change here corrects that.
171 lines
5.3 KiB
Go
171 lines
5.3 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"
|
|
|
|
// Topology for pseudo-nodes and IPs so we can differentiate them at the end
|
|
Pseudo = "pseudo"
|
|
)
|
|
|
|
func renderProcesses(rpt report.Report) bool {
|
|
return len(rpt.Process.Nodes) >= 1
|
|
}
|
|
|
|
// EndpointRenderer is a Renderer which produces a renderable endpoint graph.
|
|
var EndpointRenderer = SelectEndpoint
|
|
|
|
// ProcessRenderer is a Renderer which produces a renderable process
|
|
// graph by merging the endpoint graph and the process topology. It
|
|
// also colors connected nodes. Since the process topology views only
|
|
// show connected processes, we need this info to determine whether
|
|
// processes appearing in a details panel are linkable.
|
|
var ProcessRenderer = Memoise(ColorConnected(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 := report.Nodes{}
|
|
for id, p := range processes.Nodes {
|
|
outputs[id] = p
|
|
containerID, timestamp, ok := p.Latest.LookupEntry(docker.ContainerID)
|
|
if !ok {
|
|
continue
|
|
}
|
|
container, ok := containers.Nodes[report.MakeContainerNodeID(containerID)]
|
|
if !ok {
|
|
continue
|
|
}
|
|
p.Latest = p.Latest.Set(docker.ContainerID, timestamp, containerID)
|
|
if containerName, timestamp, ok := container.Latest.LookupEntry(docker.ContainerName); ok {
|
|
p.Latest = p.Latest.Set(docker.ContainerName, timestamp, containerName)
|
|
}
|
|
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
|
|
//
|
|
// not memoised
|
|
var ProcessWithContainerNameRenderer = processWithContainerNameRenderer{ProcessRenderer}
|
|
|
|
// ProcessNameRenderer is a Renderer which produces a renderable process
|
|
// name graph by munging the progess graph.
|
|
//
|
|
// not memoised
|
|
var ProcessNameRenderer = 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{}
|
|
}
|
|
local := LocalNetworks(rpt)
|
|
processes := SelectProcess.Render(rpt)
|
|
endpoints := SelectEndpoint.Render(rpt)
|
|
ret := newJoinResults()
|
|
|
|
for _, n := range endpoints.Nodes {
|
|
// Nodes without a hostid are treated as pseudo nodes
|
|
if hostNodeID, ok := n.Latest.Lookup(report.HostNodeID); !ok {
|
|
if id, ok := pseudoNodeID(n, local); ok {
|
|
ret.addChild(n, id, newPseudoNode)
|
|
}
|
|
} else {
|
|
pid, timestamp, ok := n.Latest.LookupEntry(process.PID)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if hasMoreThanOneConnection(n, endpoints.Nodes) {
|
|
continue
|
|
}
|
|
|
|
hostID, _, _ := report.ParseNodeID(hostNodeID)
|
|
id := report.MakeProcessNodeID(hostID, pid)
|
|
ret.addChild(n, id, func(id string) report.Node {
|
|
if processNode, found := processes.Nodes[id]; found {
|
|
return processNode
|
|
}
|
|
// we have a pid, but no matching process node; create a new one rather than dropping the data
|
|
return report.MakeNode(id).WithTopology(report.Process).
|
|
WithLatest(process.PID, timestamp, pid)
|
|
})
|
|
}
|
|
}
|
|
ret.copyUnmatched(processes)
|
|
ret.fixupAdjacencies(processes)
|
|
ret.fixupAdjacencies(endpoints)
|
|
return ret.result()
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// processes2Names maps process Nodes to Nodes for each process name.
|
|
func processes2Names(processes Nodes) Nodes {
|
|
ret := newJoinResults()
|
|
|
|
for _, n := range processes.Nodes {
|
|
if n.Topology == Pseudo {
|
|
ret.passThrough(n)
|
|
} else {
|
|
name, timestamp, ok := n.Latest.LookupEntry(process.Name)
|
|
if ok {
|
|
ret.addChildAndChildren(n, name, func(id string) report.Node {
|
|
return report.MakeNode(id).WithTopology(MakeGroupNodeTopology(n.Topology, process.Name)).
|
|
WithLatest(process.Name, timestamp, name)
|
|
})
|
|
}
|
|
}
|
|
}
|
|
ret.fixupAdjacencies(processes)
|
|
return ret.result()
|
|
}
|