veyron/services/mgmt/debug: Export vtrace information in the debug dispatcher.
Change-Id: I6e994deefeb807b2dfa10fafe264a2503d82c577
diff --git a/profiles/roaming/roaming.go b/profiles/roaming/roaming.go
index 4aa190f..33a1c20 100644
--- a/profiles/roaming/roaming.go
+++ b/profiles/roaming/roaming.go
@@ -72,7 +72,7 @@
func (p *profile) Init(rt veyron2.Runtime, publisher *config.Publisher) error {
log := rt.Logger()
- rt.ConfigureReservedName(debug.NewDispatcher(log.LogDir(), sflag.NewAuthorizerOrDie()))
+ rt.ConfigureReservedName(debug.NewDispatcher(log.LogDir(), sflag.NewAuthorizerOrDie(), rt.VtraceStore()))
lf := commonFlags.ListenFlags()
ListenSpec = ipc.ListenSpec{
diff --git a/profiles/static/static.go b/profiles/static/static.go
index 87b1780..7ff75b1 100644
--- a/profiles/static/static.go
+++ b/profiles/static/static.go
@@ -58,7 +58,7 @@
func (p *static) Init(rt veyron2.Runtime, _ *config.Publisher) error {
log := rt.Logger()
- rt.ConfigureReservedName(debug.NewDispatcher(log.LogDir(), sflag.NewAuthorizerOrDie()))
+ rt.ConfigureReservedName(debug.NewDispatcher(log.LogDir(), sflag.NewAuthorizerOrDie(), rt.VtraceStore()))
lf := commonFlags.ListenFlags()
ListenSpec = ipc.ListenSpec{
diff --git a/runtimes/google/ipc/debug_test.go b/runtimes/google/ipc/debug_test.go
index c9fff21..c537fec 100644
--- a/runtimes/google/ipc/debug_test.go
+++ b/runtimes/google/ipc/debug_test.go
@@ -16,6 +16,7 @@
"veyron.io/veyron/veyron/runtimes/google/ipc/stream/sectest"
"veyron.io/veyron/veyron/runtimes/google/ipc/stream/vc"
tnaming "veyron.io/veyron/veyron/runtimes/google/testing/mocks/naming"
+ "veyron.io/veyron/veyron/runtimes/google/vtrace"
"veyron.io/veyron/veyron/services/mgmt/debug"
)
@@ -30,7 +31,8 @@
pclient.AddToRoots(bclient) // Client recognizes "server" as a root of blessings.
pclient.BlessingStore().Set(bclient, "server") // Client presents bclient to server
- debugDisp := debug.NewDispatcher(vlog.Log.LogDir(), nil)
+ store := vtrace.NewStore(10)
+ debugDisp := debug.NewDispatcher(vlog.Log.LogDir(), nil, store)
sm := manager.InternalNew(naming.FixedRoutingID(0x555555555))
defer sm.Shutdown()
@@ -94,8 +96,8 @@
}{
{"", "*", []string{}},
{"", "__*", []string{"__debug"}},
- {"", "__*/*", []string{"__debug/logs", "__debug/pprof", "__debug/stats"}},
- {"__debug", "*", []string{"logs", "pprof", "stats"}},
+ {"", "__*/*", []string{"__debug/logs", "__debug/pprof", "__debug/stats", "__debug/vtrace"}},
+ {"__debug", "*", []string{"logs", "pprof", "stats", "vtrace"}},
}
for _, tc := range testcases {
addr := naming.JoinAddressName(ep.String(), "//"+tc.name)
diff --git a/runtimes/google/rt/rt.go b/runtimes/google/rt/rt.go
index 2cff75d..4b02edf 100644
--- a/runtimes/google/rt/rt.go
+++ b/runtimes/google/rt/rt.go
@@ -158,6 +158,10 @@
copy(rt.reservedOpts, opts)
}
+func (rt *vrt) VtraceStore() vtrace.Store {
+ return rt.traceStore
+}
+
func (rt *vrt) Cleanup() {
if rt.flags.Vtrace.DumpOnShutdown {
vtrace.FormatTraces(os.Stderr, rt.traceStore.TraceRecords(), nil)
diff --git a/runtimes/google/testing/mocks/runtime/panic_runtime.go b/runtimes/google/testing/mocks/runtime/panic_runtime.go
index f5c7c2c..1c28e1d 100644
--- a/runtimes/google/testing/mocks/runtime/panic_runtime.go
+++ b/runtimes/google/testing/mocks/runtime/panic_runtime.go
@@ -48,4 +48,5 @@
func (*PanicRuntime) ConfigureReservedName(ipc.Dispatcher, ...ipc.ServerOpt) {
panic(badRuntime)
}
-func (*PanicRuntime) Cleanup() { panic(badRuntime) }
+func (*PanicRuntime) VtraceStore() vtrace.Store { panic(badRuntime) }
+func (*PanicRuntime) Cleanup() { panic(badRuntime) }
diff --git a/runtimes/google/vtrace/collector.go b/runtimes/google/vtrace/collector.go
index 6c883cc..fc06731 100644
--- a/runtimes/google/vtrace/collector.go
+++ b/runtimes/google/vtrace/collector.go
@@ -27,10 +27,9 @@
type collector struct {
traceID uniqueid.ID
store *Store
-
- mu sync.Mutex
- method vtrace.TraceMethod // GUARDED_BY(mu)
- spans map[uniqueid.ID]*vtrace.SpanRecord // GUARDED_BY(mu)
+ mu sync.Mutex
+ method vtrace.TraceMethod // GUARDED_BY(mu)
+ spans map[uniqueid.ID]*vtrace.SpanRecord // GUARDED_BY(mu)
}
// newCollector returns a new collector for the given traceID.
diff --git a/runtimes/google/vtrace/store.go b/runtimes/google/vtrace/store.go
index 182dea5..f70dc2a 100644
--- a/runtimes/google/vtrace/store.go
+++ b/runtimes/google/vtrace/store.go
@@ -58,20 +58,20 @@
}
// TraceRecords returns TraceRecords for all traces saved in the store.
-func (s *Store) TraceRecords() []*vtrace.TraceRecord {
+func (s *Store) TraceRecords() []vtrace.TraceRecord {
s.mu.Lock()
defer s.mu.Unlock()
- out := make([]*vtrace.TraceRecord, s.size)
+ out := make([]vtrace.TraceRecord, s.size)
i := 0
for _, ts := range s.traces {
- out[i] = ts.traceRecord()
+ ts.traceRecord(&out[i])
i++
}
return out
}
-// TraceRecord returns a TraceRecord for a given uniqueid. Returns
+// TraceRecord returns a TraceRecord for a given ID. Returns
// nil if the given id is not present.
func (s *Store) TraceRecord(id uniqueid.ID) *vtrace.TraceRecord {
s.mu.Lock()
@@ -80,7 +80,9 @@
if ts == nil {
return nil
}
- return ts.traceRecord()
+ out := vtrace.TraceRecord{}
+ ts.traceRecord(&out)
+ return &out
}
// trimLocked removes elements from the store LRU first until we are
@@ -135,9 +137,7 @@
panic("unreachable")
}
-func (ts *traceSet) traceRecord() *vtrace.TraceRecord {
- var out vtrace.TraceRecord
-
+func (ts *traceSet) traceRecord(out *vtrace.TraceRecord) {
// It is possible to have duplicate copies of spans. Consider the
// case where a server calls itself (even indirectly) we'll have one
// Trace in the set for the parent call and one Trace in the set for
@@ -156,5 +156,4 @@
out.Spans = append(out.Spans, span)
}
}
- return &out
}
diff --git a/runtimes/google/vtrace/store_test.go b/runtimes/google/vtrace/store_test.go
index 9fdc06b..910dd07 100644
--- a/runtimes/google/vtrace/store_test.go
+++ b/runtimes/google/vtrace/store_test.go
@@ -27,7 +27,7 @@
return traces
}
-func recordids(records ...*vtrace.TraceRecord) map[uniqueid.ID]bool {
+func recordids(records ...vtrace.TraceRecord) map[uniqueid.ID]bool {
out := make(map[uniqueid.ID]bool)
for _, trace := range records {
out[trace.ID] = true
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)
+ }
+}
diff --git a/tools/debug/test.sh b/tools/debug/test.sh
index 4c1471a..b3078b0 100755
--- a/tools/debug/test.sh
+++ b/tools/debug/test.sh
@@ -39,7 +39,8 @@
|| (dumplogs "${DBGLOG}"; shell_test::fail "line ${LINENO}: failed to run debug")
WANT="${EP}/__debug/logs
${EP}/__debug/pprof
-${EP}/__debug/stats"
+${EP}/__debug/stats
+${EP}/__debug/vtrace"
shell_test::assert_eq "${GOT}" "${WANT}" "${LINENO}"
# Test logs glob.