veyron/services/mgmt/stats: Add implementation of the stats interface.

This is an implementation of the stats interface to export internal
counters. I moved the definition of the Histogram type to a vdl file to
avoid depending on the go types.

Change-Id: Ic28b9c8ac3c272baed23ed15c99dcbd50776fc25
diff --git a/lib/stats/counter/counter.go b/lib/stats/counter/counter.go
index bee9fe4..4aa9db3 100644
--- a/lib/stats/counter/counter.go
+++ b/lib/stats/counter/counter.go
@@ -47,11 +47,12 @@
 	return c
 }
 
-func (c *Counter) advance() {
+func (c *Counter) advance() time.Time {
 	now := Now()
 	for _, ts := range c.ts {
 		ts.advanceTime(now)
 	}
+	return now
 }
 
 // Value returns the current value of the counter.
@@ -72,8 +73,7 @@
 func (c *Counter) Set(value int64) {
 	c.mu.Lock()
 	defer c.mu.Unlock()
-	c.advance()
-	c.lastUpdate = c.ts[minute].headTime()
+	c.lastUpdate = c.advance()
 	for _, ts := range c.ts {
 		ts.set(value)
 	}
@@ -83,8 +83,7 @@
 func (c *Counter) Incr(delta int64) {
 	c.mu.Lock()
 	defer c.mu.Unlock()
-	c.advance()
-	c.lastUpdate = c.ts[minute].headTime()
+	c.lastUpdate = c.advance()
 	for _, ts := range c.ts {
 		ts.incr(delta)
 	}
diff --git a/lib/stats/histogram/histogram.go b/lib/stats/histogram/histogram.go
index 7d0c773..ec92e7b 100644
--- a/lib/stats/histogram/histogram.go
+++ b/lib/stats/histogram/histogram.go
@@ -7,12 +7,13 @@
 	"time"
 
 	"veyron/lib/stats/counter"
+	"veyron/services/mgmt/stats"
 )
 
 // A Histogram accumulates values in the form of a histogram. The type of the
 // values is int64, which is suitable for keeping track of things like RPC
-// latency in milliseconds. New histogram objects should be obtain via the New()
-// function.
+// latency in milliseconds. New histogram objects should be obtained via the
+// New() function.
 type Histogram struct {
 	opts    Options
 	buckets []bucketInternal
@@ -34,24 +35,6 @@
 	MinValue int64
 }
 
-// HistogramValue is the value returned by Value(), and the Delta*() methods.
-type HistogramValue struct {
-	// Count is the total number of values added to the histogram.
-	Count int64
-	// Sum is the sum of all the values added to the histogram.
-	Sum int64
-	// Buckets contains all the buckets of the histogram.
-	Buckets []Bucket
-}
-
-// Bucket is one histogram bucket.
-type Bucket struct {
-	// LowBound is the lower bound of the bucket.
-	LowBound int64
-	// Count is the number of values in the bucket.
-	Count int64
-}
-
 // bucketInternal is the internal representation of a bucket, which includes a
 // rate counter.
 type bucketInternal struct {
@@ -108,16 +91,16 @@
 }
 
 // Value returns the accumulated state of the histogram since it was created.
-func (h *Histogram) Value() HistogramValue {
-	b := make([]Bucket, len(h.buckets))
+func (h *Histogram) Value() stats.HistogramValue {
+	b := make([]stats.HistogramBucket, len(h.buckets))
 	for i, v := range h.buckets {
-		b[i] = Bucket{
+		b[i] = stats.HistogramBucket{
 			LowBound: v.lowBound,
 			Count:    v.count.Value(),
 		}
 	}
 
-	v := HistogramValue{
+	v := stats.HistogramValue{
 		Count:   h.count.Value(),
 		Sum:     h.sum.Value(),
 		Buckets: b,
@@ -126,16 +109,16 @@
 }
 
 // Delta1h returns the change in the last hour.
-func (h *Histogram) Delta1h() HistogramValue {
-	b := make([]Bucket, len(h.buckets))
+func (h *Histogram) Delta1h() stats.HistogramValue {
+	b := make([]stats.HistogramBucket, len(h.buckets))
 	for i, v := range h.buckets {
-		b[i] = Bucket{
+		b[i] = stats.HistogramBucket{
 			LowBound: v.lowBound,
 			Count:    v.count.Delta1h(),
 		}
 	}
 
-	v := HistogramValue{
+	v := stats.HistogramValue{
 		Count:   h.count.Delta1h(),
 		Sum:     h.sum.Delta1h(),
 		Buckets: b,
@@ -144,16 +127,16 @@
 }
 
 // Delta10m returns the change in the last 10 minutes.
-func (h *Histogram) Delta10m() HistogramValue {
-	b := make([]Bucket, len(h.buckets))
+func (h *Histogram) Delta10m() stats.HistogramValue {
+	b := make([]stats.HistogramBucket, len(h.buckets))
 	for i, v := range h.buckets {
-		b[i] = Bucket{
+		b[i] = stats.HistogramBucket{
 			LowBound: v.lowBound,
 			Count:    v.count.Delta10m(),
 		}
 	}
 
-	v := HistogramValue{
+	v := stats.HistogramValue{
 		Count:   h.count.Delta10m(),
 		Sum:     h.sum.Delta10m(),
 		Buckets: b,
@@ -162,16 +145,16 @@
 }
 
 // Delta1m returns the change in the last 10 minutes.
-func (h *Histogram) Delta1m() HistogramValue {
-	b := make([]Bucket, len(h.buckets))
+func (h *Histogram) Delta1m() stats.HistogramValue {
+	b := make([]stats.HistogramBucket, len(h.buckets))
 	for i, v := range h.buckets {
-		b[i] = Bucket{
+		b[i] = stats.HistogramBucket{
 			LowBound: v.lowBound,
 			Count:    v.count.Delta1m(),
 		}
 	}
 
-	v := HistogramValue{
+	v := stats.HistogramValue{
 		Count:   h.count.Delta1m(),
 		Sum:     h.sum.Delta1m(),
 		Buckets: b,
diff --git a/lib/stats/stats_test.go b/lib/stats/stats_test.go
index 5c10bad..9d24b8c 100644
--- a/lib/stats/stats_test.go
+++ b/lib/stats/stats_test.go
@@ -5,15 +5,17 @@
 	"testing"
 	"time"
 
-	"veyron/lib/stats"
+	libstats "veyron/lib/stats"
 	"veyron/lib/stats/counter"
 	"veyron/lib/stats/histogram"
+	istats "veyron/services/mgmt/stats"
+
 	"veyron2/rt"
 )
 
-func doGlob(root, pattern string, since time.Time) ([]stats.KeyValue, error) {
-	it := stats.Glob(root, pattern, since, true)
-	out := []stats.KeyValue{}
+func doGlob(root, pattern string, since time.Time) ([]libstats.KeyValue, error) {
+	it := libstats.Glob(root, pattern, since, true)
+	out := []libstats.KeyValue{}
 	for it.Advance() {
 		v := it.Value()
 		out = append(out, v)
@@ -30,17 +32,17 @@
 	now := time.Unix(1, 0)
 	counter.Now = func() time.Time { return now }
 
-	a := stats.NewInteger("ipc/test/aaa")
-	b := stats.NewFloat("ipc/test/bbb")
-	c := stats.NewString("ipc/test/ccc")
-	d := stats.NewCounter("ipc/test/ddd")
+	a := libstats.NewInteger("ipc/test/aaa")
+	b := libstats.NewFloat("ipc/test/bbb")
+	c := libstats.NewString("ipc/test/ccc")
+	d := libstats.NewCounter("ipc/test/ddd")
 
 	a.Set(1)
 	b.Set(2)
 	c.Set("Hello")
 	d.Set(4)
 
-	got, err := stats.Value("ipc/test/aaa")
+	got, err := libstats.Value("ipc/test/aaa")
 	if err != nil {
 		t.Errorf("unexpected error: %v", err)
 	}
@@ -48,16 +50,16 @@
 		t.Errorf("unexpected result. Got %v, want %v", got, expected)
 	}
 
-	if _, err := stats.Value(""); err != stats.ErrNotFound {
+	if _, err := libstats.Value(""); err != libstats.ErrNotFound {
 		t.Errorf("expected error, got err=%v", err)
 	}
-	if _, err := stats.Value("does/not/exist"); err != stats.ErrNotFound {
+	if _, err := libstats.Value("does/not/exist"); err != libstats.ErrNotFound {
 		t.Errorf("expected error, got err=%v", err)
 	}
 
-	root := stats.NewInteger("")
+	root := libstats.NewInteger("")
 	root.Set(42)
-	got, err = stats.Value("")
+	got, err = libstats.Value("")
 	if err != nil {
 		t.Errorf("unexpected error: %v", err)
 	}
@@ -65,9 +67,9 @@
 		t.Errorf("unexpected result. Got %v, want %v", got, expected)
 	}
 
-	foo := stats.NewInteger("foo")
+	foo := libstats.NewInteger("foo")
 	foo.Set(55)
-	got, err = stats.Value("foo")
+	got, err = libstats.Value("foo")
 	if err != nil {
 		t.Errorf("unexpected error: %v", err)
 	}
@@ -75,9 +77,9 @@
 		t.Errorf("unexpected result. Got %v, want %v", got, expected)
 	}
 
-	bar := stats.NewInteger("foo/bar")
+	bar := libstats.NewInteger("foo/bar")
 	bar.Set(44)
-	got, err = stats.Value("foo/bar")
+	got, err = libstats.Value("foo/bar")
 	if err != nil {
 		t.Errorf("unexpected error: %v", err)
 	}
@@ -89,20 +91,20 @@
 	if err != nil {
 		t.Errorf("unexpected error: %v", err)
 	}
-	expected := []stats.KeyValue{
-		stats.KeyValue{Key: "", Value: int64(42)},
-		stats.KeyValue{Key: "foo", Value: int64(55)},
-		stats.KeyValue{Key: "foo/bar", Value: int64(44)},
-		stats.KeyValue{Key: "ipc/test/aaa", Value: int64(1)},
-		stats.KeyValue{Key: "ipc/test/bbb", Value: float64(2)},
-		stats.KeyValue{Key: "ipc/test/ccc", Value: string("Hello")},
-		stats.KeyValue{Key: "ipc/test/ddd", Value: int64(4)},
-		stats.KeyValue{Key: "ipc/test/ddd/delta10m", Value: int64(0)},
-		stats.KeyValue{Key: "ipc/test/ddd/delta1h", Value: int64(0)},
-		stats.KeyValue{Key: "ipc/test/ddd/delta1m", Value: int64(0)},
-		stats.KeyValue{Key: "ipc/test/ddd/rate10m", Value: float64(0)},
-		stats.KeyValue{Key: "ipc/test/ddd/rate1h", Value: float64(0)},
-		stats.KeyValue{Key: "ipc/test/ddd/rate1m", Value: float64(0)},
+	expected := []libstats.KeyValue{
+		libstats.KeyValue{Key: "", Value: int64(42)},
+		libstats.KeyValue{Key: "foo", Value: int64(55)},
+		libstats.KeyValue{Key: "foo/bar", Value: int64(44)},
+		libstats.KeyValue{Key: "ipc/test/aaa", Value: int64(1)},
+		libstats.KeyValue{Key: "ipc/test/bbb", Value: float64(2)},
+		libstats.KeyValue{Key: "ipc/test/ccc", Value: string("Hello")},
+		libstats.KeyValue{Key: "ipc/test/ddd", Value: int64(4)},
+		libstats.KeyValue{Key: "ipc/test/ddd/delta10m", Value: int64(0)},
+		libstats.KeyValue{Key: "ipc/test/ddd/delta1h", Value: int64(0)},
+		libstats.KeyValue{Key: "ipc/test/ddd/delta1m", Value: int64(0)},
+		libstats.KeyValue{Key: "ipc/test/ddd/rate10m", Value: float64(0)},
+		libstats.KeyValue{Key: "ipc/test/ddd/rate1h", Value: float64(0)},
+		libstats.KeyValue{Key: "ipc/test/ddd/rate1m", Value: float64(0)},
 	}
 	if !reflect.DeepEqual(result, expected) {
 		t.Errorf("unexpected result. Got %#v, want %#v", result, expected)
@@ -112,11 +114,11 @@
 	if err != nil {
 		t.Errorf("unexpected error: %v", err)
 	}
-	expected = []stats.KeyValue{
-		stats.KeyValue{Key: "ipc/test/aaa", Value: int64(1)},
-		stats.KeyValue{Key: "ipc/test/bbb", Value: float64(2)},
-		stats.KeyValue{Key: "ipc/test/ccc", Value: string("Hello")},
-		stats.KeyValue{Key: "ipc/test/ddd", Value: int64(4)},
+	expected = []libstats.KeyValue{
+		libstats.KeyValue{Key: "ipc/test/aaa", Value: int64(1)},
+		libstats.KeyValue{Key: "ipc/test/bbb", Value: float64(2)},
+		libstats.KeyValue{Key: "ipc/test/ccc", Value: string("Hello")},
+		libstats.KeyValue{Key: "ipc/test/ddd", Value: int64(4)},
 	}
 	if !reflect.DeepEqual(result, expected) {
 		t.Errorf("unexpected result. Got %#v, want %#v", result, expected)
@@ -129,13 +131,13 @@
 	if err != nil {
 		t.Errorf("unexpected error: %v", err)
 	}
-	expected = []stats.KeyValue{
-		stats.KeyValue{Key: "ipc/test/ddd/delta10m", Value: int64(100)},
-		stats.KeyValue{Key: "ipc/test/ddd/delta1h", Value: int64(0)},
-		stats.KeyValue{Key: "ipc/test/ddd/delta1m", Value: int64(100)},
-		stats.KeyValue{Key: "ipc/test/ddd/rate10m", Value: float64(10)},
-		stats.KeyValue{Key: "ipc/test/ddd/rate1h", Value: float64(0)},
-		stats.KeyValue{Key: "ipc/test/ddd/rate1m", Value: float64(10)},
+	expected = []libstats.KeyValue{
+		libstats.KeyValue{Key: "ipc/test/ddd/delta10m", Value: int64(100)},
+		libstats.KeyValue{Key: "ipc/test/ddd/delta1h", Value: int64(0)},
+		libstats.KeyValue{Key: "ipc/test/ddd/delta1m", Value: int64(100)},
+		libstats.KeyValue{Key: "ipc/test/ddd/rate10m", Value: float64(10)},
+		libstats.KeyValue{Key: "ipc/test/ddd/rate1h", Value: float64(0)},
+		libstats.KeyValue{Key: "ipc/test/ddd/rate1m", Value: float64(10)},
 	}
 	if !reflect.DeepEqual(result, expected) {
 		t.Errorf("unexpected result. Got %#v, want %#v", result, expected)
@@ -146,11 +148,11 @@
 	if err != nil {
 		t.Errorf("unexpected error: %v", err)
 	}
-	expected = []stats.KeyValue{
-		stats.KeyValue{Key: "aaa", Value: int64(1)},
-		stats.KeyValue{Key: "bbb", Value: float64(2)},
-		stats.KeyValue{Key: "ccc", Value: string("Hello")},
-		stats.KeyValue{Key: "ddd", Value: int64(104)},
+	expected = []libstats.KeyValue{
+		libstats.KeyValue{Key: "aaa", Value: int64(1)},
+		libstats.KeyValue{Key: "bbb", Value: float64(2)},
+		libstats.KeyValue{Key: "ccc", Value: string("Hello")},
+		libstats.KeyValue{Key: "ddd", Value: int64(104)},
 	}
 	if !reflect.DeepEqual(result, expected) {
 		t.Errorf("unexpected result. Got %#v, want %#v", result, expected)
@@ -160,8 +162,8 @@
 	if err != nil {
 		t.Errorf("unexpected error: %v", err)
 	}
-	expected = []stats.KeyValue{
-		stats.KeyValue{Key: "", Value: int64(1)},
+	expected = []libstats.KeyValue{
+		libstats.KeyValue{Key: "", Value: int64(1)},
 	}
 	if !reflect.DeepEqual(result, expected) {
 		t.Errorf("unexpected result. Got %#v, want %#v", result, expected)
@@ -172,8 +174,8 @@
 	if err != nil {
 		t.Errorf("unexpected error: %v", err)
 	}
-	expected = []stats.KeyValue{
-		stats.KeyValue{Key: "ddd", Value: int64(104)},
+	expected = []libstats.KeyValue{
+		libstats.KeyValue{Key: "ddd", Value: int64(104)},
 	}
 	if !reflect.DeepEqual(result, expected) {
 		t.Errorf("unexpected result. Got %#v, want %#v", result, expected)
@@ -183,13 +185,13 @@
 	if err != nil {
 		t.Errorf("unexpected error: %v", err)
 	}
-	expected = []stats.KeyValue{}
+	expected = []libstats.KeyValue{}
 	if !reflect.DeepEqual(result, expected) {
 		t.Errorf("unexpected result. Got %#v, want %#v", result, expected)
 	}
 
 	// Test histogram
-	h := stats.NewHistogram("ipc/test/hhh", histogram.Options{NumBuckets: 5, GrowthFactor: 0})
+	h := libstats.NewHistogram("ipc/test/hhh", histogram.Options{NumBuckets: 5, GrowthFactor: 0})
 	h.Add(1)
 	h.Add(2)
 
@@ -197,18 +199,18 @@
 	if err != nil {
 		t.Errorf("unexpected error: %v", err)
 	}
-	expected = []stats.KeyValue{
-		stats.KeyValue{
+	expected = []libstats.KeyValue{
+		libstats.KeyValue{
 			Key: "ipc/test/hhh",
-			Value: histogram.HistogramValue{
+			Value: istats.HistogramValue{
 				Count: 2,
 				Sum:   3,
-				Buckets: []histogram.Bucket{
-					histogram.Bucket{LowBound: 0, Count: 0},
-					histogram.Bucket{LowBound: 1, Count: 1},
-					histogram.Bucket{LowBound: 2, Count: 1},
-					histogram.Bucket{LowBound: 3, Count: 0},
-					histogram.Bucket{LowBound: 4, Count: 0},
+				Buckets: []istats.HistogramBucket{
+					istats.HistogramBucket{LowBound: 0, Count: 0},
+					istats.HistogramBucket{LowBound: 1, Count: 1},
+					istats.HistogramBucket{LowBound: 2, Count: 1},
+					istats.HistogramBucket{LowBound: 3, Count: 0},
+					istats.HistogramBucket{LowBound: 4, Count: 0},
 				},
 			},
 		},
@@ -226,18 +228,18 @@
 	if err != nil {
 		t.Errorf("unexpected error: %v", err)
 	}
-	expected = []stats.KeyValue{
-		stats.KeyValue{
+	expected = []libstats.KeyValue{
+		libstats.KeyValue{
 			Key: "ipc/test/hhh/delta1m",
-			Value: histogram.HistogramValue{
+			Value: istats.HistogramValue{
 				Count: 2,
 				Sum:   5,
-				Buckets: []histogram.Bucket{
-					histogram.Bucket{LowBound: 0, Count: 0},
-					histogram.Bucket{LowBound: 1, Count: 0},
-					histogram.Bucket{LowBound: 2, Count: 1},
-					histogram.Bucket{LowBound: 3, Count: 1},
-					histogram.Bucket{LowBound: 4, Count: 0},
+				Buckets: []istats.HistogramBucket{
+					istats.HistogramBucket{LowBound: 0, Count: 0},
+					istats.HistogramBucket{LowBound: 1, Count: 0},
+					istats.HistogramBucket{LowBound: 2, Count: 1},
+					istats.HistogramBucket{LowBound: 3, Count: 1},
+					istats.HistogramBucket{LowBound: 4, Count: 0},
 				},
 			},
 		},
diff --git a/services/mgmt/stats/impl/stats_invoker.go b/services/mgmt/stats/impl/stats_invoker.go
new file mode 100644
index 0000000..99ef64d
--- /dev/null
+++ b/services/mgmt/stats/impl/stats_invoker.go
@@ -0,0 +1,107 @@
+// Package impl implements the Stats interface from
+// veyron2/services/mgmt/stats.
+package impl
+
+import (
+	"time"
+
+	"veyron/lib/stats"
+
+	"veyron2/ipc"
+	"veyron2/services/mgmt/stats/types"
+	mttypes "veyron2/services/mounttable/types"
+	watchtypes "veyron2/services/watch/types"
+	"veyron2/vdl/vdlutil"
+	"veyron2/verror"
+	"veyron2/vlog"
+)
+
+type statsInvoker struct {
+	suffix    string
+	watchFreq time.Duration
+}
+
+var (
+	errNotFound        = verror.NotFoundf("object not found")
+	errNoValue         = verror.Make(types.NoValue, "object has no value")
+	errOperationFailed = verror.Internalf("operation failed")
+)
+
+// NewStatsInvoker returns a new Invoker. The value of watchFreq is used to
+// specify the time between WatchGlob updates.
+func NewStatsInvoker(suffix string, watchFreq time.Duration) ipc.Invoker {
+	return ipc.ReflectInvoker(&statsInvoker{suffix, watchFreq})
+}
+
+// Glob returns the name of all objects that match pattern.
+func (i *statsInvoker) Glob(call ipc.ServerCall, pattern string) error {
+	vlog.VI(1).Infof("%v.Glob(%q)", i.suffix, pattern)
+
+	it := stats.Glob(i.suffix, pattern, time.Time{}, false)
+	for it.Advance() {
+		call.Send(mttypes.MountEntry{Name: it.Value().Key})
+	}
+	if err := it.Err(); err != nil {
+		if err == stats.ErrNotFound {
+			return errNotFound
+		}
+		return errOperationFailed
+	}
+	return nil
+}
+
+// WatchGlob returns the name and value of the objects that match the request,
+// followed by periodic updates when values change.
+func (i *statsInvoker) WatchGlob(call ipc.ServerCall, req watchtypes.GlobRequest) error {
+	vlog.VI(1).Infof("%v.WatchGlob(%+v)", i.suffix, req)
+
+	var t time.Time
+Loop:
+	for {
+		prevTime := t
+		t = time.Now()
+		it := stats.Glob(i.suffix, req.Pattern, prevTime, true)
+		changes := []watchtypes.Change{}
+		for it.Advance() {
+			v := it.Value()
+			c := watchtypes.Change{
+				Name:  v.Key,
+				State: watchtypes.Exists,
+				Value: v.Value,
+			}
+			changes = append(changes, c)
+		}
+		if err := it.Err(); err != nil {
+			if err == stats.ErrNotFound {
+				return errNotFound
+			}
+			return errOperationFailed
+		}
+		if len(changes) > 0 {
+			call.Send(watchtypes.ChangeBatch{Changes: changes})
+		}
+		select {
+		case <-call.Done():
+			break Loop
+		case <-time.After(i.watchFreq):
+		}
+	}
+	return nil
+}
+
+// Value returns the value of the receiver object.
+func (i *statsInvoker) Value(call ipc.ServerCall) (vdlutil.Any, error) {
+	vlog.VI(1).Infof("%v.Value()", i.suffix)
+
+	v, err := stats.Value(i.suffix)
+	switch err {
+	case stats.ErrNotFound:
+		return nil, errNotFound
+	case stats.ErrNoValue:
+		return nil, errNoValue
+	case nil:
+		return v, nil
+	default:
+		return nil, errOperationFailed
+	}
+}
diff --git a/services/mgmt/stats/impl/stats_invoker_test.go b/services/mgmt/stats/impl/stats_invoker_test.go
new file mode 100644
index 0000000..c2089ef
--- /dev/null
+++ b/services/mgmt/stats/impl/stats_invoker_test.go
@@ -0,0 +1,206 @@
+package impl_test
+
+import (
+	"reflect"
+	"sort"
+	"testing"
+	"time"
+
+	libstats "veyron/lib/stats"
+	"veyron/lib/stats/histogram"
+	istats "veyron/services/mgmt/stats"
+	"veyron/services/mgmt/stats/impl"
+
+	"veyron2/ipc"
+	"veyron2/naming"
+	"veyron2/rt"
+	"veyron2/security"
+	"veyron2/services/mgmt/stats"
+	"veyron2/services/watch/types"
+)
+
+type statsDispatcher struct {
+}
+
+func (d *statsDispatcher) Lookup(suffix, method string) (ipc.Invoker, security.Authorizer, error) {
+	return impl.NewStatsInvoker(suffix, 100*time.Millisecond), nil, nil
+}
+
+func startServer(t *testing.T) (string, func()) {
+	disp := &statsDispatcher{}
+	server, err := rt.R().NewServer()
+	if err != nil {
+		t.Fatalf("NewServer failed: %v", err)
+		return "", nil
+	}
+	endpoint, err := server.Listen("tcp", "localhost:0")
+	if err != nil {
+		t.Fatalf("Listen failed: %v", err)
+		return "", nil
+	}
+	if err := server.Serve("", disp); err != nil {
+		t.Fatalf("Serve failed: %v", err)
+		return "", nil
+	}
+	return endpoint.String(), func() { server.Stop() }
+}
+
+func TestStatsInvoker(t *testing.T) {
+	rt.Init()
+
+	endpoint, stop := startServer(t)
+	defer stop()
+
+	counter := libstats.NewCounter("testing/foo/bar")
+	counter.Incr(10)
+
+	histogram := libstats.NewHistogram("testing/hist/foo", histogram.Options{
+		NumBuckets:         5,
+		GrowthFactor:       1,
+		SmallestBucketSize: 1,
+		MinValue:           0,
+	})
+	for i := 0; i < 10; i++ {
+		histogram.Add(int64(i))
+	}
+
+	c, err := stats.BindStats(naming.JoinAddressName(endpoint, ""))
+	if err != nil {
+		t.Errorf("BindStats: %v", err)
+	}
+
+	// Test Glob()
+	{
+		stream, err := c.Glob(rt.R().NewContext(), "testing/foo/...")
+		if err != nil {
+			t.Fatalf("c.Glob failed: %v", err)
+		}
+		iterator := stream.RecvStream()
+		results := []string{}
+		for iterator.Advance() {
+			me := iterator.Value()
+			if len(me.Servers) > 0 {
+				t.Errorf("unexpected servers. Got %v, want none", me.Servers)
+			}
+			results = append(results, me.Name)
+		}
+		if err := iterator.Err(); err != nil {
+			t.Errorf("unexpected stream error: %v", err)
+		}
+		err = stream.Finish()
+		if err != nil {
+			t.Errorf("gstream.Finish failed: %v", err)
+		}
+		expected := []string{
+			"testing/foo/bar",
+			"testing/foo/bar/delta10m",
+			"testing/foo/bar/delta1h",
+			"testing/foo/bar/delta1m",
+			"testing/foo/bar/rate10m",
+			"testing/foo/bar/rate1h",
+			"testing/foo/bar/rate1m",
+		}
+		sort.Strings(results)
+		sort.Strings(expected)
+		if !reflect.DeepEqual(results, expected) {
+			t.Errorf("unexpected result. Got %v, want %v", results, expected)
+		}
+	}
+
+	// Test WatchGlob()
+	{
+		stream, err := c.WatchGlob(rt.R().NewContext(), types.GlobRequest{Pattern: "testing/foo/bar"})
+		if err != nil {
+			t.Fatalf("c.WatchGlob failed: %v", err)
+		}
+		iterator := stream.RecvStream()
+		if !iterator.Advance() {
+			t.Fatalf("expected more stream values")
+		}
+		got := iterator.Value()
+		expected := types.ChangeBatch{
+			Changes: []types.Change{
+				types.Change{Name: "testing/foo/bar", Value: int64(10)},
+			},
+		}
+		if !reflect.DeepEqual(got, expected) {
+			t.Errorf("unexpected result. Got %#v, want %#v", got, expected)
+		}
+
+		counter.Incr(5)
+
+		if !iterator.Advance() {
+			t.Fatalf("expected more stream values")
+		}
+		got = iterator.Value()
+		expected = types.ChangeBatch{
+			Changes: []types.Change{
+				types.Change{Name: "testing/foo/bar", Value: int64(15)},
+			},
+		}
+		if !reflect.DeepEqual(got, expected) {
+			t.Errorf("unexpected result. Got %#v, want %#v", got, expected)
+		}
+
+		counter.Incr(2)
+
+		if !iterator.Advance() {
+			t.Fatalf("expected more stream values")
+		}
+		got = iterator.Value()
+		expected = types.ChangeBatch{
+			Changes: []types.Change{
+				types.Change{Name: "testing/foo/bar", Value: int64(17)},
+			},
+		}
+		if !reflect.DeepEqual(got, expected) {
+			t.Errorf("unexpected result. Got %#v, want %#v", got, expected)
+		}
+		stream.Cancel()
+
+		if iterator.Advance() {
+			t.Errorf("expected no more stream values, got: %v", iterator.Value())
+		}
+	}
+
+	// Test Value()
+	{
+		c, err := stats.BindStats(naming.JoinAddressName(endpoint, "//testing/foo/bar"))
+		if err != nil {
+			t.Errorf("BindStats: %v", err)
+		}
+		value, err := c.Value(rt.R().NewContext())
+		if err != nil {
+			t.Errorf("unexpected error: %v", err)
+		}
+		if expected := int64(17); value != expected {
+			t.Errorf("unexpected result. Got %v, want %v", value, expected)
+		}
+	}
+
+	// Test Value() with Histogram
+	{
+		c, err := stats.BindStats(naming.JoinAddressName(endpoint, "//testing/hist/foo"))
+		if err != nil {
+			t.Errorf("BindStats: %v", err)
+		}
+		value, err := c.Value(rt.R().NewContext())
+		if err != nil {
+			t.Errorf("unexpected error: %v", err)
+		}
+		want := istats.HistogramValue{
+			Count: 10,
+			Sum:   45,
+			Buckets: []istats.HistogramBucket{
+				istats.HistogramBucket{LowBound: 0, Count: 1},
+				istats.HistogramBucket{LowBound: 1, Count: 2},
+				istats.HistogramBucket{LowBound: 3, Count: 4},
+				istats.HistogramBucket{LowBound: 7, Count: 3},
+				istats.HistogramBucket{LowBound: 15, Count: 0},
+			},
+		}
+		if !reflect.DeepEqual(value, want) {
+			t.Errorf("unexpected result. Got %#v, want %#v", value, want)
+		}
+	}
+}
diff --git a/services/mgmt/stats/types.vdl b/services/mgmt/stats/types.vdl
new file mode 100644
index 0000000..8aa3ec9
--- /dev/null
+++ b/services/mgmt/stats/types.vdl
@@ -0,0 +1,20 @@
+// Packages stats defines the non-native types exported by the stats service.
+package stats
+
+// HistogramValue is the value of Histogram objects.
+type HistogramValue struct {
+	// Count is the total number of values added to the histogram.
+	Count int64
+	// Sum is the sum of all the values added to the histogram.
+	Sum int64
+	// Buckets contains all the buckets of the histogram.
+	Buckets []HistogramBucket
+}
+
+// HistogramBucket is one histogram bucket.
+type HistogramBucket struct {
+	// LowBound is the lower bound of the bucket.
+	LowBound int64
+	// Count is the number of values in the bucket.
+	Count int64
+}
diff --git a/services/mgmt/stats/types.vdl.go b/services/mgmt/stats/types.vdl.go
new file mode 100644
index 0000000..7ad330c
--- /dev/null
+++ b/services/mgmt/stats/types.vdl.go
@@ -0,0 +1,23 @@
+// This file was auto-generated by the veyron vdl tool.
+// Source: types.vdl
+
+// Packages stats defines the non-native types exported by the stats service.
+package stats
+
+// HistogramValue is the value of Histogram objects.
+type HistogramValue struct {
+	// Count is the total number of values added to the histogram.
+	Count int64
+	// Sum is the sum of all the values added to the histogram.
+	Sum int64
+	// Buckets contains all the buckets of the histogram.
+	Buckets []HistogramBucket
+}
+
+// HistogramBucket is one histogram bucket.
+type HistogramBucket struct {
+	// LowBound is the lower bound of the bucket.
+	LowBound int64
+	// Count is the number of values in the bucket.
+	Count int64
+}