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