mirror of
https://github.com/weaveworks/scope.git
synced 2026-02-14 18:09:59 +00:00
Need to do the special-case merge before `n.Latest.Merge()`, otherwise it picks just one set of controls. Also modified the test to call `Merge()` where the problem happened.
286 lines
8.3 KiB
Go
286 lines
8.3 KiB
Go
package report
|
|
|
|
import (
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/weaveworks/common/mtime"
|
|
)
|
|
|
|
// Node describes a superset of the metadata that probes can collect
|
|
// about a given node in a given topology, along with the edges (aka
|
|
// adjacency) emanating from the node.
|
|
type Node struct {
|
|
ID string `json:"id,omitempty"`
|
|
Topology string `json:"topology,omitempty"`
|
|
Sets Sets `json:"sets,omitempty"`
|
|
Adjacency IDList `json:"adjacency,omitempty"`
|
|
Latest StringLatestMap `json:"latest,omitempty"`
|
|
Metrics Metrics `json:"metrics,omitempty" deepequal:"nil==empty"`
|
|
Parents Sets `json:"parents,omitempty"`
|
|
Children NodeSet `json:"children,omitempty"`
|
|
}
|
|
|
|
// MakeNode creates a new Node with no initial metadata.
|
|
func MakeNode(id string) Node {
|
|
return Node{
|
|
ID: id,
|
|
Sets: MakeSets(),
|
|
Adjacency: MakeIDList(),
|
|
Latest: MakeStringLatestMap(),
|
|
Metrics: Metrics{},
|
|
Parents: MakeSets(),
|
|
}
|
|
}
|
|
|
|
// MakeNodeWith creates a new Node with the supplied map.
|
|
func MakeNodeWith(id string, m map[string]string) Node {
|
|
return MakeNode(id).WithLatests(m)
|
|
}
|
|
|
|
// WithID returns a fresh copy of n, with ID changed.
|
|
func (n Node) WithID(id string) Node {
|
|
n.ID = id
|
|
return n
|
|
}
|
|
|
|
// WithTopology returns a fresh copy of n, with ID changed.
|
|
func (n Node) WithTopology(topology string) Node {
|
|
n.Topology = topology
|
|
return n
|
|
}
|
|
|
|
// Before is used for sorting nodes by topology and id
|
|
func (n Node) Before(other Node) bool {
|
|
return n.Topology < other.Topology || (n.Topology == other.Topology && n.ID < other.ID)
|
|
}
|
|
|
|
// Equal is used for comparing nodes by topology and id
|
|
func (n Node) Equal(other Node) bool {
|
|
return n.Topology == other.Topology && n.ID == other.ID
|
|
}
|
|
|
|
// After is used for sorting nodes by topology and id
|
|
func (n Node) After(other Node) bool {
|
|
return other.Topology < n.Topology || (other.Topology == n.Topology && other.ID < n.ID)
|
|
}
|
|
|
|
// WithLatests returns a fresh copy of n, with Metadata m merged in.
|
|
func (n Node) WithLatests(m map[string]string) Node {
|
|
ts := mtime.Now()
|
|
n.Latest = n.Latest.addMapEntries(ts, m)
|
|
return n
|
|
}
|
|
|
|
// WithLatest produces a new Node with k mapped to v in the Latest metadata.
|
|
func (n Node) WithLatest(k string, ts time.Time, v string) Node {
|
|
n.Latest = n.Latest.Set(k, ts, v)
|
|
return n
|
|
}
|
|
|
|
// LookupCounter returns the value of a counter
|
|
// (counters are stored as strings, to keep the data structure simple)
|
|
func (n Node) LookupCounter(k string) (value int, found bool) {
|
|
name := CounterPrefix + k
|
|
var str string
|
|
if str, found = n.Latest.Lookup(name); found {
|
|
value, _ = strconv.Atoi(str)
|
|
}
|
|
return value, found
|
|
}
|
|
|
|
// AddCounter returns a fresh copy of n, with Counter c added in.
|
|
// (counters are stored as strings, to keep the data structure simple)
|
|
func (n Node) AddCounter(k string, value int) Node {
|
|
name := CounterPrefix + k
|
|
if prevValue, found := n.LookupCounter(k); found {
|
|
value += prevValue
|
|
}
|
|
return n.WithLatest(name, mtime.Now(), strconv.Itoa(value))
|
|
}
|
|
|
|
// WithSet returns a fresh copy of n, with set merged in at key.
|
|
func (n Node) WithSet(key string, set StringSet) Node {
|
|
n.Sets = n.Sets.Add(key, set)
|
|
return n
|
|
}
|
|
|
|
// WithSets returns a fresh copy of n, with sets merged in.
|
|
func (n Node) WithSets(sets Sets) Node {
|
|
n.Sets = n.Sets.Merge(sets)
|
|
return n
|
|
}
|
|
|
|
// WithMetric returns a fresh copy of n, with metric merged in at key.
|
|
func (n Node) WithMetric(key string, metric Metric) Node {
|
|
n.Metrics = n.Metrics.Copy()
|
|
n.Metrics[key] = n.Metrics[key].Merge(metric)
|
|
return n
|
|
}
|
|
|
|
// WithMetrics returns a fresh copy of n, with metrics merged in.
|
|
func (n Node) WithMetrics(metrics Metrics) Node {
|
|
n.Metrics = n.Metrics.Merge(metrics)
|
|
return n
|
|
}
|
|
|
|
// WithAdjacent returns a fresh copy of n, with 'a' added to Adjacency
|
|
func (n Node) WithAdjacent(a ...string) Node {
|
|
n.Adjacency = n.Adjacency.Add(a...)
|
|
return n
|
|
}
|
|
|
|
// WithLatestActiveControls says which controls are active on this node.
|
|
// Implemented as a delimiter-separated string in Latest
|
|
func (n Node) WithLatestActiveControls(cs ...string) Node {
|
|
return n.WithLatest(NodeActiveControls, mtime.Now(), strings.Join(cs, ScopeDelim))
|
|
}
|
|
|
|
// ActiveControls returns a string slice with the names of active controls.
|
|
func (n Node) ActiveControls() []string {
|
|
activeControls, _ := n.Latest.Lookup(NodeActiveControls)
|
|
return strings.Split(activeControls, ScopeDelim)
|
|
}
|
|
|
|
// MergeActiveControls merges the control lists from n and b
|
|
func (n Node) MergeActiveControls(b Node) Node {
|
|
activeControlsB, tsB, foundB := b.Latest.LookupEntry(NodeActiveControls)
|
|
if !foundB { // nothing to merge
|
|
return n
|
|
}
|
|
activeControlsA, tsA, foundA := n.Latest.LookupEntry(NodeActiveControls)
|
|
if !foundA {
|
|
return n.WithLatest(NodeActiveControls, tsB, activeControlsB)
|
|
}
|
|
if activeControlsA == activeControlsB {
|
|
return n
|
|
}
|
|
// If we get here we have active controls in both n and b that are different
|
|
merged := make(map[string]struct{})
|
|
for _, c := range strings.Split(activeControlsA, ScopeDelim) {
|
|
merged[c] = struct{}{}
|
|
}
|
|
for _, c := range strings.Split(activeControlsB, ScopeDelim) {
|
|
merged[c] = struct{}{}
|
|
}
|
|
cs := make([]string, 0, len(merged))
|
|
for c := range merged {
|
|
cs = append(cs, c)
|
|
}
|
|
if tsA.Before(tsB) {
|
|
tsA = tsB
|
|
}
|
|
return n.WithLatest(NodeActiveControls, tsA, strings.Join(cs, ScopeDelim))
|
|
}
|
|
|
|
// WithParent returns a fresh copy of n, with one parent added
|
|
func (n Node) WithParent(key, parent string) Node {
|
|
n.Parents = n.Parents.AddString(key, parent)
|
|
return n
|
|
}
|
|
|
|
// WithParents returns a fresh copy of n, with sets merged in.
|
|
func (n Node) WithParents(parents Sets) Node {
|
|
n.Parents = n.Parents.Merge(parents)
|
|
return n
|
|
}
|
|
|
|
// PruneParents returns a fresh copy of n, without any parents.
|
|
func (n Node) PruneParents() Node {
|
|
n.Parents = MakeSets()
|
|
return n
|
|
}
|
|
|
|
// WithChildren returns a fresh copy of n, with children merged in.
|
|
func (n Node) WithChildren(children NodeSet) Node {
|
|
n.Children = n.Children.Merge(children)
|
|
return n
|
|
}
|
|
|
|
// WithChild returns a fresh copy of n, with one child merged in.
|
|
func (n Node) WithChild(child Node) Node {
|
|
n.Children = n.Children.Merge(MakeNodeSet(child))
|
|
return n
|
|
}
|
|
|
|
// Merge merges the individual components of a node and returns a
|
|
// fresh node.
|
|
func (n Node) Merge(other Node) Node {
|
|
id := n.ID
|
|
if id == "" {
|
|
id = other.ID
|
|
}
|
|
topology := n.Topology
|
|
if topology == "" {
|
|
topology = other.Topology
|
|
} else if other.Topology != "" && topology != other.Topology {
|
|
panic("Cannot merge nodes with different topology types: " + topology + " != " + other.Topology)
|
|
}
|
|
|
|
// Special case to merge controls from two different probes.
|
|
// Do this first, then the value we want will be picked as newest by n.Latest.Merge().
|
|
if topology == Host {
|
|
n = n.MergeActiveControls(other)
|
|
}
|
|
|
|
newNode := Node{
|
|
ID: id,
|
|
Topology: topology,
|
|
Sets: n.Sets.Merge(other.Sets),
|
|
Adjacency: n.Adjacency.Merge(other.Adjacency),
|
|
Latest: n.Latest.Merge(other.Latest),
|
|
Metrics: n.Metrics.Merge(other.Metrics),
|
|
Parents: n.Parents.Merge(other.Parents),
|
|
Children: n.Children.Merge(other.Children),
|
|
}
|
|
|
|
return newNode
|
|
}
|
|
|
|
// UnsafeUnMerge removes data from n that would be added by merging other,
|
|
// modifying the original.
|
|
// returns true if n.Merge(other) is the same as n
|
|
func (n *Node) UnsafeUnMerge(other Node) bool {
|
|
// If it's not the same ID and topology then just bail out
|
|
if n.ID != other.ID || n.Topology != other.Topology {
|
|
return false
|
|
}
|
|
n.ID = ""
|
|
n.Topology = ""
|
|
remove := true
|
|
// We either keep a whole section or drop it if anything changed
|
|
// - a trade-off of some extra data size in favour of faster simpler code.
|
|
// (in practice, very few values reported by Scope probes do change over time)
|
|
if n.Latest.EqualIgnoringTimestamps(other.Latest) {
|
|
n.Latest = nil
|
|
} else {
|
|
remove = false
|
|
}
|
|
if n.Sets.DeepEqual(other.Sets) {
|
|
n.Sets = MakeSets()
|
|
} else {
|
|
remove = false
|
|
}
|
|
if n.Parents.DeepEqual(other.Parents) {
|
|
n.Parents = MakeSets()
|
|
} else {
|
|
remove = false
|
|
}
|
|
if n.Adjacency.Equal(other.Adjacency) {
|
|
n.Adjacency = nil
|
|
} else {
|
|
remove = false
|
|
}
|
|
// counters and children are not created in the probe so we don't check those
|
|
// metrics don't overlap so just check if we have any
|
|
return remove && len(n.Metrics) == 0
|
|
}
|
|
|
|
// If a node is removed from source between two full reports, then we
|
|
// might only have a delta of its last state. Detect that from a blank topology,
|
|
// which cannot arise in a properly-formed Node.
|
|
func (n *Node) isPartMerged() bool {
|
|
return n.Topology == ""
|
|
}
|