internal/telemetry: use atomics to get the exporter

We change the main exporter to be stored and fetched using atomics rather
than aquiring a mutex for a mild (but significant in the disabled case) speedup.
Also has the benefit of not holding a global lock over all telemetry operations.

benchstat of logging benchmatks before and after:

name                 old time/op    new time/op    delta
Baseline-8              329ns ± 2%     327ns ± 2%     ~     (p=0.181 n=19+17)
LoggingNoExporter-8    3.08µs ± 3%    2.42µs ± 2%  -21.42%  (p=0.000 n=20+19)
Logging-8              13.7µs ± 2%    13.2µs ± 1%   -3.49%  (p=0.000 n=19+19)
LoggingStdlib-8        5.39µs ± 3%    5.41µs ± 2%     ~     (p=0.177 n=19+20)

This is a replacement for https://go-review.googlesource.com/c/tools/+/212244
but built on the single exporter principle rather than the exporter list.

Change-Id: Icc99319c4357e0bcb63386c64372a733e8a76796
Reviewed-on: https://go-review.googlesource.com/c/tools/+/221218
Run-TryBot: Emmanuel Odeke <emm.odeke@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Emmanuel Odeke <emm.odeke@gmail.com>
diff --git a/internal/lsp/debug/trace.go b/internal/lsp/debug/trace.go
index f71b5a6..a455048 100644
--- a/internal/lsp/debug/trace.go
+++ b/internal/lsp/debug/trace.go
@@ -12,6 +12,7 @@
 	"net/http"
 	"sort"
 	"strings"
+	"sync"
 	"time"
 
 	"golang.org/x/tools/internal/telemetry"
@@ -35,6 +36,7 @@
 `))
 
 type traces struct {
+	mu         sync.Mutex
 	sets       map[string]*traceSet
 	unfinished map[telemetry.SpanContext]*traceData
 }
@@ -71,6 +73,8 @@
 }
 
 func (t *traces) StartSpan(ctx context.Context, span *telemetry.Span) {
+	t.mu.Lock()
+	defer t.mu.Unlock()
 	if t.sets == nil {
 		t.sets = make(map[string]*traceSet)
 		t.unfinished = make(map[telemetry.SpanContext]*traceData)
@@ -99,6 +103,8 @@
 }
 
 func (t *traces) FinishSpan(ctx context.Context, span *telemetry.Span) {
+	t.mu.Lock()
+	defer t.mu.Unlock()
 	// finishing, must be already in the map
 	td, found := t.unfinished[span.ID]
 	if !found {
diff --git a/internal/telemetry/export/export.go b/internal/telemetry/export/export.go
index 00b9ce1..775c8e3 100644
--- a/internal/telemetry/export/export.go
+++ b/internal/telemetry/export/export.go
@@ -10,8 +10,9 @@
 import (
 	"context"
 	"os"
-	"sync"
+	"sync/atomic"
 	"time"
+	"unsafe"
 
 	"golang.org/x/tools/internal/telemetry"
 )
@@ -29,40 +30,42 @@
 }
 
 var (
-	exporterMu sync.Mutex
-	exporter   = LogWriter(os.Stderr, true)
+	exporter unsafe.Pointer
 )
 
+func init() {
+	SetExporter(LogWriter(os.Stderr, true))
+}
+
 func SetExporter(e Exporter) {
-	exporterMu.Lock()
-	defer exporterMu.Unlock()
-	exporter = e
+	p := unsafe.Pointer(&e)
+	if e == nil {
+		p = nil
+	}
+	atomic.StorePointer(&exporter, p)
 }
 
 func StartSpan(ctx context.Context, span *telemetry.Span, at time.Time) {
-	exporterMu.Lock()
-	defer exporterMu.Unlock()
-	if exporter == nil {
+	exporterPtr := (*Exporter)(atomic.LoadPointer(&exporter))
+	if exporterPtr == nil {
 		return
 	}
 	span.Start = at
-	exporter.StartSpan(ctx, span)
+	(*exporterPtr).StartSpan(ctx, span)
 }
 
 func FinishSpan(ctx context.Context, span *telemetry.Span, at time.Time) {
-	exporterMu.Lock()
-	defer exporterMu.Unlock()
-	if exporter == nil {
+	exporterPtr := (*Exporter)(atomic.LoadPointer(&exporter))
+	if exporterPtr == nil {
 		return
 	}
 	span.Finish = at
-	exporter.FinishSpan(ctx, span)
+	(*exporterPtr).FinishSpan(ctx, span)
 }
 
 func Tag(ctx context.Context, at time.Time, tags telemetry.TagList) {
-	exporterMu.Lock()
-	defer exporterMu.Unlock()
-	if exporter == nil {
+	exporterPtr := (*Exporter)(atomic.LoadPointer(&exporter))
+	if exporterPtr == nil {
 		return
 	}
 	// If context has a span we need to add the tags to it
@@ -83,9 +86,8 @@
 }
 
 func Log(ctx context.Context, event telemetry.Event) {
-	exporterMu.Lock()
-	defer exporterMu.Unlock()
-	if exporter == nil {
+	exporterPtr := (*Exporter)(atomic.LoadPointer(&exporter))
+	if exporterPtr == nil {
 		return
 	}
 	// If context has a span we need to add the event to it
@@ -94,14 +96,13 @@
 		span.Events = append(span.Events, event)
 	}
 	// and now also hand the event of to the current observer
-	exporter.Log(ctx, event)
+	(*exporterPtr).Log(ctx, event)
 }
 
 func Metric(ctx context.Context, data telemetry.MetricData) {
-	exporterMu.Lock()
-	defer exporterMu.Unlock()
-	if exporter == nil {
+	exporterPtr := (*Exporter)(atomic.LoadPointer(&exporter))
+	if exporterPtr == nil {
 		return
 	}
-	exporter.Metric(ctx, data)
+	(*exporterPtr).Metric(ctx, data)
 }