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()