use immutability for the NodeSet

This commit is contained in:
Paul Bellamy
2016-01-25 14:22:04 +00:00
parent 1943ad440a
commit 66790f4436
5 changed files with 194 additions and 93 deletions

View File

@@ -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 {

View File

@@ -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) {

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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{