Report number of filtered nodes in topology stats.

Include filtered psuedo nodes in stats.
This commit is contained in:
Tom Wilkie
2015-09-24 06:24:11 +00:00
parent ff6f87d404
commit 5c0555ae39
7 changed files with 100 additions and 37 deletions

View File

@@ -4,6 +4,7 @@ import (
"net/http"
"github.com/weaveworks/scope/render"
"github.com/weaveworks/scope/report"
"github.com/weaveworks/scope/xfer"
)
@@ -27,6 +28,7 @@ type topologyStats struct {
NodeCount int `json:"node_count"`
NonpseudoNodeCount int `json:"nonpseudo_node_count"`
EdgeCount int `json:"edge_count"`
FilteredNodes int `json:"filtered_nodes"`
}
// makeTopologyList returns a handler that yields an APITopologyList.
@@ -41,16 +43,18 @@ func makeTopologyList(rep xfer.Reporter) func(w http.ResponseWriter, r *http.Req
if def.parent != "" {
continue
}
decorateTopologyForRequest(r, &def)
// Collect all sub-topologies of this one, depth=1 only.
subTopologies := []APITopologyDesc{}
for subName, subDef := range topologyRegistry {
if subDef.parent == name {
decorateTopologyForRequest(r, &subDef)
subTopologies = append(subTopologies, APITopologyDesc{
Name: subDef.human,
URL: "/api/topology/" + subName,
Options: makeTopologyOptions(subDef),
Stats: stats(subDef.renderer.Render(rpt)),
Stats: stats(subDef.renderer, rpt),
})
}
}
@@ -61,7 +65,7 @@ func makeTopologyList(rep xfer.Reporter) func(w http.ResponseWriter, r *http.Req
URL: "/api/topology/" + name,
SubTopologies: subTopologies,
Options: makeTopologyOptions(def),
Stats: stats(def.renderer.Render(rpt)),
Stats: stats(def.renderer, rpt),
})
}
respondWith(w, http.StatusOK, topologies)
@@ -82,14 +86,14 @@ func makeTopologyOptions(view topologyView) map[string][]APITopologyOption {
return options
}
func stats(r render.RenderableNodes) *topologyStats {
func stats(renderer render.Renderer, rpt report.Report) *topologyStats {
var (
nodes int
realNodes int
edges int
)
for _, n := range r {
for _, n := range renderer.Render(rpt) {
nodes++
if !n.Pseudo {
realNodes++
@@ -97,9 +101,12 @@ func stats(r render.RenderableNodes) *topologyStats {
edges += len(n.Adjacency)
}
renderStats := renderer.Stats(rpt)
return &topologyStats{
NodeCount: nodes,
NonpseudoNodeCount: realNodes,
EdgeCount: edges,
FilteredNodes: renderStats.FilteredNodes,
}
}

View File

@@ -100,6 +100,17 @@ func makeReportPostHandler(a xfer.Adder) http.HandlerFunc {
}
}
func decorateTopologyForRequest(r *http.Request, topology *topologyView) {
for param, opts := range topology.options {
value := r.FormValue(param)
for _, opt := range opts {
if (value == "" && opt.def) || (opt.value != "" && opt.value == value) {
topology.renderer = opt.decorator(topology.renderer)
}
}
}
}
func captureTopology(rep xfer.Reporter, f func(xfer.Reporter, topologyView, http.ResponseWriter, *http.Request)) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
topology, ok := topologyRegistry[mux.Vars(r)["topology"]]
@@ -107,14 +118,7 @@ func captureTopology(rep xfer.Reporter, f func(xfer.Reporter, topologyView, http
http.NotFound(w, r)
return
}
for param, opts := range topology.options {
value := r.FormValue(param)
for _, opt := range opts {
if (value == "" && opt.def) || (opt.value != "" && opt.value == value) {
topology.renderer = opt.decorator(topology.renderer)
}
}
}
decorateTopologyForRequest(r, &topology)
f(rep, topology, w, r)
}
}
@@ -135,7 +139,7 @@ var topologyRegistry = map[string]topologyView{
"applications": {
human: "Applications",
parent: "",
renderer: render.FilterUnconnected(render.ProcessWithContainerNameRenderer{}),
renderer: render.FilterUnconnected(render.ProcessWithContainerNameRenderer),
},
"applications-by-name": {
human: "by name",
@@ -145,7 +149,7 @@ var topologyRegistry = map[string]topologyView{
"containers": {
human: "Containers",
parent: "",
renderer: render.ContainerWithImageNameRenderer{},
renderer: render.ContainerWithImageNameRenderer,
options: optionParams{"system": {
{"show", "System containers shown", false, nop},
{"hide", "System containers hidden", true, render.FilterSystem},

View File

@@ -11,6 +11,18 @@ import (
type Renderer interface {
Render(report.Report) RenderableNodes
EdgeMetadata(rpt report.Report, localID, remoteID string) report.EdgeMetadata
Stats(report.Report) Stats
}
// Stats is the type returned by Renderer.Stats
type Stats struct {
FilteredNodes int
}
func (s Stats) merge(other Stats) Stats {
return Stats{
FilteredNodes: s.FilteredNodes + other.FilteredNodes,
}
}
// Reduce renderer is a Renderer which merges together the output of several
@@ -40,6 +52,15 @@ func (r Reduce) EdgeMetadata(rpt report.Report, localID, remoteID string) report
return metadata
}
// Stats implements Renderer
func (r Reduce) Stats(rpt report.Report) Stats {
var result Stats
for _, renderer := range r {
result = result.merge(renderer.Stats(rpt))
}
return result
}
// Map is a Renderer which produces a set of RenderableNodes from the set of
// RenderableNodes produced by another Renderer.
type Map struct {
@@ -54,6 +75,14 @@ func (m Map) Render(rpt report.Report) RenderableNodes {
return output
}
// Stats implements Renderer
func (m Map) Stats(rpt report.Report) Stats {
// There doesn't seem to be an instance where we want stats to recurse
// through Maps - for instance we don't want to see the number of filtered
// processes in the container renderer.
return Stats{}
}
func (m Map) render(rpt report.Report) (RenderableNodes, map[string]report.IDList) {
var (
input = m.Renderer.Render(rpt)
@@ -177,13 +206,21 @@ type Filter struct {
// Render implements Renderer
func (f Filter) Render(rpt report.Report) RenderableNodes {
nodes, _ := f.render(rpt)
return nodes
}
func (f Filter) render(rpt report.Report) (RenderableNodes, int) {
output := RenderableNodes{}
inDegrees := map[string]int{}
filtered := 0
for id, node := range f.Renderer.Render(rpt) {
if f.FilterFunc(node) {
output[id] = node
inDegrees[id] = 0
} else {
filtered++
}
inDegrees[id] = 0
}
// Deleted nodes also need to be cut as destinations in adjacency lists.
@@ -209,8 +246,17 @@ func (f Filter) Render(rpt report.Report) RenderableNodes {
continue
}
delete(output, id)
filtered++
}
return output
return output, filtered
}
// Stats implements Renderer
func (f Filter) Stats(rpt report.Report) Stats {
_, filtered := f.render(rpt)
var upstream = f.Renderer.Stats(rpt)
upstream.FilteredNodes += filtered
return upstream
}
// IsConnected is the key added to Node.Metadata by ColorConnected

View File

@@ -20,6 +20,9 @@ func (m mockRenderer) Render(rpt report.Report) render.RenderableNodes {
func (m mockRenderer) EdgeMetadata(rpt report.Report, localID, remoteID string) report.EdgeMetadata {
return m.edgeMetadata
}
func (m mockRenderer) Stats(rpt report.Report) render.Stats {
return render.Stats{}
}
func TestReduceRender(t *testing.T) {
renderer := render.Reduce([]render.Renderer{

View File

@@ -29,6 +29,11 @@ func (t TopologySelector) EdgeMetadata(rpt report.Report, srcID, dstID string) r
return metadata
}
// Stats implements Renderer
func (t TopologySelector) Stats(r report.Report) Stats {
return Stats{}
}
// MakeRenderableNodes converts a topology to a set of RenderableNodes
func MakeRenderableNodes(t report.Topology) RenderableNodes {
result := RenderableNodes{}

View File

@@ -27,14 +27,14 @@ var ProcessRenderer = MakeReduce(
},
)
// ProcessWithContainerNameRenderer is a Renderer which produces a process
// processWithContainerNameRenderer is a Renderer which produces a process
// graph enriched with container names where appropriate
type ProcessWithContainerNameRenderer struct{}
type processWithContainerNameRenderer struct {
Renderer
}
// Render produces a process graph where the minor labels contain the
// container name, if found.
func (r ProcessWithContainerNameRenderer) Render(rpt report.Report) RenderableNodes {
processes := ProcessRenderer.Render(rpt)
func (r processWithContainerNameRenderer) Render(rpt report.Report) RenderableNodes {
processes := r.Renderer.Render(rpt)
containers := Map{
MapFunc: MapContainerIdentity,
Renderer: SelectContainer,
@@ -60,10 +60,9 @@ func (r ProcessWithContainerNameRenderer) Render(rpt report.Report) RenderableNo
return processes
}
// EdgeMetadata produces an EdgeMetadata for a given edge.
func (r ProcessWithContainerNameRenderer) EdgeMetadata(rpt report.Report, localID, remoteID string) report.EdgeMetadata {
return ProcessRenderer.EdgeMetadata(rpt, localID, remoteID)
}
// ProcessWithContainerNameRenderer is a Renderer which produces a process
// graph enriched with container names where appropriate
var ProcessWithContainerNameRenderer = processWithContainerNameRenderer{ProcessRenderer}
// ProcessRenderer is a Renderer which produces a renderable process
// name graph by munging the progess graph.
@@ -120,14 +119,14 @@ var ContainerRenderer = MakeReduce(
},
)
// ContainerWithImageNameRenderer is a Renderer which produces a container
// graph where the ranks are the image names, not their IDs
type ContainerWithImageNameRenderer struct{}
type containerWithImageNameRenderer struct {
Renderer
}
// Render produces a process graph where the minor labels contain the
// container name, if found.
func (r ContainerWithImageNameRenderer) Render(rpt report.Report) RenderableNodes {
containers := ContainerRenderer.Render(rpt)
func (r containerWithImageNameRenderer) Render(rpt report.Report) RenderableNodes {
containers := r.Renderer.Render(rpt)
images := Map{
MapFunc: MapContainerImageIdentity,
Renderer: SelectContainerImage,
@@ -149,10 +148,9 @@ func (r ContainerWithImageNameRenderer) Render(rpt report.Report) RenderableNode
return containers
}
// EdgeMetadata produces an EdgeMetadata for a given edge.
func (r ContainerWithImageNameRenderer) EdgeMetadata(rpt report.Report, localID, remoteID string) report.EdgeMetadata {
return ContainerRenderer.EdgeMetadata(rpt, localID, remoteID)
}
// ContainerWithImageNameRenderer is a Renderer which produces a container
// graph where the ranks are the image names, not their IDs
var ContainerWithImageNameRenderer = containerWithImageNameRenderer{ContainerRenderer}
// ContainerImageRenderer is a Renderer which produces a renderable container
// image graph by merging the container graph and the container image topology.

View File

@@ -27,7 +27,7 @@ func TestProcessNameRenderer(t *testing.T) {
}
func TestContainerRenderer(t *testing.T) {
have := (render.ContainerWithImageNameRenderer{}.Render(test.Report)).Prune()
have := (render.ContainerWithImageNameRenderer.Render(test.Report)).Prune()
want := expected.RenderedContainers
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
@@ -39,7 +39,7 @@ func TestContainerFilterRenderer(t *testing.T) {
// it is filtered out correctly.
input := test.Report.Copy()
input.Container.Nodes[test.ClientContainerNodeID].Metadata[docker.LabelPrefix+"works.weave.role"] = "system"
have := render.FilterSystem(render.ContainerWithImageNameRenderer{}).Render(input).Prune()
have := render.FilterSystem(render.ContainerWithImageNameRenderer).Render(input).Prune()
want := expected.RenderedContainers.Copy()
delete(want, test.ClientContainerID)
if !reflect.DeepEqual(want, have) {