mirror of
https://github.com/weaveworks/scope.git
synced 2026-02-15 18:39:53 +00:00
255 lines
7.2 KiB
Go
255 lines
7.2 KiB
Go
package render
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"github.com/weaveworks/scope/probe/docker"
|
|
"github.com/weaveworks/scope/report"
|
|
)
|
|
|
|
// Renderer is something that can render a report to a set of RenderableNodes.
|
|
type Renderer interface {
|
|
Render(report.Report) RenderableNodes
|
|
EdgeMetadata(rpt report.Report, localID, remoteID string) report.EdgeMetadata
|
|
}
|
|
|
|
// Reduce renderer is a Renderer which merges together the output of several
|
|
// other renderers.
|
|
type Reduce []Renderer
|
|
|
|
// MakeReduce is the only sane way to produce a Reduce Renderer.
|
|
func MakeReduce(renderers ...Renderer) Renderer {
|
|
return Reduce(renderers)
|
|
}
|
|
|
|
// Render produces a set of RenderableNodes given a Report.
|
|
func (r Reduce) Render(rpt report.Report) RenderableNodes {
|
|
result := RenderableNodes{}
|
|
for _, renderer := range r {
|
|
result = result.Merge(renderer.Render(rpt))
|
|
}
|
|
return result
|
|
}
|
|
|
|
// EdgeMetadata produces an EdgeMetadata for a given edge.
|
|
func (r Reduce) EdgeMetadata(rpt report.Report, localID, remoteID string) report.EdgeMetadata {
|
|
metadata := report.EdgeMetadata{}
|
|
for _, renderer := range r {
|
|
metadata = metadata.Merge(renderer.EdgeMetadata(rpt, localID, remoteID))
|
|
}
|
|
return metadata
|
|
}
|
|
|
|
// Map is a Renderer which produces a set of RenderableNodes from the set of
|
|
// RenderableNodes produced by another Renderer.
|
|
type Map struct {
|
|
MapFunc
|
|
Renderer
|
|
}
|
|
|
|
// Render transforms a set of RenderableNodes produces by another Renderer.
|
|
// using a map function
|
|
func (m Map) Render(rpt report.Report) RenderableNodes {
|
|
output, _ := m.render(rpt)
|
|
return output
|
|
}
|
|
|
|
func (m Map) render(rpt report.Report) (RenderableNodes, map[string]report.IDList) {
|
|
var (
|
|
input = m.Renderer.Render(rpt)
|
|
output = RenderableNodes{}
|
|
mapped = map[string]report.IDList{} // input node ID -> output node IDs
|
|
adjacencies = map[string]report.IDList{} // output node ID -> input node Adjacencies
|
|
localNetworks = LocalNetworks(rpt)
|
|
)
|
|
|
|
// Rewrite all the nodes according to the map function
|
|
for _, inRenderable := range input {
|
|
for _, outRenderable := range m.MapFunc(inRenderable, localNetworks) {
|
|
existing, ok := output[outRenderable.ID]
|
|
if ok {
|
|
outRenderable = outRenderable.Merge(existing)
|
|
}
|
|
|
|
output[outRenderable.ID] = outRenderable
|
|
mapped[inRenderable.ID] = mapped[inRenderable.ID].Add(outRenderable.ID)
|
|
adjacencies[outRenderable.ID] = adjacencies[outRenderable.ID].Merge(inRenderable.Adjacency)
|
|
}
|
|
}
|
|
|
|
// Rewrite Adjacency for new node IDs.
|
|
for outNodeID, inAdjacency := range adjacencies {
|
|
outAdjacency := report.MakeIDList()
|
|
for _, inAdjacent := range inAdjacency {
|
|
for _, outAdjacent := range mapped[inAdjacent] {
|
|
outAdjacency = outAdjacency.Add(outAdjacent)
|
|
}
|
|
}
|
|
outNode := output[outNodeID]
|
|
outNode.Adjacency = outAdjacency
|
|
output[outNodeID] = outNode
|
|
}
|
|
|
|
return output, mapped
|
|
}
|
|
|
|
// EdgeMetadata gives the metadata of an edge from the perspective of the
|
|
// srcRenderableID. Since an edgeID can have multiple edges on the address
|
|
// level, it uses the supplied mapping function to translate address IDs to
|
|
// renderable node (mapped) IDs.
|
|
func (m Map) EdgeMetadata(rpt report.Report, srcRenderableID, dstRenderableID string) report.EdgeMetadata {
|
|
// First we need to map the ids in this layer into the ids in the underlying layer
|
|
_, mapped := m.render(rpt) // this maps from old -> new
|
|
inverted := map[string][]string{} // this maps from new -> old(s)
|
|
for k, vs := range mapped {
|
|
for _, v := range vs {
|
|
existing := inverted[v]
|
|
existing = append(existing, k)
|
|
inverted[v] = existing
|
|
}
|
|
}
|
|
|
|
// Now work out a slice of edges this edge is constructed from
|
|
oldEdges := []struct{ src, dst string }{}
|
|
for _, oldSrcID := range inverted[srcRenderableID] {
|
|
for _, oldDstID := range inverted[dstRenderableID] {
|
|
oldEdges = append(oldEdges, struct{ src, dst string }{oldSrcID, oldDstID})
|
|
}
|
|
}
|
|
|
|
// Now recurse for each old edge
|
|
output := report.EdgeMetadata{}
|
|
for _, edge := range oldEdges {
|
|
metadata := m.Renderer.EdgeMetadata(rpt, edge.src, edge.dst)
|
|
output = output.Merge(metadata)
|
|
}
|
|
return output
|
|
}
|
|
|
|
// CustomRenderer allow for mapping functions that recived the entire topology
|
|
// in one call - useful for functions that need to consider the entire graph.
|
|
// We should minimise the use of this renderer type, as it is very inflexible.
|
|
type CustomRenderer struct {
|
|
RenderFunc func(RenderableNodes) RenderableNodes
|
|
Renderer
|
|
}
|
|
|
|
// Render implements Renderer
|
|
func (c CustomRenderer) Render(rpt report.Report) RenderableNodes {
|
|
return c.RenderFunc(c.Renderer.Render(rpt))
|
|
}
|
|
|
|
// ColorConnected colors nodes with the IsConnected key if
|
|
// they have edges to or from them.
|
|
func ColorConnected(r Renderer) Renderer {
|
|
return CustomRenderer{
|
|
Renderer: r,
|
|
RenderFunc: func(input RenderableNodes) RenderableNodes {
|
|
connected := map[string]struct{}{}
|
|
void := struct{}{}
|
|
|
|
for id, node := range input {
|
|
if len(node.Adjacency) == 0 {
|
|
continue
|
|
}
|
|
|
|
connected[id] = void
|
|
for _, id := range node.Adjacency {
|
|
connected[id] = void
|
|
}
|
|
}
|
|
|
|
for id := range connected {
|
|
node := input[id]
|
|
node.Metadata[IsConnected] = "true"
|
|
input[id] = node
|
|
}
|
|
return input
|
|
},
|
|
}
|
|
}
|
|
|
|
// Filter removes nodes from a view based on a predicate.
|
|
type Filter struct {
|
|
Renderer
|
|
FilterFunc func(RenderableNode) bool
|
|
}
|
|
|
|
// Render implements Renderer
|
|
func (f Filter) Render(rpt report.Report) RenderableNodes {
|
|
output := RenderableNodes{}
|
|
for id, node := range f.Renderer.Render(rpt) {
|
|
if f.FilterFunc(node) {
|
|
output[id] = node
|
|
}
|
|
}
|
|
|
|
// Deleted nodes also need to be cut as destinations in adjacency lists.
|
|
for id, node := range output {
|
|
newAdjacency := make(report.IDList, 0, len(node.Adjacency))
|
|
for _, dstID := range node.Adjacency {
|
|
if _, ok := output[dstID]; ok {
|
|
newAdjacency = newAdjacency.Add(dstID)
|
|
}
|
|
}
|
|
node.Adjacency = newAdjacency
|
|
output[id] = node
|
|
}
|
|
return output
|
|
}
|
|
|
|
// IsConnected is the key added to Node.Metadata by ColorConnected
|
|
// to indicate a node has an edge pointing to it or from it
|
|
const IsConnected = "is_connected"
|
|
|
|
// FilterUnconnected produces a renderer that filters unconnected nodes
|
|
// from the given renderer
|
|
func FilterUnconnected(r Renderer) Renderer {
|
|
return Filter{
|
|
Renderer: ColorConnected(r),
|
|
FilterFunc: func(node RenderableNode) bool {
|
|
_, ok := node.Metadata[IsConnected]
|
|
return ok
|
|
},
|
|
}
|
|
}
|
|
|
|
// FilterSystem is a Renderer which filters out system nodes.
|
|
func FilterSystem(r Renderer) Renderer {
|
|
return Filter{
|
|
Renderer: r,
|
|
FilterFunc: func(node RenderableNode) bool {
|
|
containerName := node.Metadata[docker.ContainerName]
|
|
if _, ok := systemContainerNames[containerName]; ok {
|
|
return false
|
|
}
|
|
imagePrefix := strings.SplitN(node.Metadata[docker.ImageName], ":", 2)[0] // :(
|
|
if _, ok := systemImagePrefixes[imagePrefix]; ok {
|
|
return false
|
|
}
|
|
if node.Metadata[docker.LabelPrefix+"works.weave.role"] == "system" {
|
|
return false
|
|
}
|
|
return true
|
|
},
|
|
}
|
|
}
|
|
|
|
var systemContainerNames = map[string]struct{}{
|
|
"weavescope": {},
|
|
"weavedns": {},
|
|
"weave": {},
|
|
"weaveproxy": {},
|
|
"weaveexec": {},
|
|
"ecs-agent": {},
|
|
}
|
|
|
|
var systemImagePrefixes = map[string]struct{}{
|
|
"weaveworks/scope": {},
|
|
"weaveworks/weavedns": {},
|
|
"weaveworks/weave": {},
|
|
"weaveworks/weaveproxy": {},
|
|
"weaveworks/weaveexec": {},
|
|
"amazon/amazon-ecs-agent": {},
|
|
}
|