From 7f46b90e272f00275ce47a29e0cef0c62bbc4e38 Mon Sep 17 00:00:00 2001 From: Krzesimir Nowak Date: Thu, 21 Jul 2016 11:55:08 +0200 Subject: [PATCH] Make LatestMap "generic" This commit makes the LatestMap type a sort of a base class, that should not be used directly. This also adds a generator for LatestMap "concrete" types with a specific data type in the LatestEntry. --- probe/docker/container_test.go | 2 +- report/latest_map.go | 87 +++++++------- report/latest_map_generated.go | 124 ++++++++++++++++++++ report/latest_map_internal_test.go | 100 ++++++++++------ report/node.go | 24 ++-- report/table.go | 3 +- tools/generate_latest_map | 176 +++++++++++++++++++++++++++++ 7 files changed, 431 insertions(+), 85 deletions(-) create mode 100644 report/latest_map_generated.go create mode 100755 tools/generate_latest_map diff --git a/probe/docker/container_test.go b/probe/docker/container_test.go index 3d200a337..a8c3b17b5 100644 --- a/probe/docker/container_test.go +++ b/probe/docker/container_test.go @@ -100,7 +100,7 @@ func TestContainer(t *testing.T) { test.Poll(t, 100*time.Millisecond, want, func() interface{} { node := c.GetNode() - node.Latest.ForEach(func(k, v string) { + node.Latest.ForEach(func(k string, _ time.Time, v string) { if v == "0" || v == "" { node.Latest = node.Latest.Delete(k) } diff --git a/report/latest_map.go b/report/latest_map.go index d45e119c7..da767c1c7 100644 --- a/report/latest_map.go +++ b/report/latest_map.go @@ -10,20 +10,27 @@ import ( "github.com/weaveworks/ps" ) -// LatestMap is a persitent map which support latest-win merges. We have to -// embed ps.Map as its an interface. LatestMaps are immutable. +// 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 string `json:"value"` + Timestamp time.Time `json:"timestamp"` + Value interface{} `json:"value"` } +// String returns the LatestEntry's string representation. func (e LatestEntry) String() string { - return fmt.Sprintf("\"%s\" (%s)", e.Value, e.Timestamp.String()) + return fmt.Sprintf("%v (%s)", e.Value, e.Timestamp.String()) } // Equal returns true if the supplied LatestEntry is equal to this one. @@ -31,12 +38,9 @@ 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 +// 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. @@ -44,7 +48,7 @@ func (m LatestMap) Copy() LatestMap { return m } -// Size returns the number of elements +// Size returns the number of elements. func (m LatestMap) Size() int { if m.Map == nil { return 0 @@ -52,8 +56,9 @@ func (m LatestMap) Size() int { 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. +// 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 { var ( mSize = m.Size() @@ -69,6 +74,9 @@ func (m LatestMap) Merge(other LatestMap) LatestMap { 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 { @@ -80,58 +88,59 @@ func (m LatestMap) Merge(other LatestMap) LatestMap { } }) - return LatestMap{output} + return LatestMap{output, m.decoder} } // Lookup the value for the given key. -func (m LatestMap) Lookup(key string) (string, bool) { +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) (string, time.Time, bool) { +func (m LatestMap) LookupEntry(key string) (interface{}, time.Time, bool) { if m.Map == nil { - return "", time.Time{}, false + return nil, time.Time{}, false } value, ok := m.Map.Lookup(key) if !ok { - return "", time.Time{}, false + return nil, 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 { +// 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 = EmptyLatestMap + m = MakeLatestMapWithDecoder(m.decoder) } - return LatestMap{m.Map.Set(key, LatestEntry{timestamp, value})} + 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 = EmptyLatestMap + m = MakeLatestMapWithDecoder(m.decoder) } - return LatestMap{m.Map.Delete(key)} + return LatestMap{m.Map.Delete(key), m.decoder} } -// ForEach executes f on each key value pair in the map -func (m LatestMap) ForEach(fn func(k, v string)) { +// 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).Value) + fn(key, value.(LatestEntry).Timestamp, value.(LatestEntry).Value) }) } +// String returns the LatestMap's string representation. func (m LatestMap) String() string { keys := []string{} if m.Map == nil { - m = EmptyLatestMap + m = MakeLatestMapWithDecoder(m.decoder) } for _, k := range m.Map.Keys() { keys = append(keys, k) @@ -147,7 +156,7 @@ func (m LatestMap) String() string { return buf.String() } -// DeepEqual tests equality with other LatestMap +// DeepEqual tests equality with other LatestMap. func (m LatestMap) DeepEqual(n LatestMap) bool { if m.Size() != n.Size() { return false @@ -155,7 +164,9 @@ func (m LatestMap) DeepEqual(n LatestMap) bool { 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 { @@ -177,7 +188,7 @@ func (m LatestMap) toIntermediate() map[string]LatestEntry { return intermediate } -// CodecEncodeSelf implements codec.Selfer +// CodecEncodeSelf implements codec.Selfer. func (m *LatestMap) CodecEncodeSelf(encoder *codec.Encoder) { if m.Map != nil { encoder.Encode(m.toIntermediate()) @@ -193,7 +204,7 @@ const ( containerMapEnd = 4 ) -// CodecDecodeSelf implements codec.Selfer +// 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. @@ -201,7 +212,7 @@ const ( func (m *LatestMap) CodecDecodeSelf(decoder *codec.Decoder) { z, r := codec.GenHelperDecoder(decoder) if r.TryDecodeAsNil() { - *m = LatestMap{} + *m = MakeLatestMapWithDecoder(m.decoder) return } @@ -221,21 +232,21 @@ func (m *LatestMap) CodecDecodeSelf(decoder *codec.Decoder) { var value LatestEntry z.DecSendContainerState(containerMapValue) if !r.TryDecodeAsNil() { - decoder.Decode(&value) + m.decoder.Decode(decoder, &value) } out = out.UnsafeMutableSet(key, value) } z.DecSendContainerState(containerMapEnd) - *m = LatestMap{out} + *m = LatestMap{out, m.decoder} } -// MarshalJSON shouldn't be used, use CodecEncodeSelf instead +// 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 +// 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 new file mode 100644 index 000000000..c0a280d43 --- /dev/null +++ b/report/latest_map_generated.go @@ -0,0 +1,124 @@ +// Generated file, do not edit. +// To regenerate, run ./tools/generate_latest_map ./report/latest_map_generated.go string + +package report + +import ( + "time" + + "github.com/ugorji/go/codec" +) + +type wireStringLatestEntry 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 +} + +// StringLatestEntryDecoder is an implementation of LatestEntryDecoder +// that decodes the LatestEntry instances having a string value. +var StringLatestEntryDecoder LatestEntryDecoder = &stringLatestEntryDecoder{} + +// StringLatestMap holds latest string instances. +type StringLatestMap LatestMap + +// EmptyStringLatestMap is an empty StringLatestMap. Start with this. +var EmptyStringLatestMap = (StringLatestMap)(MakeLatestMapWithDecoder(StringLatestEntryDecoder)) + +// MakeStringLatestMap makes an empty StringLatestMap. +func MakeStringLatestMap() StringLatestMap { + return EmptyStringLatestMap +} + +// Copy is a noop, as StringLatestMaps are immutable. +func (m StringLatestMap) Copy() StringLatestMap { + return (StringLatestMap)((LatestMap)(m).Copy()) +} + +// Size returns the number of elements. +func (m StringLatestMap) Size() int { + return (LatestMap)(m).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))) +} + +// Lookup the value for the given key. +func (m StringLatestMap) Lookup(key string) (string, bool) { + v, ok := (LatestMap)(m).Lookup(key) + if !ok { + var zero string + return zero, false + } + return v.(string), 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 !ok { + var zero string + return zero, timestamp, false + } + return v.(string), 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)) +} + +// Delete the value for the given key. +func (m StringLatestMap) Delete(key string) StringLatestMap { + return (StringLatestMap)((LatestMap)(m).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)) + }) +} + +// String returns the StringLatestMap's string representation. +func (m StringLatestMap) String() string { + return (LatestMap)(m).String() +} + +// DeepEqual tests equality with other StringLatestMap. +func (m StringLatestMap) DeepEqual(n StringLatestMap) bool { + return (LatestMap)(m).DeepEqual((LatestMap)(n)) +} + +// CodecEncodeSelf implements codec.Selfer. +func (m *StringLatestMap) CodecEncodeSelf(encoder *codec.Encoder) { + (*LatestMap)(m).CodecEncodeSelf(encoder) +} + +// CodecDecodeSelf implements codec.Selfer. +func (m *StringLatestMap) CodecDecodeSelf(decoder *codec.Decoder) { + bm := (*LatestMap)(m) + bm.decoder = StringLatestEntryDecoder + bm.CodecDecodeSelf(decoder) +} + +// MarshalJSON shouldn't be used, use CodecEncodeSelf instead. +func (StringLatestMap) MarshalJSON() ([]byte, error) { + panic("MarshalJSON shouldn't be used, use CodecEncodeSelf instead") +} + +// UnmarshalJSON shouldn't be used, use CodecDecodeSelf instead. +func (*StringLatestMap) UnmarshalJSON(b []byte) error { + panic("UnmarshalJSON shouldn't be used, use CodecDecodeSelf instead") +} diff --git a/report/latest_map_internal_test.go b/report/latest_map_internal_test.go index b5072f8b2..286b265b0 100644 --- a/report/latest_map_internal_test.go +++ b/report/latest_map_internal_test.go @@ -14,7 +14,7 @@ import ( func TestLatestMapAdd(t *testing.T) { now := time.Now() - have := EmptyLatestMap. + have := EmptyStringLatestMap. Set("foo", now.Add(-1), "Baz"). Set("foo", now, "Bar") if v, ok := have.Lookup("foo"); !ok || v != "Bar" { @@ -23,7 +23,7 @@ func TestLatestMapAdd(t *testing.T) { if v, ok := have.Lookup("bar"); ok || v != "" { t.Errorf("v != nil") } - have.ForEach(func(k, v string) { + have.ForEach(func(k string, _ time.Time, v string) { if k != "foo" || v != "Bar" { t.Errorf("v != Bar") } @@ -33,7 +33,7 @@ func TestLatestMapAdd(t *testing.T) { func TestLatestMapLookupEntry(t *testing.T) { now := time.Now() entry := LatestEntry{Timestamp: now, Value: "Bar"} - have := EmptyLatestMap.Set("foo", entry.Timestamp, entry.Value) + have := EmptyStringLatestMap.Set("foo", entry.Timestamp, entry.Value.(string)) if got, timestamp, ok := have.LookupEntry("foo"); !ok || got != entry.Value || !timestamp.Equal(entry.Timestamp) { t.Errorf("got: %#v %v != expected %#v", got, timestamp, entry) } @@ -44,7 +44,7 @@ func TestLatestMapLookupEntry(t *testing.T) { func TestLatestMapAddNil(t *testing.T) { now := time.Now() - have := LatestMap{}.Set("foo", now, "Bar") + have := StringLatestMap{}.Set("foo", now, "Bar") if v, ok := have.Lookup("foo"); !ok || v != "Bar" { t.Errorf("v != Bar") } @@ -52,14 +52,14 @@ func TestLatestMapAddNil(t *testing.T) { func TestLatestMapDeepEquals(t *testing.T) { now := time.Now() - want := EmptyLatestMap. + want := EmptyStringLatestMap. Set("foo", now, "Bar") - have := EmptyLatestMap. + have := EmptyStringLatestMap. Set("foo", now, "Bar") if !reflect.DeepEqual(want, have) { t.Errorf(test.Diff(want, have)) } - notequal := EmptyLatestMap. + notequal := EmptyStringLatestMap. Set("foo", now, "Baz") if reflect.DeepEqual(want, notequal) { t.Errorf(test.Diff(want, have)) @@ -68,8 +68,8 @@ func TestLatestMapDeepEquals(t *testing.T) { func TestLatestMapDelete(t *testing.T) { now := time.Now() - want := EmptyLatestMap - have := EmptyLatestMap. + want := EmptyStringLatestMap + have := EmptyStringLatestMap. Set("foo", now, "Baz"). Delete("foo") if !reflect.DeepEqual(want, have) { @@ -78,54 +78,60 @@ func TestLatestMapDelete(t *testing.T) { } func TestLatestMapDeleteNil(t *testing.T) { - want := LatestMap{} - have := LatestMap{}.Delete("foo") + want := StringLatestMap{} + have := StringLatestMap{}.Delete("foo") if !reflect.DeepEqual(want, have) { t.Errorf(test.Diff(want, have)) } } +func nilStringLatestMap() StringLatestMap { + m := EmptyStringLatestMap + m.Map = nil + return m +} + func TestLatestMapMerge(t *testing.T) { now := time.Now() then := now.Add(-1) for name, c := range map[string]struct { - a, b, want LatestMap + a, b, want StringLatestMap }{ "nils": { - a: LatestMap{}, - b: LatestMap{}, - want: LatestMap{}, + a: nilStringLatestMap(), + b: nilStringLatestMap(), + want: nilStringLatestMap(), }, "Empty a": { - a: EmptyLatestMap, - b: EmptyLatestMap. + a: EmptyStringLatestMap, + b: EmptyStringLatestMap. Set("foo", now, "bar"), - want: EmptyLatestMap. + want: EmptyStringLatestMap. Set("foo", now, "bar"), }, "Empty b": { - a: EmptyLatestMap. + a: EmptyStringLatestMap. Set("foo", now, "bar"), - b: EmptyLatestMap, - want: EmptyLatestMap. + b: EmptyStringLatestMap, + want: EmptyStringLatestMap. Set("foo", now, "bar"), }, "Disjoint a & b": { - a: EmptyLatestMap. + a: EmptyStringLatestMap. Set("foo", now, "bar"), - b: EmptyLatestMap. + b: EmptyStringLatestMap. Set("baz", now, "bop"), - want: EmptyLatestMap. + want: EmptyStringLatestMap. Set("foo", now, "bar"). Set("baz", now, "bop"), }, "Common a & b": { - a: EmptyLatestMap. + a: EmptyStringLatestMap. Set("foo", now, "bar"), - b: EmptyLatestMap. + b: EmptyStringLatestMap. Set("foo", then, "baz"), - want: EmptyLatestMap. + want: EmptyStringLatestMap. Set("foo", now, "bar"), }, } { @@ -137,8 +143,8 @@ func TestLatestMapMerge(t *testing.T) { func BenchmarkLatestMapMerge(b *testing.B) { var ( - left = EmptyLatestMap - right = EmptyLatestMap + left = EmptyStringLatestMap + right = EmptyStringLatestMap now = time.Now() ) @@ -159,7 +165,7 @@ func BenchmarkLatestMapMerge(b *testing.B) { func TestLatestMapEncoding(t *testing.T) { now := time.Now() - want := EmptyLatestMap. + want := EmptyStringLatestMap. Set("foo", now, "bar"). Set("bar", now, "baz") @@ -171,7 +177,7 @@ func TestLatestMapEncoding(t *testing.T) { encoder := codec.NewEncoder(buf, h) want.CodecEncodeSelf(encoder) decoder := codec.NewDecoder(buf, h) - have := EmptyLatestMap + have := EmptyStringLatestMap have.CodecDecodeSelf(decoder) if !reflect.DeepEqual(want, have) { t.Error(test.Diff(want, have)) @@ -181,7 +187,7 @@ func TestLatestMapEncoding(t *testing.T) { } func TestLatestMapEncodingNil(t *testing.T) { - want := LatestMap{} + want := nilStringLatestMap() for _, h := range []codec.Handle{ codec.Handle(&codec.MsgpackHandle{}), @@ -191,7 +197,7 @@ func TestLatestMapEncodingNil(t *testing.T) { encoder := codec.NewEncoder(buf, h) want.CodecEncodeSelf(encoder) decoder := codec.NewDecoder(buf, h) - have := EmptyLatestMap + have := EmptyStringLatestMap have.CodecDecodeSelf(decoder) if !reflect.DeepEqual(want, have) { t.Error(test.Diff(want, have)) @@ -199,3 +205,31 @@ func TestLatestMapEncodingNil(t *testing.T) { } } + +func TestLatestMapMergeEqualDecoderTypes(t *testing.T) { + defer func() { + if r := recover(); r != nil { + t.Error("Merging two maps with the same decoders should not panic") + } + }() + m1 := MakeStringLatestMap().Set("a", time.Now(), "bar") + 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) +} diff --git a/report/node.go b/report/node.go index 4ff2547f0..9321f8d10 100644 --- a/report/node.go +++ b/report/node.go @@ -10,17 +10,17 @@ import ( // given node in a given topology, along with the edges emanating from the // node and metadata about those edges. type Node struct { - ID string `json:"id,omitempty"` - Topology string `json:"topology,omitempty"` - Counters Counters `json:"counters,omitempty"` - Sets Sets `json:"sets,omitempty"` - Adjacency IDList `json:"adjacency"` - Edges EdgeMetadatas `json:"edges,omitempty"` - Controls NodeControls `json:"controls,omitempty"` - Latest LatestMap `json:"latest,omitempty"` - Metrics Metrics `json:"metrics,omitempty"` - Parents Sets `json:"parents,omitempty"` - Children NodeSet `json:"children,omitempty"` + ID string `json:"id,omitempty"` + Topology string `json:"topology,omitempty"` + Counters Counters `json:"counters,omitempty"` + Sets Sets `json:"sets,omitempty"` + Adjacency IDList `json:"adjacency"` + Edges EdgeMetadatas `json:"edges,omitempty"` + Controls NodeControls `json:"controls,omitempty"` + Latest StringLatestMap `json:"latest,omitempty"` + Metrics Metrics `json:"metrics,omitempty"` + Parents Sets `json:"parents,omitempty"` + Children NodeSet `json:"children,omitempty"` } // MakeNode creates a new Node with no initial metadata. @@ -32,7 +32,7 @@ func MakeNode(id string) Node { Adjacency: EmptyIDList, Edges: EmptyEdgeMetadatas, Controls: MakeNodeControls(), - Latest: EmptyLatestMap, + Latest: EmptyStringLatestMap, Metrics: Metrics{}, Parents: EmptySets, } diff --git a/report/table.go b/report/table.go index 10d4aa40c..feaec789d 100644 --- a/report/table.go +++ b/report/table.go @@ -4,6 +4,7 @@ import ( "fmt" "sort" "strings" + "time" log "github.com/Sirupsen/logrus" "github.com/weaveworks/scope/common/mtime" @@ -37,7 +38,7 @@ func (node Node) AddTable(prefix string, labels map[string]string) Node { func (node Node) ExtractTable(prefix string) (rows map[string]string, truncationCount int) { rows = map[string]string{} truncationCount = 0 - node.Latest.ForEach(func(key, value string) { + node.Latest.ForEach(func(key string, _ time.Time, value string) { if strings.HasPrefix(key, prefix) { label := key[len(prefix):] rows[label] = value diff --git a/tools/generate_latest_map b/tools/generate_latest_map new file mode 100755 index 000000000..366a718d4 --- /dev/null +++ b/tools/generate_latest_map @@ -0,0 +1,176 @@ +#!/usr/bin/env bash +# +# Generate concrete implementations of LatestMap. +# +# e.g. +# $ generate_latest_map ./report/out.go string NodeControlData ... +# +# Depends on: +# - gofmt + +function generate_header { + local out_file="${1}" + local cmd="${2}" + + cat << EOF >"${out_file}" + // Generated file, do not edit. + // To regenerate, run ${cmd} + + package report + + import ( + "time" + + "github.com/ugorji/go/codec" + ) +EOF +} + +function generate_latest_map { + local out_file="$1" + 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 latest_map_type="${uppercase_data_type}LatestMap" + local empty_latest_map_variable="Empty${latest_map_type}" + local make_function="Make${latest_map_type}" + + local json_timestamp='`json:"timestamp"`' + local json_value='`json:"value"`' + + cat << EOF >>"${out_file}" + type ${wire_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 + } + + // ${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}{} + + // ${latest_map_type} holds latest ${data_type} instances. + type ${latest_map_type} LatestMap + + // ${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})) + + // ${make_function} makes an empty ${latest_map_type}. + func ${make_function}() ${latest_map_type} { + return ${empty_latest_map_variable} + } + + // 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()) + } + + // Size returns the number of elements. + func (m ${latest_map_type}) Size() int { + return (LatestMap)(m).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))) + } + + // Lookup the value for the given key. + func (m ${latest_map_type}) Lookup(key string) (${data_type}, bool) { + v, ok := (LatestMap)(m).Lookup(key) + if !ok { + var zero ${data_type} + return zero, false + } + return v.(${data_type}), 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 + } + return v.(${data_type}), 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)) + } + + // 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)) + } + + // 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})) + }) + } + + // String returns the ${latest_map_type}'s string representation. + func (m ${latest_map_type}) String() string { + return (LatestMap)(m).String() + } + + // 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)) + } + + // CodecEncodeSelf implements codec.Selfer. + func (m *${latest_map_type}) CodecEncodeSelf(encoder *codec.Encoder) { + (*LatestMap)(m).CodecEncodeSelf(encoder) + } + + // CodecDecodeSelf implements codec.Selfer. + func (m *${latest_map_type}) CodecDecodeSelf(decoder *codec.Decoder) { + bm := (*LatestMap)(m) + bm.decoder = ${iface_decoder_variable} + bm.CodecDecodeSelf(decoder) + } + + // MarshalJSON shouldn't be used, use CodecEncodeSelf instead. + func (${latest_map_type}) MarshalJSON() ([]byte, error) { + panic("MarshalJSON shouldn't be used, use CodecEncodeSelf instead") + } + + // UnmarshalJSON shouldn't be used, use CodecDecodeSelf instead. + func (*${latest_map_type}) UnmarshalJSON(b []byte) error { + panic("UnmarshalJSON shouldn't be used, use CodecDecodeSelf instead") + } +EOF +} + +if [ -z "${1}" ]; then + echo "No output file given" + exit 1 +fi + +out="${1}" +outtmp="${out}.tmp" + +generate_header "${outtmp}" "${0} ${*}" +shift +for t in ${*}; do + generate_latest_map "${outtmp}" "${t}" +done + +gofmt -s -w "${outtmp}" +mv "${outtmp}" "${out}"