diff --git a/render/detailed/node.go b/render/detailed/node.go index 3126cd5d4..80dce216f 100644 --- a/render/detailed/node.go +++ b/render/detailed/node.go @@ -101,15 +101,13 @@ var ( func children(n render.RenderableNode) []NodeSummaryGroup { summaries := map[string][]NodeSummary{} - for _, child := range n.Children { - if child.ID == n.ID { - continue + n.Children.ForEach(func(child report.Node) { + if child.ID != n.ID { + if summary, ok := MakeNodeSummary(child); ok { + summaries[child.Topology] = append(summaries[child.Topology], summary) + } } - - if summary, ok := MakeNodeSummary(child); ok { - summaries[child.Topology] = append(summaries[child.Topology], summary) - } - } + }) nodeSummaryGroups := []NodeSummaryGroup{} for _, spec := range nodeSummaryGroupSpecs { diff --git a/render/filters_test.go b/render/filters_test.go index 6722462cf..70af0b88d 100644 --- a/render/filters_test.go +++ b/render/filters_test.go @@ -1,12 +1,12 @@ package render_test import ( - "reflect" "testing" "github.com/weaveworks/scope/render" "github.com/weaveworks/scope/report" "github.com/weaveworks/scope/test" + "github.com/weaveworks/scope/test/reflect" ) func TestFilterRender(t *testing.T) { diff --git a/render/renderable_node.go b/render/renderable_node.go index 368e0353b..e79657137 100644 --- a/render/renderable_node.go +++ b/render/renderable_node.go @@ -141,7 +141,7 @@ func (rn RenderableNode) Copy() RenderableNode { func (rn RenderableNode) Prune() RenderableNode { cp := rn.Copy() cp.Node = report.MakeNode().WithAdjacent(cp.Node.Adjacency...) - cp.Children = nil + cp.Children = report.EmptyNodeSet return cp } diff --git a/report/node_set.go b/report/node_set.go index c35950c16..f0d43492f 100644 --- a/report/node_set.go +++ b/report/node_set.go @@ -1,94 +1,197 @@ package report import ( + "bytes" + "encoding/gob" + "encoding/json" + "fmt" + "reflect" "sort" + + "github.com/mndrix/ps" ) -// NodeSet is a sorted set of nodes keyed on (Topology, ID). Clients must use +// NodeSet is a set of nodes keyed on (Topology, ID). Clients must use // the Add method to add nodes -type NodeSet []Node +type NodeSet struct { + psMap ps.Map +} + +// EmptyNodeSet is the empty set of nodes. +var EmptyNodeSet = NodeSet{ps.NewMap()} // MakeNodeSet makes a new NodeSet with the given nodes. func MakeNodeSet(nodes ...Node) NodeSet { - if len(nodes) <= 0 { - return nil - } - result := make(NodeSet, len(nodes)) - copy(result, nodes) - sort.Sort(result) - for i := 1; i < len(result); { // remove any duplicates - if result[i-1].Equal(result[i]) { - result = append(result[:i-1], result[i:]...) - continue - } - i++ - } - return result + return EmptyNodeSet.Add(nodes...) } -// Implementation of sort.Interface -func (n NodeSet) Len() int { return len(n) } -func (n NodeSet) Swap(i, j int) { n[i], n[j] = n[j], n[i] } -func (n NodeSet) Less(i, j int) bool { return n[i].Before(n[j]) } - // Add adds the nodes to the NodeSet. Add is the only valid way to grow a // NodeSet. Add returns the NodeSet to enable chaining. func (n NodeSet) Add(nodes ...Node) NodeSet { - for _, node := range nodes { - i := sort.Search(len(n), func(i int) bool { - return n[i].Topology >= node.Topology && n[i].ID >= node.ID - }) - if i < len(n) && n[i].Topology == node.Topology && n[i].ID == node.ID { - // The list already has the element. - continue - } - // It a new element, insert it in order. - n = append(n, Node{}) - copy(n[i+1:], n[i:]) - n[i] = node + result := n.psMap + if result == nil { + result = ps.NewMap() } - return n + for _, node := range nodes { + result = result.Set(fmt.Sprintf("%s|%s", node.Topology, node.ID), node) + } + return NodeSet{result} } // Merge combines the two NodeSets and returns a new result. func (n NodeSet) Merge(other NodeSet) NodeSet { - switch { - case len(other) <= 0: // Optimise special case, to avoid allocating - return n // (note unit test DeepEquals breaks if we don't do this) - case len(n) <= 0: + nSize, otherSize := n.Size(), other.Size() + if nSize == 0 { return other } - - result := make([]Node, 0, len(n)+len(other)) - for len(n) > 0 || len(other) > 0 { - switch { - case len(n) == 0: - return append(result, other...) - case len(other) == 0: - return append(result, n...) - case n[0].Before(other[0]): - result = append(result, n[0]) - n = n[1:] - case n[0].After(other[0]): - result = append(result, other[0]) - other = other[1:] - default: // equal - result = append(result, other[0]) - n = n[1:] - other = other[1:] - } - } - return result -} - -// Copy returns a value copy of the NodeSet. -func (n NodeSet) Copy() NodeSet { - if n == nil { + if otherSize == 0 { return n } - result := make(NodeSet, len(n)) - for i, node := range n { - result[i] = node + result, iter := n.psMap, other.psMap + if nSize < otherSize { + result, iter = iter, result } - return result + iter.ForEach(func(key string, otherVal interface{}) { + result = result.Set(key, otherVal) + }) + return NodeSet{result} +} + +// Lookup the node 'key' +func (n NodeSet) Lookup(key string) (Node, bool) { + if n.psMap != nil { + value, ok := n.psMap.Lookup(key) + if ok { + return value.(Node), true + } + } + return Node{}, false +} + +// Keys is a list of all the keys in this set. +func (n NodeSet) Keys() []string { + if n.psMap == nil { + return nil + } + k := n.psMap.Keys() + sort.Strings(k) + return k +} + +// Size is the number of nodes in the set +func (n NodeSet) Size() int { + if n.psMap == nil { + return 0 + } + return n.psMap.Size() +} + +// ForEach executes f for each node in the set. Nodes are traversed in sorted +// order. +func (n NodeSet) ForEach(f func(Node)) { + for _, key := range n.Keys() { + if val, ok := n.psMap.Lookup(key); ok { + f(val.(Node)) + } + } +} + +// Copy is a noop +func (n NodeSet) Copy() NodeSet { + return n +} + +func (n NodeSet) String() string { + keys := []string{} + if n.psMap == nil { + n = EmptyNodeSet + } + psMap := n.psMap + if psMap == nil { + psMap = ps.NewMap() + } + for _, k := range psMap.Keys() { + keys = append(keys, k) + } + sort.Strings(keys) + + buf := bytes.NewBufferString("{") + for _, key := range keys { + val, _ := psMap.Lookup(key) + fmt.Fprintf(buf, "%s: %v, ", key, val) + } + fmt.Fprintf(buf, "}\n") + return buf.String() +} + +// DeepEqual tests equality with other NodeSets +func (n NodeSet) DeepEqual(i interface{}) bool { + d, ok := i.(NodeSet) + if !ok { + return false + } + + if n.Size() != d.Size() { + return false + } + if n.Size() == 0 { + return true + } + + equal := true + n.psMap.ForEach(func(k string, val interface{}) { + if otherValue, ok := d.psMap.Lookup(k); !ok { + equal = false + } else { + equal = equal && reflect.DeepEqual(val, otherValue) + } + }) + return equal +} + +func (n NodeSet) toIntermediate() []Node { + intermediate := make([]Node, 0, n.Size()) + n.ForEach(func(node Node) { + intermediate = append(intermediate, node) + }) + return intermediate +} + +func (n NodeSet) fromIntermediate(nodes []Node) NodeSet { + return MakeNodeSet(nodes...) +} + +// MarshalJSON implements json.Marshaller +func (n NodeSet) MarshalJSON() ([]byte, error) { + if n.psMap != nil { + return json.Marshal(n.toIntermediate()) + } + return json.Marshal(nil) +} + +// UnmarshalJSON implements json.Unmarshaler +func (n *NodeSet) UnmarshalJSON(input []byte) error { + in := []Node{} + if err := json.Unmarshal(input, &in); err != nil { + return err + } + *n = NodeSet{}.fromIntermediate(in) + return nil +} + +// GobEncode implements gob.Marshaller +func (n NodeSet) GobEncode() ([]byte, error) { + buf := bytes.Buffer{} + err := gob.NewEncoder(&buf).Encode(n.toIntermediate()) + return buf.Bytes(), err +} + +// GobDecode implements gob.Unmarshaller +func (n *NodeSet) GobDecode(input []byte) error { + in := []Node{} + if err := gob.NewDecoder(bytes.NewBuffer(input)).Decode(&in); err != nil { + return err + } + *n = NodeSet{}.fromIntermediate(in) + return nil } diff --git a/report/node_set_test.go b/report/node_set_test.go index f0331b4ce..05434ccb2 100644 --- a/report/node_set_test.go +++ b/report/node_set_test.go @@ -49,7 +49,7 @@ func TestMakeNodeSet(t *testing.T) { for _, spec := range testcase.wants { wants = append(wants, report.MakeNode().WithTopology(spec.topology).WithID(spec.id)) } - if want, have := report.NodeSet(wants), report.MakeNodeSet(inputs...); !reflect.DeepEqual(want, have) { + if want, have := report.MakeNodeSet(wants...), report.MakeNodeSet(inputs...); !reflect.DeepEqual(want, have) { t.Errorf("%#v: want %#v, have %#v", inputs, wants, have) } } @@ -78,11 +78,11 @@ func TestNodeSetAdd(t *testing.T) { nodes []report.Node want report.NodeSet }{ - {input: report.NodeSet(nil), nodes: []report.Node{}, want: report.NodeSet(nil)}, + {input: report.NodeSet{}, nodes: []report.Node{}, want: report.NodeSet{}}, { - input: report.MakeNodeSet(), + input: report.EmptyNodeSet, nodes: []report.Node{}, - want: report.MakeNodeSet(), + want: report.EmptyNodeSet, }, { input: report.MakeNodeSet(report.MakeNode().WithID("a")), @@ -90,7 +90,7 @@ func TestNodeSetAdd(t *testing.T) { want: report.MakeNodeSet(report.MakeNode().WithID("a")), }, { - input: report.MakeNodeSet(), + input: report.EmptyNodeSet, nodes: []report.Node{report.MakeNode().WithID("a")}, want: report.MakeNodeSet(report.MakeNode().WithID("a")), }, @@ -115,18 +115,18 @@ func TestNodeSetAdd(t *testing.T) { want: report.MakeNodeSet(report.MakeNode().WithID("a"), report.MakeNode().WithID("b"), report.MakeNode().WithID("c")), }, } { - originalLen := len(testcase.input) + originalLen := testcase.input.Size() if want, have := testcase.want, testcase.input.Add(testcase.nodes...); !reflect.DeepEqual(want, have) { t.Errorf("%v + %v: want %v, have %v", testcase.input, testcase.nodes, want, have) } - if len(testcase.input) != originalLen { + if testcase.input.Size() != originalLen { t.Errorf("%v + %v: modified the original input!", testcase.input, testcase.nodes) } } } func BenchmarkNodeSetAdd(b *testing.B) { - n := report.MakeNodeSet() + n := report.EmptyNodeSet for i := 0; i < 600; i++ { n = n.Add( report.MakeNode().WithID(fmt.Sprint(i)).WithLatests(map[string]string{ @@ -155,15 +155,15 @@ func TestNodeSetMerge(t *testing.T) { other report.NodeSet want report.NodeSet }{ - {input: report.NodeSet(nil), other: report.NodeSet(nil), want: report.NodeSet(nil)}, - {input: report.MakeNodeSet(), other: report.MakeNodeSet(), want: report.MakeNodeSet()}, + {input: report.NodeSet{}, other: report.NodeSet{}, want: report.NodeSet{}}, + {input: report.EmptyNodeSet, other: report.EmptyNodeSet, want: report.EmptyNodeSet}, { input: report.MakeNodeSet(report.MakeNode().WithID("a")), - other: report.MakeNodeSet(), + other: report.EmptyNodeSet, want: report.MakeNodeSet(report.MakeNode().WithID("a")), }, { - input: report.MakeNodeSet(), + input: report.EmptyNodeSet, other: report.MakeNodeSet(report.MakeNode().WithID("a")), want: report.MakeNodeSet(report.MakeNode().WithID("a")), }, @@ -193,18 +193,18 @@ func TestNodeSetMerge(t *testing.T) { want: report.MakeNodeSet(report.MakeNode().WithID("a"), report.MakeNode().WithID("b")), }, } { - originalLen := len(testcase.input) + originalLen := testcase.input.Size() if want, have := testcase.want, testcase.input.Merge(testcase.other); !reflect.DeepEqual(want, have) { t.Errorf("%v + %v: want %v, have %v", testcase.input, testcase.other, want, have) } - if len(testcase.input) != originalLen { + if testcase.input.Size() != originalLen { t.Errorf("%v + %v: modified the original input!", testcase.input, testcase.other) } } } func BenchmarkNodeSetMerge(b *testing.B) { - n, other := report.MakeNodeSet(), report.MakeNodeSet() + n, other := report.NodeSet{}, report.NodeSet{} for i := 0; i < 600; i++ { n = n.Add( report.MakeNode().WithID(fmt.Sprint(i)).WithLatests(map[string]string{