mirror of
https://github.com/weaveworks/scope.git
synced 2026-02-14 18:09:59 +00:00
Similar to video compression which uses key-frames and differences between them: every N publishes we send a full report, but inbetween we only send what has changed. Fairly simple approach in the probe - hold on to the last full report, and for the deltas remove anything that would be merged in from the full report. On the receiving side in the app it already merges a set of reports together to produce the final output for rendering, so provided N is smaller than that set we don't need to do anything different. Deltas don't need to represent nodes that have disappeared - an earlier full node will have that node so it would be merged into the final output anyway.
313 lines
9.2 KiB
Go
313 lines
9.2 KiB
Go
package report
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// Topology describes a specific view of a network. It consists of
|
|
// nodes with metadata, and edges. Edges are directional, and embedded
|
|
// in the Node struct.
|
|
type Topology struct {
|
|
Shape string `json:"shape,omitempty"`
|
|
Tag string `json:"tag,omitempty"`
|
|
Label string `json:"label,omitempty"`
|
|
LabelPlural string `json:"label_plural,omitempty"`
|
|
Nodes Nodes `json:"nodes,omitempty" deepequal:"nil==empty"`
|
|
Controls Controls `json:"controls,omitempty" deepequal:"nil==empty"`
|
|
MetadataTemplates MetadataTemplates `json:"metadata_templates,omitempty"`
|
|
MetricTemplates MetricTemplates `json:"metric_templates,omitempty"`
|
|
TableTemplates TableTemplates `json:"table_templates,omitempty"`
|
|
}
|
|
|
|
// MakeTopology gives you a Topology.
|
|
func MakeTopology() Topology {
|
|
return Topology{
|
|
Nodes: map[string]Node{},
|
|
Controls: Controls{},
|
|
}
|
|
}
|
|
|
|
// WithMetadataTemplates merges some metadata templates into this topology,
|
|
// returning a new topology.
|
|
func (t Topology) WithMetadataTemplates(other MetadataTemplates) Topology {
|
|
return Topology{
|
|
Shape: t.Shape,
|
|
Tag: t.Tag,
|
|
Label: t.Label,
|
|
LabelPlural: t.LabelPlural,
|
|
Nodes: t.Nodes.Copy(),
|
|
Controls: t.Controls.Copy(),
|
|
MetadataTemplates: t.MetadataTemplates.Merge(other),
|
|
MetricTemplates: t.MetricTemplates.Copy(),
|
|
TableTemplates: t.TableTemplates.Copy(),
|
|
}
|
|
}
|
|
|
|
// WithMetricTemplates merges some metadata templates into this topology,
|
|
// returning a new topology.
|
|
func (t Topology) WithMetricTemplates(other MetricTemplates) Topology {
|
|
return Topology{
|
|
Shape: t.Shape,
|
|
Tag: t.Tag,
|
|
Label: t.Label,
|
|
LabelPlural: t.LabelPlural,
|
|
Nodes: t.Nodes.Copy(),
|
|
Controls: t.Controls.Copy(),
|
|
MetadataTemplates: t.MetadataTemplates.Copy(),
|
|
MetricTemplates: t.MetricTemplates.Merge(other),
|
|
TableTemplates: t.TableTemplates.Copy(),
|
|
}
|
|
}
|
|
|
|
// WithTableTemplates merges some table templates into this topology,
|
|
// returning a new topology.
|
|
func (t Topology) WithTableTemplates(other TableTemplates) Topology {
|
|
return Topology{
|
|
Shape: t.Shape,
|
|
Tag: t.Tag,
|
|
Label: t.Label,
|
|
LabelPlural: t.LabelPlural,
|
|
Nodes: t.Nodes.Copy(),
|
|
Controls: t.Controls.Copy(),
|
|
MetadataTemplates: t.MetadataTemplates.Copy(),
|
|
MetricTemplates: t.MetricTemplates.Copy(),
|
|
TableTemplates: t.TableTemplates.Merge(other),
|
|
}
|
|
}
|
|
|
|
// WithShape sets the shape of nodes from this topology, returning a new topology.
|
|
func (t Topology) WithShape(shape string) Topology {
|
|
return Topology{
|
|
Shape: shape,
|
|
Tag: t.Tag,
|
|
Label: t.Label,
|
|
LabelPlural: t.LabelPlural,
|
|
Nodes: t.Nodes.Copy(),
|
|
Controls: t.Controls.Copy(),
|
|
MetadataTemplates: t.MetadataTemplates.Copy(),
|
|
MetricTemplates: t.MetricTemplates.Copy(),
|
|
TableTemplates: t.TableTemplates.Copy(),
|
|
}
|
|
}
|
|
|
|
// WithTag sets the tag of nodes from this topology, returning a new topology.
|
|
func (t Topology) WithTag(tag string) Topology {
|
|
return Topology{
|
|
Shape: t.Shape,
|
|
Tag: tag,
|
|
Label: t.Label,
|
|
LabelPlural: t.LabelPlural,
|
|
Nodes: t.Nodes.Copy(),
|
|
Controls: t.Controls.Copy(),
|
|
MetadataTemplates: t.MetadataTemplates.Copy(),
|
|
MetricTemplates: t.MetricTemplates.Copy(),
|
|
TableTemplates: t.TableTemplates.Copy(),
|
|
}
|
|
}
|
|
|
|
// WithLabel sets the label terminology of this topology, returning a new topology.
|
|
func (t Topology) WithLabel(label, labelPlural string) Topology {
|
|
return Topology{
|
|
Shape: t.Shape,
|
|
Tag: t.Tag,
|
|
Label: label,
|
|
LabelPlural: labelPlural,
|
|
Nodes: t.Nodes.Copy(),
|
|
Controls: t.Controls.Copy(),
|
|
MetadataTemplates: t.MetadataTemplates.Copy(),
|
|
MetricTemplates: t.MetricTemplates.Copy(),
|
|
TableTemplates: t.TableTemplates.Copy(),
|
|
}
|
|
}
|
|
|
|
// AddNode adds node to the topology under key nodeID; if a
|
|
// node already exists for this key, nmd is merged with that node.
|
|
// This method is different from all the other similar methods
|
|
// in that it mutates the Topology, to solve issues of GC pressure.
|
|
func (t Topology) AddNode(node Node) {
|
|
if existing, ok := t.Nodes[node.ID]; ok {
|
|
node = node.Merge(existing)
|
|
}
|
|
t.Nodes[node.ID] = node
|
|
}
|
|
|
|
// ReplaceNode adds node to the topology under key nodeID; if a
|
|
// node already exists for this key, node replaces that node.
|
|
// Like AddNode, it mutates the Topology
|
|
func (t Topology) ReplaceNode(node Node) {
|
|
t.Nodes[node.ID] = node
|
|
}
|
|
|
|
// GetShape returns the current topology shape, or the default if there isn't one.
|
|
func (t Topology) GetShape() string {
|
|
if t.Shape == "" {
|
|
return Circle
|
|
}
|
|
return t.Shape
|
|
}
|
|
|
|
// Copy returns a value copy of the Topology.
|
|
func (t Topology) Copy() Topology {
|
|
return Topology{
|
|
Shape: t.Shape,
|
|
Tag: t.Tag,
|
|
Label: t.Label,
|
|
LabelPlural: t.LabelPlural,
|
|
Nodes: t.Nodes.Copy(),
|
|
Controls: t.Controls.Copy(),
|
|
MetadataTemplates: t.MetadataTemplates.Copy(),
|
|
MetricTemplates: t.MetricTemplates.Copy(),
|
|
TableTemplates: t.TableTemplates.Copy(),
|
|
}
|
|
}
|
|
|
|
// Merge merges the other object into this one, and returns the result object.
|
|
// The original is not modified.
|
|
func (t Topology) Merge(other Topology) Topology {
|
|
shape := t.Shape
|
|
if shape == "" {
|
|
shape = other.Shape
|
|
}
|
|
label, labelPlural := t.Label, t.LabelPlural
|
|
if label == "" {
|
|
label, labelPlural = other.Label, other.LabelPlural
|
|
}
|
|
tag := t.Tag
|
|
if tag == "" {
|
|
tag = other.Tag
|
|
}
|
|
return Topology{
|
|
Shape: shape,
|
|
Tag: tag,
|
|
Label: label,
|
|
LabelPlural: labelPlural,
|
|
Nodes: t.Nodes.Merge(other.Nodes),
|
|
Controls: t.Controls.Merge(other.Controls),
|
|
MetadataTemplates: t.MetadataTemplates.Merge(other.MetadataTemplates),
|
|
MetricTemplates: t.MetricTemplates.Merge(other.MetricTemplates),
|
|
TableTemplates: t.TableTemplates.Merge(other.TableTemplates),
|
|
}
|
|
}
|
|
|
|
// UnsafeMerge merges the other object into this one, modifying the original.
|
|
func (t *Topology) UnsafeMerge(other Topology) {
|
|
if t.Shape == "" {
|
|
t.Shape = other.Shape
|
|
}
|
|
if t.Label == "" {
|
|
t.Label, t.LabelPlural = other.Label, other.LabelPlural
|
|
}
|
|
if t.Tag == "" {
|
|
t.Tag = other.Tag
|
|
}
|
|
t.Nodes.UnsafeMerge(other.Nodes)
|
|
t.Controls = t.Controls.Merge(other.Controls)
|
|
t.MetadataTemplates = t.MetadataTemplates.Merge(other.MetadataTemplates)
|
|
t.MetricTemplates = t.MetricTemplates.Merge(other.MetricTemplates)
|
|
t.TableTemplates = t.TableTemplates.Merge(other.TableTemplates)
|
|
}
|
|
|
|
// UnsafeUnMerge removes any information from t that would be added by merging other,
|
|
// modifying the original.
|
|
func (t *Topology) UnsafeUnMerge(other Topology) {
|
|
if t.Shape == other.Shape {
|
|
t.Shape = ""
|
|
}
|
|
if t.Label == other.Label && t.LabelPlural == other.LabelPlural {
|
|
t.Label, t.LabelPlural = "", ""
|
|
}
|
|
if t.Tag == other.Tag {
|
|
t.Tag = ""
|
|
}
|
|
t.Nodes.UnsafeUnMerge(other.Nodes)
|
|
// TODO Controls
|
|
// NOTE: taking a shortcut and assuming templates are static, which they have always been in Scope
|
|
// If you break that assumption please change this.
|
|
t.MetadataTemplates = nil
|
|
t.MetricTemplates = nil
|
|
t.TableTemplates = nil
|
|
}
|
|
|
|
// Nodes is a collection of nodes in a topology. Keys are node IDs.
|
|
// TODO(pb): type Topology map[string]Node
|
|
type Nodes map[string]Node
|
|
|
|
// Copy returns a value copy of the Nodes.
|
|
func (n Nodes) Copy() Nodes {
|
|
if n == nil {
|
|
return nil
|
|
}
|
|
cp := make(Nodes, len(n))
|
|
for k, v := range n {
|
|
cp[k] = v
|
|
}
|
|
return cp
|
|
}
|
|
|
|
// Merge merges the other object into this one, and returns the result object.
|
|
// The original is not modified.
|
|
func (n Nodes) Merge(other Nodes) Nodes {
|
|
if len(other) > len(n) {
|
|
n, other = other, n
|
|
}
|
|
if len(other) == 0 {
|
|
return n
|
|
}
|
|
cp := n.Copy()
|
|
cp.UnsafeMerge(other)
|
|
return cp
|
|
}
|
|
|
|
// UnsafeMerge merges the other object into this one, modifying the original.
|
|
func (n *Nodes) UnsafeMerge(other Nodes) {
|
|
for k, v := range other {
|
|
if existing, ok := (*n)[k]; ok { // don't overwrite
|
|
(*n)[k] = v.Merge(existing)
|
|
} else {
|
|
(*n)[k] = v
|
|
}
|
|
}
|
|
}
|
|
|
|
// UnsafeUnMerge removes nodes from n that would be added by merging other,
|
|
// modifying the original.
|
|
func (n *Nodes) UnsafeUnMerge(other Nodes) {
|
|
for k, node := range *n {
|
|
if otherNode, ok := (other)[k]; ok {
|
|
remove := node.UnsafeUnMerge(otherNode)
|
|
if remove {
|
|
delete(*n, k)
|
|
} else {
|
|
(*n)[k] = node
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Validate checks the topology for various inconsistencies.
|
|
func (t Topology) Validate() error {
|
|
errs := []string{}
|
|
|
|
// Check all nodes are valid, and the keys are parseable, i.e.
|
|
// contain a scope.
|
|
for nodeID, nmd := range t.Nodes {
|
|
if _, _, ok := ParseNodeID(nodeID); !ok {
|
|
errs = append(errs, fmt.Sprintf("invalid node ID %q", nodeID))
|
|
}
|
|
|
|
// Check all adjancency keys has entries in Node.
|
|
for _, dstNodeID := range nmd.Adjacency {
|
|
if _, ok := t.Nodes[dstNodeID]; !ok {
|
|
errs = append(errs, fmt.Sprintf("node missing from adjacency %q -> %q", nodeID, dstNodeID))
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(errs) > 0 {
|
|
return fmt.Errorf("%d error(s): %s", len(errs), strings.Join(errs, "; "))
|
|
}
|
|
|
|
return nil
|
|
}
|