//go/tracing/loggingtracer/loggingtracer.go: Log span durations.

Change-Id: If411d8fab4e8daa2dd24edaf5702d903272c415b
Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/610663
Reviewed-by: Joe Gregorio <jcgregorio@google.com>
Auto-Submit: Leandro Lovisolo <lovisolo@google.com>
Commit-Queue: Joe Gregorio <jcgregorio@google.com>
diff --git a/go/tracing/loggingtracer/BUILD.bazel b/go/tracing/loggingtracer/BUILD.bazel
index ec6dc1d..8f2ffdd 100644
--- a/go/tracing/loggingtracer/BUILD.bazel
+++ b/go/tracing/loggingtracer/BUILD.bazel
@@ -7,6 +7,7 @@
     importpath = "go.skia.org/infra/go/tracing/loggingtracer",
     visibility = ["//visibility:public"],
     deps = [
+        "//go/now",
         "//go/sklog",
         "@io_opencensus_go//trace",
     ],
@@ -17,6 +18,7 @@
     srcs = ["loggingtracer_test.go"],
     embed = [":loggingtracer"],
     deps = [
+        "//go/now",
         "@com_github_stretchr_testify//assert",
         "@com_github_stretchr_testify//require",
         "@io_opencensus_go//trace",
diff --git a/go/tracing/loggingtracer/loggingtracer.go b/go/tracing/loggingtracer/loggingtracer.go
index 8de37c5..45bae6f 100644
--- a/go/tracing/loggingtracer/loggingtracer.go
+++ b/go/tracing/loggingtracer/loggingtracer.go
@@ -1,6 +1,6 @@
 // Package loggingtracer provides a trace.Tracer that wraps trace.DefaultTracer and logs span
-// starts/ends. It is intended for local debugging only, as usage in production might cause noisy
-// and/or verbose logs.
+// starts/ends and durations. It is intended for local debugging only, as usage in production might
+// cause noisy and/or verbose logs.
 //
 // To use this logger, call Initialize(). To customize the logger function, override Logf.
 package loggingtracer
@@ -9,8 +9,10 @@
 	"context"
 	"fmt"
 	"runtime"
+	"time"
 
 	"go.opencensus.io/trace"
+	"go.skia.org/infra/go/now"
 	"go.skia.org/infra/go/sklog"
 )
 
@@ -22,7 +24,7 @@
 //
 // This is intended for local debugging only. Usage in production might cuase noisy/verbose logs.
 func Initialize() {
-	trace.DefaultTracer = loggingTracer{actualTracer: trace.DefaultTracer}
+	trace.DefaultTracer = &loggingTracer{actualTracer: trace.DefaultTracer}
 }
 
 // getCaller returns the calling file:line via runtime.Caller.
@@ -40,84 +42,103 @@
 }
 
 // StartSpan implements the trace.Tracer interface.
