lib/stats: Add Incr() to stats.Map

Incr() increments the value associated with a key. It is a no-op for
non-numeric types.

Change-Id: I4d14eb1da7ac045a965bd20c852695c162ec40ec
diff --git a/lib/stats/map.go b/lib/stats/map.go
index 5987430..6e801eb 100644
--- a/lib/stats/map.go
+++ b/lib/stats/map.go
@@ -82,6 +82,39 @@
 	m.insertMissingNodes()
 }
 
+// Incr increments the value of the given key and returns the new value.
+func (m *Map) Incr(key string, delta int64) interface{} {
+	now := time.Now()
+	m.mu.Lock()
+	defer m.mu.Unlock()
+	if _, exists := m.value[key]; !exists {
+		m.value[key] = mapValue{now, int64(0)}
+		oName := path.Join(m.name, key)
+		lock.Lock()
+		if n := findNodeLocked(oName, true); n.object == nil {
+			n.object = &mapValueWrapper{m, key}
+		}
+		lock.Unlock()
+	}
+	var result interface{}
+	switch value := m.value[key].value.(type) {
+	case int64:
+		result = value + delta
+	case uint64:
+		if delta >= 0 {
+			result = value + uint64(delta)
+		} else {
+			result = value - uint64(-delta)
+		}
+	case float64:
+		result = value + float64(delta)
+	default:
+		return nil
+	}
+	m.value[key] = mapValue{now, result}
+	return result
+}
+
 // Delete deletes the given keys from the map object.
 func (m *Map) Delete(keys []string) {
 	// The lock order is important.
diff --git a/lib/stats/stats_test.go b/lib/stats/stats_test.go
index 4115942..605f7c6 100644
--- a/lib/stats/stats_test.go
+++ b/lib/stats/stats_test.go
@@ -279,7 +279,7 @@
 
 func TestMap(t *testing.T) {
 	m := libstats.NewMap("testing/foo")
-	m.Set([]libstats.KeyValue{{"a", 1}, {"b", 2}})
+	m.Set([]libstats.KeyValue{{"a", uint64(1)}, {"b", 2}, {"c", float64(10.0)}})
 
 	// Test the Value of the map.
 	{
@@ -299,13 +299,38 @@
 		}
 		expected := []libstats.KeyValue{
 			libstats.KeyValue{Key: "foo", Value: nil},
-			libstats.KeyValue{Key: "foo/a", Value: int64(1)},
+			libstats.KeyValue{Key: "foo/a", Value: uint64(1)},
 			libstats.KeyValue{Key: "foo/b", Value: int64(2)},
+			libstats.KeyValue{Key: "foo/c", Value: float64(10.0)},
 		}
 		if !reflect.DeepEqual(got, expected) {
 			t.Errorf("unexpected result. Got %#v, want %#v", got, expected)
 		}
 	}
+	// Test Incr
+	testcases := []struct {
+		key      string
+		incr     int64
+		expected interface{}
+	}{
+		{"a", 2, uint64(3)},
+		{"a", -1, uint64(2)},
+		{"b", 5, int64(7)},
+		{"c", -2, float64(8)},
+		{"d", -2, int64(-2)},
+	}
+	for i, tc := range testcases {
+		if got := m.Incr(tc.key, tc.incr); got != tc.expected {
+			t.Errorf("unexpected result for #%d. Got %v, expected %v", got, tc.expected)
+		}
+		got, err := libstats.Value("testing/foo/" + tc.key)
+		if err != nil {
+			t.Errorf("unexpected error for #%d: %v", i, err)
+		}
+		if got != tc.expected {
+			t.Errorf("unexpected result for #%d. Got %v, want %v", i, got, tc.expected)
+		}
+	}
 
 	m.Delete([]string{"a"})
 
@@ -317,7 +342,9 @@
 		}
 		expected := []libstats.KeyValue{
 			libstats.KeyValue{Key: "foo", Value: nil},
-			libstats.KeyValue{Key: "foo/b", Value: int64(2)},
+			libstats.KeyValue{Key: "foo/b", Value: int64(7)},
+			libstats.KeyValue{Key: "foo/c", Value: float64(8)},
+			libstats.KeyValue{Key: "foo/d", Value: int64(-2)},
 		}
 		if !reflect.DeepEqual(got, expected) {
 			t.Errorf("unexpected result. Got %#v, want %#v", got, expected)