services/mounttable/mounttablelib: Add num-mounted-servers

This change adds a counter for the total number of mounted servers. The
name of the stats object is mounttable/num-mounted-servers.

Partly addresses https://github.com/veyron/release-issues/issues/1896

Change-Id: I5b8e58a3d561c2e28998799971d674bbe03a2f4b
diff --git a/services/mounttable/mounttablelib/mounttable.go b/services/mounttable/mounttablelib/mounttable.go
index 8140ef8..ca9f9f1 100644
--- a/services/mounttable/mounttablelib/mounttable.go
+++ b/services/mounttable/mounttablelib/mounttable.go
@@ -53,9 +53,10 @@
 
 // mountTable represents a namespace.  One exists per server instance.
 type mountTable struct {
-	root        *node
-	superUsers  access.AccessList
-	nodeCounter *stats.Integer
+	root          *node
+	superUsers    access.AccessList
+	nodeCounter   *stats.Integer
+	serverCounter *stats.Integer
 }
 
 var _ rpc.Dispatcher = (*mountTable)(nil)
@@ -99,8 +100,9 @@
 // statsPrefix is the prefix for for exported statistics objects.
 func NewMountTableDispatcher(aclfile, statsPrefix string) (rpc.Dispatcher, error) {
 	mt := &mountTable{
-		root:        new(node),
-		nodeCounter: stats.NewInteger(naming.Join(statsPrefix, "num-nodes")),
+		root:          new(node),
+		nodeCounter:   stats.NewInteger(naming.Join(statsPrefix, "num-nodes")),
+		serverCounter: stats.NewInteger(naming.Join(statsPrefix, "num-mounted-servers")),
 	}
 	mt.root.parent = mt.newNode() // just for its lock
 	if err := mt.parseAccessLists(aclfile); err != nil && !os.IsNotExist(err) {
@@ -125,19 +127,22 @@
 	if n == nil {
 		return
 	}
-	count := int64(0)
+	nodeCount := int64(0)
+	serverCount := int64(0)
 	queue := []*node{n}
 	for len(queue) > 0 {
-		count++
 		n := queue[0]
 		queue = queue[1:]
+		nodeCount++
+		serverCount += numServers(n)
 		for _, ch := range n.children {
 			ch.Lock() // Keep locked until it is deleted.
 			queue = append(queue, ch)
 		}
 	}
 
-	mt.nodeCounter.Incr(-count)
+	mt.nodeCounter.Incr(-nodeCount)
+	mt.serverCounter.Incr(-serverCount)
 	delete(parent.children, child)
 }
 
@@ -471,6 +476,13 @@
 	return (flags & naming.Replace) == naming.Replace
 }
 
+func numServers(n *node) int64 {
+	if n == nil || n.mount == nil || n.mount.servers == nil {
+		return 0
+	}
+	return int64(n.mount.servers.len())
+}
+
 // Mount a server onto the name in the receiver.
 func (ms *mountContext) Mount(ctx *context.T, call rpc.ServerCall, server string, ttlsecs uint32, flags naming.MountFlag) error {
 	mt := ms.mt
@@ -500,14 +512,10 @@
 	// We don't need the parent lock
 	n.parent.Unlock()
 	defer n.Unlock()
-	if hasReplaceFlag(flags) {
-		n.mount = nil
-	}
+
 	wantMT := hasMTFlag(flags)
 	wantLeaf := hasLeafFlag(flags)
-	if n.mount == nil {
-		n.mount = &mount{servers: newServerList(), mt: wantMT, leaf: wantLeaf}
-	} else {
+	if n.mount != nil {
 		if wantMT != n.mount.mt {
 			return verror.New(errMTDoesntMatch, ctx)
 		}
@@ -515,7 +523,15 @@
 			return verror.New(errLeafDoesntMatch, ctx)
 		}
 	}
+	nServersBefore := numServers(n)
+	if hasReplaceFlag(flags) {
+		n.mount = nil
+	}
+	if n.mount == nil {
+		n.mount = &mount{servers: newServerList(), mt: wantMT, leaf: wantLeaf}
+	}
 	n.mount.servers.add(server, time.Duration(ttlsecs)*time.Second)
+	mt.serverCounter.Incr(numServers(n) - nServersBefore)
 	return nil
 }
 
@@ -581,11 +597,13 @@
 	if n == nil {
 		return nil
 	}
+	nServersBefore := numServers(n)
 	if server == "" {
 		n.mount = nil
 	} else if n.mount != nil && n.mount.servers.remove(server) == 0 {
 		n.mount = nil
 	}
+	mt.serverCounter.Incr(numServers(n) - nServersBefore)
 	removed := n.removeUseless(mt)
 	n.parent.Unlock()
 	n.Unlock()
diff --git a/services/mounttable/mounttablelib/mounttable_test.go b/services/mounttable/mounttablelib/mounttable_test.go
index fdb08c2..88dcd40 100644
--- a/services/mounttable/mounttablelib/mounttable_test.go
+++ b/services/mounttable/mounttablelib/mounttable_test.go
@@ -601,21 +601,31 @@
 	}
 }
 
