veyron/lib/stats: Add Map object

Add a Map object to export a set of key/value pairs. The key/value pairs
can be updated atomically.

Export some system stats, including runtime.MemStats which uses the new
Map object.

Change-Id: Ib2cc9ca773df81751f60721115de08389188cfd0
diff --git a/lib/stats/map.go b/lib/stats/map.go
new file mode 100644
index 0000000..89312f6
--- /dev/null
+++ b/lib/stats/map.go
@@ -0,0 +1,165 @@
+package stats
+
+import (
+	"path"
+	"sort"
+	"sync"
+	"time"
+)
+
+// NewMap creates a new Map StatsObject with the given name and
+// returns a pointer to it.
+func NewMap(name string) *Map {
+	lock.Lock()
+	defer lock.Unlock()
+	node := findNodeLocked(name, true)
+	m := Map{name: name, value: make(map[string]mapValue)}
+	node.object = &m
+	return &m
+}
+
+// Map implements the StatsObject interface. The map keys are strings and the
+// values can be bool, int64, uint64, float64, or string.
+type Map struct {
+	mu    sync.RWMutex // ACQUIRED_BEFORE(stats.lock)
+	name  string
+	value map[string]mapValue // GUARDED_BY(mu)
+}
+
+type mapValue struct {
+	lastUpdate time.Time
+	value      interface{}
+}
+
+// Set sets the values of the given keys. There must be exactly one value for
+// each key.
+func (m *Map) Set(kvpairs []KeyValue) {
+	now := time.Now()
+	m.mu.Lock()
+	defer m.mu.Unlock()
+	for _, kv := range kvpairs {
+		var v interface{}
+		switch value := kv.Value.(type) {
+		case bool:
+			v = bool(value)
+		case int:
+			v = int64(value)
+		case int8:
+			v = int64(value)
+		case int16:
+			v = int64(value)
+		case int32:
+			v = int64(value)
+		case int64:
+			v = int64(value)
+		case uint:
+			v = uint64(value)
+		case uint8:
+			v = uint64(value)
+		case uint16:
+			v = uint64(value)
+		case uint32:
+			v = uint64(value)
+		case uint64:
+			v = uint64(value)
+		case float32:
+			v = float64(value)
+		case float64:
+			v = float64(value)
+		case string:
+			v = string(value)
+		default:
+			panic("attempt to use an unsupported type as value")
+		}
+		m.value[kv.Key] = mapValue{now, v}
+	}
+	m.insertMissingNodes()
+}
+
+// Delete deletes the given keys from the map object.
+func (m *Map) Delete(keys []string) {
+	// The lock order is important.
+	m.mu.Lock()
+	defer m.mu.Unlock()
+	lock.Lock()
+	defer lock.Unlock()
+	n := findNodeLocked(m.name, false)
+	for _, k := range keys {
+		delete(m.value, k)
+		if n != nil {
+			delete(n.children, k)
+		}
+	}
+}
+
+// Keys returns a sorted list of all the keys in the map.
+func (m *Map) Keys() []string {
+	m.mu.RLock()
+	defer m.mu.RUnlock()
+	keys := []string{}
+	for k, _ := range m.value {
+		keys = append(keys, k)
+	}
+	sort.Strings(keys)
+	return keys
+}
+
+// LastUpdate always returns a zero-value Time for a Map.
+func (m *Map) LastUpdate() time.Time {
+	return time.Time{}
+}
+
+// Value always returns nil for a Map.
+func (m *Map) Value() interface{} {
+	return nil
+}
+
+// insertMissingNodes inserts all the missing nodes.
+func (m *Map) insertMissingNodes() {
+	missing := []string{}
+	lock.RLock()
+	for key, _ := range m.value {
+		oName := path.Join(m.name, key)
+		if n := findNodeLocked(oName, false); n == nil {
+			missing = append(missing, key)
+		}
+	}
+	lock.RUnlock()
+	if len(missing) == 0 {
+		return
+	}
+
+	lock.Lock()
+	for _, key := range missing {
+		oName := path.Join(m.name, key)
+		if n := findNodeLocked(oName, true); n.object == nil {
+			n.object = &mapValueWrapper{m, key}
+		}
+	}
+	lock.Unlock()
+}
+
+type mapValueWrapper struct {
+	m   *Map
+	key string
+}
+
+// LastUpdate returns the time at which the parent map object was last updated.
+func (w *mapValueWrapper) LastUpdate() time.Time {
+	w.m.mu.RLock()
+	defer w.m.mu.RUnlock()
+	if v, ok := w.m.value[w.key]; ok {
+		return v.lastUpdate
+	}
+	return time.Time{}
+}
+
+// Value returns the current value for the map key.
+func (w *mapValueWrapper) Value() interface{} {
+	w.m.mu.RLock()
+	defer w.m.mu.RUnlock()
+	if v, ok := w.m.value[w.key]; ok {
+		return v.value
+	}
+	return nil
+}