-func (t loggingTracer) StartSpan(ctx context.Context, name string, o ...trace.StartOption) (context.Context, *trace.Span) {
+func (t *loggingTracer) StartSpan(ctx context.Context, name string, o ...trace.StartOption) (context.Context, *trace.Span) {
 	Logf("Starting span: %s [%s]", name, getCaller(3))
 	ctx, actualSpan := t.actualTracer.StartSpan(ctx, name, o...)
-	return ctx, trace.NewSpan(loggingSpan{actualSpan: actualSpan, name: name})
+	return ctx, trace.NewSpan(&loggingSpan{actualSpan: actualSpan, ctx: ctx, name: name, startTime: now.Now(ctx)})
 }
 
 // StartSpanWithRemoteParent implements the trace.Tracer interface.
-func (t loggingTracer) StartSpanWithRemoteParent(ctx context.Context, name string, parent trace.SpanContext, o ...trace.StartOption) (context.Context, *trace.Span) {
+func (t *loggingTracer) StartSpanWithRemoteParent(ctx context.Context, name string, parent trace.SpanContext, o ...trace.StartOption) (context.Context, *trace.Span) {
 	Logf("Starting span with remote parent: %s [%s]", name, getCaller(3))
 	ctx, actualSpan := t.actualTracer.StartSpanWithRemoteParent(ctx, name, parent, o...)
-	return ctx, trace.NewSpan(loggingSpan{actualSpan: actualSpan, name: name})
+	return ctx, trace.NewSpan(&loggingSpan{actualSpan: actualSpan, ctx: ctx, name: name, startTime: now.Now(ctx)})
 }
 
 // FromContext implements the trace.Tracer interface.
-func (t loggingTracer) FromContext(ctx context.Context) *trace.Span {
+func (t *loggingTracer) FromContext(ctx context.Context) *trace.Span {
 	return t.actualTracer.FromContext(ctx)
 }
 
 // NewContext implements the trace.Tracer interface.
-func (t loggingTracer) NewContext(parent context.Context, s *trace.Span) context.Context {
+func (t *loggingTracer) NewContext(parent context.Context, s *trace.Span) context.Context {
 	return t.actualTracer.NewContext(parent, s)
 }
 
 var _ trace.Tracer = (*loggingTracer)(nil)
 
-// loggingSpan is a trace.SpanInterface that wraps a trace.Span and logs the span end.
+// loggingSpan is a trace.SpanInterface that wraps a trace.Span and logs the span end. It also
+// implements the spanCtxGetterSetter interface so we can get and set its context from tests, which
+// is necessary for time traveling.
 type loggingSpan struct {
 	actualSpan *trace.Span
+	ctx        context.Context
 	name       string
+	startTime  time.Time
 }
 
+// spanCtxGetterSetter allows manipulating the context of a span from tests, which is necessary for
+// time traveling.
+type spanCtxGetterSetter interface {
+	GetCtx() context.Context
+	SetCtx(context.Context)
+}
+
+// GetCtx implements the spanCtxGetterSetter interface.
+func (s *loggingSpan) GetCtx() context.Context { return s.ctx }
+
+// SetCtx implements the spanCtxGetterSetter interface.
+func (s *loggingSpan) SetCtx(ctx context.Context) { s.ctx = ctx }
+
+var _ spanCtxGetterSetter = (*loggingSpan)(nil)
+
 // IsRecordingEvents implements the trace.SpanInterface interface.
-func (s loggingSpan) IsRecordingEvents() bool { return s.actualSpan.IsRecordingEvents() }
+func (s *loggingSpan) IsRecordingEvents() bool { return s.actualSpan.IsRecordingEvents() }
 
 // End implements the trace.SpanInterface interface.
-func (s loggingSpan) End() {
-	Logf("Ending span: %s [%s]", s.name, getCaller(3))
+func (s *loggingSpan) End() {
+	Logf("Ending span: %s [%s] (%s)", s.name, getCaller(3), now.Now(s.ctx).Sub(s.startTime))
 	s.actualSpan.End()
 }
 
 // SpanContext implements the trace.SpanInterface interface.
-func (s loggingSpan) SpanContext() trace.SpanContext { return s.actualSpan.SpanContext() }
+func (s *loggingSpan) SpanContext() trace.SpanContext { return s.actualSpan.SpanContext() }
 
 // SetName implements the trace.SpanInterface interface.
-func (s loggingSpan) SetName(name string) { s.actualSpan.SetName(name) }
+func (s *loggingSpan) SetName(name string) { s.actualSpan.SetName(name) }
 
 // SetStatus implements the trace.SpanInterface interface.
-func (s loggingSpan) SetStatus(status trace.Status) { s.actualSpan.SetStatus(status) }
+func (s *loggingSpan) SetStatus(status trace.Status) { s.actualSpan.SetStatus(status) }
 
 // AddAttributes implements the trace.SpanInterface interface.
-func (s loggingSpan) AddAttributes(attributes ...trace.Attribute) {
+func (s *loggingSpan) AddAttributes(attributes ...trace.Attribute) {
 	s.actualSpan.AddAttributes(attributes...)
 }
 
 // Annotate implements the trace.SpanInterface interface.
-func (s loggingSpan) Annotate(attributes []trace.Attribute, str string) {
+func (s *loggingSpan) Annotate(attributes []trace.Attribute, str string) {
 	s.actualSpan.Annotate(attributes, str)
 }
 
 // Annotatef implements the trace.SpanInterface interface.
-func (s loggingSpan) Annotatef(attributes []trace.Attribute, format string, a ...interface{}) {
+func (s *loggingSpan) Annotatef(attributes []trace.Attribute, format string, a ...interface{}) {
 	s.actualSpan.Annotatef(attributes, format, a...)
 }
 
 // AddMessageSendEvent implements the trace.SpanInterface interface.
-func (s loggingSpan) AddMessageSendEvent(messageID, uncompressedByteSize, compressedByteSize int64) {
+func (s *loggingSpan) AddMessageSendEvent(messageID, uncompressedByteSize, compressedByteSize int64) {
 	s.actualSpan.AddMessageSendEvent(messageID, uncompressedByteSize, compressedByteSize)
 }
 
 // AddMessageReceiveEvent implements the trace.SpanInterface interface.
-func (s loggingSpan) AddMessageReceiveEvent(messageID, uncompressedByteSize, compressedByteSize int64) {
+func (s *loggingSpan) AddMessageReceiveEvent(messageID, uncompressedByteSize, compressedByteSize int64) {
 	s.actualSpan.AddMessageReceiveEvent(messageID, uncompressedByteSize, compressedByteSize)
 }
 
 // AddLink implements the trace.SpanInterface interface.
-func (s loggingSpan) AddLink(l trace.Link) { s.actualSpan.AddLink(l) }
+func (s *loggingSpan) AddLink(l trace.Link) { s.actualSpan.AddLink(l) }
 
 // String implements the trace.SpanInterface interface.
-func (s loggingSpan) String() string { return s.actualSpan.String() }
+func (s *loggingSpan) String() string { return s.actualSpan.String() }
 
 var _ trace.SpanInterface = (*loggingSpan)(nil)
diff --git a/go/tracing/loggingtracer/loggingtracer_test.go b/go/tracing/loggingtracer/loggingtracer_test.go
index 2ec2b09..23b4161 100644
--- a/go/tracing/loggingtracer/loggingtracer_test.go
+++ b/go/tracing/loggingtracer/loggingtracer_test.go
@@ -5,13 +5,17 @@
 	"fmt"
 	"regexp"
 	"testing"
+	"time"
 
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 	"go.opencensus.io/trace"
+	"go.skia.org/infra/go/now"
 )
 
 func TestLoggingTracer(t *testing.T) {
+	fakeNow := time.Date(2023, time.January, 1, 0, 0, 0, 0, time.UTC)
+
 	// Initialize logging tracer.
 	originalTracer := trace.DefaultTracer
 	Initialize()
@@ -25,27 +29,27 @@
 	}
 	defer func() { Logf = originalLogf }()
 
-	ctx := context.Background()
+	ctx := now.TimeTravelingContext(fakeNow)
 	a(ctx)
 
 	// These patterns make this test case fragile (i.e. inserting/deleting lines in this file might
 	// break this test case), but it is important to test that we are reporting the correct line
 	// numbers.
 	expectedLogPatterns := []string{
-		`Starting span: a \[.*/loggingtracer_test.go:65]`,
-		`Starting span: b \[.*/loggingtracer_test.go:72]`,
-		`Starting span: c \[.*/loggingtracer_test.go:79]`,
-		`Starting span: d \[.*/loggingtracer_test.go:85]`,
-		`Ending span: d \[.*/loggingtracer_test.go:87]`,
-		`Ending span: c \[.*/loggingtracer_test.go:82]`,
-		`Starting span: c \[.*/loggingtracer_test.go:79]`,
-		`Starting span: d \[.*/loggingtracer_test.go:85]`,
-		`Ending span: d \[.*/loggingtracer_test.go:87]`,
-		`Ending span: c \[.*/loggingtracer_test.go:82]`,
-		`Ending span: b \[.*/loggingtracer_test.go:76]`,
-		`Starting span: e \[.*/loggingtracer_test.go:90]`,
-		`Ending span: e \[.*/loggingtracer_test.go:92]`,
-		`Ending span: a \[.*/loggingtracer_test.go:69]`,
+		`^Starting span: a \[.*/loggingtracer_test.go:69]$`,
+		`^Starting span: b \[.*/loggingtracer_test.go:77]$`,
+		`^Starting span: c \[.*/loggingtracer_test.go:85]$`,
+		`^Starting span: d \[.*/loggingtracer_test.go:92]$`,
+		`^Ending span: d \[.*/loggingtracer_test.go:94] \(1s\)$`,
+		`^Ending span: c \[.*/loggingtracer_test.go:88] \(3s\)$`,
+		`^Starting span: c \[.*/loggingtracer_test.go:85]$`,
+		`^Starting span: d \[.*/loggingtracer_test.go:92]$`,
+		`^Ending span: d \[.*/loggingtracer_test.go:94] \(1s\)$`,
+		`^Ending span: c \[.*/loggingtracer_test.go:88] \(3s\)$`,
+		`^Ending span: b \[.*/loggingtracer_test.go:81] \(11s\)$`,
+		`^Starting span: e \[.*/loggingtracer_test.go:98]$`,
+		`^Ending span: e \[.*/loggingtracer_test.go:100] \(500ms\)$`,
+		`^Ending span: a \[.*/loggingtracer_test.go:73] \(21.5s\)$`,
 	}
 
 	require.Len(t, logs, len(expectedLogPatterns))
@@ -61,32 +65,44 @@
 	}
 }
 
-func a(ctx context.Context) {
+func a(ctx context.Context) context.Context {
 	ctx, span := trace.StartSpan(ctx, "a")
 	defer span.End()
-	b(ctx)
-	e(ctx)
+	ctx = b(ctx)
+	ctx = e(ctx)
+	return fakeSpanDelay(ctx, span, 10*time.Second)
 }
 
-func b(ctx context.Context) {
+func b(ctx context.Context) context.Context {
 	ctx, span := trace.StartSpan(ctx, "b")
 	defer span.End()
-	c(ctx)
-	c(ctx)
+	ctx = c(ctx)
+	ctx = c(ctx)
+	return fakeSpanDelay(ctx, span, 5*time.Second)
 }
 
-func c(ctx context.Context) {
+func c(ctx context.Context) context.Context {
 	ctx, span := trace.StartSpan(ctx, "c")
 	defer span.End()
-	d(ctx)
+	ctx = d(ctx)
+	return fakeSpanDelay(ctx, span, 2*time.Second)
 }
 
-func d(ctx context.Context) {
+func d(ctx context.Context) context.Context {
 	ctx, span := trace.StartSpan(ctx, "d")
 	defer span.End()
+	return fakeSpanDelay(ctx, span, time.Second)
 }
 
-func e(ctx context.Context) {
+func e(ctx context.Context) context.Context {
 	ctx, span := trace.StartSpan(ctx, "e")
 	defer span.End()
+	return fakeSpanDelay(ctx, span, 500*time.Millisecond)
+}
+
+func fakeSpanDelay(ctx context.Context, span *trace.Span, duration time.Duration) context.Context {
+	ctxGetterSetter := span.Internal().(spanCtxGetterSetter)
+	ctx = context.WithValue(ctxGetterSetter.GetCtx(), now.ContextKey, now.Now(ctx).Add(duration))
+	ctxGetterSetter.SetCtx(ctx)
+	return ctx
 }