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
+}