veyron/runtimes/google/ipc: Adding per-method stats
This change adds two per-method stats to server calls: the number of
calls, and the latency distribution.
It is intended as a proof of concept.
Change-Id: Id0e9abb8b744c74b71a4db88941ba234114cff56
diff --git a/runtimes/google/ipc/flow_test.go b/runtimes/google/ipc/flow_test.go
index 9e3304d..b097171 100644
--- a/runtimes/google/ipc/flow_test.go
+++ b/runtimes/google/ipc/flow_test.go
@@ -119,8 +119,9 @@
}
ipcServer := &server{
- ctx: testContext(),
- disp: testDisp{newEchoInvoker},
+ ctx: testContext(),
+ disp: testDisp{newEchoInvoker},
+ stats: newIPCStats(""),
}
for _, test := range tests {
clientFlow, serverFlow := newTestFlows()
diff --git a/runtimes/google/ipc/server.go b/runtimes/google/ipc/server.go
index 5b5b36d..817e8b2 100644
--- a/runtimes/google/ipc/server.go
+++ b/runtimes/google/ipc/server.go
@@ -48,6 +48,7 @@
ns naming.Namespace
preferredAddress func(network string) (net.Addr, error)
servesMountTable bool
+ stats *ipcStats // stats for this server.
}
func InternalNewServer(ctx context.T, streamMgr stream.Manager, ns naming.Namespace, opts ...ipc.ServerOpt) (ipc.Server, error) {
@@ -59,6 +60,7 @@
stoppedChan: make(chan struct{}),
preferredAddress: preferredIPAddress,
ns: ns,
+ stats: newIPCStats(naming.Join("ipc", "server", streamMgr.RoutingID().String())),
}
for _, opt := range opts {
switch opt := opt.(type) {
@@ -603,6 +605,7 @@
return nil, errNotAuthorized(fmt.Errorf("%q not authorized for method %q: %v", fs.RemoteID(), fs.Method(), err))
}
results, err := invoker.Invoke(req.Method, fs, argptrs)
+ fs.server.stats.record(req.Method, time.Since(start))
return results, verror.Convert(err)
}
diff --git a/runtimes/google/ipc/stats.go b/runtimes/google/ipc/stats.go
new file mode 100644
index 0000000..1b8344d
--- /dev/null
+++ b/runtimes/google/ipc/stats.go
@@ -0,0 +1,59 @@
+package ipc
+
+import (
+ "sync"
+ "time"
+
+ "veyron/lib/stats"
+ "veyron/lib/stats/histogram"
+
+ "veyron2/naming"
+)
+
+type ipcStats struct {
+ mu sync.RWMutex
+ prefix string
+ methods map[string]*perMethodStats
+}
+
+func newIPCStats(prefix string) *ipcStats {
+ return &ipcStats{prefix: prefix, methods: make(map[string]*perMethodStats)}
+}
+
+type perMethodStats struct {
+ latency *histogram.Histogram
+}
+
+func (s *ipcStats) record(method string, latency time.Duration) {
+ // Try first with a read lock. This will succeed in the most common
+ // case. If it fails, try again with a write lock and create the stats
+ // objects if they are still not there.
+ s.mu.RLock()
+ m, ok := s.methods[method]
+ s.mu.RUnlock()
+ if !ok {
+ m = s.newPerMethodStats(method)
+ }
+ m.latency.Add(int64(latency / time.Millisecond))
+}
+
+// newPerMethodStats creates a new perMethodStats object if one doesn't exist
+// already. It returns the newly created object, or the already existing one.
+func (s *ipcStats) newPerMethodStats(method string) *perMethodStats {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ m, ok := s.methods[method]
+ if !ok {
+ name := naming.Join(s.prefix, method, "latency-ms")
+ s.methods[method] = &perMethodStats{
+ latency: stats.NewHistogram(name, histogram.Options{
+ NumBuckets: 25,
+ GrowthFactor: 1,
+ SmallestBucketSize: 1,
+ MinValue: 0,
+ }),
+ }
+ m = s.methods[method]
+ }
+ return m
+}
diff --git a/runtimes/google/ipc/stream/manager/manager.go b/runtimes/google/ipc/stream/manager/manager.go
index 47646d5..389a30c 100644
--- a/runtimes/google/ipc/stream/manager/manager.go
+++ b/runtimes/google/ipc/stream/manager/manager.go
@@ -215,6 +215,10 @@
}
}
+func (m *manager) RoutingID() naming.RoutingID {
+ return m.rid
+}
+
func (m *manager) DebugString() string {
vifs := m.vifs.List()