mirror of
https://github.com/weaveworks/scope.git
synced 2026-02-14 18:09:59 +00:00
use immutability for the NodeSet
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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{
|
||||
|
||||
Reference in New Issue
Block a user