-func nodeCount(t *testing.T, ctx *context.T, addr string) int64 {
-	st := stats.StatsClient(naming.JoinAddressName(addr, "__debug/stats/mounttable/num-nodes"))
+func getCounter(t *testing.T, ctx *context.T, name string) int64 {
+	st := stats.StatsClient(name)
 	v, err := st.Value(ctx)
 	if err != nil {
-		t.Fatalf("Failed to get mounttable/num-nodes: %v", err)
+		t.Fatalf("Failed to get %q: %v", name, err)
 		return -1
 	}
 	var value int64
 	if err := vdl.Convert(&value, v); err != nil {
-		t.Fatalf("Unexpected value type for mounttable/num-nodes: %v", err)
+		t.Fatalf("Unexpected value type for %q: %v", name, err)
 	}
 	return value
 }
 
-func TestNodeCounter(t *testing.T) {
+func nodeCount(t *testing.T, ctx *context.T, addr string) int64 {
+	name := naming.JoinAddressName(addr, "__debug/stats/mounttable/num-nodes")
+	return getCounter(t, ctx, name)
+}
+
+func serverCount(t *testing.T, ctx *context.T, addr string) int64 {
+	name := naming.JoinAddressName(addr, "__debug/stats/mounttable/num-mounted-servers")
+	return getCounter(t, ctx, name)
+}
+
+func TestStatsCounters(t *testing.T) {
 	rootCtx, shutdown := test.InitForTest()
 	defer shutdown()
 
@@ -630,6 +640,9 @@
 		if expected, got := int64(i+1), nodeCount(t, rootCtx, estr); got != expected {
 			t.Errorf("Unexpected number of nodes. Got %d, expected %d", got, expected)
 		}
+		if expected, got := int64(i), serverCount(t, rootCtx, estr); got != expected {
+			t.Errorf("Unexpected number of servers. Got %d, expected %d", got, expected)
+		}
 	}
 	for i := 1; i <= 10; i++ {
 		name := fmt.Sprintf("node%d", i)
@@ -641,6 +654,9 @@
 		if expected, got := int64(11-i), nodeCount(t, rootCtx, estr); got != expected {
 			t.Errorf("Unexpected number of nodes. Got %d, expected %d", got, expected)
 		}
+		if expected, got := int64(10-i), serverCount(t, rootCtx, estr); got != expected {
+			t.Errorf("Unexpected number of server. Got %d, expected %d", got, expected)
+		}
 	}
 
 	// Test deep tree
@@ -649,14 +665,54 @@
 	if expected, got := int64(13), nodeCount(t, rootCtx, estr); got != expected {
 		t.Errorf("Unexpected number of nodes. Got %d, expected %d", got, expected)
 	}
+	if expected, got := int64(2), serverCount(t, rootCtx, estr); got != expected {
+		t.Errorf("Unexpected number of servers. Got %d, expected %d", got, expected)
+	}
 	doDeleteSubtree(t, rootCtx, estr, "1/2/3/4/5", true)
 	if expected, got := int64(5), nodeCount(t, rootCtx, estr); got != expected {
 		t.Errorf("Unexpected number of nodes. Got %d, expected %d", got, expected)
 	}
+	if expected, got := int64(0), serverCount(t, rootCtx, estr); got != expected {
+		t.Errorf("Unexpected number of servers. Got %d, expected %d", got, expected)
+	}
 	doDeleteSubtree(t, rootCtx, estr, "1", true)
 	if expected, got := int64(1), nodeCount(t, rootCtx, estr); got != expected {
 		t.Errorf("Unexpected number of nodes. Got %d, expected %d", got, expected)
 	}
+
+	// Test multiple servers per node
+	for i := 1; i <= 5; i++ {
+		server := naming.JoinAddressName(estr, fmt.Sprintf("addr%d", i))
+		doMount(t, rootCtx, estr, "node1", server, true)
+		doMount(t, rootCtx, estr, "node2", server, true)
+		if expected, got := int64(3), nodeCount(t, rootCtx, estr); got != expected {
+			t.Errorf("Unexpected number of nodes. Got %d, expected %d", got, expected)
+		}
+		if expected, got := int64(2*i), serverCount(t, rootCtx, estr); got != expected {
+			t.Errorf("Unexpected number of servers. Got %d, expected %d", got, expected)
+		}
+	}
+	doUnmount(t, rootCtx, estr, "node1", "", true)
+	if expected, got := int64(2), nodeCount(t, rootCtx, estr); got != expected {
+		t.Errorf("Unexpected number of nodes. Got %d, expected %d", got, expected)
+	}
+	if expected, got := int64(5), serverCount(t, rootCtx, estr); got != expected {
+		t.Errorf("Unexpected number of servers. Got %d, expected %d", got, expected)
+	}
+	for i := 1; i <= 5; i++ {
+		server := naming.JoinAddressName(estr, fmt.Sprintf("addr%d", i))
+		doUnmount(t, rootCtx, estr, "node2", server, true)
+		expectedNodes := int64(2)
+		if i == 5 {
+			expectedNodes = 1
+		}
+		if expected, got := expectedNodes, nodeCount(t, rootCtx, estr); got != expected {
+			t.Errorf("Unexpected number of nodes. Got %d, expected %d", got, expected)
+		}
+		if expected, got := int64(5-i), serverCount(t, rootCtx, estr); got != expected {
+			t.Errorf("Unexpected number of servers. Got %d, expected %d", got, expected)
+		}
+	}
 }
 
 func initTest() (rootCtx *context.T, aliceCtx *context.T, bobCtx *context.T, shutdown v23.Shutdown) {