veyron/services/mgmt/debug: Export vtrace information in the debug dispatcher.

Change-Id: I6e994deefeb807b2dfa10fafe264a2503d82c577
diff --git a/services/mgmt/debug/dispatcher.go b/services/mgmt/debug/dispatcher.go
index 30a8750..89154c5 100644
--- a/services/mgmt/debug/dispatcher.go
+++ b/services/mgmt/debug/dispatcher.go
@@ -6,22 +6,25 @@
 
 	"veyron.io/veyron/veyron2/ipc"
 	"veyron.io/veyron/veyron2/security"
+	"veyron.io/veyron/veyron2/vtrace"
 
 	logreaderimpl "veyron.io/veyron/veyron/services/mgmt/logreader/impl"
 	pprofimpl "veyron.io/veyron/veyron/services/mgmt/pprof/impl"
 	statsimpl "veyron.io/veyron/veyron/services/mgmt/stats/impl"
+	vtraceimpl "veyron.io/veyron/veyron/services/mgmt/vtrace/impl"
 )
 
 // dispatcher holds the state of the debug dispatcher.
 type dispatcher struct {
 	logsDir string // The root of the logs directory.
 	auth    security.Authorizer
+	store   vtrace.Store
 }
 
 var _ ipc.Dispatcher = (*dispatcher)(nil)
 
-func NewDispatcher(logsDir string, authorizer security.Authorizer) *dispatcher {
-	return &dispatcher{logsDir, authorizer}
+func NewDispatcher(logsDir string, authorizer security.Authorizer, store vtrace.Store) *dispatcher {
+	return &dispatcher{logsDir, authorizer, store}
 }
 
 // The first part of the names of the objects served by this dispatcher.
@@ -41,7 +44,7 @@
 		return NewSignatureInvoker(suffix), d.auth, nil
 	}
 	if suffix == "" {
-		return ipc.VChildrenGlobberInvoker("logs", "pprof", "stats"), d.auth, nil
+		return ipc.VChildrenGlobberInvoker("logs", "pprof", "stats", "vtrace"), d.auth, nil
 	}
 	parts := strings.SplitN(suffix, "/", 2)
 	if len(parts) == 2 {
@@ -59,6 +62,8 @@
 		return pprofimpl.NewInvoker(), d.auth, nil
 	case "stats":
 		return statsimpl.NewStatsInvoker(suffix, 10*time.Second), d.auth, nil
+	case "vtrace":
+		return vtraceimpl.NewVtraceService(d.store), d.auth, nil
 	}
 	return nil, d.auth, nil
 }
diff --git a/services/mgmt/debug/dispatcher_test.go b/services/mgmt/debug/dispatcher_test.go
index 9055d94..2a19ee3 100644
--- a/services/mgmt/debug/dispatcher_test.go
+++ b/services/mgmt/debug/dispatcher_test.go
@@ -2,6 +2,7 @@
 
 import (
 	"fmt"
+	"io"
 	"io/ioutil"
 	"os"
 	"path/filepath"
@@ -17,6 +18,7 @@
 	"veyron.io/veyron/veyron2/rt"
 	"veyron.io/veyron/veyron2/services/mgmt/logreader"
 	"veyron.io/veyron/veyron2/services/mgmt/stats"
+	"veyron.io/veyron/veyron2/services/mgmt/vtrace"
 	"veyron.io/veyron/veyron2/verror"
 
 	libstats "veyron.io/veyron/veyron/lib/stats"
@@ -29,7 +31,7 @@
 	if len(logsDir) == 0 {
 		return "", nil, fmt.Errorf("logs directory missing")
 	}
-	disp := NewDispatcher(logsDir, nil)
+	disp := NewDispatcher(logsDir, nil, rt.VtraceStore())
 	server, err := rt.NewServer()
 	if err != nil {
 		return "", nil, fmt.Errorf("failed to start debug server: %v", err)
@@ -131,6 +133,27 @@
 		}
 	}
 
