From 970ab66ea0c1623ae90da427d9d70b9d07df2855 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Sun, 19 Mar 2017 16:36:10 +0000 Subject: [PATCH 1/2] Extract map helper functions --- report/latest_map.go | 103 ++++-------------------------------- report/map_helpers.go | 120 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+), 93 deletions(-) create mode 100644 report/map_helpers.go diff --git a/report/latest_map.go b/report/latest_map.go index da767c1c7..37cbdf6c1 100644 --- a/report/latest_map.go +++ b/report/latest_map.go @@ -1,9 +1,7 @@ package report import ( - "bytes" "fmt" - "sort" "time" "github.com/ugorji/go/codec" @@ -60,34 +58,12 @@ func (m LatestMap) Size() int { // both inputs. When both inputs contain the same key, the newer 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 - } if m.decoder != other.decoder { panic(fmt.Sprintf("Cannot merge maps with different entry value types, this has %#v, other has %#v", m.decoder, other.decoder)) } - - 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) - } + output := mergeMaps(m.Map, other.Map, func(a, b interface{}) bool { + return a.(LatestEntry).Timestamp.Before(b.(LatestEntry).Timestamp) }) - return LatestMap{output, m.decoder} } @@ -138,44 +114,17 @@ func (m LatestMap) ForEach(fn func(k string, ts time.Time, v interface{})) { // String returns the LatestMap's string representation. func (m LatestMap) String() string { - keys := []string{} - if m.Map == nil { - m = MakeLatestMapWithDecoder(m.decoder) - } - 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() + return mapToString(m.Map) } // 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 - } if m.decoder != n.decoder { panic(fmt.Sprintf("Cannot check equality of maps with different entry value types, this has %#v, other has %#v", m.decoder, n.decoder)) } - 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 mapEqual(m.Map, n.Map, func(val, otherValue interface{}) bool { + return val.(LatestEntry).Equal(otherValue.(LatestEntry)) }) - return equal } func (m LatestMap) toIntermediate() map[string]LatestEntry { @@ -197,47 +146,15 @@ func (m *LatestMap) CodecEncodeSelf(encoder *codec.Encoder) { } } -// constants from https://github.com/ugorji/go/blob/master/codec/helper.go#L207 -const ( - containerMapKey = 2 - containerMapValue = 3 - containerMapEnd = 4 -) - // CodecDecodeSelf implements codec.Selfer. -// This implementation does not use the intermediate form as that was a -// performance issue; skipping it saved almost 10% CPU. Note this means -// we are using undocumented, internal APIs, which could break in the future. -// See https://github.com/weaveworks/scope/pull/1709 for more information. func (m *LatestMap) CodecDecodeSelf(decoder *codec.Decoder) { - z, r := codec.GenHelperDecoder(decoder) - if r.TryDecodeAsNil() { - *m = MakeLatestMapWithDecoder(m.decoder) - return - } - - length := r.ReadMapStart() - out := ps.NewMap() - for i := 0; length < 0 || i < length; i++ { - if length < 0 && r.CheckBreak() { - break - } - - var key string - z.DecSendContainerState(containerMapKey) - if !r.TryDecodeAsNil() { - key = r.DecodeString() - } - - var value LatestEntry - z.DecSendContainerState(containerMapValue) - if !r.TryDecodeAsNil() { + out := mapRead(decoder, func(isNil bool) interface{} { + value := LatestEntry{} + if !isNil { m.decoder.Decode(decoder, &value) } - - out = out.UnsafeMutableSet(key, value) - } - z.DecSendContainerState(containerMapEnd) + return value + }) *m = LatestMap{out, m.decoder} } diff --git a/report/map_helpers.go b/report/map_helpers.go new file mode 100644 index 000000000..d88175833 --- /dev/null +++ b/report/map_helpers.go @@ -0,0 +1,120 @@ +package report + +import ( + "bytes" + "fmt" + "sort" + + "github.com/ugorji/go/codec" + "github.com/weaveworks/ps" +) + +// Helper functions for ps.Map, without considering what is inside + +// Return a new map containing all the elements of the two input maps +// and where the same key is in both, pick 'b' where prefer(a,b) is true +func mergeMaps(m, n ps.Map, prefer func(a, b interface{}) bool) ps.Map { + switch { + case m == nil: + return n + case n == nil: + return m + case m.Size() < n.Size(): + m, n = n, m + } + + n.ForEach(func(key string, val interface{}) { + if existingVal, found := m.Lookup(key); found { + if prefer(existingVal, val) { + m = m.Set(key, val) + } + } else { + m = m.Set(key, val) + } + }) + + return m +} + +func mapEqual(m, n ps.Map, equalf func(a, b interface{}) bool) bool { + var mSize, nSize int + if m != nil { + mSize = m.Size() + } + if n != nil { + nSize = n.Size() + } + if mSize != nSize { + return false + } + if mSize == 0 { + return true + } + equal := true + m.ForEach(func(k string, val interface{}) { + if otherValue, ok := n.Lookup(k); !ok { + equal = false + } else { + equal = equal && equalf(val, otherValue) + } + }) + return equal +} + +// very similar to ps.Map.String() but with keys sorted +func mapToString(m ps.Map) string { + keys := []string{} + if m != nil { + for _, k := range m.Keys() { + keys = append(keys, k) + } + sort.Strings(keys) + } + + buf := bytes.NewBufferString("{") + for _, key := range keys { + val, _ := m.Lookup(key) + fmt.Fprintf(buf, "%s: %s,\n", key, val) + } + fmt.Fprintf(buf, "}") + return buf.String() +} + +// constants from https://github.com/ugorji/go/blob/master/codec/helper.go#L207 +const ( + containerMapKey = 2 + containerMapValue = 3 + containerMapEnd = 4 +) + +// CodecDecodeSelf implements codec.Selfer. +// This implementation does not use the intermediate form as that was a +// performance issue; skipping it saved almost 10% CPU. Note this means +// we are using undocumented, internal APIs, which could break in the future. +// See https://github.com/weaveworks/scope/pull/1709 for more information. +func mapRead(decoder *codec.Decoder, decodeValue func(isNil bool) interface{}) ps.Map { + z, r := codec.GenHelperDecoder(decoder) + if r.TryDecodeAsNil() { + return ps.NewMap() + } + + length := r.ReadMapStart() + out := ps.NewMap() + for i := 0; length < 0 || i < length; i++ { + if length < 0 && r.CheckBreak() { + break + } + + var key string + z.DecSendContainerState(containerMapKey) + if !r.TryDecodeAsNil() { + key = r.DecodeString() + } + + z.DecSendContainerState(containerMapValue) + value := decodeValue(r.TryDecodeAsNil()) + out = out.UnsafeMutableSet(key, value) + } + z.DecSendContainerState(containerMapEnd) + return out +} From de97a72661ee3dc082bc3f7a0565aa73818acf65 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Sun, 19 Mar 2017 16:52:29 +0000 Subject: [PATCH 2/2] Remove indirection via LatestMap Structs like StringLatestMap now use ps.Map directly, which saves a memory allocation for LatestEntry.Value to point to. The values in the ps.Map are now pointers, which saves a memory allocation indirecting a value type to an interface{} --- extras/generate_latest_map | 111 ++++++++++------ report/latest_map.go | 169 ----------------------- report/latest_map_generated.go | 206 ++++++++++++++++++++--------- report/latest_map_internal_test.go | 21 +-- 4 files changed, 219 insertions(+), 288 deletions(-) delete mode 100644 report/latest_map.go diff --git a/extras/generate_latest_map b/extras/generate_latest_map index 04e886a1c..1639bb15b 100755 --- a/extras/generate_latest_map +++ b/extras/generate_latest_map @@ -19,9 +19,11 @@ function generate_header() { package report import ( + "fmt" "time" "github.com/ugorji/go/codec" + "github.com/weaveworks/ps" ) EOF } @@ -31,9 +33,7 @@ function generate_latest_map() { local data_type="$2" local uppercase_data_type="${data_type^}" local lowercase_data_type="${data_type,}" - local wire_entry_type="wire${uppercase_data_type}LatestEntry" - local decoder_type="${lowercase_data_type}LatestEntryDecoder" - local iface_decoder_variable="${uppercase_data_type}LatestEntryDecoder" + local entry_type="${lowercase_data_type}LatestEntry" local latest_map_type="${uppercase_data_type}LatestMap" local empty_latest_map_variable="Empty${latest_map_type}" local make_function="Make${latest_map_type}" @@ -44,29 +44,26 @@ function generate_latest_map() { local json_value='`json:"value"`' cat <>"${out_file}" - type ${wire_entry_type} struct { + type ${entry_type} struct { Timestamp time.Time ${json_timestamp} Value ${data_type} ${json_value} } - type ${decoder_type} struct {} - - func (d *${decoder_type}) Decode(decoder *codec.Decoder, entry *LatestEntry) { - wire := ${wire_entry_type}{} - decoder.Decode(&wire) - entry.Timestamp = wire.Timestamp - entry.Value = wire.Value + // String returns the StringLatestEntry's string representation. + func (e *${entry_type}) String() string { + return fmt.Sprintf("%v (%s)", e.Value, e.Timestamp.String()) } - // ${iface_decoder_variable} is an implementation of LatestEntryDecoder - // that decodes the LatestEntry instances having a ${data_type} value. - var ${iface_decoder_variable} LatestEntryDecoder = &${decoder_type}{} + // Equal returns true if the supplied StringLatestEntry is equal to this one. + func (e *${entry_type}) Equal(e2 *${entry_type}) bool { + return e.Timestamp.Equal(e2.Timestamp) && e.Value == e2.Value + } // ${latest_map_type} holds latest ${data_type} instances. - type ${latest_map_type} LatestMap + type ${latest_map_type} struct { ps.Map } // ${empty_latest_map_variable} is an empty ${latest_map_type}. Start with this. - var ${empty_latest_map_variable} = (${latest_map_type})(MakeLatestMapWithDecoder(${iface_decoder_variable})) + var ${empty_latest_map_variable} = ${latest_map_type}{ps.NewMap()} // ${make_function} makes an empty ${latest_map_type}. func ${make_function}() ${latest_map_type} { @@ -75,77 +72,117 @@ function generate_latest_map() { // Copy is a noop, as ${latest_map_type}s are immutable. func (m ${latest_map_type}) Copy() ${latest_map_type} { - return (${latest_map_type})((LatestMap)(m).Copy()) + return m } // Size returns the number of elements. func (m ${latest_map_type}) Size() int { - return (LatestMap)(m).Size() + if m.Map == nil { + return 0 + } + return m.Map.Size() } // Merge produces a fresh ${latest_map_type} containing the keys from both inputs. // When both inputs contain the same key, the newer value is used. func (m ${latest_map_type}) Merge(other ${latest_map_type}) ${latest_map_type} { - return (${latest_map_type})((LatestMap)(m).Merge((LatestMap)(other))) + output := mergeMaps(m.Map, other.Map, func(a, b interface{}) bool { + return a.(*${entry_type}).Timestamp.Before(b.(*${entry_type}).Timestamp) + }) + return ${latest_map_type}{output} } // Lookup the value for the given key. func (m ${latest_map_type}) Lookup(key string) (${data_type}, bool) { - v, ok := (LatestMap)(m).Lookup(key) + v, _, ok := m.LookupEntry(key) if !ok { var zero ${data_type} return zero, false } - return v.(${data_type}), true + return v, true } // LookupEntry returns the raw entry for the given key. func (m ${latest_map_type}) LookupEntry(key string) (${data_type}, time.Time, bool) { - v, timestamp, ok := (LatestMap)(m).LookupEntry(key) - if !ok { - var zero ${data_type} - return zero, timestamp, false + if m.Map == nil { + var zero ${data_type} + return zero, time.Time{}, false } - return v.(${data_type}), timestamp, true + value, ok := m.Map.Lookup(key) + if !ok { + var zero ${data_type} + return zero, time.Time{}, false + } + e := value.(*${entry_type}) + return e.Value, e.Timestamp, true } // Set the value for the given key. func (m ${latest_map_type}) Set(key string, timestamp time.Time, value ${data_type}) ${latest_map_type} { - return (${latest_map_type})((LatestMap)(m).Set(key, timestamp, value)) + if m.Map == nil { + m.Map = ps.NewMap() + } + return ${latest_map_type}{m.Map.Set(key, &${entry_type}{timestamp, value})} } // Delete the value for the given key. func (m ${latest_map_type}) Delete(key string) ${latest_map_type} { - return (${latest_map_type})((LatestMap)(m).Delete(key)) + if m.Map == nil { + return m + } + return ${latest_map_type}{m.Map.Delete(key)} } // ForEach executes fn on each key value pair in the map. func (m ${latest_map_type}) ForEach(fn func(k string, timestamp time.Time, v ${data_type})) { - (LatestMap)(m).ForEach(func(key string, ts time.Time, value interface{}) { - fn(key, ts, value.(${data_type})) - }) + if m.Map != nil { + m.Map.ForEach(func(key string, value interface{}) { + fn(key, value.(*${entry_type}).Timestamp, value.(*${entry_type}).Value) + }) + } } // String returns the ${latest_map_type}'s string representation. func (m ${latest_map_type}) String() string { - return (LatestMap)(m).String() + return mapToString(m.Map) } // DeepEqual tests equality with other ${latest_map_type}. func (m ${latest_map_type}) DeepEqual(n ${latest_map_type}) bool { - return (LatestMap)(m).DeepEqual((LatestMap)(n)) + return mapEqual(m.Map, n.Map, func(val, otherValue interface{}) bool { + return val.(*${entry_type}).Equal(otherValue.(*${entry_type})) + }) + } + + func (m ${latest_map_type}) toIntermediate() map[string]${entry_type} { + intermediate := make(map[string]${entry_type}, m.Size()) + if m.Map != nil { + m.Map.ForEach(func(key string, val interface{}) { + intermediate[key] = *val.(*${entry_type}) + }) + } + return intermediate } // CodecEncodeSelf implements codec.Selfer. func (m *${latest_map_type}) CodecEncodeSelf(encoder *codec.Encoder) { - (*LatestMap)(m).CodecEncodeSelf(encoder) + if m.Map != nil { + encoder.Encode(m.toIntermediate()) + } else { + encoder.Encode(nil) + } } // CodecDecodeSelf implements codec.Selfer. func (m *${latest_map_type}) CodecDecodeSelf(decoder *codec.Decoder) { - bm := (*LatestMap)(m) - bm.decoder = ${iface_decoder_variable} - bm.CodecDecodeSelf(decoder) + out := mapRead(decoder, func(isNil bool) interface{} { + value := &${entry_type}{} + if !isNil { + decoder.Decode(value) + } + return value + }) + *m = ${latest_map_type}{out} } // MarshalJSON shouldn't be used, use CodecEncodeSelf instead. diff --git a/report/latest_map.go b/report/latest_map.go deleted file mode 100644 index 37cbdf6c1..000000000 --- a/report/latest_map.go +++ /dev/null @@ -1,169 +0,0 @@ -package report - -import ( - "fmt" - "time" - - "github.com/ugorji/go/codec" - "github.com/weaveworks/ps" -) - -// LatestEntryDecoder is an interface for decoding the LatestEntry instances. -type LatestEntryDecoder interface { - Decode(decoder *codec.Decoder, entry *LatestEntry) -} - -// LatestMap is a persistent map which support latest-win merges. We -// have to embed ps.Map as its interface. LatestMaps are immutable. -type LatestMap struct { - ps.Map - decoder LatestEntryDecoder -} - -// LatestEntry represents a timestamped value inside the LatestMap. -type LatestEntry struct { - Timestamp time.Time `json:"timestamp"` - Value interface{} `json:"value"` -} - -// String returns the LatestEntry's string representation. -func (e LatestEntry) String() string { - return fmt.Sprintf("%v (%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 -} - -// MakeLatestMapWithDecoder makes an empty LatestMap holding custom values. -func MakeLatestMapWithDecoder(decoder LatestEntryDecoder) LatestMap { - return LatestMap{ps.NewMap(), decoder} -} - -// 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 StringLatestMap containing the keys from -// both inputs. When both inputs contain the same key, the newer value -// is used. -func (m LatestMap) Merge(other LatestMap) LatestMap { - if m.decoder != other.decoder { - panic(fmt.Sprintf("Cannot merge maps with different entry value types, this has %#v, other has %#v", m.decoder, other.decoder)) - } - output := mergeMaps(m.Map, other.Map, func(a, b interface{}) bool { - return a.(LatestEntry).Timestamp.Before(b.(LatestEntry).Timestamp) - }) - return LatestMap{output, m.decoder} -} - -// Lookup the value for the given key. -func (m LatestMap) Lookup(key string) (interface{}, bool) { - v, _, ok := m.LookupEntry(key) - return v, ok -} - -// LookupEntry returns the raw entry for the given key. -func (m LatestMap) LookupEntry(key string) (interface{}, time.Time, bool) { - if m.Map == nil { - return nil, time.Time{}, false - } - value, ok := m.Map.Lookup(key) - if !ok { - return nil, time.Time{}, false - } - e := value.(LatestEntry) - return e.Value, e.Timestamp, true -} - -// Set sets the value for the given key. -func (m LatestMap) Set(key string, timestamp time.Time, value interface{}) LatestMap { - if m.Map == nil { - m = MakeLatestMapWithDecoder(m.decoder) - } - return LatestMap{m.Map.Set(key, LatestEntry{timestamp, value}), m.decoder} -} - -// Delete the value for the given key. -func (m LatestMap) Delete(key string) LatestMap { - if m.Map == nil { - m = MakeLatestMapWithDecoder(m.decoder) - } - return LatestMap{m.Map.Delete(key), m.decoder} -} - -// ForEach executes fn on each key, timestamp, value triple in the map. -func (m LatestMap) ForEach(fn func(k string, ts time.Time, v interface{})) { - if m.Map == nil { - return - } - m.Map.ForEach(func(key string, value interface{}) { - fn(key, value.(LatestEntry).Timestamp, value.(LatestEntry).Value) - }) -} - -// String returns the LatestMap's string representation. -func (m LatestMap) String() string { - return mapToString(m.Map) -} - -// DeepEqual tests equality with other LatestMap. -func (m LatestMap) DeepEqual(n LatestMap) bool { - if m.decoder != n.decoder { - panic(fmt.Sprintf("Cannot check equality of maps with different entry value types, this has %#v, other has %#v", m.decoder, n.decoder)) - } - return mapEqual(m.Map, n.Map, func(val, otherValue interface{}) bool { - return val.(LatestEntry).Equal(otherValue.(LatestEntry)) - }) -} - -func (m LatestMap) toIntermediate() map[string]LatestEntry { - intermediate := make(map[string]LatestEntry, m.Size()) - if m.Map != nil { - m.Map.ForEach(func(key string, val interface{}) { - intermediate[key] = val.(LatestEntry) - }) - } - return intermediate -} - -// 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) { - out := mapRead(decoder, func(isNil bool) interface{} { - value := LatestEntry{} - if !isNil { - m.decoder.Decode(decoder, &value) - } - return value - }) - *m = LatestMap{out, m.decoder} -} - -// 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") -} diff --git a/report/latest_map_generated.go b/report/latest_map_generated.go index cd062bcb4..1786b0a46 100644 --- a/report/latest_map_generated.go +++ b/report/latest_map_generated.go @@ -1,37 +1,36 @@ // Generated file, do not edit. -// To regenerate, run ./tools/generate_latest_map ./report/latest_map_generated.go string NodeControlData +// To regenerate, run ../extras/generate_latest_map ./latest_map_generated.go string NodeControlData package report import ( + "fmt" "time" "github.com/ugorji/go/codec" + "github.com/weaveworks/ps" ) -type wireStringLatestEntry struct { +type stringLatestEntry struct { Timestamp time.Time `json:"timestamp"` Value string `json:"value"` } -type stringLatestEntryDecoder struct{} - -func (d *stringLatestEntryDecoder) Decode(decoder *codec.Decoder, entry *LatestEntry) { - wire := wireStringLatestEntry{} - decoder.Decode(&wire) - entry.Timestamp = wire.Timestamp - entry.Value = wire.Value +// String returns the StringLatestEntry's string representation. +func (e *stringLatestEntry) String() string { + return fmt.Sprintf("%v (%s)", e.Value, e.Timestamp.String()) } -// StringLatestEntryDecoder is an implementation of LatestEntryDecoder -// that decodes the LatestEntry instances having a string value. -var StringLatestEntryDecoder LatestEntryDecoder = &stringLatestEntryDecoder{} +// Equal returns true if the supplied StringLatestEntry is equal to this one. +func (e *stringLatestEntry) Equal(e2 *stringLatestEntry) bool { + return e.Timestamp.Equal(e2.Timestamp) && e.Value == e2.Value +} // StringLatestMap holds latest string instances. -type StringLatestMap LatestMap +type StringLatestMap struct{ ps.Map } // EmptyStringLatestMap is an empty StringLatestMap. Start with this. -var EmptyStringLatestMap = (StringLatestMap)(MakeLatestMapWithDecoder(StringLatestEntryDecoder)) +var EmptyStringLatestMap = StringLatestMap{ps.NewMap()} // MakeStringLatestMap makes an empty StringLatestMap. func MakeStringLatestMap() StringLatestMap { @@ -40,77 +39,117 @@ func MakeStringLatestMap() StringLatestMap { // Copy is a noop, as StringLatestMaps are immutable. func (m StringLatestMap) Copy() StringLatestMap { - return (StringLatestMap)((LatestMap)(m).Copy()) + return m } // Size returns the number of elements. func (m StringLatestMap) Size() int { - return (LatestMap)(m).Size() + if m.Map == nil { + return 0 + } + return m.Map.Size() } // Merge produces a fresh StringLatestMap containing the keys from both inputs. // When both inputs contain the same key, the newer value is used. func (m StringLatestMap) Merge(other StringLatestMap) StringLatestMap { - return (StringLatestMap)((LatestMap)(m).Merge((LatestMap)(other))) + output := mergeMaps(m.Map, other.Map, func(a, b interface{}) bool { + return a.(*stringLatestEntry).Timestamp.Before(b.(*stringLatestEntry).Timestamp) + }) + return StringLatestMap{output} } // Lookup the value for the given key. func (m StringLatestMap) Lookup(key string) (string, bool) { - v, ok := (LatestMap)(m).Lookup(key) + v, _, ok := m.LookupEntry(key) if !ok { var zero string return zero, false } - return v.(string), true + return v, true } // LookupEntry returns the raw entry for the given key. func (m StringLatestMap) LookupEntry(key string) (string, time.Time, bool) { - v, timestamp, ok := (LatestMap)(m).LookupEntry(key) + if m.Map == nil { + var zero string + return zero, time.Time{}, false + } + value, ok := m.Map.Lookup(key) if !ok { var zero string - return zero, timestamp, false + return zero, time.Time{}, false } - return v.(string), timestamp, true + e := value.(*stringLatestEntry) + return e.Value, e.Timestamp, true } // Set the value for the given key. func (m StringLatestMap) Set(key string, timestamp time.Time, value string) StringLatestMap { - return (StringLatestMap)((LatestMap)(m).Set(key, timestamp, value)) + if m.Map == nil { + m.Map = ps.NewMap() + } + return StringLatestMap{m.Map.Set(key, &stringLatestEntry{timestamp, value})} } // Delete the value for the given key. func (m StringLatestMap) Delete(key string) StringLatestMap { - return (StringLatestMap)((LatestMap)(m).Delete(key)) + if m.Map == nil { + return m + } + return StringLatestMap{m.Map.Delete(key)} } // ForEach executes fn on each key value pair in the map. func (m StringLatestMap) ForEach(fn func(k string, timestamp time.Time, v string)) { - (LatestMap)(m).ForEach(func(key string, ts time.Time, value interface{}) { - fn(key, ts, value.(string)) - }) + if m.Map != nil { + m.Map.ForEach(func(key string, value interface{}) { + fn(key, value.(*stringLatestEntry).Timestamp, value.(*stringLatestEntry).Value) + }) + } } // String returns the StringLatestMap's string representation. func (m StringLatestMap) String() string { - return (LatestMap)(m).String() + return mapToString(m.Map) } // DeepEqual tests equality with other StringLatestMap. func (m StringLatestMap) DeepEqual(n StringLatestMap) bool { - return (LatestMap)(m).DeepEqual((LatestMap)(n)) + return mapEqual(m.Map, n.Map, func(val, otherValue interface{}) bool { + return val.(*stringLatestEntry).Equal(otherValue.(*stringLatestEntry)) + }) +} + +func (m StringLatestMap) toIntermediate() map[string]stringLatestEntry { + intermediate := make(map[string]stringLatestEntry, m.Size()) + if m.Map != nil { + m.Map.ForEach(func(key string, val interface{}) { + intermediate[key] = *val.(*stringLatestEntry) + }) + } + return intermediate } // CodecEncodeSelf implements codec.Selfer. func (m *StringLatestMap) CodecEncodeSelf(encoder *codec.Encoder) { - (*LatestMap)(m).CodecEncodeSelf(encoder) + if m.Map != nil { + encoder.Encode(m.toIntermediate()) + } else { + encoder.Encode(nil) + } } // CodecDecodeSelf implements codec.Selfer. func (m *StringLatestMap) CodecDecodeSelf(decoder *codec.Decoder) { - bm := (*LatestMap)(m) - bm.decoder = StringLatestEntryDecoder - bm.CodecDecodeSelf(decoder) + out := mapRead(decoder, func(isNil bool) interface{} { + value := &stringLatestEntry{} + if !isNil { + decoder.Decode(value) + } + return value + }) + *m = StringLatestMap{out} } // MarshalJSON shouldn't be used, use CodecEncodeSelf instead. @@ -123,29 +162,26 @@ func (*StringLatestMap) UnmarshalJSON(b []byte) error { panic("UnmarshalJSON shouldn't be used, use CodecDecodeSelf instead") } -type wireNodeControlDataLatestEntry struct { +type nodeControlDataLatestEntry struct { Timestamp time.Time `json:"timestamp"` Value NodeControlData `json:"value"` } -type nodeControlDataLatestEntryDecoder struct{} - -func (d *nodeControlDataLatestEntryDecoder) Decode(decoder *codec.Decoder, entry *LatestEntry) { - wire := wireNodeControlDataLatestEntry{} - decoder.Decode(&wire) - entry.Timestamp = wire.Timestamp - entry.Value = wire.Value +// String returns the StringLatestEntry's string representation. +func (e *nodeControlDataLatestEntry) String() string { + return fmt.Sprintf("%v (%s)", e.Value, e.Timestamp.String()) } -// NodeControlDataLatestEntryDecoder is an implementation of LatestEntryDecoder -// that decodes the LatestEntry instances having a NodeControlData value. -var NodeControlDataLatestEntryDecoder LatestEntryDecoder = &nodeControlDataLatestEntryDecoder{} +// Equal returns true if the supplied StringLatestEntry is equal to this one. +func (e *nodeControlDataLatestEntry) Equal(e2 *nodeControlDataLatestEntry) bool { + return e.Timestamp.Equal(e2.Timestamp) && e.Value == e2.Value +} // NodeControlDataLatestMap holds latest NodeControlData instances. -type NodeControlDataLatestMap LatestMap +type NodeControlDataLatestMap struct{ ps.Map } // EmptyNodeControlDataLatestMap is an empty NodeControlDataLatestMap. Start with this. -var EmptyNodeControlDataLatestMap = (NodeControlDataLatestMap)(MakeLatestMapWithDecoder(NodeControlDataLatestEntryDecoder)) +var EmptyNodeControlDataLatestMap = NodeControlDataLatestMap{ps.NewMap()} // MakeNodeControlDataLatestMap makes an empty NodeControlDataLatestMap. func MakeNodeControlDataLatestMap() NodeControlDataLatestMap { @@ -154,77 +190,117 @@ func MakeNodeControlDataLatestMap() NodeControlDataLatestMap { // Copy is a noop, as NodeControlDataLatestMaps are immutable. func (m NodeControlDataLatestMap) Copy() NodeControlDataLatestMap { - return (NodeControlDataLatestMap)((LatestMap)(m).Copy()) + return m } // Size returns the number of elements. func (m NodeControlDataLatestMap) Size() int { - return (LatestMap)(m).Size() + if m.Map == nil { + return 0 + } + return m.Map.Size() } // Merge produces a fresh NodeControlDataLatestMap containing the keys from both inputs. // When both inputs contain the same key, the newer value is used. func (m NodeControlDataLatestMap) Merge(other NodeControlDataLatestMap) NodeControlDataLatestMap { - return (NodeControlDataLatestMap)((LatestMap)(m).Merge((LatestMap)(other))) + output := mergeMaps(m.Map, other.Map, func(a, b interface{}) bool { + return a.(*nodeControlDataLatestEntry).Timestamp.Before(b.(*nodeControlDataLatestEntry).Timestamp) + }) + return NodeControlDataLatestMap{output} } // Lookup the value for the given key. func (m NodeControlDataLatestMap) Lookup(key string) (NodeControlData, bool) { - v, ok := (LatestMap)(m).Lookup(key) + v, _, ok := m.LookupEntry(key) if !ok { var zero NodeControlData return zero, false } - return v.(NodeControlData), true + return v, true } // LookupEntry returns the raw entry for the given key. func (m NodeControlDataLatestMap) LookupEntry(key string) (NodeControlData, time.Time, bool) { - v, timestamp, ok := (LatestMap)(m).LookupEntry(key) + if m.Map == nil { + var zero NodeControlData + return zero, time.Time{}, false + } + value, ok := m.Map.Lookup(key) if !ok { var zero NodeControlData - return zero, timestamp, false + return zero, time.Time{}, false } - return v.(NodeControlData), timestamp, true + e := value.(*nodeControlDataLatestEntry) + return e.Value, e.Timestamp, true } // Set the value for the given key. func (m NodeControlDataLatestMap) Set(key string, timestamp time.Time, value NodeControlData) NodeControlDataLatestMap { - return (NodeControlDataLatestMap)((LatestMap)(m).Set(key, timestamp, value)) + if m.Map == nil { + m.Map = ps.NewMap() + } + return NodeControlDataLatestMap{m.Map.Set(key, &nodeControlDataLatestEntry{timestamp, value})} } // Delete the value for the given key. func (m NodeControlDataLatestMap) Delete(key string) NodeControlDataLatestMap { - return (NodeControlDataLatestMap)((LatestMap)(m).Delete(key)) + if m.Map == nil { + return m + } + return NodeControlDataLatestMap{m.Map.Delete(key)} } // ForEach executes fn on each key value pair in the map. func (m NodeControlDataLatestMap) ForEach(fn func(k string, timestamp time.Time, v NodeControlData)) { - (LatestMap)(m).ForEach(func(key string, ts time.Time, value interface{}) { - fn(key, ts, value.(NodeControlData)) - }) + if m.Map != nil { + m.Map.ForEach(func(key string, value interface{}) { + fn(key, value.(*nodeControlDataLatestEntry).Timestamp, value.(*nodeControlDataLatestEntry).Value) + }) + } } // String returns the NodeControlDataLatestMap's string representation. func (m NodeControlDataLatestMap) String() string { - return (LatestMap)(m).String() + return mapToString(m.Map) } // DeepEqual tests equality with other NodeControlDataLatestMap. func (m NodeControlDataLatestMap) DeepEqual(n NodeControlDataLatestMap) bool { - return (LatestMap)(m).DeepEqual((LatestMap)(n)) + return mapEqual(m.Map, n.Map, func(val, otherValue interface{}) bool { + return val.(*nodeControlDataLatestEntry).Equal(otherValue.(*nodeControlDataLatestEntry)) + }) +} + +func (m NodeControlDataLatestMap) toIntermediate() map[string]nodeControlDataLatestEntry { + intermediate := make(map[string]nodeControlDataLatestEntry, m.Size()) + if m.Map != nil { + m.Map.ForEach(func(key string, val interface{}) { + intermediate[key] = *val.(*nodeControlDataLatestEntry) + }) + } + return intermediate } // CodecEncodeSelf implements codec.Selfer. func (m *NodeControlDataLatestMap) CodecEncodeSelf(encoder *codec.Encoder) { - (*LatestMap)(m).CodecEncodeSelf(encoder) + if m.Map != nil { + encoder.Encode(m.toIntermediate()) + } else { + encoder.Encode(nil) + } } // CodecDecodeSelf implements codec.Selfer. func (m *NodeControlDataLatestMap) CodecDecodeSelf(decoder *codec.Decoder) { - bm := (*LatestMap)(m) - bm.decoder = NodeControlDataLatestEntryDecoder - bm.CodecDecodeSelf(decoder) + out := mapRead(decoder, func(isNil bool) interface{} { + value := &nodeControlDataLatestEntry{} + if !isNil { + decoder.Decode(value) + } + return value + }) + *m = NodeControlDataLatestMap{out} } // MarshalJSON shouldn't be used, use CodecEncodeSelf instead. diff --git a/report/latest_map_internal_test.go b/report/latest_map_internal_test.go index 16c1cfddc..653ba3bf7 100644 --- a/report/latest_map_internal_test.go +++ b/report/latest_map_internal_test.go @@ -32,6 +32,10 @@ func TestLatestMapAdd(t *testing.T) { func TestLatestMapLookupEntry(t *testing.T) { now := time.Now() + type LatestEntry struct { + Timestamp time.Time + Value interface{} + } entry := LatestEntry{Timestamp: now, Value: "Bar"} have := EmptyStringLatestMap.Set("foo", entry.Timestamp, entry.Value.(string)) if got, timestamp, ok := have.LookupEntry("foo"); !ok || got != entry.Value || !timestamp.Equal(entry.Timestamp) { @@ -216,20 +220,3 @@ func TestLatestMapMergeEqualDecoderTypes(t *testing.T) { m2 := MakeStringLatestMap().Set("b", time.Now(), "foo") m1.Merge(m2) } - -type TestLatestEntryDecoder struct{} - -func (d *TestLatestEntryDecoder) Decode(decoder *codec.Decoder, entry *LatestEntry) { - decoder.Decode(entry) -} - -func TestLatestMapMergeDifferentDecoderTypes(t *testing.T) { - defer func() { - if r := recover(); r == nil { - t.Error("Merging two maps with different decoders should panic") - } - }() - m1 := MakeStringLatestMap().Set("a", time.Now(), "bar") - m2 := ((StringLatestMap)(MakeLatestMapWithDecoder(&TestLatestEntryDecoder{}))).Set("b", time.Now(), "foo") - m1.Merge(m2) -}