Files
weave-scope/report/latest_map.go
Paul Bellamy fe6203fd3f Review Feedback
Squash of:
- including children in topologies_test.go
- report.Node.Prune should prune children also
- rewrote ShortLivedInternetConnections test to express its intent
- adding tests for detail Summary rendering
2016-03-29 14:13:20 +01:00

233 lines
5.4 KiB
Go

package report
import (
"bytes"
"encoding/gob"
"fmt"
"sort"
"time"
"github.com/mndrix/ps"
"github.com/ugorji/go/codec"
)
// LatestMap is a persitent map which support latest-win merges. We have to
// embed ps.Map as its an interface. LatestMaps are immutable.
type LatestMap struct {
ps.Map
}
// LatestEntry represents a timestamped value inside the LatestMap.
type LatestEntry struct {
Timestamp time.Time `json:"timestamp"`
Value string `json:"value"`
}
func (e LatestEntry) String() string {
return fmt.Sprintf("\"%s\" (%s)", e.Value, e.Timestamp.String())
}
// Equal returns true if the supplied LatestEntry is equal to this one.
func (e LatestEntry) Equal(e2 LatestEntry) bool {
return e.Timestamp.Equal(e2.Timestamp) && e.Value == e2.Value
}
// EmptyLatestMap is an empty LatestMap. Start with this.
var EmptyLatestMap = LatestMap{ps.NewMap()}
// MakeLatestMap makes an empty LatestMap
func MakeLatestMap() LatestMap {
return EmptyLatestMap
}
// Copy is a noop, as LatestMaps are immutable.
func (m LatestMap) Copy() LatestMap {
return m
}
// Size returns the number of elements
func (m LatestMap) Size() int {
if m.Map == nil {
return 0
}
return m.Map.Size()
}
// Merge produces a fresh LatestMap, container the kers from both inputs. When
// both inputs container the same key, the latter value is used.
func (m LatestMap) Merge(other LatestMap) LatestMap {
var (
mSize = m.Size()
otherSize = other.Size()
output = m.Map
iter = other.Map
)
switch {
case mSize == 0:
return other
case otherSize == 0:
return m
case mSize < otherSize:
output, iter = iter, output
}
iter.ForEach(func(key string, iterVal interface{}) {
if existingVal, ok := output.Lookup(key); ok {
if existingVal.(LatestEntry).Timestamp.Before(iterVal.(LatestEntry).Timestamp) {
output = output.Set(key, iterVal)
}
} else {
output = output.Set(key, iterVal)
}
})
return LatestMap{output}
}
// Lookup the value for the given key.
func (m LatestMap) Lookup(key string) (string, bool) {
v, _, ok := m.LookupEntry(key)
return v, ok
}
// LookupEntry returns the raw entry for the given key.
func (m LatestMap) LookupEntry(key string) (string, time.Time, bool) {
if m.Map == nil {
return "", time.Time{}, false
}
value, ok := m.Map.Lookup(key)
if !ok {
return "", time.Time{}, false
}
e := value.(LatestEntry)
return e.Value, e.Timestamp, true
}
// Set the value for the given key.
func (m LatestMap) Set(key string, timestamp time.Time, value string) LatestMap {
if m.Map == nil {
m = EmptyLatestMap
}
return LatestMap{m.Map.Set(key, LatestEntry{timestamp, value})}
}
// Delete the value for the given key.
func (m LatestMap) Delete(key string) LatestMap {
if m.Map == nil {
m = EmptyLatestMap
}
return LatestMap{m.Map.Delete(key)}
}
// ForEach executes f on each key value pair in the map
func (m LatestMap) ForEach(fn func(k, v string)) {
if m.Map == nil {
return
}
m.Map.ForEach(func(key string, value interface{}) {
fn(key, value.(LatestEntry).Value)
})
}
func (m LatestMap) String() string {
keys := []string{}
if m.Map == nil {
m = EmptyLatestMap
}
for _, k := range m.Map.Keys() {
keys = append(keys, k)
}
sort.Strings(keys)
buf := bytes.NewBufferString("{")
for _, key := range keys {
val, _ := m.Map.Lookup(key)
fmt.Fprintf(buf, "%s: %s,\n", key, val)
}
fmt.Fprintf(buf, "}")
return buf.String()
}
// DeepEqual tests equality with other LatestMap
func (m LatestMap) DeepEqual(n LatestMap) bool {
if m.Size() != n.Size() {
return false
}
if m.Size() == 0 {
return true
}
equal := true
m.Map.ForEach(func(k string, val interface{}) {
if otherValue, ok := n.Map.Lookup(k); !ok {
equal = false
} else {
equal = equal && val.(LatestEntry).Equal(otherValue.(LatestEntry))
}
})
return equal
}
func (m LatestMap) toIntermediate() map[string]LatestEntry {
intermediate := map[string]LatestEntry{}
if m.Map != nil {
m.Map.ForEach(func(key string, val interface{}) {
intermediate[key] = val.(LatestEntry)
})
}
return intermediate
}
func (m LatestMap) fromIntermediate(in map[string]LatestEntry) LatestMap {
out := ps.NewMap()
for k, v := range in {
out = out.Set(k, v)
}
return LatestMap{out}
}
// CodecEncodeSelf implements codec.Selfer
func (m *LatestMap) CodecEncodeSelf(encoder *codec.Encoder) {
if m.Map != nil {
encoder.Encode(m.toIntermediate())
} else {
encoder.Encode(nil)
}
}
// CodecDecodeSelf implements codec.Selfer
func (m *LatestMap) CodecDecodeSelf(decoder *codec.Decoder) {
in := map[string]LatestEntry{}
if err := decoder.Decode(&in); err != nil {
return
}
*m = LatestMap{}.fromIntermediate(in)
}
// MarshalJSON shouldn't be used, use CodecEncodeSelf instead
func (LatestMap) MarshalJSON() ([]byte, error) {
panic("MarshalJSON shouldn't be used, use CodecEncodeSelf instead")
}
// UnmarshalJSON shouldn't be used, use CodecDecodeSelf instead
func (*LatestMap) UnmarshalJSON(b []byte) error {
panic("UnmarshalJSON shouldn't be used, use CodecDecodeSelf instead")
}
// GobEncode implements gob.Marshaller
func (m LatestMap) GobEncode() ([]byte, error) {
buf := bytes.Buffer{}
err := gob.NewEncoder(&buf).Encode(m.toIntermediate())
return buf.Bytes(), err
}
// GobDecode implements gob.Unmarshaller
func (m *LatestMap) GobDecode(input []byte) error {
in := map[string]LatestEntry{}
if err := gob.NewDecoder(bytes.NewBuffer(input)).Decode(&in); err != nil {
return err
}
*m = LatestMap{}.fromIntermediate(in)
return nil
}