+	// Access vtrace.
+	{
+		vt := vtrace.StoreClient(naming.JoinAddressName(endpoint, "debug/vtrace"))
+		call, err := vt.AllTraces(runtime.NewContext())
+		if err != nil {
+			t.Errorf("AllTraces failed: %v", err)
+		}
+		ntraces := 0
+		stream := call.RecvStream()
+		for stream.Advance() {
+			stream.Value()
+			ntraces++
+		}
+		if err = stream.Err(); err != nil && err != io.EOF {
+			t.Fatalf("Unexpected error reading trace stream: %s", err)
+		}
+		if ntraces < 1 {
+			t.Errorf("We expected at least one trace, got: %d", ntraces)
+		}
+	}
+
 	// Glob from the root.
 	{
 		ns := rt.R().Namespace()
@@ -183,6 +206,7 @@
 			"logs",
 			"pprof",
 			"stats",
+			"vtrace",
 		}
 		if !reflect.DeepEqual(expected, results) {
 			t.Errorf("unexpected result. Got %v, want %v", results, expected)
@@ -208,6 +232,7 @@
 			"pprof",
 			"stats",
 			"stats/testing/foo",
+			"vtrace",
 		}
 		if !reflect.DeepEqual(expected, results) {
 			t.Errorf("unexpected result. Got %v, want %v", results, expected)
diff --git a/services/mgmt/vtrace/impl/vtrace_invoker.go b/services/mgmt/vtrace/impl/vtrace_invoker.go
new file mode 100644
index 0000000..d9cbe0a
--- /dev/null
+++ b/services/mgmt/vtrace/impl/vtrace_invoker.go
@@ -0,0 +1,40 @@
+package impl
+
+import (
+	"veyron.io/veyron/veyron2/ipc"
+	"veyron.io/veyron/veyron2/uniqueid"
+	"veyron.io/veyron/veyron2/verror2"
+	"veyron.io/veyron/veyron2/vtrace"
+)
+
+type vtraceServer struct {
+	store vtrace.Store
+}
+
+func (v *vtraceServer) Trace(call ipc.ServerCall, id uniqueid.ID) (vtrace.TraceRecord, error) {
+	tr := v.store.TraceRecord(id)
+	if tr == nil {
+		return vtrace.TraceRecord{}, verror2.Make(verror2.NoExist, call, "No trace with id %x", id)
+	}
+	return *tr, nil
+}
+
+func (v *vtraceServer) AllTraces(call ipc.ServerCall) error {
+	// TODO(mattr): Consider changing the store to allow us to iterate through traces
+	// when there are many.
+	traces := v.store.TraceRecords()
+	for i := range traces {
+		if err := call.Send(traces[i]); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func NewVtraceInvoker(store vtrace.Store) ipc.Invoker {
+	return ipc.ReflectInvoker(&vtraceServer{store})
+}
+
+func NewVtraceService(store vtrace.Store) interface{} {
+	return &vtraceServer{store}
+}
diff --git a/services/mgmt/vtrace/impl/vtrace_invoker_test.go b/services/mgmt/vtrace/impl/vtrace_invoker_test.go
new file mode 100644
index 0000000..c967d79
--- /dev/null
+++ b/services/mgmt/vtrace/impl/vtrace_invoker_test.go
@@ -0,0 +1,90 @@
+package impl_test
+
+import (
+	"io"
+	"testing"
+
+	"veyron.io/veyron/veyron2"
+	"veyron.io/veyron/veyron2/ipc"
+	"veyron.io/veyron/veyron2/naming"
+	"veyron.io/veyron/veyron2/rt"
+	service "veyron.io/veyron/veyron2/services/mgmt/vtrace"
+	"veyron.io/veyron/veyron2/vtrace"
+
+	"veyron.io/veyron/veyron/profiles"
+	"veyron.io/veyron/veyron/services/mgmt/vtrace/impl"
+)
+
+func setup(t *testing.T) (string, ipc.Server, veyron2.Runtime) {
+	runtime, err := rt.New()
+	if err != nil {
+		t.Fatalf("Could not create runtime: %s", err)
+	}
+
+	server, err := runtime.NewServer()
+	if err != nil {
+		t.Fatalf("Could not create server: %s", err)
+	}
+	endpoint, err := server.Listen(profiles.LocalListenSpec)
+	if err != nil {
+		t.Fatalf("Listen failed: %s", err)
+	}
+	if err := server.Serve("", impl.NewVtraceService(runtime.VtraceStore()), nil); err != nil {
+		t.Fatalf("Serve failed: %s", err)
+	}
+	return endpoint.String(), server, runtime
+}
+
+func TestVtraceInvoker(t *testing.T) {
+	endpoint, server, runtime := setup(t)
+	defer server.Stop()
+
+	sctx := runtime.NewContext()
+	sctx, span := runtime.WithNewSpan(sctx, "The Span")
+	span.Trace().ForceCollect()
+	span.Finish()
+	id := span.Trace().ID()
+
+	client := service.StoreClient(naming.JoinAddressName(endpoint, ""))
+
+	trace, err := client.Trace(runtime.NewContext(), id)
+	if err != nil {
+		t.Fatalf("Unexpected error getting trace: %s", err)
+	}
+	if len(trace.Spans) != 1 {
+		t.Errorf("Returned trace should have 1 span, found %#v", trace)
+	}
+	if trace.Spans[0].Name != "The Span" {
+		t.Errorf("Returned span has wrong name: %#v", trace)
+	}
+
+	call, err := client.AllTraces(runtime.NewContext())
+	if err != nil {
+		t.Fatalf("Unexpected error getting traces: %s", err)
+	}
+	ntraces := 0
+	stream := call.RecvStream()
+	var tr *vtrace.TraceRecord
+	for stream.Advance() {
+		trace := stream.Value()
+		if trace.ID == id {
+			tr = &trace
+		}
+		ntraces++
+	}
+	if err = stream.Err(); err != nil && err != io.EOF {
+		t.Fatalf("Unexpected error reading trace stream: %s", err)
+	}
+	if ntraces < 2 {
+		t.Fatalf("Expected at least 2 traces, got %#v", ntraces)
+	}
+	if tr == nil {
+		t.Fatalf("Desired trace %x not found.", id)
+	}
+	if len(tr.Spans) != 1 {
+		t.Errorf("Returned trace should have 1 span, found %#v", tr)
+	}
+	if tr.Spans[0].Name != "The Span" {
+		t.Fatalf("Returned span has wrong name: %#v", tr)
+	}
+}