mirror of
https://github.com/weaveworks/scope.git
synced 2026-03-05 11:11:13 +00:00
Merge pull request #116 from tomwilkie/105-grouping-pseudo
Group pseudo nodes by the node the referred to and their address in grouped topologies.
This commit is contained in:
@@ -42,7 +42,7 @@ func makeTopologyList(rep Reporter) func(w http.ResponseWriter, r *http.Request)
|
||||
Name: def.human,
|
||||
URL: url,
|
||||
GroupedURL: groupedURL,
|
||||
Stats: stats(def.topologySelecter(rpt).RenderBy(def.MapFunc, false)),
|
||||
Stats: stats(def.topologySelecter(rpt).RenderBy(def.MapFunc, def.PseudoFunc, false)),
|
||||
})
|
||||
}
|
||||
respondWith(w, http.StatusOK, a)
|
||||
|
||||
@@ -46,6 +46,7 @@ func makeTopologyHandlers(
|
||||
rep Reporter,
|
||||
topo topologySelecter,
|
||||
mapping report.MapFunc,
|
||||
pseudo report.PseudoFunc,
|
||||
grouped bool,
|
||||
get *mux.Router,
|
||||
base string,
|
||||
@@ -53,7 +54,7 @@ func makeTopologyHandlers(
|
||||
// Full topology.
|
||||
get.HandleFunc(base, func(w http.ResponseWriter, r *http.Request) {
|
||||
respondWith(w, http.StatusOK, APITopology{
|
||||
Nodes: topo(rep.Report()).RenderBy(mapping, grouped),
|
||||
Nodes: topo(rep.Report()).RenderBy(mapping, pseudo, grouped),
|
||||
})
|
||||
})
|
||||
|
||||
@@ -71,7 +72,7 @@ func makeTopologyHandlers(
|
||||
return
|
||||
}
|
||||
}
|
||||
handleWebsocket(w, r, rep, topo, mapping, grouped, loop)
|
||||
handleWebsocket(w, r, rep, topo, mapping, pseudo, grouped, loop)
|
||||
})
|
||||
|
||||
// Individual nodes.
|
||||
@@ -80,7 +81,7 @@ func makeTopologyHandlers(
|
||||
vars = mux.Vars(r)
|
||||
nodeID = vars["id"]
|
||||
rpt = rep.Report()
|
||||
node, ok = topo(rpt).RenderBy(mapping, grouped)[nodeID]
|
||||
node, ok = topo(rpt).RenderBy(mapping, pseudo, grouped)[nodeID]
|
||||
)
|
||||
if !ok {
|
||||
http.NotFound(w, r)
|
||||
@@ -114,6 +115,7 @@ func handleWebsocket(
|
||||
rep Reporter,
|
||||
topo topologySelecter,
|
||||
mapping report.MapFunc,
|
||||
psuedo report.PseudoFunc,
|
||||
grouped bool,
|
||||
loop time.Duration,
|
||||
) {
|
||||
@@ -139,7 +141,7 @@ func handleWebsocket(
|
||||
tick = time.Tick(loop)
|
||||
)
|
||||
for {
|
||||
newTopo := topo(rep.Report()).RenderBy(mapping, grouped)
|
||||
newTopo := topo(rep.Report()).RenderBy(mapping, psuedo, grouped)
|
||||
diff := report.TopoDiff(previousTopo, newTopo)
|
||||
previousTopo = newTopo
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ func Router(c Reporter) *mux.Router {
|
||||
c,
|
||||
def.topologySelecter,
|
||||
def.MapFunc,
|
||||
def.PseudoFunc,
|
||||
false, // not grouped
|
||||
get,
|
||||
"/api/topology/"+name,
|
||||
@@ -28,6 +29,7 @@ func Router(c Reporter) *mux.Router {
|
||||
c,
|
||||
def.topologySelecter,
|
||||
def.MapFunc,
|
||||
def.PseudoFunc,
|
||||
true, // grouped
|
||||
get,
|
||||
"/api/topology/"+name+"grouped",
|
||||
@@ -44,9 +46,10 @@ var topologyRegistry = map[string]struct {
|
||||
human string
|
||||
topologySelecter
|
||||
report.MapFunc
|
||||
report.PseudoFunc
|
||||
hasGrouped bool
|
||||
}{
|
||||
"applications": {"Applications", selectProcess, report.ProcessPID, true},
|
||||
"containers": {"Containers", selectProcess, report.ProcessContainer, true},
|
||||
"hosts": {"Hosts", selectNetwork, report.NetworkHostname, false},
|
||||
"applications": {"Applications", selectProcess, report.ProcessPID, report.GenericPseudoNode, true},
|
||||
"containers": {"Containers", selectProcess, report.ProcessContainer, report.NoPseudoNode, true},
|
||||
"hosts": {"Hosts", selectNetwork, report.NetworkHostname, report.GenericPseudoNode, false},
|
||||
}
|
||||
|
||||
@@ -25,6 +25,12 @@ type MappedNode struct {
|
||||
// rendered topology.
|
||||
type MapFunc func(string, NodeMetadata, bool) (MappedNode, bool)
|
||||
|
||||
// PseudoFunc creates MappedNode representing pseudo nodes given the dstNodeID.
|
||||
// The srcNode renderable node is essentially from MapFunc, representing one of
|
||||
// the rendered nodes this pseudo node refers to. srcNodeID and dstNodeID are
|
||||
// node IDs prior to mapping.
|
||||
type PseudoFunc func(srcNodeID string, srcNode RenderableNode, dstNodeID string, grouped bool) (MappedNode, bool)
|
||||
|
||||
// ProcessPID takes a node NodeMetadata from a Process topology, and returns a
|
||||
// representation with the ID based on the process PID and the labels based
|
||||
// on the process name.
|
||||
@@ -100,3 +106,47 @@ func NetworkHostname(_ string, m NodeMetadata, _ bool) (MappedNode, bool) {
|
||||
Rank: parts[0],
|
||||
}, name != ""
|
||||
}
|
||||
|
||||
// GenericPseudoNode contains heuristics for building sensible pseudo nodes.
|
||||
// It should go away.
|
||||
func GenericPseudoNode(src string, srcMapped RenderableNode, dst string, grouped bool) (MappedNode, bool) {
|
||||
var maj, min, outputID string
|
||||
|
||||
if dst == TheInternet {
|
||||
outputID = dst
|
||||
maj, min = "the Internet", ""
|
||||
} else if grouped {
|
||||
// When grouping, emit one pseudo node per (srcNodeAddress, dstNodeAddr)
|
||||
dstNodeAddr, _ := trySplitAddr(dst)
|
||||
|
||||
outputID = strings.Join([]string{"pseudo:", dstNodeAddr, srcMapped.ID}, ScopeDelim)
|
||||
maj, min = dstNodeAddr, ""
|
||||
} else {
|
||||
// Rule for non-internet psuedo nodes; emit 1 new node for each
|
||||
// dstNodeAddr, srcNodeAddr, srcNodePort.
|
||||
srcNodeAddr, srcNodePort := trySplitAddr(src)
|
||||
dstNodeAddr, _ := trySplitAddr(dst)
|
||||
|
||||
outputID = strings.Join([]string{"pseudo:", dstNodeAddr, srcNodeAddr, srcNodePort}, ScopeDelim)
|
||||
maj, min = dstNodeAddr, ""
|
||||
}
|
||||
|
||||
return MappedNode{
|
||||
ID: outputID,
|
||||
Major: maj,
|
||||
Minor: min,
|
||||
}, true
|
||||
}
|
||||
|
||||
// NoPseudoNode never creates a pseudo node.
|
||||
func NoPseudoNode(string, RenderableNode, string, bool) (MappedNode, bool) {
|
||||
return MappedNode{}, false
|
||||
}
|
||||
|
||||
func trySplitAddr(addr string) (string, string) {
|
||||
fields := strings.SplitN(addr, ScopeDelim, 3)
|
||||
if len(fields) == 3 {
|
||||
return fields[1], fields[2]
|
||||
}
|
||||
return fields[1], ""
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package report
|
||||
|
||||
import (
|
||||
"net"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
@@ -70,7 +69,7 @@ func NewTopology() Topology {
|
||||
//
|
||||
// RenderBy takes a a MapFunc, which defines how to group and label nodes. If
|
||||
// grouped is true, nodes that belong to the same "class" will be merged.
|
||||
func (t Topology) RenderBy(f MapFunc, grouped bool) map[string]RenderableNode {
|
||||
func (t Topology) RenderBy(mapFunc MapFunc, pseudoFunc PseudoFunc, grouped bool) map[string]RenderableNode {
|
||||
nodes := map[string]RenderableNode{}
|
||||
|
||||
// Build a set of RenderableNodes for all non-pseudo probes, and an
|
||||
@@ -78,7 +77,7 @@ func (t Topology) RenderBy(f MapFunc, grouped bool) map[string]RenderableNode {
|
||||
// RenderableNodes.
|
||||
address2mapped := map[string]string{}
|
||||
for addressID, metadata := range t.NodeMetadatas {
|
||||
mapped, ok := f(addressID, metadata, grouped)
|
||||
mapped, ok := mapFunc(addressID, metadata, grouped)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
@@ -113,29 +112,15 @@ func (t Topology) RenderBy(f MapFunc, grouped bool) map[string]RenderableNode {
|
||||
for _, dstNodeAddress := range dsts {
|
||||
dstRenderableID, ok := address2mapped[dstNodeAddress]
|
||||
if !ok {
|
||||
// We don't have a node for this target address. So we'll make
|
||||
// a pseudonode for it, instead.
|
||||
var maj, min string
|
||||
if dstNodeAddress == TheInternet {
|
||||
dstRenderableID = dstNodeAddress
|
||||
maj, min = formatLabel(dstNodeAddress)
|
||||
} else if grouped {
|
||||
dstRenderableID = localUnknown
|
||||
maj, min = "", ""
|
||||
} else {
|
||||
// Rule for non-internet psuedo nodes; emit 1 new node for each
|
||||
// dstNodeAddr, srcNodeAddr, srcNodePort.
|
||||
srcNodeAddr, srcNodePort := trySplitAddr(srcNodeAddress)
|
||||
dstNodeAddr, _ := trySplitAddr(dstNodeAddress)
|
||||
|
||||
// We don't care about <s>dst</s> remote port, just <s>src</s> local.
|
||||
dstRenderableID = strings.Join([]string{"pseudo:", dstNodeAddr, srcNodeAddr, srcNodePort}, ScopeDelim)
|
||||
maj, min = dstNodeAddr, ""
|
||||
pseudoNode, ok := pseudoFunc(srcNodeAddress, srcRenderableNode, dstNodeAddress, grouped)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
dstRenderableID = pseudoNode.ID
|
||||
nodes[dstRenderableID] = RenderableNode{
|
||||
ID: dstRenderableID,
|
||||
LabelMajor: maj,
|
||||
LabelMinor: min,
|
||||
ID: pseudoNode.ID,
|
||||
LabelMajor: pseudoNode.Major,
|
||||
LabelMinor: pseudoNode.Minor,
|
||||
Pseudo: true,
|
||||
Metadata: AggregateMetadata{}, // populated below - or not?
|
||||
}
|
||||
@@ -157,30 +142,22 @@ func (t Topology) RenderBy(f MapFunc, grouped bool) map[string]RenderableNode {
|
||||
return nodes
|
||||
}
|
||||
|
||||
func trySplitAddr(addr string) (string, string) {
|
||||
fields := strings.SplitN(addr, ScopeDelim, 3)
|
||||
if len(fields) == 3 {
|
||||
return fields[1], fields[2]
|
||||
}
|
||||
return fields[1], ""
|
||||
}
|
||||
|
||||
// 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 (t Topology) EdgeMetadata(f MapFunc, grouped bool, srcRenderableID, dstRenderableID string) EdgeMetadata {
|
||||
func (t Topology) EdgeMetadata(mapFunc MapFunc, grouped bool, srcRenderableID, dstRenderableID string) EdgeMetadata {
|
||||
metadata := EdgeMetadata{}
|
||||
for edgeID, edgeMeta := range t.EdgeMetadatas {
|
||||
edgeParts := strings.SplitN(edgeID, IDDelim, 2)
|
||||
src := edgeParts[0]
|
||||
if src != TheInternet {
|
||||
mapped, _ := f(src, t.NodeMetadatas[src], grouped)
|
||||
mapped, _ := mapFunc(src, t.NodeMetadatas[src], grouped)
|
||||
src = mapped.ID
|
||||
}
|
||||
dst := edgeParts[1]
|
||||
if dst != TheInternet {
|
||||
mapped, _ := f(dst, t.NodeMetadatas[dst], grouped)
|
||||
mapped, _ := mapFunc(dst, t.NodeMetadatas[dst], grouped)
|
||||
dst = mapped.ID
|
||||
}
|
||||
if src == srcRenderableID && dst == dstRenderableID {
|
||||
@@ -190,26 +167,6 @@ func (t Topology) EdgeMetadata(f MapFunc, grouped bool, srcRenderableID, dstRend
|
||||
return metadata
|
||||
}
|
||||
|
||||
// formatLabel is an opportunistic helper to format any addressID into
|
||||
// something we can show on screen.
|
||||
func formatLabel(s string) (major, minor string) {
|
||||
if s == TheInternet {
|
||||
return "the Internet", ""
|
||||
}
|
||||
|
||||
// Format is either "scope;ip;port", "scope;ip", or some process id.
|
||||
parts := strings.SplitN(s, ScopeDelim, 3)
|
||||
if len(parts) < 2 {
|
||||
return s, ""
|
||||
}
|
||||
|
||||
if len(parts) == 2 {
|
||||
return parts[1], ""
|
||||
}
|
||||
|
||||
return net.JoinHostPort(parts[1], parts[2]), ""
|
||||
}
|
||||
|
||||
// Diff is returned by TopoDiff. It represents the changes between two
|
||||
// RenderableNode maps.
|
||||
type Diff struct {
|
||||
|
||||
@@ -183,29 +183,19 @@ func TestRenderByProcessPID(t *testing.T) {
|
||||
},
|
||||
},
|
||||
"pseudo:;10.10.10.10;192.168.1.1;80": {
|
||||
ID: "pseudo:;10.10.10.10;192.168.1.1;80",
|
||||
LabelMajor: "10.10.10.10",
|
||||
LabelMinor: "",
|
||||
Rank: "",
|
||||
Pseudo: true,
|
||||
Adjacency: nil,
|
||||
OriginHosts: nil,
|
||||
OriginNodes: nil,
|
||||
Metadata: AggregateMetadata{},
|
||||
ID: "pseudo:;10.10.10.10;192.168.1.1;80",
|
||||
LabelMajor: "10.10.10.10",
|
||||
Pseudo: true,
|
||||
Metadata: AggregateMetadata{},
|
||||
},
|
||||
"pseudo:;10.10.10.11;192.168.1.1;80": {
|
||||
ID: "pseudo:;10.10.10.11;192.168.1.1;80",
|
||||
LabelMajor: "10.10.10.11",
|
||||
LabelMinor: "",
|
||||
Rank: "",
|
||||
Pseudo: true,
|
||||
Adjacency: nil,
|
||||
OriginHosts: nil,
|
||||
OriginNodes: nil,
|
||||
Metadata: AggregateMetadata{},
|
||||
ID: "pseudo:;10.10.10.11;192.168.1.1;80",
|
||||
LabelMajor: "10.10.10.11",
|
||||
Pseudo: true,
|
||||
Metadata: AggregateMetadata{},
|
||||
},
|
||||
}
|
||||
have := report.Process.RenderBy(ProcessPID, false)
|
||||
have := report.Process.RenderBy(ProcessPID, GenericPseudoNode, false)
|
||||
if !reflect.DeepEqual(want, have) {
|
||||
t.Error("\n" + diff(want, have))
|
||||
}
|
||||
@@ -231,12 +221,16 @@ func TestRenderByProcessPIDGrouped(t *testing.T) {
|
||||
},
|
||||
},
|
||||
"apache": {
|
||||
ID: "apache",
|
||||
LabelMajor: "apache",
|
||||
LabelMinor: "",
|
||||
Rank: "215",
|
||||
Pseudo: false,
|
||||
Adjacency: NewIDList("curl", "localUnknown"),
|
||||
ID: "apache",
|
||||
LabelMajor: "apache",
|
||||
LabelMinor: "",
|
||||
Rank: "215",
|
||||
Pseudo: false,
|
||||
Adjacency: NewIDList(
|
||||
"curl",
|
||||
"pseudo:;10.10.10.10;apache",
|
||||
"pseudo:;10.10.10.11;apache",
|
||||
),
|
||||
OriginHosts: NewIDList("server.hostname.com"),
|
||||
OriginNodes: NewIDList(";192.168.1.1;80"),
|
||||
Metadata: AggregateMetadata{
|
||||
@@ -244,19 +238,20 @@ func TestRenderByProcessPIDGrouped(t *testing.T) {
|
||||
KeyBytesEgress: 1500,
|
||||
},
|
||||
},
|
||||
"localUnknown": {
|
||||
ID: "localUnknown",
|
||||
LabelMajor: "",
|
||||
LabelMinor: "",
|
||||
Rank: "",
|
||||
Pseudo: true,
|
||||
Adjacency: nil,
|
||||
OriginHosts: nil,
|
||||
OriginNodes: nil,
|
||||
Metadata: AggregateMetadata{},
|
||||
"pseudo:;10.10.10.10;apache": {
|
||||
ID: "pseudo:;10.10.10.10;apache",
|
||||
LabelMajor: "10.10.10.10",
|
||||
Pseudo: true,
|
||||
Metadata: AggregateMetadata{},
|
||||
},
|
||||
"pseudo:;10.10.10.11;apache": {
|
||||
ID: "pseudo:;10.10.10.11;apache",
|
||||
LabelMajor: "10.10.10.11",
|
||||
Pseudo: true,
|
||||
Metadata: AggregateMetadata{},
|
||||
},
|
||||
}
|
||||
have := report.Process.RenderBy(ProcessPID, true)
|
||||
have := report.Process.RenderBy(ProcessPID, GenericPseudoNode, true)
|
||||
if !reflect.DeepEqual(want, have) {
|
||||
t.Error("\n" + diff(want, have))
|
||||
}
|
||||
@@ -315,7 +310,7 @@ func TestRenderByNetworkHostname(t *testing.T) {
|
||||
Metadata: AggregateMetadata{},
|
||||
},
|
||||
}
|
||||
have := report.Network.RenderBy(NetworkHostname, false)
|
||||
have := report.Network.RenderBy(NetworkHostname, GenericPseudoNode, false)
|
||||
if !reflect.DeepEqual(want, have) {
|
||||
t.Error("\n" + diff(want, have))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user