veyron/runtimes/google/vtrace: Add capturing via regexps to vtrace.
This CL makes VTrace easier to use.
- Add a flag that specifies a regexp pattern so you can select
traces by span name or annotation.
- Turn dump_on_shutdown on by default.
Now you can run vtrace with just:
--veyron.vtrace.collect_regexp=.*
Change-Id: I9b3692aa9871e466aa3f5c0fbeaa4719e843d9e1
diff --git a/lib/flags/flags.go b/lib/flags/flags.go
index 929d32f..634e688 100644
--- a/lib/flags/flags.go
+++ b/lib/flags/flags.go
@@ -17,6 +17,10 @@
// used by the Vanadium process runtime. Namely:
// --veyron.namespace.root (which may be repeated to supply multiple values)
// --veyron.credentials
+ // --veyron.vtrace.sample_rate
+ // --veyron.vtrace.dump_on_shutdown
+ // --veyron.vtrace.cache_size
+ // --veyron.vtrace.collect_regexp
Runtime FlagGroup = iota
// Listen identifies the flags typically required to configure
// ipc.ListenSpec. Namely:
@@ -124,6 +128,10 @@
// TODO(mattr): Traces can be of widely varying size, we should have
// some better measurement then just number of traces.
CacheSize int
+
+ // SpanRegexp matches a regular expression against span names and
+ // annotations and forces any trace matching trace to be collected.
+ CollectRegexp string
}
// ACLFlags contains the values of the ACLFlags flag group.
@@ -214,8 +222,9 @@
fs.StringVar(&f.I18nCatalogue, "vanadium.i18n_catalogue", i18nCatalogue, "18n catalogue files to load, comma separated")
fs.Float64Var(&f.Vtrace.SampleRate, "veyron.vtrace.sample_rate", 0.0, "Rate (from 0.0 to 1.0) to sample vtrace traces.")
- fs.BoolVar(&f.Vtrace.DumpOnShutdown, "veyron.vtrace.dump_on_shutdown", false, "If true, dump all stored traces on runtime shutdown.")
+ fs.BoolVar(&f.Vtrace.DumpOnShutdown, "veyron.vtrace.dump_on_shutdown", true, "If true, dump all stored traces on runtime shutdown.")
fs.IntVar(&f.Vtrace.CacheSize, "veyron.vtrace.cache_size", 1024, "The number of vtrace traces to store in memory.")
+ fs.StringVar(&f.Vtrace.CollectRegexp, "veyron.vtrace.collect_regexp", "", "Spans and annotations that match this regular expression will trigger trace collection.")
return f
}
diff --git a/runtimes/google/ipc/full_test.go b/runtimes/google/ipc/full_test.go
index cbb9a69..55d4bcb 100644
--- a/runtimes/google/ipc/full_test.go
+++ b/runtimes/google/ipc/full_test.go
@@ -89,7 +89,10 @@
func testContextWithoutDeadline() *context.T {
var ctx *context.T
- ctx = ivtrace.Init(ctx, flags.VtraceFlags{})
+ ctx, err := ivtrace.Init(ctx, flags.VtraceFlags{})
+ if err != nil {
+ panic(err)
+ }
ctx, _ = vtrace.SetNewTrace(ctx)
return ctx
}
diff --git a/runtimes/google/lib/publisher/publisher_test.go b/runtimes/google/lib/publisher/publisher_test.go
index fadbf29..7169025 100644
--- a/runtimes/google/lib/publisher/publisher_test.go
+++ b/runtimes/google/lib/publisher/publisher_test.go
@@ -18,7 +18,10 @@
func testContext() *context.T {
var ctx *context.T
- ctx = ivtrace.Init(ctx, flags.VtraceFlags{})
+ ctx, err := ivtrace.Init(ctx, flags.VtraceFlags{})
+ if err != nil {
+ panic(err)
+ }
ctx, _ = vtrace.SetNewSpan(ctx, "")
ctx, _ = context.WithDeadline(ctx, time.Now().Add(20*time.Second))
return ctx
diff --git a/runtimes/google/rt/rt.go b/runtimes/google/rt/rt.go
index 86ab5ac..f38deb6 100644
--- a/runtimes/google/rt/rt.go
+++ b/runtimes/google/rt/rt.go
@@ -83,12 +83,19 @@
}
runtimeFlags.Parse(os.Args[1:], config)
})
+
flags := runtimeFlags.RuntimeFlags()
+
+ traceStore, err := ivtrace.NewStore(flags.Vtrace)
+ if err != nil {
+ return nil, fmt.Errorf("Could not initialize VTrace: %v", err)
+ }
+
rt := &vrt{
lang: i18n.LangIDFromEnv(),
program: filepath.Base(os.Args[0]),
flags: flags,
- traceStore: ivtrace.NewStore(flags.Vtrace),
+ traceStore: traceStore,
}
for _, o := range opts {
diff --git a/runtimes/google/vtrace/store.go b/runtimes/google/vtrace/store.go
index 682b532..b6ed2de 100644
--- a/runtimes/google/vtrace/store.go
+++ b/runtimes/google/vtrace/store.go
@@ -2,6 +2,7 @@
import (
"math/rand"
+ "regexp"
"sync"
"time"
@@ -22,7 +23,8 @@
// specifically tell us to capture a specific trace. LRU will work OK
// for many testing scenarios and low volume applications.
type Store struct {
- opts flags.VtraceFlags
+ opts flags.VtraceFlags
+ collectRegexp *regexp.Regexp
// traces and head together implement a linked-hash-map.
// head points to the head and tail of the doubly-linked-list
@@ -34,15 +36,24 @@
}
// NewStore creates a new store according to the passed in opts.
-func NewStore(opts flags.VtraceFlags) *Store {
+func NewStore(opts flags.VtraceFlags) (*Store, error) {
head := &traceStore{}
head.next, head.prev = head, head
- return &Store{
- opts: opts,
- traces: make(map[uniqueid.ID]*traceStore),
- head: head,
+ var collectRegexp *regexp.Regexp
+ if opts.CollectRegexp != "" {
+ var err error
+ if collectRegexp, err = regexp.Compile(opts.CollectRegexp); err != nil {
+ return nil, err
+ }
}
+
+ return &Store{
+ opts: opts,
+ collectRegexp: collectRegexp,
+ traces: make(map[uniqueid.ID]*traceStore),
+ head: head,
+ }, nil
}
func (s *Store) ForceCollect(id uniqueid.ID) {
@@ -87,7 +98,14 @@
func (s *Store) annotate(span *span, msg string) {
s.mu.Lock()
defer s.mu.Unlock()
- if ts := s.traces[span.Trace()]; ts != nil {
+ ts := s.traces[span.trace]
+ if ts == nil {
+ if s.collectRegexp != nil && s.collectRegexp.MatchString(msg) {
+ ts = s.forceCollectLocked(span.trace)
+ }
+ }
+
+ if ts != nil {
ts.annotate(span, msg)
ts.moveAfter(s.head)
}
@@ -98,13 +116,16 @@
s.mu.Lock()
defer s.mu.Unlock()
- var ts *traceStore
- sr := s.opts.SampleRate
- // If this is a root span, we may automatically sample it for collection.
- if span.trace == span.parent && sr > 0.0 && (sr >= 1.0 || rand.Float64() < sr) {
- ts = s.forceCollectLocked(span.Trace())
- } else {
- ts = s.traces[span.Trace()]
+ ts := s.traces[span.trace]
+ if ts == nil {
+ sr := s.opts.SampleRate
+ if span.trace == span.parent && sr > 0.0 && (sr >= 1.0 || rand.Float64() < sr) {
+ // If this is a root span, we may automatically sample it for collection.
+ ts = s.forceCollectLocked(span.trace)
+ } else if s.collectRegexp != nil && s.collectRegexp.MatchString(span.name) {
+ // If this span matches collectRegexp, then force collect its trace.
+ ts = s.forceCollectLocked(span.trace)
+ }
}
if ts != nil {
ts.start(span)
@@ -116,7 +137,7 @@
func (s *Store) finish(span *span) {
s.mu.Lock()
defer s.mu.Unlock()
- if ts := s.traces[span.Trace()]; ts != nil {
+ if ts := s.traces[span.trace]; ts != nil {
ts.finish(span)
ts.moveAfter(s.head)
}
diff --git a/runtimes/google/vtrace/store_test.go b/runtimes/google/vtrace/store_test.go
index 3aac93a..c24d120 100644
--- a/runtimes/google/vtrace/store_test.go
+++ b/runtimes/google/vtrace/store_test.go
@@ -64,7 +64,10 @@
}
func TestTrimming(t *testing.T) {
- st := NewStore(flags.VtraceFlags{CacheSize: 5})
+ st, err := NewStore(flags.VtraceFlags{CacheSize: 5})
+ if err != nil {
+ t.Fatalf("Could not create store: %v", err)
+ }
traces := makeTraces(10, st)
compare(t, traceids(traces[5:]...), st.TraceRecords())
@@ -87,3 +90,35 @@
st.ForceCollect(traces[12])
compare(t, traceids(traces[10], traces[11], traces[12], traces[7], traces[9]), st.TraceRecords())
}
+
+func TestRegexp(t *testing.T) {
+ traces := []uniqueid.ID{id(), id(), id()}
+
+ type testcase struct {
+ pattern string
+ results []uniqueid.ID
+ }
+ tests := []testcase{
+ {".*", traces},
+ {"foo.*", traces},
+ {".*bar", traces[1:2]},
+ {".*bang", traces[2:3]},
+ }
+
+ for _, test := range tests {
+ st, err := NewStore(flags.VtraceFlags{
+ CacheSize: 10,
+ CollectRegexp: test.pattern,
+ })
+ if err != nil {
+ t.Fatalf("Could not create store: %v", err)
+ }
+
+ newSpan(traces[0], "foo", traces[0], st)
+ newSpan(traces[1], "foobar", traces[1], st)
+ sp := newSpan(traces[2], "baz", traces[2], st)
+ sp.Annotate("foobang")
+
+ compare(t, traceids(test.results...), st.TraceRecords())
+ }
+}
diff --git a/runtimes/google/vtrace/vtrace.go b/runtimes/google/vtrace/vtrace.go
index 5eef4c7..a9adefd 100644
--- a/runtimes/google/vtrace/vtrace.go
+++ b/runtimes/google/vtrace/vtrace.go
@@ -163,11 +163,14 @@
}
// Init initializes vtrace and attaches some state to the context.
-// This should be called by
-func Init(ctx *context.T, opts flags.VtraceFlags) *context.T {
- ctx = vtrace.WithManager(ctx, manager{})
- ctx = context.WithValue(ctx, storeKey, NewStore(opts))
- return ctx
+// This should be called by the runtimes initialization function.
+func Init(ctx *context.T, opts flags.VtraceFlags) (*context.T, error) {
+ nctx := vtrace.WithManager(ctx, manager{})
+ store, err := NewStore(opts)
+ if err != nil {
+ return ctx, err
+ }
+ return context.WithValue(nctx, storeKey, store), nil
}
// TODO(mattr): Remove this function once the old Runtime type is deprecated.
diff --git a/runtimes/google/vtrace/vtrace_test.go b/runtimes/google/vtrace/vtrace_test.go
index 659ed1f..7e12d89 100644
--- a/runtimes/google/vtrace/vtrace_test.go
+++ b/runtimes/google/vtrace/vtrace_test.go
@@ -27,7 +27,11 @@
// implementation should not ever use the Runtime from a context.
func testContext() *context.T {
var ctx *context.T
- return ivtrace.Init(ctx, flags.VtraceFlags{CacheSize: 100})
+ ctx, err := ivtrace.Init(ctx, flags.VtraceFlags{CacheSize: 100})
+ if err != nil {
+ panic(err)
+ }
+ return ctx
}
func TestNewFromContext(t *testing.T) {