Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 23 additions & 3 deletions present/path.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"strconv"
"strings"
"sync"
"time"

"github.com/spacemonkeygo/monkit/v3"
"github.com/spacemonkeygo/monkit/v3/collect"
Expand Down Expand Up @@ -73,6 +74,11 @@ func curry(reg *monkit.Registry,
// trace id will start a trace until the triggering Span ends,
// provided the regex matches. NOTE: the trace_id will be parsed
// in hex.
// - min_duration - Optional. If provided, only traces where the matched span
// has a duration >= this value will be returned. Shorter
// traces will be discarded and the wait will continue.
// Format: Go duration string (e.g., "2s", "500ms", "1m30s").
// Only supported for /trace/svg and /trace/json endpoints.
//
// By default, regular expressions are matched ahead of time against all known
// Funcs, but perhaps the Func you want to trace hasn't been observed by the
Expand Down Expand Up @@ -193,14 +199,28 @@ func FromRequest(reg *monkit.Registry, path string, query url.Values) (
}
}

var minDuration time.Duration
if minDurationStr := query.Get("min_duration"); minDurationStr != "" {
var err error
minDuration, err = time.ParseDuration(minDurationStr)
if err != nil {
return nil, "", errBadRequest.New("invalid min_duration %#v: %v",
minDurationStr, err)
}
if minDuration < 0 {
return nil, "", errBadRequest.New("min_duration must be non-negative: %v",
minDuration)
}
}

switch second {
case "svg":
return func(w io.Writer) error {
return TraceQuerySVG(reg, w, spanMatcher)
return TraceQuerySVG(reg, w, spanMatcher, minDuration)
}, "image/svg+xml; charset=utf-8", nil
case "json":
return func(w io.Writer) error {
return TraceQueryJSON(reg, w, spanMatcher)
return TraceQueryJSON(reg, w, spanMatcher, minDuration)
}, "application/json; charset=utf-8", nil
case "remote":
viz := query.Get("viz")
Expand Down Expand Up @@ -316,7 +336,7 @@ func writeIndex(w io.Writer) error {

<dt><a href="trace/json">/trace/json</a></dt>
<dt><a href="trace/svg">/trace/svg</a></dt>
<dd>Trace the next scope that matches one of the <code>?regex=</code> or <code>?trace_id=</code> query arguments. By default, regular expressions are matched ahead of time against all known Funcs, but perhaps the Func you want to trace hasn't been observed by the process yet, in which case the regex will fail to match anything. You can turn off this preselection behavior by providing <code>&preselect=false</code> as an additional query param. Be advised that until a trace completes, whether or not it has started, it adds a small amount of overhead (a comparison or two) to every monitored function.</dd>
<dd>Trace the next scope that matches one of the <code>?regex=</code> or <code>?trace_id=</code> query arguments. By default, regular expressions are matched ahead of time against all known Funcs, but perhaps the Func you want to trace hasn't been observed by the process yet, in which case the regex will fail to match anything. You can turn off this preselection behavior by providing <code>&preselect=false</code> as an additional query param. Optionally, use <code>&min_duration=</code> (e.g., "2s", "500ms") to filter for traces that exceed a minimum duration. Be advised that until a trace completes, whether or not it has started, it adds a small amount of overhead (a comparison or two) to every monitored function.</dd>
</dl>
</body>
</html>`))
Expand Down
54 changes: 48 additions & 6 deletions present/trace.go
Original file line number Diff line number Diff line change
Expand Up @@ -391,9 +391,18 @@ func unwrapError(err error) error {
// TraceQuerySVG uses WatchForSpans to write all Spans from 'reg' matching
// 'matcher' to 'w' in SVG format.
func TraceQuerySVG(reg *monkit.Registry, w io.Writer,
matcher func(*monkit.Span) bool) error {
spans, err := watchForSpansWithKeepalive(context.TODO(),
reg, w, matcher, []byte("\n"))
matcher func(*monkit.Span) bool, minDuration time.Duration) error {
var spans []*collect.FinishedSpan
var err error

if minDuration > 0 {
spans, err = watchForSpansWithMinDuration(
context.TODO(), reg, w, matcher, minDuration, []byte("\n"))
} else {
spans, err = watchForSpansWithKeepalive(
context.TODO(), reg, w, matcher, []byte("\n"))
}

if err != nil {
return err
}
Expand All @@ -405,10 +414,19 @@ func TraceQuerySVG(reg *monkit.Registry, w io.Writer,
// TraceQueryJSON uses WatchForSpans to write all Spans from 'reg' matching
// 'matcher' to 'w' in JSON format.
func TraceQueryJSON(reg *monkit.Registry, w io.Writer,
matcher func(*monkit.Span) bool) (write_err error) {
matcher func(*monkit.Span) bool, minDuration time.Duration) (write_err error) {

var spans []*collect.FinishedSpan
var err error

if minDuration > 0 {
spans, err = watchForSpansWithMinDuration(
context.TODO(), reg, w, matcher, minDuration, []byte("\n"))
} else {
spans, err = watchForSpansWithKeepalive(
context.TODO(), reg, w, matcher, []byte("\n"))
}

spans, err := watchForSpansWithKeepalive(context.TODO(),
reg, w, matcher, []byte("\n"))
if err != nil {
return err
}
Expand All @@ -425,6 +443,30 @@ func SpansToJSON(w io.Writer, spans []*collect.FinishedSpan) error {
return lw.done()
}

func watchForSpansWithMinDuration(ctx context.Context, reg *monkit.Registry, w io.Writer,
matcher func(s *monkit.Span) bool, minDuration time.Duration, keepalive []byte) (
spans []*collect.FinishedSpan, err error) {

for {
spans, err = watchForSpansWithKeepalive(ctx, reg, w, matcher, keepalive)
if err != nil {
return nil, err
}
if len(spans) == 0 {
continue
}

// Check root span duration
rootSpan := spans[0]
duration := rootSpan.Finish.Sub(rootSpan.Span.Start())
if duration >= minDuration {
return spans, nil
}

// Duration threshold not met, continue watching for another trace
}
}

func watchForSpansWithKeepalive(ctx context.Context, reg *monkit.Registry, w io.Writer,
matcher func(s *monkit.Span) bool, keepalive []byte) (
spans []*collect.FinishedSpan, writeErr error) {
Expand Down