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)
}