whatcanGOwrong

This commit is contained in:
2024-09-19 21:38:24 -04:00
commit d0ae4d841d
17908 changed files with 4096831 additions and 0 deletions
@@ -0,0 +1,158 @@
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package event_test
import (
"context"
"io"
"log"
"testing"
"golang.org/x/tools/internal/event"
"golang.org/x/tools/internal/event/core"
"golang.org/x/tools/internal/event/export"
"golang.org/x/tools/internal/event/keys"
"golang.org/x/tools/internal/event/label"
)
type Hooks struct {
A func(ctx context.Context, a int) (context.Context, func())
B func(ctx context.Context, b string) (context.Context, func())
}
var (
aValue = keys.NewInt("a", "")
bValue = keys.NewString("b", "")
aCount = keys.NewInt64("aCount", "Count of time A is called.")
aStat = keys.NewInt("aValue", "A value.")
bCount = keys.NewInt64("B", "Count of time B is called.")
bLength = keys.NewInt("BLen", "B length.")
Baseline = Hooks{
A: func(ctx context.Context, a int) (context.Context, func()) {
return ctx, func() {}
},
B: func(ctx context.Context, b string) (context.Context, func()) {
return ctx, func() {}
},
}
StdLog = Hooks{
A: func(ctx context.Context, a int) (context.Context, func()) {
log.Printf("A where a=%d", a)
return ctx, func() {}
},
B: func(ctx context.Context, b string) (context.Context, func()) {
log.Printf("B where b=%q", b)
return ctx, func() {}
},
}
Log = Hooks{
A: func(ctx context.Context, a int) (context.Context, func()) {
core.Log1(ctx, "A", aValue.Of(a))
return ctx, func() {}
},
B: func(ctx context.Context, b string) (context.Context, func()) {
core.Log1(ctx, "B", bValue.Of(b))
return ctx, func() {}
},
}
Trace = Hooks{
A: func(ctx context.Context, a int) (context.Context, func()) {
return core.Start1(ctx, "A", aValue.Of(a))
},
B: func(ctx context.Context, b string) (context.Context, func()) {
return core.Start1(ctx, "B", bValue.Of(b))
},
}
Stats = Hooks{
A: func(ctx context.Context, a int) (context.Context, func()) {
core.Metric1(ctx, aStat.Of(a))
core.Metric1(ctx, aCount.Of(1))
return ctx, func() {}
},
B: func(ctx context.Context, b string) (context.Context, func()) {
core.Metric1(ctx, bLength.Of(len(b)))
core.Metric1(ctx, bCount.Of(1))
return ctx, func() {}
},
}
initialList = []int{0, 1, 22, 333, 4444, 55555, 666666, 7777777}
stringList = []string{
"A value",
"Some other value",
"A nice longer value but not too long",
"V",
"",
"ı",
"prime count of values",
}
)
type namedBenchmark struct {
name string
test func(*testing.B)
}
func Benchmark(b *testing.B) {
b.Run("Baseline", Baseline.runBenchmark)
b.Run("StdLog", StdLog.runBenchmark)
benchmarks := []namedBenchmark{
{"Log", Log.runBenchmark},
{"Trace", Trace.runBenchmark},
{"Stats", Stats.runBenchmark},
}
event.SetExporter(nil)
for _, t := range benchmarks {
b.Run(t.name+"NoExporter", t.test)
}
event.SetExporter(noopExporter)
for _, t := range benchmarks {
b.Run(t.name+"Noop", t.test)
}
event.SetExporter(export.Spans(export.LogWriter(io.Discard, false)))
for _, t := range benchmarks {
b.Run(t.name, t.test)
}
}
func A(ctx context.Context, hooks Hooks, a int) int {
ctx, done := hooks.A(ctx, a)
defer done()
return B(ctx, hooks, a, stringList[a%len(stringList)])
}
func B(ctx context.Context, hooks Hooks, a int, b string) int {
_, done := hooks.B(ctx, b)
defer done()
return a + len(b)
}
func (hooks Hooks) runBenchmark(b *testing.B) {
ctx := context.Background()
b.ReportAllocs()
b.ResetTimer()
var acc int
for i := 0; i < b.N; i++ {
for _, value := range initialList {
acc += A(ctx, hooks, value)
}
}
}
func init() {
log.SetOutput(io.Discard)
}
func noopExporter(ctx context.Context, ev core.Event, lm label.Map) context.Context {
return ctx
}
@@ -0,0 +1,85 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package core provides support for event based telemetry.
package core
import (
"fmt"
"time"
"golang.org/x/tools/internal/event/label"
)
// Event holds the information about an event of note that occurred.
type Event struct {
at time.Time
// As events are often on the stack, storing the first few labels directly
// in the event can avoid an allocation at all for the very common cases of
// simple events.
// The length needs to be large enough to cope with the majority of events
// but no so large as to cause undue stack pressure.
// A log message with two values will use 3 labels (one for each value and
// one for the message itself).
static [3]label.Label // inline storage for the first few labels
dynamic []label.Label // dynamically sized storage for remaining labels
}
// eventLabelMap implements label.Map for a the labels of an Event.
type eventLabelMap struct {
event Event
}
func (ev Event) At() time.Time { return ev.at }
func (ev Event) Format(f fmt.State, r rune) {
if !ev.at.IsZero() {
fmt.Fprint(f, ev.at.Format("2006/01/02 15:04:05 "))
}
for index := 0; ev.Valid(index); index++ {
if l := ev.Label(index); l.Valid() {
fmt.Fprintf(f, "\n\t%v", l)
}
}
}
func (ev Event) Valid(index int) bool {
return index >= 0 && index < len(ev.static)+len(ev.dynamic)
}
func (ev Event) Label(index int) label.Label {
if index < len(ev.static) {
return ev.static[index]
}
return ev.dynamic[index-len(ev.static)]
}
func (ev Event) Find(key label.Key) label.Label {
for _, l := range ev.static {
if l.Key() == key {
return l
}
}
for _, l := range ev.dynamic {
if l.Key() == key {
return l
}
}
return label.Label{}
}
func MakeEvent(static [3]label.Label, labels []label.Label) Event {
return Event{
static: static,
dynamic: labels,
}
}
// CloneEvent event returns a copy of the event with the time adjusted to at.
func CloneEvent(ev Event, at time.Time) Event {
ev.at = at
return ev
}
@@ -0,0 +1,70 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package core
import (
"context"
"sync/atomic"
"time"
"unsafe"
"golang.org/x/tools/internal/event/label"
)
// Exporter is a function that handles events.
// It may return a modified context and event.
type Exporter func(context.Context, Event, label.Map) context.Context
var (
exporter unsafe.Pointer
)
// SetExporter sets the global exporter function that handles all events.
// The exporter is called synchronously from the event call site, so it should
// return quickly so as not to hold up user code.
func SetExporter(e Exporter) {
p := unsafe.Pointer(&e)
if e == nil {
// &e is always valid, and so p is always valid, but for the early abort
// of ProcessEvent to be efficient it needs to make the nil check on the
// pointer without having to dereference it, so we make the nil function
// also a nil pointer
p = nil
}
atomic.StorePointer(&exporter, p)
}
// deliver is called to deliver an event to the supplied exporter.
// it will fill in the time.
func deliver(ctx context.Context, exporter Exporter, ev Event) context.Context {
// add the current time to the event
ev.at = time.Now()
// hand the event off to the current exporter
return exporter(ctx, ev, ev)
}
// Export is called to deliver an event to the global exporter if set.
func Export(ctx context.Context, ev Event) context.Context {
// get the global exporter and abort early if there is not one
exporterPtr := (*Exporter)(atomic.LoadPointer(&exporter))
if exporterPtr == nil {
return ctx
}
return deliver(ctx, *exporterPtr, ev)
}
// ExportPair is called to deliver a start event to the supplied exporter.
// It also returns a function that will deliver the end event to the same
// exporter.
// It will fill in the time.
func ExportPair(ctx context.Context, begin, end Event) (context.Context, func()) {
// get the global exporter and abort early if there is not one
exporterPtr := (*Exporter)(atomic.LoadPointer(&exporter))
if exporterPtr == nil {
return ctx, func() {}
}
ctx = deliver(ctx, *exporterPtr, begin)
return ctx, func() { deliver(ctx, *exporterPtr, end) }
}
@@ -0,0 +1,77 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package core
import (
"context"
"golang.org/x/tools/internal/event/keys"
"golang.org/x/tools/internal/event/label"
)
// Log1 takes a message and one label delivers a log event to the exporter.
// It is a customized version of Print that is faster and does no allocation.
func Log1(ctx context.Context, message string, t1 label.Label) {
Export(ctx, MakeEvent([3]label.Label{
keys.Msg.Of(message),
t1,
}, nil))
}
// Log2 takes a message and two labels and delivers a log event to the exporter.
// It is a customized version of Print that is faster and does no allocation.
func Log2(ctx context.Context, message string, t1 label.Label, t2 label.Label) {
Export(ctx, MakeEvent([3]label.Label{
keys.Msg.Of(message),
t1,
t2,
}, nil))
}
// Metric1 sends a label event to the exporter with the supplied labels.
func Metric1(ctx context.Context, t1 label.Label) context.Context {
return Export(ctx, MakeEvent([3]label.Label{
keys.Metric.New(),
t1,
}, nil))
}
// Metric2 sends a label event to the exporter with the supplied labels.
func Metric2(ctx context.Context, t1, t2 label.Label) context.Context {
return Export(ctx, MakeEvent([3]label.Label{
keys.Metric.New(),
t1,
t2,
}, nil))
}
// Start1 sends a span start event with the supplied label list to the exporter.
// It also returns a function that will end the span, which should normally be
// deferred.
func Start1(ctx context.Context, name string, t1 label.Label) (context.Context, func()) {
return ExportPair(ctx,
MakeEvent([3]label.Label{
keys.Start.Of(name),
t1,
}, nil),
MakeEvent([3]label.Label{
keys.End.New(),
}, nil))
}
// Start2 sends a span start event with the supplied label list to the exporter.
// It also returns a function that will end the span, which should normally be
// deferred.
func Start2(ctx context.Context, name string, t1, t2 label.Label) (context.Context, func()) {
return ExportPair(ctx,
MakeEvent([3]label.Label{
keys.Start.Of(name),
t1,
t2,
}, nil),
MakeEvent([3]label.Label{
keys.End.New(),
}, nil))
}
@@ -0,0 +1,7 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package event provides a set of packages that cover the main
// concepts of telemetry in an implementation agnostic way.
package event
@@ -0,0 +1,127 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package event
import (
"context"
"golang.org/x/tools/internal/event/core"
"golang.org/x/tools/internal/event/keys"
"golang.org/x/tools/internal/event/label"
)
// Exporter is a function that handles events.
// It may return a modified context and event.
type Exporter func(context.Context, core.Event, label.Map) context.Context
// SetExporter sets the global exporter function that handles all events.
// The exporter is called synchronously from the event call site, so it should
// return quickly so as not to hold up user code.
func SetExporter(e Exporter) {
core.SetExporter(core.Exporter(e))
}
// Log takes a message and a label list and combines them into a single event
// before delivering them to the exporter.
func Log(ctx context.Context, message string, labels ...label.Label) {
core.Export(ctx, core.MakeEvent([3]label.Label{
keys.Msg.Of(message),
}, labels))
}
// IsLog returns true if the event was built by the Log function.
// It is intended to be used in exporters to identify the semantics of the
// event when deciding what to do with it.
func IsLog(ev core.Event) bool {
return ev.Label(0).Key() == keys.Msg
}
// Error takes a message and a label list and combines them into a single event
// before delivering them to the exporter. It captures the error in the
// delivered event.
func Error(ctx context.Context, message string, err error, labels ...label.Label) {
core.Export(ctx, core.MakeEvent([3]label.Label{
keys.Msg.Of(message),
keys.Err.Of(err),
}, labels))
}
// IsError returns true if the event was built by the Error function.
// It is intended to be used in exporters to identify the semantics of the
// event when deciding what to do with it.
func IsError(ev core.Event) bool {
return ev.Label(0).Key() == keys.Msg &&
ev.Label(1).Key() == keys.Err
}
// Metric sends a label event to the exporter with the supplied labels.
func Metric(ctx context.Context, labels ...label.Label) {
core.Export(ctx, core.MakeEvent([3]label.Label{
keys.Metric.New(),
}, labels))
}
// IsMetric returns true if the event was built by the Metric function.
// It is intended to be used in exporters to identify the semantics of the
// event when deciding what to do with it.
func IsMetric(ev core.Event) bool {
return ev.Label(0).Key() == keys.Metric
}
// Label sends a label event to the exporter with the supplied labels.
func Label(ctx context.Context, labels ...label.Label) context.Context {
return core.Export(ctx, core.MakeEvent([3]label.Label{
keys.Label.New(),
}, labels))
}
// IsLabel returns true if the event was built by the Label function.
// It is intended to be used in exporters to identify the semantics of the
// event when deciding what to do with it.
func IsLabel(ev core.Event) bool {
return ev.Label(0).Key() == keys.Label
}
// Start sends a span start event with the supplied label list to the exporter.
// It also returns a function that will end the span, which should normally be
// deferred.
func Start(ctx context.Context, name string, labels ...label.Label) (context.Context, func()) {
return core.ExportPair(ctx,
core.MakeEvent([3]label.Label{
keys.Start.Of(name),
}, labels),
core.MakeEvent([3]label.Label{
keys.End.New(),
}, nil))
}
// IsStart returns true if the event was built by the Start function.
// It is intended to be used in exporters to identify the semantics of the
// event when deciding what to do with it.
func IsStart(ev core.Event) bool {
return ev.Label(0).Key() == keys.Start
}
// IsEnd returns true if the event was built by the End function.
// It is intended to be used in exporters to identify the semantics of the
// event when deciding what to do with it.
func IsEnd(ev core.Event) bool {
return ev.Label(0).Key() == keys.End
}
// Detach returns a context without an associated span.
// This allows the creation of spans that are not children of the current span.
func Detach(ctx context.Context) context.Context {
return core.Export(ctx, core.MakeEvent([3]label.Label{
keys.Detach.New(),
}, nil))
}
// IsDetach returns true if the event was built by the Detach function.
// It is intended to be used in exporters to identify the semantics of the
// event when deciding what to do with it.
func IsDetach(ev core.Event) bool {
return ev.Label(0).Key() == keys.Detach
}
@@ -0,0 +1,64 @@
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package eventtest supports logging events to a test.
// You can use NewContext to create a context that knows how to deliver
// telemetry events back to the test.
// You must use this context or a derived one anywhere you want telemetry to be
// correctly routed back to the test it was constructed with.
// Any events delivered to a background context will be dropped.
//
// Importing this package will cause it to register a new global telemetry
// exporter that understands the special contexts returned by NewContext.
// This means you should not import this package if you are not going to call
// NewContext.
package eventtest
import (
"bytes"
"context"
"sync"
"testing"
"golang.org/x/tools/internal/event"
"golang.org/x/tools/internal/event/core"
"golang.org/x/tools/internal/event/export"
"golang.org/x/tools/internal/event/label"
)
func init() {
e := &testExporter{buffer: &bytes.Buffer{}}
e.logger = export.LogWriter(e.buffer, false)
event.SetExporter(export.Spans(e.processEvent))
}
type testingKeyType int
const testingKey = testingKeyType(0)
// NewContext returns a context you should use for the active test.
func NewContext(ctx context.Context, t testing.TB) context.Context {
return context.WithValue(ctx, testingKey, t)
}
type testExporter struct {
mu sync.Mutex
buffer *bytes.Buffer
logger event.Exporter
}
func (w *testExporter) processEvent(ctx context.Context, ev core.Event, tm label.Map) context.Context {
w.mu.Lock()
defer w.mu.Unlock()
// build our log message in buffer
result := w.logger(ctx, ev, tm)
v := ctx.Value(testingKey)
// get the testing.TB
if w.buffer.Len() > 0 && v != nil {
v.(testing.TB).Log(w.buffer)
}
w.buffer.Truncate(0)
return result
}
@@ -0,0 +1,71 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package export
import (
crand "crypto/rand"
"encoding/binary"
"fmt"
"math/rand"
"sync"
"sync/atomic"
)
type TraceID [16]byte
type SpanID [8]byte
func (t TraceID) String() string {
return fmt.Sprintf("%02x", t[:])
}
func (s SpanID) String() string {
return fmt.Sprintf("%02x", s[:])
}
func (s SpanID) IsValid() bool {
return s != SpanID{}
}
var (
generationMu sync.Mutex
nextSpanID uint64
spanIDInc uint64
traceIDAdd [2]uint64
traceIDRand *rand.Rand
)
func initGenerator() {
var rngSeed int64
for _, p := range []interface{}{
&rngSeed, &traceIDAdd, &nextSpanID, &spanIDInc,
} {
binary.Read(crand.Reader, binary.LittleEndian, p)
}
traceIDRand = rand.New(rand.NewSource(rngSeed))
spanIDInc |= 1
}
func newTraceID() TraceID {
generationMu.Lock()
defer generationMu.Unlock()
if traceIDRand == nil {
initGenerator()
}
var tid [16]byte
binary.LittleEndian.PutUint64(tid[0:8], traceIDRand.Uint64()+traceIDAdd[0])
binary.LittleEndian.PutUint64(tid[8:16], traceIDRand.Uint64()+traceIDAdd[1])
return tid
}
func newSpanID() SpanID {
var id uint64
for id == 0 {
id = atomic.AddUint64(&nextSpanID, spanIDInc)
}
var sid [8]byte
binary.LittleEndian.PutUint64(sid[:], id)
return sid
}
@@ -0,0 +1,37 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package export
import (
"context"
"golang.org/x/tools/internal/event"
"golang.org/x/tools/internal/event/core"
"golang.org/x/tools/internal/event/label"
)
// Labels builds an exporter that manipulates the context using the event.
// If the event is type IsLabel or IsStartSpan then it returns a context updated
// with label values from the event.
// For all other event types the event labels will be updated with values from the
// context if they are missing.
func Labels(output event.Exporter) event.Exporter {
return func(ctx context.Context, ev core.Event, lm label.Map) context.Context {
stored, _ := ctx.Value(labelContextKey).(label.Map)
if event.IsLabel(ev) || event.IsStart(ev) {
// update the label map stored in the context
fromEvent := label.Map(ev)
if stored == nil {
stored = fromEvent
} else {
stored = label.MergeMaps(fromEvent, stored)
}
ctx = context.WithValue(ctx, labelContextKey, stored)
}
// add the stored label context to the label map
lm = label.MergeMaps(lm, stored)
return output(ctx, ev, lm)
}
}
@@ -0,0 +1,57 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package export
import (
"context"
"fmt"
"io"
"sync"
"golang.org/x/tools/internal/event"
"golang.org/x/tools/internal/event/core"
"golang.org/x/tools/internal/event/label"
)
// LogWriter returns an Exporter that logs events to the supplied writer.
// If onlyErrors is true it does not log any event that did not have an
// associated error.
// It ignores all telemetry other than log events.
func LogWriter(w io.Writer, onlyErrors bool) event.Exporter {
lw := &logWriter{writer: w, onlyErrors: onlyErrors}
return lw.ProcessEvent
}
type logWriter struct {
mu sync.Mutex
printer Printer
writer io.Writer
onlyErrors bool
}
func (w *logWriter) ProcessEvent(ctx context.Context, ev core.Event, lm label.Map) context.Context {
switch {
case event.IsLog(ev):
if w.onlyErrors && !event.IsError(ev) {
return ctx
}
w.mu.Lock()
defer w.mu.Unlock()
w.printer.WriteEvent(w.writer, ev, lm)
case event.IsStart(ev):
if span := GetSpan(ctx); span != nil {
fmt.Fprintf(w.writer, "start: %v %v", span.Name, span.ID)
if span.ParentID.IsValid() {
fmt.Fprintf(w.writer, "[%v]", span.ParentID)
}
}
case event.IsEnd(ev):
if span := GetSpan(ctx); span != nil {
fmt.Fprintf(w.writer, "finish: %v %v", span.Name, span.ID)
}
}
return ctx
}
@@ -0,0 +1,40 @@
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package export_test
import (
"context"
"errors"
"os"
"time"
"golang.org/x/tools/internal/event"
"golang.org/x/tools/internal/event/core"
"golang.org/x/tools/internal/event/export"
"golang.org/x/tools/internal/event/keys"
"golang.org/x/tools/internal/event/label"
)
func ExampleLog() {
ctx := context.Background()
event.SetExporter(timeFixer(export.LogWriter(os.Stdout, false)))
anInt := keys.NewInt("myInt", "an integer")
aString := keys.NewString("myString", "a string")
event.Log(ctx, "my event", anInt.Of(6))
event.Error(ctx, "error event", errors.New("an error"), aString.Of("some string value"))
// Output:
// 2020/03/05 14:27:48 my event
// myInt=6
// 2020/03/05 14:27:48 error event: an error
// myString="some string value"
}
func timeFixer(output event.Exporter) event.Exporter {
at, _ := time.Parse(time.RFC3339Nano, "2020-03-05T14:27:48Z")
return func(ctx context.Context, ev core.Event, lm label.Map) context.Context {
copy := core.CloneEvent(ev, at)
return output(ctx, copy, lm)
}
}
@@ -0,0 +1,304 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package metric
import (
"fmt"
"sort"
"time"
"golang.org/x/tools/internal/event/keys"
"golang.org/x/tools/internal/event/label"
)
// Data represents a single point in the time series of a metric.
// This provides the common interface to all metrics no matter their data
// format.
// To get the actual values for the metric you must type assert to a concrete
// metric type.
type Data interface {
// Handle returns the metric handle this data is for.
//TODO: rethink the concept of metric handles
Handle() string
// Groups reports the rows that currently exist for this metric.
Groups() [][]label.Label
}
// Int64Data is a concrete implementation of Data for int64 scalar metrics.
type Int64Data struct {
// Info holds the original construction information.
Info *Scalar
// IsGauge is true for metrics that track values, rather than increasing over time.
IsGauge bool
// Rows holds the per group values for the metric.
Rows []int64
// End is the last time this metric was updated.
EndTime time.Time
groups [][]label.Label
key *keys.Int64
}
// Float64Data is a concrete implementation of Data for float64 scalar metrics.
type Float64Data struct {
// Info holds the original construction information.
Info *Scalar
// IsGauge is true for metrics that track values, rather than increasing over time.
IsGauge bool
// Rows holds the per group values for the metric.
Rows []float64
// End is the last time this metric was updated.
EndTime time.Time
groups [][]label.Label
key *keys.Float64
}
// HistogramInt64Data is a concrete implementation of Data for int64 histogram metrics.
type HistogramInt64Data struct {
// Info holds the original construction information.
Info *HistogramInt64
// Rows holds the per group values for the metric.
Rows []*HistogramInt64Row
// End is the last time this metric was updated.
EndTime time.Time
groups [][]label.Label
key *keys.Int64
}
// HistogramInt64Row holds the values for a single row of a HistogramInt64Data.
type HistogramInt64Row struct {
// Values is the counts per bucket.
Values []int64
// Count is the total count.
Count int64
// Sum is the sum of all the values recorded.
Sum int64
// Min is the smallest recorded value.
Min int64
// Max is the largest recorded value.
Max int64
}
// HistogramFloat64Data is a concrete implementation of Data for float64 histogram metrics.
type HistogramFloat64Data struct {
// Info holds the original construction information.
Info *HistogramFloat64
// Rows holds the per group values for the metric.
Rows []*HistogramFloat64Row
// End is the last time this metric was updated.
EndTime time.Time
groups [][]label.Label
key *keys.Float64
}
// HistogramFloat64Row holds the values for a single row of a HistogramFloat64Data.
type HistogramFloat64Row struct {
// Values is the counts per bucket.
Values []int64
// Count is the total count.
Count int64
// Sum is the sum of all the values recorded.
Sum float64
// Min is the smallest recorded value.
Min float64
// Max is the largest recorded value.
Max float64
}
func labelListEqual(a, b []label.Label) bool {
//TODO: make this more efficient
return fmt.Sprint(a) == fmt.Sprint(b)
}
func labelListLess(a, b []label.Label) bool {
//TODO: make this more efficient
return fmt.Sprint(a) < fmt.Sprint(b)
}
func getGroup(lm label.Map, g *[][]label.Label, keys []label.Key) (int, bool) {
group := make([]label.Label, len(keys))
for i, key := range keys {
l := lm.Find(key)
if l.Valid() {
group[i] = l
}
}
old := *g
index := sort.Search(len(old), func(i int) bool {
return !labelListLess(old[i], group)
})
if index < len(old) && labelListEqual(group, old[index]) {
// not a new group
return index, false
}
*g = make([][]label.Label, len(old)+1)
copy(*g, old[:index])
copy((*g)[index+1:], old[index:])
(*g)[index] = group
return index, true
}
func (data *Int64Data) Handle() string { return data.Info.Name }
func (data *Int64Data) Groups() [][]label.Label { return data.groups }
func (data *Int64Data) modify(at time.Time, lm label.Map, f func(v int64) int64) Data {
index, insert := getGroup(lm, &data.groups, data.Info.Keys)
old := data.Rows
if insert {
data.Rows = make([]int64, len(old)+1)
copy(data.Rows, old[:index])
copy(data.Rows[index+1:], old[index:])
} else {
data.Rows = make([]int64, len(old))
copy(data.Rows, old)
}
data.Rows[index] = f(data.Rows[index])
data.EndTime = at
frozen := *data
return &frozen
}
func (data *Int64Data) count(at time.Time, lm label.Map, l label.Label) Data {
return data.modify(at, lm, func(v int64) int64 {
return v + 1
})
}
func (data *Int64Data) sum(at time.Time, lm label.Map, l label.Label) Data {
return data.modify(at, lm, func(v int64) int64 {
return v + data.key.From(l)
})
}
func (data *Int64Data) latest(at time.Time, lm label.Map, l label.Label) Data {
return data.modify(at, lm, func(v int64) int64 {
return data.key.From(l)
})
}
func (data *Float64Data) Handle() string { return data.Info.Name }
func (data *Float64Data) Groups() [][]label.Label { return data.groups }
func (data *Float64Data) modify(at time.Time, lm label.Map, f func(v float64) float64) Data {
index, insert := getGroup(lm, &data.groups, data.Info.Keys)
old := data.Rows
if insert {
data.Rows = make([]float64, len(old)+1)
copy(data.Rows, old[:index])
copy(data.Rows[index+1:], old[index:])
} else {
data.Rows = make([]float64, len(old))
copy(data.Rows, old)
}
data.Rows[index] = f(data.Rows[index])
data.EndTime = at
frozen := *data
return &frozen
}
func (data *Float64Data) sum(at time.Time, lm label.Map, l label.Label) Data {
return data.modify(at, lm, func(v float64) float64 {
return v + data.key.From(l)
})
}
func (data *Float64Data) latest(at time.Time, lm label.Map, l label.Label) Data {
return data.modify(at, lm, func(v float64) float64 {
return data.key.From(l)
})
}
func (data *HistogramInt64Data) Handle() string { return data.Info.Name }
func (data *HistogramInt64Data) Groups() [][]label.Label { return data.groups }
func (data *HistogramInt64Data) modify(at time.Time, lm label.Map, f func(v *HistogramInt64Row)) Data {
index, insert := getGroup(lm, &data.groups, data.Info.Keys)
old := data.Rows
var v HistogramInt64Row
if insert {
data.Rows = make([]*HistogramInt64Row, len(old)+1)
copy(data.Rows, old[:index])
copy(data.Rows[index+1:], old[index:])
} else {
data.Rows = make([]*HistogramInt64Row, len(old))
copy(data.Rows, old)
v = *data.Rows[index]
}
oldValues := v.Values
v.Values = make([]int64, len(data.Info.Buckets))
copy(v.Values, oldValues)
f(&v)
data.Rows[index] = &v
data.EndTime = at
frozen := *data
return &frozen
}
func (data *HistogramInt64Data) record(at time.Time, lm label.Map, l label.Label) Data {
return data.modify(at, lm, func(v *HistogramInt64Row) {
value := data.key.From(l)
v.Sum += value
if v.Min > value || v.Count == 0 {
v.Min = value
}
if v.Max < value || v.Count == 0 {
v.Max = value
}
v.Count++
for i, b := range data.Info.Buckets {
if value <= b {
v.Values[i]++
}
}
})
}
func (data *HistogramFloat64Data) Handle() string { return data.Info.Name }
func (data *HistogramFloat64Data) Groups() [][]label.Label { return data.groups }
func (data *HistogramFloat64Data) modify(at time.Time, lm label.Map, f func(v *HistogramFloat64Row)) Data {
index, insert := getGroup(lm, &data.groups, data.Info.Keys)
old := data.Rows
var v HistogramFloat64Row
if insert {
data.Rows = make([]*HistogramFloat64Row, len(old)+1)
copy(data.Rows, old[:index])
copy(data.Rows[index+1:], old[index:])
} else {
data.Rows = make([]*HistogramFloat64Row, len(old))
copy(data.Rows, old)
v = *data.Rows[index]
}
oldValues := v.Values
v.Values = make([]int64, len(data.Info.Buckets))
copy(v.Values, oldValues)
f(&v)
data.Rows[index] = &v
data.EndTime = at
frozen := *data
return &frozen
}
func (data *HistogramFloat64Data) record(at time.Time, lm label.Map, l label.Label) Data {
return data.modify(at, lm, func(v *HistogramFloat64Row) {
value := data.key.From(l)
v.Sum += value
if v.Min > value || v.Count == 0 {
v.Min = value
}
if v.Max < value || v.Count == 0 {
v.Max = value
}
v.Count++
for i, b := range data.Info.Buckets {
if value <= b {
v.Values[i]++
}
}
})
}
@@ -0,0 +1,58 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package metric aggregates events into metrics that can be exported.
package metric
import (
"context"
"sync"
"time"
"golang.org/x/tools/internal/event"
"golang.org/x/tools/internal/event/core"
"golang.org/x/tools/internal/event/keys"
"golang.org/x/tools/internal/event/label"
)
var Entries = keys.New("metric_entries", "The set of metrics calculated for an event")
type Config struct {
subscribers map[interface{}][]subscriber
}
type subscriber func(time.Time, label.Map, label.Label) Data
func (e *Config) subscribe(key label.Key, s subscriber) {
if e.subscribers == nil {
e.subscribers = make(map[interface{}][]subscriber)
}
e.subscribers[key] = append(e.subscribers[key], s)
}
func (e *Config) Exporter(output event.Exporter) event.Exporter {
var mu sync.Mutex
return func(ctx context.Context, ev core.Event, lm label.Map) context.Context {
if !event.IsMetric(ev) {
return output(ctx, ev, lm)
}
mu.Lock()
defer mu.Unlock()
var metrics []Data
for index := 0; ev.Valid(index); index++ {
l := ev.Label(index)
if !l.Valid() {
continue
}
id := l.Key()
if list := e.subscribers[id]; len(list) > 0 {
for _, s := range list {
metrics = append(metrics, s(ev.At(), lm, l))
}
}
}
lm = label.MergeMaps(label.NewMap(Entries.Of(metrics)), lm)
return output(ctx, ev, lm)
}
}
@@ -0,0 +1,100 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package metric
import (
"golang.org/x/tools/internal/event/keys"
"golang.org/x/tools/internal/event/label"
)
// Scalar represents the construction information for a scalar metric.
type Scalar struct {
// Name is the unique name of this metric.
Name string
// Description can be used by observers to describe the metric to users.
Description string
// Keys is the set of labels that collectively describe rows of the metric.
Keys []label.Key
}
// HistogramInt64 represents the construction information for an int64 histogram metric.
type HistogramInt64 struct {
// Name is the unique name of this metric.
Name string
// Description can be used by observers to describe the metric to users.
Description string
// Keys is the set of labels that collectively describe rows of the metric.
Keys []label.Key
// Buckets holds the inclusive upper bound of each bucket in the histogram.
Buckets []int64
}
// HistogramFloat64 represents the construction information for an float64 histogram metric.
type HistogramFloat64 struct {
// Name is the unique name of this metric.
Name string
// Description can be used by observers to describe the metric to users.
Description string
// Keys is the set of labels that collectively describe rows of the metric.
Keys []label.Key
// Buckets holds the inclusive upper bound of each bucket in the histogram.
Buckets []float64
}
// Count creates a new metric based on the Scalar information that counts
// the number of times the supplied int64 measure is set.
// Metrics of this type will use Int64Data.
func (info Scalar) Count(e *Config, key label.Key) {
data := &Int64Data{Info: &info, key: nil}
e.subscribe(key, data.count)
}
// SumInt64 creates a new metric based on the Scalar information that sums all
// the values recorded on the int64 measure.
// Metrics of this type will use Int64Data.
func (info Scalar) SumInt64(e *Config, key *keys.Int64) {
data := &Int64Data{Info: &info, key: key}
e.subscribe(key, data.sum)
}
// LatestInt64 creates a new metric based on the Scalar information that tracks
// the most recent value recorded on the int64 measure.
// Metrics of this type will use Int64Data.
func (info Scalar) LatestInt64(e *Config, key *keys.Int64) {
data := &Int64Data{Info: &info, IsGauge: true, key: key}
e.subscribe(key, data.latest)
}
// SumFloat64 creates a new metric based on the Scalar information that sums all
// the values recorded on the float64 measure.
// Metrics of this type will use Float64Data.
func (info Scalar) SumFloat64(e *Config, key *keys.Float64) {
data := &Float64Data{Info: &info, key: key}
e.subscribe(key, data.sum)
}
// LatestFloat64 creates a new metric based on the Scalar information that tracks
// the most recent value recorded on the float64 measure.
// Metrics of this type will use Float64Data.
func (info Scalar) LatestFloat64(e *Config, key *keys.Float64) {
data := &Float64Data{Info: &info, IsGauge: true, key: key}
e.subscribe(key, data.latest)
}
// Record creates a new metric based on the HistogramInt64 information that
// tracks the bucketized counts of values recorded on the int64 measure.
// Metrics of this type will use HistogramInt64Data.
func (info HistogramInt64) Record(e *Config, key *keys.Int64) {
data := &HistogramInt64Data{Info: &info, key: key}
e.subscribe(key, data.record)
}
// Record creates a new metric based on the HistogramFloat64 information that
// tracks the bucketized counts of values recorded on the float64 measure.
// Metrics of this type will use HistogramFloat64Data.
func (info HistogramFloat64) Record(e *Config, key *keys.Float64) {
data := &HistogramFloat64Data{Info: &info, key: key}
e.subscribe(key, data.record)
}
@@ -0,0 +1,139 @@
# Exporting Metrics and Traces with OpenCensus, Zipkin, and Prometheus
This tutorial provides a minimum example to verify that metrics and traces
can be exported to OpenCensus from Go tools.
## Setting up oragent
1. Ensure you have [docker](https://www.docker.com/get-started) and [docker-compose](https://docs.docker.com/compose/install/).
2. Clone [oragent](https://github.com/orijtech/oragent).
3. In the oragent directory, start the services:
```bash
docker-compose up
```
If everything goes well, you should see output resembling the following:
```
Starting oragent_zipkin_1 ... done
Starting oragent_oragent_1 ... done
Starting oragent_prometheus_1 ... done
...
```
* You can check the status of the OpenCensus agent using zPages at http://localhost:55679/debug/tracez.
* You can now access the Prometheus UI at http://localhost:9445.
* You can now access the Zipkin UI at http://localhost:9444.
4. To shut down oragent, hit Ctrl+C in the terminal.
5. You can also start oragent in detached mode by running `docker-compose up -d`. To stop oragent while detached, run `docker-compose down`.
## Exporting Metrics and Traces
1. Clone the [tools](https://golang.org/x/tools) subrepository.
1. Inside `internal`, create a file named `main.go` with the following contents:
```go
package main
import (
"context"
"fmt"
"math/rand"
"net/http"
"time"
"golang.org/x/tools/internal/event"
"golang.org/x/tools/internal/event/export"
"golang.org/x/tools/internal/event/export/metric"
"golang.org/x/tools/internal/event/export/ocagent"
)
type testExporter struct {
metrics metric.Exporter
ocagent *ocagent.Exporter
}
func (e *testExporter) ProcessEvent(ctx context.Context, ev event.Event) (context.Context, event.Event) {
ctx, ev = export.Tag(ctx, ev)
ctx, ev = export.ContextSpan(ctx, ev)
ctx, ev = e.metrics.ProcessEvent(ctx, ev)
ctx, ev = e.ocagent.ProcessEvent(ctx, ev)
return ctx, ev
}
func main() {
exporter := &testExporter{}
exporter.ocagent = ocagent.Connect(&ocagent.Config{
Start: time.Now(),
Address: "http://127.0.0.1:55678",
Service: "go-tools-test",
Rate: 5 * time.Second,
Client: &http.Client{},
})
event.SetExporter(exporter)
ctx := context.TODO()
mLatency := event.NewFloat64Key("latency", "the latency in milliseconds")
distribution := metric.HistogramFloat64Data{
Info: &metric.HistogramFloat64{
Name: "latencyDistribution",
Description: "the various latencies",
Buckets: []float64{0, 10, 50, 100, 200, 400, 800, 1000, 1400, 2000, 5000, 10000, 15000},
},
}
distribution.Info.Record(&exporter.metrics, mLatency)
for {
sleep := randomSleep()
_, end := event.StartSpan(ctx, "main.randomSleep()")
time.Sleep(time.Duration(sleep) * time.Millisecond)
end()
event.Record(ctx, mLatency.Of(float64(sleep)))
fmt.Println("Latency: ", float64(sleep))
}
}
func randomSleep() int64 {
var max int64
switch modulus := time.Now().Unix() % 5; modulus {
case 0:
max = 17001
case 1:
max = 8007
case 2:
max = 917
case 3:
max = 87
case 4:
max = 1173
}
return rand.Int63n(max)
}
```
3. Run the new file from within the tools repository:
```bash
go run internal/main.go
```
4. After about 5 seconds, OpenCensus should start receiving your new metrics, which you can see at http://localhost:8844/metrics. This page will look similar to the following:
```
# HELP promdemo_latencyDistribution the various latencies
# TYPE promdemo_latencyDistribution histogram
promdemo_latencyDistribution_bucket{vendor="otc",le="0"} 0
promdemo_latencyDistribution_bucket{vendor="otc",le="10"} 2
promdemo_latencyDistribution_bucket{vendor="otc",le="50"} 9
promdemo_latencyDistribution_bucket{vendor="otc",le="100"} 22
promdemo_latencyDistribution_bucket{vendor="otc",le="200"} 35
promdemo_latencyDistribution_bucket{vendor="otc",le="400"} 49
promdemo_latencyDistribution_bucket{vendor="otc",le="800"} 63
promdemo_latencyDistribution_bucket{vendor="otc",le="1000"} 78
promdemo_latencyDistribution_bucket{vendor="otc",le="1400"} 93
promdemo_latencyDistribution_bucket{vendor="otc",le="2000"} 108
promdemo_latencyDistribution_bucket{vendor="otc",le="5000"} 123
promdemo_latencyDistribution_bucket{vendor="otc",le="10000"} 138
promdemo_latencyDistribution_bucket{vendor="otc",le="15000"} 153
promdemo_latencyDistribution_bucket{vendor="otc",le="+Inf"} 15
promdemo_latencyDistribution_sum{vendor="otc"} 1641
promdemo_latencyDistribution_count{vendor="otc"} 15
```
5. After a few more seconds, Prometheus should start displaying your new metrics. You can view the distribution at http://localhost:9445/graph?g0.range_input=5m&g0.stacked=1&g0.expr=rate(oragent_latencyDistribution_bucket%5B5m%5D)&g0.tab=0.
6. Zipkin should also start displaying traces. You can view them at http://localhost:9444/zipkin/?limit=10&lookback=300000&serviceName=go-tools-test.
@@ -0,0 +1,213 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ocagent
import (
"time"
"golang.org/x/tools/internal/event/export/metric"
"golang.org/x/tools/internal/event/export/ocagent/wire"
"golang.org/x/tools/internal/event/label"
)
// dataToMetricDescriptor return a *wire.MetricDescriptor based on data.
func dataToMetricDescriptor(data metric.Data) *wire.MetricDescriptor {
if data == nil {
return nil
}
descriptor := &wire.MetricDescriptor{
Name: data.Handle(),
Description: getDescription(data),
// TODO: Unit?
Type: dataToMetricDescriptorType(data),
LabelKeys: getLabelKeys(data),
}
return descriptor
}
// getDescription returns the description of data.
func getDescription(data metric.Data) string {
switch d := data.(type) {
case *metric.Int64Data:
return d.Info.Description
case *metric.Float64Data:
return d.Info.Description
case *metric.HistogramInt64Data:
return d.Info.Description
case *metric.HistogramFloat64Data:
return d.Info.Description
}
return ""
}
// getLabelKeys returns a slice of *wire.LabelKeys based on the keys
// in data.
func getLabelKeys(data metric.Data) []*wire.LabelKey {
switch d := data.(type) {
case *metric.Int64Data:
return infoKeysToLabelKeys(d.Info.Keys)
case *metric.Float64Data:
return infoKeysToLabelKeys(d.Info.Keys)
case *metric.HistogramInt64Data:
return infoKeysToLabelKeys(d.Info.Keys)
case *metric.HistogramFloat64Data:
return infoKeysToLabelKeys(d.Info.Keys)
}
return nil
}
// dataToMetricDescriptorType returns a wire.MetricDescriptor_Type based on the
// underlying type of data.
func dataToMetricDescriptorType(data metric.Data) wire.MetricDescriptor_Type {
switch d := data.(type) {
case *metric.Int64Data:
if d.IsGauge {
return wire.MetricDescriptor_GAUGE_INT64
}
return wire.MetricDescriptor_CUMULATIVE_INT64
case *metric.Float64Data:
if d.IsGauge {
return wire.MetricDescriptor_GAUGE_DOUBLE
}
return wire.MetricDescriptor_CUMULATIVE_DOUBLE
case *metric.HistogramInt64Data:
return wire.MetricDescriptor_CUMULATIVE_DISTRIBUTION
case *metric.HistogramFloat64Data:
return wire.MetricDescriptor_CUMULATIVE_DISTRIBUTION
}
return wire.MetricDescriptor_UNSPECIFIED
}
// dataToTimeseries returns a slice of *wire.TimeSeries based on the
// points in data.
func dataToTimeseries(data metric.Data, start time.Time) []*wire.TimeSeries {
if data == nil {
return nil
}
numRows := numRows(data)
startTimestamp := convertTimestamp(start)
timeseries := make([]*wire.TimeSeries, 0, numRows)
for i := 0; i < numRows; i++ {
timeseries = append(timeseries, &wire.TimeSeries{
StartTimestamp: &startTimestamp,
// TODO: labels?
Points: dataToPoints(data, i),
})
}
return timeseries
}
// numRows returns the number of rows in data.
func numRows(data metric.Data) int {
switch d := data.(type) {
case *metric.Int64Data:
return len(d.Rows)
case *metric.Float64Data:
return len(d.Rows)
case *metric.HistogramInt64Data:
return len(d.Rows)
case *metric.HistogramFloat64Data:
return len(d.Rows)
}
return 0
}
// dataToPoints returns an array of *wire.Points based on the point(s)
// in data at index i.
func dataToPoints(data metric.Data, i int) []*wire.Point {
switch d := data.(type) {
case *metric.Int64Data:
timestamp := convertTimestamp(d.EndTime)
return []*wire.Point{
{
Value: wire.PointInt64Value{
Int64Value: d.Rows[i],
},
Timestamp: &timestamp,
},
}
case *metric.Float64Data:
timestamp := convertTimestamp(d.EndTime)
return []*wire.Point{
{
Value: wire.PointDoubleValue{
DoubleValue: d.Rows[i],
},
Timestamp: &timestamp,
},
}
case *metric.HistogramInt64Data:
row := d.Rows[i]
bucketBounds := make([]float64, len(d.Info.Buckets))
for i, val := range d.Info.Buckets {
bucketBounds[i] = float64(val)
}
return distributionToPoints(row.Values, row.Count, float64(row.Sum), bucketBounds, d.EndTime)
case *metric.HistogramFloat64Data:
row := d.Rows[i]
return distributionToPoints(row.Values, row.Count, row.Sum, d.Info.Buckets, d.EndTime)
}
return nil
}
// distributionToPoints returns an array of *wire.Points containing a
// wire.PointDistributionValue representing a distribution with the
// supplied counts, count, and sum.
func distributionToPoints(counts []int64, count int64, sum float64, bucketBounds []float64, end time.Time) []*wire.Point {
buckets := make([]*wire.Bucket, len(counts))
for i := 0; i < len(counts); i++ {
buckets[i] = &wire.Bucket{
Count: counts[i],
}
}
timestamp := convertTimestamp(end)
return []*wire.Point{
{
Value: wire.PointDistributionValue{
DistributionValue: &wire.DistributionValue{
Count: count,
Sum: sum,
// TODO: SumOfSquaredDeviation?
Buckets: buckets,
BucketOptions: &wire.BucketOptionsExplicit{
Bounds: bucketBounds,
},
},
},
Timestamp: &timestamp,
},
}
}
// infoKeysToLabelKeys returns an array of *wire.LabelKeys containing the
// string values of the elements of labelKeys.
func infoKeysToLabelKeys(infoKeys []label.Key) []*wire.LabelKey {
labelKeys := make([]*wire.LabelKey, 0, len(infoKeys))
for _, key := range infoKeys {
labelKeys = append(labelKeys, &wire.LabelKey{
Key: key.Name(),
})
}
return labelKeys
}
@@ -0,0 +1,144 @@
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ocagent_test
import (
"context"
"errors"
"testing"
"golang.org/x/tools/internal/event"
"golang.org/x/tools/internal/event/keys"
)
func TestEncodeMetric(t *testing.T) {
exporter := registerExporter()
const prefix = testNodeStr + `
"metrics":[`
const suffix = `]}`
tests := []struct {
name string
run func(ctx context.Context)
want string
}{
{
name: "HistogramFloat64, HistogramInt64",
run: func(ctx context.Context) {
ctx = event.Label(ctx, keyMethod.Of("godoc.ServeHTTP"))
event.Metric(ctx, latencyMs.Of(96.58))
ctx = event.Label(ctx, keys.Err.Of(errors.New("panic: fatal signal")))
event.Metric(ctx, bytesIn.Of(97e2))
},
want: prefix + `
{
"metric_descriptor": {
"name": "latency_ms",
"description": "The latency of calls in milliseconds",
"type": 6,
"label_keys": [
{
"key": "method"
},
{
"key": "route"
}
]
},
"timeseries": [
{
"start_timestamp": "1970-01-01T00:00:00Z",
"points": [
{
"timestamp": "1970-01-01T00:00:40Z",
"distributionValue": {
"count": 1,
"sum": 96.58,
"bucket_options": {
"explicit": {
"bounds": [
0,
5,
10,
25,
50
]
}
},
"buckets": [
{},
{},
{},
{},
{}
]
}
}
]
}
]
},
{
"metric_descriptor": {
"name": "latency_ms",
"description": "The latency of calls in milliseconds",
"type": 6,
"label_keys": [
{
"key": "method"
},
{
"key": "route"
}
]
},
"timeseries": [
{
"start_timestamp": "1970-01-01T00:00:00Z",
"points": [
{
"timestamp": "1970-01-01T00:00:40Z",
"distributionValue": {
"count": 1,
"sum": 9700,
"bucket_options": {
"explicit": {
"bounds": [
0,
10,
50,
100,
500,
1000,
2000
]
}
},
"buckets": [
{},
{},
{},
{},
{},
{},
{}
]
}
}
]
}
]
}` + suffix,
},
}
ctx := context.TODO()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.run(ctx)
got := exporter.Output("/v1/metrics")
checkJSON(t, got, []byte(tt.want))
})
}
}
@@ -0,0 +1,358 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package ocagent adds the ability to export all telemetry to an ocagent.
// This keeps the compile time dependencies to zero and allows the agent to
// have the exporters needed for telemetry aggregation and viewing systems.
package ocagent
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"os"
"path/filepath"
"sync"
"time"
"golang.org/x/tools/internal/event"
"golang.org/x/tools/internal/event/core"
"golang.org/x/tools/internal/event/export"
"golang.org/x/tools/internal/event/export/metric"
"golang.org/x/tools/internal/event/export/ocagent/wire"
"golang.org/x/tools/internal/event/keys"
"golang.org/x/tools/internal/event/label"
)
type Config struct {
Start time.Time
Host string
Process uint32
Client *http.Client
Service string
Address string
Rate time.Duration
}
var (
connectMu sync.Mutex
exporters = make(map[Config]*Exporter)
)
// Discover finds the local agent to export to, it will return nil if there
// is not one running.
// TODO: Actually implement a discovery protocol rather than a hard coded address
func Discover() *Config {
return &Config{
Address: "http://localhost:55678",
}
}
type Exporter struct {
mu sync.Mutex
config Config
spans []*export.Span
metrics []metric.Data
}
// Connect creates a process specific exporter with the specified
// serviceName and the address of the ocagent to which it will upload
// its telemetry.
func Connect(config *Config) *Exporter {
if config == nil || config.Address == "off" {
return nil
}
resolved := *config
if resolved.Host == "" {
hostname, _ := os.Hostname()
resolved.Host = hostname
}
if resolved.Process == 0 {
resolved.Process = uint32(os.Getpid())
}
if resolved.Client == nil {
resolved.Client = http.DefaultClient
}
if resolved.Service == "" {
resolved.Service = filepath.Base(os.Args[0])
}
if resolved.Rate == 0 {
resolved.Rate = 2 * time.Second
}
connectMu.Lock()
defer connectMu.Unlock()
if exporter, found := exporters[resolved]; found {
return exporter
}
exporter := &Exporter{config: resolved}
exporters[resolved] = exporter
if exporter.config.Start.IsZero() {
exporter.config.Start = time.Now()
}
go func() {
for range time.Tick(exporter.config.Rate) {
exporter.Flush()
}
}()
return exporter
}
func (e *Exporter) ProcessEvent(ctx context.Context, ev core.Event, lm label.Map) context.Context {
switch {
case event.IsEnd(ev):
e.mu.Lock()
defer e.mu.Unlock()
span := export.GetSpan(ctx)
if span != nil {
e.spans = append(e.spans, span)
}
case event.IsMetric(ev):
e.mu.Lock()
defer e.mu.Unlock()
data := metric.Entries.Get(lm).([]metric.Data)
e.metrics = append(e.metrics, data...)
}
return ctx
}
func (e *Exporter) Flush() {
e.mu.Lock()
defer e.mu.Unlock()
spans := make([]*wire.Span, len(e.spans))
for i, s := range e.spans {
spans[i] = convertSpan(s)
}
e.spans = nil
metrics := make([]*wire.Metric, len(e.metrics))
for i, m := range e.metrics {
metrics[i] = convertMetric(m, e.config.Start)
}
e.metrics = nil
if len(spans) > 0 {
e.send("/v1/trace", &wire.ExportTraceServiceRequest{
Node: e.config.buildNode(),
Spans: spans,
//TODO: Resource?
})
}
if len(metrics) > 0 {
e.send("/v1/metrics", &wire.ExportMetricsServiceRequest{
Node: e.config.buildNode(),
Metrics: metrics,
//TODO: Resource?
})
}
}
func (cfg *Config) buildNode() *wire.Node {
return &wire.Node{
Identifier: &wire.ProcessIdentifier{
HostName: cfg.Host,
Pid: cfg.Process,
StartTimestamp: convertTimestamp(cfg.Start),
},
LibraryInfo: &wire.LibraryInfo{
Language: wire.LanguageGo,
ExporterVersion: "0.0.1",
CoreLibraryVersion: "x/tools",
},
ServiceInfo: &wire.ServiceInfo{
Name: cfg.Service,
},
}
}
func (e *Exporter) send(endpoint string, message interface{}) {
blob, err := json.Marshal(message)
if err != nil {
errorInExport("ocagent failed to marshal message for %v: %v", endpoint, err)
return
}
uri := e.config.Address + endpoint
req, err := http.NewRequest("POST", uri, bytes.NewReader(blob))
if err != nil {
errorInExport("ocagent failed to build request for %v: %v", uri, err)
return
}
req.Header.Set("Content-Type", "application/json")
res, err := e.config.Client.Do(req)
if err != nil {
errorInExport("ocagent failed to send message: %v \n", err)
return
}
if res.Body != nil {
res.Body.Close()
}
}
func errorInExport(message string, args ...interface{}) {
// This function is useful when debugging the exporter, but in general we
// want to just drop any export
}
func convertTimestamp(t time.Time) wire.Timestamp {
return t.Format(time.RFC3339Nano)
}
func toTruncatableString(s string) *wire.TruncatableString {
if s == "" {
return nil
}
return &wire.TruncatableString{Value: s}
}
func convertSpan(span *export.Span) *wire.Span {
result := &wire.Span{
TraceID: span.ID.TraceID[:],
SpanID: span.ID.SpanID[:],
TraceState: nil, //TODO?
ParentSpanID: span.ParentID[:],
Name: toTruncatableString(span.Name),
Kind: wire.UnspecifiedSpanKind,
StartTime: convertTimestamp(span.Start().At()),
EndTime: convertTimestamp(span.Finish().At()),
Attributes: convertAttributes(span.Start(), 1),
TimeEvents: convertEvents(span.Events()),
SameProcessAsParentSpan: true,
//TODO: StackTrace?
//TODO: Links?
//TODO: Status?
//TODO: Resource?
}
return result
}
func convertMetric(data metric.Data, start time.Time) *wire.Metric {
descriptor := dataToMetricDescriptor(data)
timeseries := dataToTimeseries(data, start)
if descriptor == nil && timeseries == nil {
return nil
}
// TODO: handle Histogram metrics
return &wire.Metric{
MetricDescriptor: descriptor,
Timeseries: timeseries,
// TODO: attach Resource?
}
}
func skipToValidLabel(list label.List, index int) (int, label.Label) {
// skip to the first valid label
for ; list.Valid(index); index++ {
l := list.Label(index)
if !l.Valid() || l.Key() == keys.Label {
continue
}
return index, l
}
return -1, label.Label{}
}
func convertAttributes(list label.List, index int) *wire.Attributes {
index, l := skipToValidLabel(list, index)
if !l.Valid() {
return nil
}
attributes := make(map[string]wire.Attribute)
for {
if l.Valid() {
attributes[l.Key().Name()] = convertAttribute(l)
}
index++
if !list.Valid(index) {
return &wire.Attributes{AttributeMap: attributes}
}
l = list.Label(index)
}
}
func convertAttribute(l label.Label) wire.Attribute {
switch key := l.Key().(type) {
case *keys.Int:
return wire.IntAttribute{IntValue: int64(key.From(l))}
case *keys.Int8:
return wire.IntAttribute{IntValue: int64(key.From(l))}
case *keys.Int16:
return wire.IntAttribute{IntValue: int64(key.From(l))}
case *keys.Int32:
return wire.IntAttribute{IntValue: int64(key.From(l))}
case *keys.Int64:
return wire.IntAttribute{IntValue: int64(key.From(l))}
case *keys.UInt:
return wire.IntAttribute{IntValue: int64(key.From(l))}
case *keys.UInt8:
return wire.IntAttribute{IntValue: int64(key.From(l))}
case *keys.UInt16:
return wire.IntAttribute{IntValue: int64(key.From(l))}
case *keys.UInt32:
return wire.IntAttribute{IntValue: int64(key.From(l))}
case *keys.UInt64:
return wire.IntAttribute{IntValue: int64(key.From(l))}
case *keys.Float32:
return wire.DoubleAttribute{DoubleValue: float64(key.From(l))}
case *keys.Float64:
return wire.DoubleAttribute{DoubleValue: key.From(l)}
case *keys.Boolean:
return wire.BoolAttribute{BoolValue: key.From(l)}
case *keys.String:
return wire.StringAttribute{StringValue: toTruncatableString(key.From(l))}
case *keys.Error:
return wire.StringAttribute{StringValue: toTruncatableString(key.From(l).Error())}
case *keys.Value:
return wire.StringAttribute{StringValue: toTruncatableString(fmt.Sprint(key.From(l)))}
default:
return wire.StringAttribute{StringValue: toTruncatableString(fmt.Sprintf("%T", key))}
}
}
func convertEvents(events []core.Event) *wire.TimeEvents {
//TODO: MessageEvents?
result := make([]wire.TimeEvent, len(events))
for i, event := range events {
result[i] = convertEvent(event)
}
return &wire.TimeEvents{TimeEvent: result}
}
func convertEvent(ev core.Event) wire.TimeEvent {
return wire.TimeEvent{
Time: convertTimestamp(ev.At()),
Annotation: convertAnnotation(ev),
}
}
func getAnnotationDescription(ev core.Event) (string, int) {
l := ev.Label(0)
if l.Key() != keys.Msg {
return "", 0
}
if msg := keys.Msg.From(l); msg != "" {
return msg, 1
}
l = ev.Label(1)
if l.Key() != keys.Err {
return "", 1
}
if err := keys.Err.From(l); err != nil {
return err.Error(), 2
}
return "", 2
}
func convertAnnotation(ev core.Event) *wire.Annotation {
description, index := getAnnotationDescription(ev)
if _, l := skipToValidLabel(ev, index); !l.Valid() && description == "" {
return nil
}
return &wire.Annotation{
Description: toTruncatableString(description),
Attributes: convertAttributes(ev, index),
}
}
@@ -0,0 +1,210 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ocagent_test
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"sync"
"testing"
"time"
"golang.org/x/tools/internal/event"
"golang.org/x/tools/internal/event/core"
"golang.org/x/tools/internal/event/export"
"golang.org/x/tools/internal/event/export/metric"
"golang.org/x/tools/internal/event/export/ocagent"
"golang.org/x/tools/internal/event/keys"
"golang.org/x/tools/internal/event/label"
)
const testNodeStr = `{
"node":{
"identifier":{
"host_name":"tester",
"pid":1,
"start_timestamp":"1970-01-01T00:00:00Z"
},
"library_info":{
"language":4,
"exporter_version":"0.0.1",
"core_library_version":"x/tools"
},
"service_info":{
"name":"ocagent-tests"
}
},`
var (
keyDB = keys.NewString("db", "the database name")
keyMethod = keys.NewString("method", "a metric grouping key")
keyRoute = keys.NewString("route", "another metric grouping key")
key1DB = keys.NewString("1_db", "A test string key")
key2aAge = keys.NewFloat64("2a_age", "A test float64 key")
key2bTTL = keys.NewFloat32("2b_ttl", "A test float32 key")
key2cExpiryMS = keys.NewFloat64("2c_expiry_ms", "A test float64 key")
key3aRetry = keys.NewBoolean("3a_retry", "A test boolean key")
key3bStale = keys.NewBoolean("3b_stale", "Another test boolean key")
key4aMax = keys.NewInt("4a_max", "A test int key")
key4bOpcode = keys.NewInt8("4b_opcode", "A test int8 key")
key4cBase = keys.NewInt16("4c_base", "A test int16 key")
key4eChecksum = keys.NewInt32("4e_checksum", "A test int32 key")
key4fMode = keys.NewInt64("4f_mode", "A test int64 key")
key5aMin = keys.NewUInt("5a_min", "A test uint key")
key5bMix = keys.NewUInt8("5b_mix", "A test uint8 key")
key5cPort = keys.NewUInt16("5c_port", "A test uint16 key")
key5dMinHops = keys.NewUInt32("5d_min_hops", "A test uint32 key")
key5eMaxHops = keys.NewUInt64("5e_max_hops", "A test uint64 key")
recursiveCalls = keys.NewInt64("recursive_calls", "Number of recursive calls")
bytesIn = keys.NewInt64("bytes_in", "Number of bytes in") //, unit.Bytes)
latencyMs = keys.NewFloat64("latency", "The latency in milliseconds") //, unit.Milliseconds)
metricLatency = metric.HistogramFloat64{
Name: "latency_ms",
Description: "The latency of calls in milliseconds",
Keys: []label.Key{keyMethod, keyRoute},
Buckets: []float64{0, 5, 10, 25, 50},
}
metricBytesIn = metric.HistogramInt64{
Name: "latency_ms",
Description: "The latency of calls in milliseconds",
Keys: []label.Key{keyMethod, keyRoute},
Buckets: []int64{0, 10, 50, 100, 500, 1000, 2000},
}
metricRecursiveCalls = metric.Scalar{
Name: "latency_ms",
Description: "The latency of calls in milliseconds",
Keys: []label.Key{keyMethod, keyRoute},
}
)
type testExporter struct {
ocagent *ocagent.Exporter
sent fakeSender
}
func registerExporter() *testExporter {
exporter := &testExporter{}
cfg := ocagent.Config{
Host: "tester",
Process: 1,
Service: "ocagent-tests",
Client: &http.Client{Transport: &exporter.sent},
}
cfg.Start, _ = time.Parse(time.RFC3339Nano, "1970-01-01T00:00:00Z")
exporter.ocagent = ocagent.Connect(&cfg)
metrics := metric.Config{}
metricLatency.Record(&metrics, latencyMs)
metricBytesIn.Record(&metrics, bytesIn)
metricRecursiveCalls.SumInt64(&metrics, recursiveCalls)
e := exporter.ocagent.ProcessEvent
e = metrics.Exporter(e)
e = spanFixer(e)
e = export.Spans(e)
e = export.Labels(e)
e = timeFixer(e)
event.SetExporter(e)
return exporter
}
func timeFixer(output event.Exporter) event.Exporter {
start, _ := time.Parse(time.RFC3339Nano, "1970-01-01T00:00:30Z")
at, _ := time.Parse(time.RFC3339Nano, "1970-01-01T00:00:40Z")
end, _ := time.Parse(time.RFC3339Nano, "1970-01-01T00:00:50Z")
return func(ctx context.Context, ev core.Event, lm label.Map) context.Context {
switch {
case event.IsStart(ev):
ev = core.CloneEvent(ev, start)
case event.IsEnd(ev):
ev = core.CloneEvent(ev, end)
default:
ev = core.CloneEvent(ev, at)
}
return output(ctx, ev, lm)
}
}
func spanFixer(output event.Exporter) event.Exporter {
return func(ctx context.Context, ev core.Event, lm label.Map) context.Context {
if event.IsStart(ev) {
span := export.GetSpan(ctx)
span.ID = export.SpanContext{}
}
return output(ctx, ev, lm)
}
}
func (e *testExporter) Output(route string) []byte {
e.ocagent.Flush()
return e.sent.get(route)
}
func checkJSON(t *testing.T, got, want []byte) {
// compare the compact form, to allow for formatting differences
g := &bytes.Buffer{}
if err := json.Compact(g, got); err != nil {
t.Fatal(err)
}
w := &bytes.Buffer{}
if err := json.Compact(w, want); err != nil {
t.Fatal(err)
}
if g.String() != w.String() {
t.Fatalf("Got:\n%s\nWant:\n%s", g, w)
}
}
type fakeSender struct {
mu sync.Mutex
data map[string][]byte
}
func (s *fakeSender) get(route string) []byte {
s.mu.Lock()
defer s.mu.Unlock()
data, found := s.data[route]
if found {
delete(s.data, route)
}
return data
}
func (s *fakeSender) RoundTrip(req *http.Request) (*http.Response, error) {
s.mu.Lock()
defer s.mu.Unlock()
if s.data == nil {
s.data = make(map[string][]byte)
}
data, err := io.ReadAll(req.Body)
if err != nil {
return nil, err
}
path := req.URL.EscapedPath()
if _, found := s.data[path]; found {
return nil, fmt.Errorf("duplicate delivery to %v", path)
}
s.data[path] = data
return &http.Response{
Status: "200 OK",
StatusCode: 200,
Proto: "HTTP/1.0",
ProtoMajor: 1,
ProtoMinor: 0,
}, nil
}
@@ -0,0 +1,158 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ocagent_test
import (
"context"
"errors"
"testing"
"golang.org/x/tools/internal/event"
)
func TestTrace(t *testing.T) {
exporter := registerExporter()
const prefix = testNodeStr + `
"spans":[{
"trace_id":"AAAAAAAAAAAAAAAAAAAAAA==",
"span_id":"AAAAAAAAAAA=",
"parent_span_id":"AAAAAAAAAAA=",
"name":{"value":"event span"},
"start_time":"1970-01-01T00:00:30Z",
"end_time":"1970-01-01T00:00:50Z",
"time_events":{
`
const suffix = `
},
"same_process_as_parent_span":true
}]
}`
tests := []struct {
name string
run func(ctx context.Context)
want string
}{
{
name: "no labels",
run: func(ctx context.Context) {
event.Label(ctx)
},
want: prefix + `
"timeEvent":[{"time":"1970-01-01T00:00:40Z"}]
` + suffix,
},
{
name: "description no error",
run: func(ctx context.Context) {
event.Log(ctx, "cache miss", keyDB.Of("godb"))
},
want: prefix + `"timeEvent":[{"time":"1970-01-01T00:00:40Z","annotation":{
"description": { "value": "cache miss" },
"attributes": {
"attributeMap": {
"db": { "stringValue": { "value": "godb" } }
}
}
}}]` + suffix,
},
{
name: "description and error",
run: func(ctx context.Context) {
event.Error(ctx, "cache miss",
errors.New("no network connectivity"),
keyDB.Of("godb"),
)
},
want: prefix + `"timeEvent":[{"time":"1970-01-01T00:00:40Z","annotation":{
"description": { "value": "cache miss" },
"attributes": {
"attributeMap": {
"db": { "stringValue": { "value": "godb" } },
"error": { "stringValue": { "value": "no network connectivity" } }
}
}
}}]` + suffix,
},
{
name: "no description, but error",
run: func(ctx context.Context) {
event.Error(ctx, "",
errors.New("no network connectivity"),
keyDB.Of("godb"),
)
},
want: prefix + `"timeEvent":[{"time":"1970-01-01T00:00:40Z","annotation":{
"description": { "value": "no network connectivity" },
"attributes": {
"attributeMap": {
"db": { "stringValue": { "value": "godb" } }
}
}
}}]` + suffix,
},
{
name: "enumerate all attribute types",
run: func(ctx context.Context) {
event.Log(ctx, "cache miss",
key1DB.Of("godb"),
key2aAge.Of(0.456), // Constant converted into "float64"
key2bTTL.Of(float32(5000)),
key2cExpiryMS.Of(float64(1e3)),
key3aRetry.Of(false),
key3bStale.Of(true),
key4aMax.Of(0x7fff), // Constant converted into "int"
key4bOpcode.Of(int8(0x7e)),
key4cBase.Of(int16(1<<9)),
key4eChecksum.Of(int32(0x11f7e294)),
key4fMode.Of(int64(0644)),
key5aMin.Of(uint(1)),
key5bMix.Of(uint8(44)),
key5cPort.Of(uint16(55678)),
key5dMinHops.Of(uint32(1<<9)),
key5eMaxHops.Of(uint64(0xffffff)),
)
},
want: prefix + `"timeEvent":[{"time":"1970-01-01T00:00:40Z","annotation":{
"description": { "value": "cache miss" },
"attributes": {
"attributeMap": {
"1_db": { "stringValue": { "value": "godb" } },
"2a_age": { "doubleValue": 0.456 },
"2b_ttl": { "doubleValue": 5000 },
"2c_expiry_ms": { "doubleValue": 1000 },
"3a_retry": {},
"3b_stale": { "boolValue": true },
"4a_max": { "intValue": 32767 },
"4b_opcode": { "intValue": 126 },
"4c_base": { "intValue": 512 },
"4e_checksum": { "intValue": 301458068 },
"4f_mode": { "intValue": 420 },
"5a_min": { "intValue": 1 },
"5b_mix": { "intValue": 44 },
"5c_port": { "intValue": 55678 },
"5d_min_hops": { "intValue": 512 },
"5e_max_hops": { "intValue": 16777215 }
}
}
}}]` + suffix,
},
}
ctx := context.TODO()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx, done := event.Start(ctx, "event span")
tt.run(ctx)
done()
got := exporter.Output("/v1/trace")
checkJSON(t, got, []byte(tt.want))
})
}
}
@@ -0,0 +1,101 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package wire
// This file holds common ocagent types
type Node struct {
Identifier *ProcessIdentifier `json:"identifier,omitempty"`
LibraryInfo *LibraryInfo `json:"library_info,omitempty"`
ServiceInfo *ServiceInfo `json:"service_info,omitempty"`
Attributes map[string]string `json:"attributes,omitempty"`
}
type Resource struct {
Type string `json:"type,omitempty"`
Labels map[string]string `json:"labels,omitempty"`
}
type TruncatableString struct {
Value string `json:"value,omitempty"`
TruncatedByteCount int32 `json:"truncated_byte_count,omitempty"`
}
type Attributes struct {
AttributeMap map[string]Attribute `json:"attributeMap,omitempty"`
DroppedAttributesCount int32 `json:"dropped_attributes_count,omitempty"`
}
type StringAttribute struct {
StringValue *TruncatableString `json:"stringValue,omitempty"`
}
type IntAttribute struct {
IntValue int64 `json:"intValue,omitempty"`
}
type BoolAttribute struct {
BoolValue bool `json:"boolValue,omitempty"`
}
type DoubleAttribute struct {
DoubleValue float64 `json:"doubleValue,omitempty"`
}
type Attribute interface {
labelAttribute()
}
func (StringAttribute) labelAttribute() {}
func (IntAttribute) labelAttribute() {}
func (BoolAttribute) labelAttribute() {}
func (DoubleAttribute) labelAttribute() {}
type StackTrace struct {
StackFrames *StackFrames `json:"stack_frames,omitempty"`
StackTraceHashID uint64 `json:"stack_trace_hash_id,omitempty"`
}
type StackFrames struct {
Frame []*StackFrame `json:"frame,omitempty"`
DroppedFramesCount int32 `json:"dropped_frames_count,omitempty"`
}
type StackFrame struct {
FunctionName *TruncatableString `json:"function_name,omitempty"`
OriginalFunctionName *TruncatableString `json:"original_function_name,omitempty"`
FileName *TruncatableString `json:"file_name,omitempty"`
LineNumber int64 `json:"line_number,omitempty"`
ColumnNumber int64 `json:"column_number,omitempty"`
LoadModule *Module `json:"load_module,omitempty"`
SourceVersion *TruncatableString `json:"source_version,omitempty"`
}
type Module struct {
Module *TruncatableString `json:"module,omitempty"`
BuildID *TruncatableString `json:"build_id,omitempty"`
}
type ProcessIdentifier struct {
HostName string `json:"host_name,omitempty"`
Pid uint32 `json:"pid,omitempty"`
StartTimestamp Timestamp `json:"start_timestamp,omitempty"`
}
type LibraryInfo struct {
Language Language `json:"language,omitempty"`
ExporterVersion string `json:"exporter_version,omitempty"`
CoreLibraryVersion string `json:"core_library_version,omitempty"`
}
type Language int32
const (
LanguageGo Language = 4
)
type ServiceInfo struct {
Name string `json:"name,omitempty"`
}
@@ -0,0 +1,17 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package wire
// This file contains type that match core proto types
type Timestamp = string
type Int64Value struct {
Value int64 `json:"value,omitempty"`
}
type DoubleValue struct {
Value float64 `json:"value,omitempty"`
}
@@ -0,0 +1,204 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package wire
import (
"encoding/json"
"fmt"
)
type ExportMetricsServiceRequest struct {
Node *Node `json:"node,omitempty"`
Metrics []*Metric `json:"metrics,omitempty"`
Resource *Resource `json:"resource,omitempty"`
}
type Metric struct {
MetricDescriptor *MetricDescriptor `json:"metric_descriptor,omitempty"`
Timeseries []*TimeSeries `json:"timeseries,omitempty"`
Resource *Resource `json:"resource,omitempty"`
}
type MetricDescriptor struct {
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
Unit string `json:"unit,omitempty"`
Type MetricDescriptor_Type `json:"type,omitempty"`
LabelKeys []*LabelKey `json:"label_keys,omitempty"`
}
type MetricDescriptor_Type int32
const (
MetricDescriptor_UNSPECIFIED MetricDescriptor_Type = 0
MetricDescriptor_GAUGE_INT64 MetricDescriptor_Type = 1
MetricDescriptor_GAUGE_DOUBLE MetricDescriptor_Type = 2
MetricDescriptor_GAUGE_DISTRIBUTION MetricDescriptor_Type = 3
MetricDescriptor_CUMULATIVE_INT64 MetricDescriptor_Type = 4
MetricDescriptor_CUMULATIVE_DOUBLE MetricDescriptor_Type = 5
MetricDescriptor_CUMULATIVE_DISTRIBUTION MetricDescriptor_Type = 6
MetricDescriptor_SUMMARY MetricDescriptor_Type = 7
)
type LabelKey struct {
Key string `json:"key,omitempty"`
Description string `json:"description,omitempty"`
}
type TimeSeries struct {
StartTimestamp *Timestamp `json:"start_timestamp,omitempty"`
LabelValues []*LabelValue `json:"label_values,omitempty"`
Points []*Point `json:"points,omitempty"`
}
type LabelValue struct {
Value string `json:"value,omitempty"`
HasValue bool `json:"has_value,omitempty"`
}
type Point struct {
Timestamp *Timestamp `json:"timestamp,omitempty"`
Value PointValue `json:"value,omitempty"`
}
type PointInt64Value struct {
Int64Value int64 `json:"int64Value,omitempty"`
}
// MarshalJSON creates JSON formatted the same way as jsonpb so that the
// OpenCensus service can correctly determine the underlying value type.
// This custom MarshalJSON exists because,
// by default *Point is JSON marshalled as:
//
// {"value": {"int64Value": 1}}
//
// but it should be marshalled as:
//
// {"int64Value": 1}
func (p *Point) MarshalJSON() ([]byte, error) {
if p == nil {
return []byte("null"), nil
}
switch d := p.Value.(type) {
case PointInt64Value:
return json.Marshal(&struct {
Timestamp *Timestamp `json:"timestamp,omitempty"`
Value int64 `json:"int64Value,omitempty"`
}{
Timestamp: p.Timestamp,
Value: d.Int64Value,
})
case PointDoubleValue:
return json.Marshal(&struct {
Timestamp *Timestamp `json:"timestamp,omitempty"`
Value float64 `json:"doubleValue,omitempty"`
}{
Timestamp: p.Timestamp,
Value: d.DoubleValue,
})
case PointDistributionValue:
return json.Marshal(&struct {
Timestamp *Timestamp `json:"timestamp,omitempty"`
Value *DistributionValue `json:"distributionValue,omitempty"`
}{
Timestamp: p.Timestamp,
Value: d.DistributionValue,
})
default:
return nil, fmt.Errorf("unknown point type %T", p.Value)
}
}
type PointDoubleValue struct {
DoubleValue float64 `json:"doubleValue,omitempty"`
}
type PointDistributionValue struct {
DistributionValue *DistributionValue `json:"distributionValue,omitempty"`
}
type PointSummaryValue struct {
SummaryValue *SummaryValue `json:"summaryValue,omitempty"`
}
type PointValue interface {
labelPointValue()
}
func (PointInt64Value) labelPointValue() {}
func (PointDoubleValue) labelPointValue() {}
func (PointDistributionValue) labelPointValue() {}
func (PointSummaryValue) labelPointValue() {}
type DistributionValue struct {
Count int64 `json:"count,omitempty"`
Sum float64 `json:"sum,omitempty"`
SumOfSquaredDeviation float64 `json:"sum_of_squared_deviation,omitempty"`
BucketOptions BucketOptions `json:"bucket_options,omitempty"`
Buckets []*Bucket `json:"buckets,omitempty"`
}
type BucketOptionsExplicit struct {
Bounds []float64 `json:"bounds,omitempty"`
}
type BucketOptions interface {
labelBucketOptions()
}
func (*BucketOptionsExplicit) labelBucketOptions() {}
var _ BucketOptions = (*BucketOptionsExplicit)(nil)
var _ json.Marshaler = (*BucketOptionsExplicit)(nil)
// Declared for the purpose of custom JSON marshaling without cycles.
type bucketOptionsExplicitAlias BucketOptionsExplicit
// MarshalJSON creates JSON formatted the same way as jsonpb so that the
// OpenCensus service can correctly determine the underlying value type.
// This custom MarshalJSON exists because,
// by default BucketOptionsExplicit is JSON marshalled as:
//
// {"bounds":[1,2,3]}
//
// but it should be marshalled as:
//
// {"explicit":{"bounds":[1,2,3]}}
func (be *BucketOptionsExplicit) MarshalJSON() ([]byte, error) {
return json.Marshal(&struct {
Explicit *bucketOptionsExplicitAlias `json:"explicit,omitempty"`
}{
Explicit: (*bucketOptionsExplicitAlias)(be),
})
}
type Bucket struct {
Count int64 `json:"count,omitempty"`
Exemplar *Exemplar `json:"exemplar,omitempty"`
}
type Exemplar struct {
Value float64 `json:"value,omitempty"`
Timestamp *Timestamp `json:"timestamp,omitempty"`
Attachments map[string]string `json:"attachments,omitempty"`
}
type SummaryValue struct {
Count *Int64Value `json:"count,omitempty"`
Sum *DoubleValue `json:"sum,omitempty"`
Snapshot *Snapshot `json:"snapshot,omitempty"`
}
type Snapshot struct {
Count *Int64Value `json:"count,omitempty"`
Sum *DoubleValue `json:"sum,omitempty"`
PercentileValues []*SnapshotValueAtPercentile `json:"percentile_values,omitempty"`
}
type SnapshotValueAtPercentile struct {
Percentile float64 `json:"percentile,omitempty"`
Value float64 `json:"value,omitempty"`
}
@@ -0,0 +1,80 @@
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package wire
import (
"reflect"
"testing"
)
func TestMarshalJSON(t *testing.T) {
tests := []struct {
name string
pt *Point
want string
}{
{
"PointInt64",
&Point{
Value: PointInt64Value{
Int64Value: 5,
},
},
`{"int64Value":5}`,
},
{
"PointDouble",
&Point{
Value: PointDoubleValue{
DoubleValue: 3.14,
},
},
`{"doubleValue":3.14}`,
},
{
"PointDistribution",
&Point{
Value: PointDistributionValue{
DistributionValue: &DistributionValue{
Count: 3,
Sum: 10,
Buckets: []*Bucket{
{
Count: 1,
},
{
Count: 2,
},
},
BucketOptions: &BucketOptionsExplicit{
Bounds: []float64{
0, 5,
},
},
},
},
},
`{"distributionValue":{"count":3,"sum":10,"bucket_options":{"explicit":{"bounds":[0,5]}},"buckets":[{"count":1},{"count":2}]}}`,
},
{
"nil point",
nil,
`null`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
buf, err := tt.pt.MarshalJSON()
if err != nil {
t.Fatalf("Got:\n%v\nWant:\n%v", err, nil)
}
got := string(buf)
if !reflect.DeepEqual(got, tt.want) {
t.Fatalf("Got:\n%s\nWant:\n%s", got, tt.want)
}
})
}
}
@@ -0,0 +1,112 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package wire
type ExportTraceServiceRequest struct {
Node *Node `json:"node,omitempty"`
Spans []*Span `json:"spans,omitempty"`
Resource *Resource `json:"resource,omitempty"`
}
type Span struct {
TraceID []byte `json:"trace_id,omitempty"`
SpanID []byte `json:"span_id,omitempty"`
TraceState *TraceState `json:"tracestate,omitempty"`
ParentSpanID []byte `json:"parent_span_id,omitempty"`
Name *TruncatableString `json:"name,omitempty"`
Kind SpanKind `json:"kind,omitempty"`
StartTime Timestamp `json:"start_time,omitempty"`
EndTime Timestamp `json:"end_time,omitempty"`
Attributes *Attributes `json:"attributes,omitempty"`
StackTrace *StackTrace `json:"stack_trace,omitempty"`
TimeEvents *TimeEvents `json:"time_events,omitempty"`
Links *Links `json:"links,omitempty"`
Status *Status `json:"status,omitempty"`
Resource *Resource `json:"resource,omitempty"`
SameProcessAsParentSpan bool `json:"same_process_as_parent_span,omitempty"`
ChildSpanCount bool `json:"child_span_count,omitempty"`
}
type TraceState struct {
Entries []*TraceStateEntry `json:"entries,omitempty"`
}
type TraceStateEntry struct {
Key string `json:"key,omitempty"`
Value string `json:"value,omitempty"`
}
type SpanKind int32
const (
UnspecifiedSpanKind SpanKind = 0
ServerSpanKind SpanKind = 1
ClientSpanKind SpanKind = 2
)
type TimeEvents struct {
TimeEvent []TimeEvent `json:"timeEvent,omitempty"`
DroppedAnnotationsCount int32 `json:"dropped_annotations_count,omitempty"`
DroppedMessageEventsCount int32 `json:"dropped_message_events_count,omitempty"`
}
type TimeEvent struct {
Time Timestamp `json:"time,omitempty"`
MessageEvent *MessageEvent `json:"messageEvent,omitempty"`
Annotation *Annotation `json:"annotation,omitempty"`
}
type Annotation struct {
Description *TruncatableString `json:"description,omitempty"`
Attributes *Attributes `json:"attributes,omitempty"`
}
type MessageEvent struct {
Type MessageEventType `json:"type,omitempty"`
ID uint64 `json:"id,omitempty"`
UncompressedSize uint64 `json:"uncompressed_size,omitempty"`
CompressedSize uint64 `json:"compressed_size,omitempty"`
}
type MessageEventType int32
const (
UnspecifiedMessageEvent MessageEventType = iota
SentMessageEvent
ReceivedMessageEvent
)
type TimeEventValue interface {
labelTimeEventValue()
}
func (Annotation) labelTimeEventValue() {}
func (MessageEvent) labelTimeEventValue() {}
type Links struct {
Link []*Link `json:"link,omitempty"`
DroppedLinksCount int32 `json:"dropped_links_count,omitempty"`
}
type Link struct {
TraceID []byte `json:"trace_id,omitempty"`
SpanID []byte `json:"span_id,omitempty"`
Type LinkType `json:"type,omitempty"`
Attributes *Attributes `json:"attributes,omitempty"`
TraceState *TraceState `json:"tracestate,omitempty"`
}
type LinkType int32
const (
UnspecifiedLinkType LinkType = 0
ChildLinkType LinkType = 1
ParentLinkType LinkType = 2
)
type Status struct {
Code int32 `json:"code,omitempty"`
Message string `json:"message,omitempty"`
}
@@ -0,0 +1,43 @@
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package export
import (
"io"
"golang.org/x/tools/internal/event/core"
"golang.org/x/tools/internal/event/keys"
"golang.org/x/tools/internal/event/label"
)
type Printer struct {
buffer [128]byte
}
func (p *Printer) WriteEvent(w io.Writer, ev core.Event, lm label.Map) {
buf := p.buffer[:0]
if !ev.At().IsZero() {
w.Write(ev.At().AppendFormat(buf, "2006/01/02 15:04:05 "))
}
msg := keys.Msg.Get(lm)
io.WriteString(w, msg)
if err := keys.Err.Get(lm); err != nil {
if msg != "" {
io.WriteString(w, ": ")
}
io.WriteString(w, err.Error())
}
for index := 0; ev.Valid(index); index++ {
l := ev.Label(index)
if !l.Valid() || l.Key() == keys.Msg || l.Key() == keys.Err {
continue
}
io.WriteString(w, "\n\t")
io.WriteString(w, l.Key().Name())
io.WriteString(w, "=")
l.Key().Format(w, buf, l)
}
io.WriteString(w, "\n")
}
@@ -0,0 +1,129 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package prometheus
import (
"bytes"
"context"
"fmt"
"net/http"
"sort"
"sync"
"golang.org/x/tools/internal/event"
"golang.org/x/tools/internal/event/core"
"golang.org/x/tools/internal/event/export/metric"
"golang.org/x/tools/internal/event/label"
)
func New() *Exporter {
return &Exporter{}
}
type Exporter struct {
mu sync.Mutex
metrics []metric.Data
}
func (e *Exporter) ProcessEvent(ctx context.Context, ev core.Event, lm label.Map) context.Context {
if !event.IsMetric(ev) {
return ctx
}
e.mu.Lock()
defer e.mu.Unlock()
metrics := metric.Entries.Get(lm).([]metric.Data)
for _, data := range metrics {
name := data.Handle()
// We keep the metrics in name sorted order so the page is stable and easy
// to read. We do this with an insertion sort rather than sorting the list
// each time
index := sort.Search(len(e.metrics), func(i int) bool {
return e.metrics[i].Handle() >= name
})
if index >= len(e.metrics) || e.metrics[index].Handle() != name {
// we have a new metric, so we need to make a space for it
old := e.metrics
e.metrics = make([]metric.Data, len(old)+1)
copy(e.metrics, old[:index])
copy(e.metrics[index+1:], old[index:])
}
e.metrics[index] = data
}
return ctx
}
func (e *Exporter) header(w http.ResponseWriter, name, description string, isGauge, isHistogram bool) {
kind := "counter"
if isGauge {
kind = "gauge"
}
if isHistogram {
kind = "histogram"
}
fmt.Fprintf(w, "# HELP %s %s\n", name, description)
fmt.Fprintf(w, "# TYPE %s %s\n", name, kind)
}
func (e *Exporter) row(w http.ResponseWriter, name string, group []label.Label, extra string, value interface{}) {
fmt.Fprint(w, name)
buf := &bytes.Buffer{}
fmt.Fprint(buf, group)
if extra != "" {
if buf.Len() > 0 {
fmt.Fprint(buf, ",")
}
fmt.Fprint(buf, extra)
}
if buf.Len() > 0 {
fmt.Fprint(w, "{")
buf.WriteTo(w)
fmt.Fprint(w, "}")
}
fmt.Fprintf(w, " %v\n", value)
}
func (e *Exporter) Serve(w http.ResponseWriter, r *http.Request) {
e.mu.Lock()
defer e.mu.Unlock()
for _, data := range e.metrics {
switch data := data.(type) {
case *metric.Int64Data:
e.header(w, data.Info.Name, data.Info.Description, data.IsGauge, false)
for i, group := range data.Groups() {
e.row(w, data.Info.Name, group, "", data.Rows[i])
}
case *metric.Float64Data:
e.header(w, data.Info.Name, data.Info.Description, data.IsGauge, false)
for i, group := range data.Groups() {
e.row(w, data.Info.Name, group, "", data.Rows[i])
}
case *metric.HistogramInt64Data:
e.header(w, data.Info.Name, data.Info.Description, false, true)
for i, group := range data.Groups() {
row := data.Rows[i]
for j, b := range data.Info.Buckets {
e.row(w, data.Info.Name+"_bucket", group, fmt.Sprintf(`le="%v"`, b), row.Values[j])
}
e.row(w, data.Info.Name+"_bucket", group, `le="+Inf"`, row.Count)
e.row(w, data.Info.Name+"_count", group, "", row.Count)
e.row(w, data.Info.Name+"_sum", group, "", row.Sum)
}
case *metric.HistogramFloat64Data:
e.header(w, data.Info.Name, data.Info.Description, false, true)
for i, group := range data.Groups() {
row := data.Rows[i]
for j, b := range data.Info.Buckets {
e.row(w, data.Info.Name+"_bucket", group, fmt.Sprintf(`le="%v"`, b), row.Values[j])
}
e.row(w, data.Info.Name+"_bucket", group, `le="+Inf"`, row.Count)
e.row(w, data.Info.Name+"_count", group, "", row.Count)
e.row(w, data.Info.Name+"_sum", group, "", row.Sum)
}
}
}
}
@@ -0,0 +1,117 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package export
import (
"context"
"fmt"
"sync"
"golang.org/x/tools/internal/event"
"golang.org/x/tools/internal/event/core"
"golang.org/x/tools/internal/event/keys"
"golang.org/x/tools/internal/event/label"
)
type SpanContext struct {
TraceID TraceID
SpanID SpanID
}
type Span struct {
Name string
ID SpanContext
ParentID SpanID
mu sync.Mutex
start core.Event
finish core.Event
events []core.Event
}
type contextKeyType int
const (
spanContextKey = contextKeyType(iota)
labelContextKey
)
func GetSpan(ctx context.Context) *Span {
v := ctx.Value(spanContextKey)
if v == nil {
return nil
}
return v.(*Span)
}
// Spans creates an exporter that maintains hierarchical span structure in the
// context.
// It creates new spans on start events, adds events to the current span on
// log or label, and closes the span on end events.
// The span structure can then be used by other exporters.
func Spans(output event.Exporter) event.Exporter {
return func(ctx context.Context, ev core.Event, lm label.Map) context.Context {
switch {
case event.IsLog(ev), event.IsLabel(ev):
if span := GetSpan(ctx); span != nil {
span.mu.Lock()
span.events = append(span.events, ev)
span.mu.Unlock()
}
case event.IsStart(ev):
span := &Span{
Name: keys.Start.Get(lm),
start: ev,
}
if parent := GetSpan(ctx); parent != nil {
span.ID.TraceID = parent.ID.TraceID
span.ParentID = parent.ID.SpanID
} else {
span.ID.TraceID = newTraceID()
}
span.ID.SpanID = newSpanID()
ctx = context.WithValue(ctx, spanContextKey, span)
case event.IsEnd(ev):
if span := GetSpan(ctx); span != nil {
span.mu.Lock()
span.finish = ev
span.mu.Unlock()
}
case event.IsDetach(ev):
ctx = context.WithValue(ctx, spanContextKey, nil)
}
return output(ctx, ev, lm)
}
}
func (s *SpanContext) Format(f fmt.State, r rune) {
fmt.Fprintf(f, "%v:%v", s.TraceID, s.SpanID)
}
func (s *Span) Start() core.Event {
// start never changes after construction, so we don't need to hold the mutex
return s.start
}
func (s *Span) Finish() core.Event {
s.mu.Lock()
defer s.mu.Unlock()
return s.finish
}
func (s *Span) Events() []core.Event {
s.mu.Lock()
defer s.mu.Unlock()
return s.events
}
func (s *Span) Format(f fmt.State, r rune) {
s.mu.Lock()
defer s.mu.Unlock()
fmt.Fprintf(f, "%v %v", s.Name, s.ID)
if s.ParentID.IsValid() {
fmt.Fprintf(f, "[%v]", s.ParentID)
}
fmt.Fprintf(f, " %v->%v", s.start, s.finish)
}
@@ -0,0 +1,564 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package keys
import (
"fmt"
"io"
"math"
"strconv"
"golang.org/x/tools/internal/event/label"
)
// Value represents a key for untyped values.
type Value struct {
name string
description string
}
// New creates a new Key for untyped values.
func New(name, description string) *Value {
return &Value{name: name, description: description}
}
func (k *Value) Name() string { return k.name }
func (k *Value) Description() string { return k.description }
func (k *Value) Format(w io.Writer, buf []byte, l label.Label) {
fmt.Fprint(w, k.From(l))
}
// Get can be used to get a label for the key from a label.Map.
func (k *Value) Get(lm label.Map) interface{} {
if t := lm.Find(k); t.Valid() {
return k.From(t)
}
return nil
}
// From can be used to get a value from a Label.
func (k *Value) From(t label.Label) interface{} { return t.UnpackValue() }
// Of creates a new Label with this key and the supplied value.
func (k *Value) Of(value interface{}) label.Label { return label.OfValue(k, value) }
// Tag represents a key for tagging labels that have no value.
// These are used when the existence of the label is the entire information it
// carries, such as marking events to be of a specific kind, or from a specific
// package.
type Tag struct {
name string
description string
}
// NewTag creates a new Key for tagging labels.
func NewTag(name, description string) *Tag {
return &Tag{name: name, description: description}
}
func (k *Tag) Name() string { return k.name }
func (k *Tag) Description() string { return k.description }
func (k *Tag) Format(w io.Writer, buf []byte, l label.Label) {}
// New creates a new Label with this key.
func (k *Tag) New() label.Label { return label.OfValue(k, nil) }
// Int represents a key
type Int struct {
name string
description string
}
// NewInt creates a new Key for int values.
func NewInt(name, description string) *Int {
return &Int{name: name, description: description}
}
func (k *Int) Name() string { return k.name }
func (k *Int) Description() string { return k.description }
func (k *Int) Format(w io.Writer, buf []byte, l label.Label) {
w.Write(strconv.AppendInt(buf, int64(k.From(l)), 10))
}
// Of creates a new Label with this key and the supplied value.
func (k *Int) Of(v int) label.Label { return label.Of64(k, uint64(v)) }
// Get can be used to get a label for the key from a label.Map.
func (k *Int) Get(lm label.Map) int {
if t := lm.Find(k); t.Valid() {
return k.From(t)
}
return 0
}
// From can be used to get a value from a Label.
func (k *Int) From(t label.Label) int { return int(t.Unpack64()) }
// Int8 represents a key
type Int8 struct {
name string
description string
}
// NewInt8 creates a new Key for int8 values.
func NewInt8(name, description string) *Int8 {
return &Int8{name: name, description: description}
}
func (k *Int8) Name() string { return k.name }
func (k *Int8) Description() string { return k.description }
func (k *Int8) Format(w io.Writer, buf []byte, l label.Label) {
w.Write(strconv.AppendInt(buf, int64(k.From(l)), 10))
}
// Of creates a new Label with this key and the supplied value.
func (k *Int8) Of(v int8) label.Label { return label.Of64(k, uint64(v)) }
// Get can be used to get a label for the key from a label.Map.
func (k *Int8) Get(lm label.Map) int8 {
if t := lm.Find(k); t.Valid() {
return k.From(t)
}
return 0
}
// From can be used to get a value from a Label.
func (k *Int8) From(t label.Label) int8 { return int8(t.Unpack64()) }
// Int16 represents a key
type Int16 struct {
name string
description string
}
// NewInt16 creates a new Key for int16 values.
func NewInt16(name, description string) *Int16 {
return &Int16{name: name, description: description}
}
func (k *Int16) Name() string { return k.name }
func (k *Int16) Description() string { return k.description }
func (k *Int16) Format(w io.Writer, buf []byte, l label.Label) {
w.Write(strconv.AppendInt(buf, int64(k.From(l)), 10))
}
// Of creates a new Label with this key and the supplied value.
func (k *Int16) Of(v int16) label.Label { return label.Of64(k, uint64(v)) }
// Get can be used to get a label for the key from a label.Map.
func (k *Int16) Get(lm label.Map) int16 {
if t := lm.Find(k); t.Valid() {
return k.From(t)
}
return 0
}
// From can be used to get a value from a Label.
func (k *Int16) From(t label.Label) int16 { return int16(t.Unpack64()) }
// Int32 represents a key
type Int32 struct {
name string
description string
}
// NewInt32 creates a new Key for int32 values.
func NewInt32(name, description string) *Int32 {
return &Int32{name: name, description: description}
}
func (k *Int32) Name() string { return k.name }
func (k *Int32) Description() string { return k.description }
func (k *Int32) Format(w io.Writer, buf []byte, l label.Label) {
w.Write(strconv.AppendInt(buf, int64(k.From(l)), 10))
}
// Of creates a new Label with this key and the supplied value.
func (k *Int32) Of(v int32) label.Label { return label.Of64(k, uint64(v)) }
// Get can be used to get a label for the key from a label.Map.
func (k *Int32) Get(lm label.Map) int32 {
if t := lm.Find(k); t.Valid() {
return k.From(t)
}
return 0
}
// From can be used to get a value from a Label.
func (k *Int32) From(t label.Label) int32 { return int32(t.Unpack64()) }
// Int64 represents a key
type Int64 struct {
name string
description string
}
// NewInt64 creates a new Key for int64 values.
func NewInt64(name, description string) *Int64 {
return &Int64{name: name, description: description}
}
func (k *Int64) Name() string { return k.name }
func (k *Int64) Description() string { return k.description }
func (k *Int64) Format(w io.Writer, buf []byte, l label.Label) {
w.Write(strconv.AppendInt(buf, k.From(l), 10))
}
// Of creates a new Label with this key and the supplied value.
func (k *Int64) Of(v int64) label.Label { return label.Of64(k, uint64(v)) }
// Get can be used to get a label for the key from a label.Map.
func (k *Int64) Get(lm label.Map) int64 {
if t := lm.Find(k); t.Valid() {
return k.From(t)
}
return 0
}
// From can be used to get a value from a Label.
func (k *Int64) From(t label.Label) int64 { return int64(t.Unpack64()) }
// UInt represents a key
type UInt struct {
name string
description string
}
// NewUInt creates a new Key for uint values.
func NewUInt(name, description string) *UInt {
return &UInt{name: name, description: description}
}
func (k *UInt) Name() string { return k.name }
func (k *UInt) Description() string { return k.description }
func (k *UInt) Format(w io.Writer, buf []byte, l label.Label) {
w.Write(strconv.AppendUint(buf, uint64(k.From(l)), 10))
}
// Of creates a new Label with this key and the supplied value.
func (k *UInt) Of(v uint) label.Label { return label.Of64(k, uint64(v)) }
// Get can be used to get a label for the key from a label.Map.
func (k *UInt) Get(lm label.Map) uint {
if t := lm.Find(k); t.Valid() {
return k.From(t)
}
return 0
}
// From can be used to get a value from a Label.
func (k *UInt) From(t label.Label) uint { return uint(t.Unpack64()) }
// UInt8 represents a key
type UInt8 struct {
name string
description string
}
// NewUInt8 creates a new Key for uint8 values.
func NewUInt8(name, description string) *UInt8 {
return &UInt8{name: name, description: description}
}
func (k *UInt8) Name() string { return k.name }
func (k *UInt8) Description() string { return k.description }
func (k *UInt8) Format(w io.Writer, buf []byte, l label.Label) {
w.Write(strconv.AppendUint(buf, uint64(k.From(l)), 10))
}
// Of creates a new Label with this key and the supplied value.
func (k *UInt8) Of(v uint8) label.Label { return label.Of64(k, uint64(v)) }
// Get can be used to get a label for the key from a label.Map.
func (k *UInt8) Get(lm label.Map) uint8 {
if t := lm.Find(k); t.Valid() {
return k.From(t)
}
return 0
}
// From can be used to get a value from a Label.
func (k *UInt8) From(t label.Label) uint8 { return uint8(t.Unpack64()) }
// UInt16 represents a key
type UInt16 struct {
name string
description string
}
// NewUInt16 creates a new Key for uint16 values.
func NewUInt16(name, description string) *UInt16 {
return &UInt16{name: name, description: description}
}
func (k *UInt16) Name() string { return k.name }
func (k *UInt16) Description() string { return k.description }
func (k *UInt16) Format(w io.Writer, buf []byte, l label.Label) {
w.Write(strconv.AppendUint(buf, uint64(k.From(l)), 10))
}
// Of creates a new Label with this key and the supplied value.
func (k *UInt16) Of(v uint16) label.Label { return label.Of64(k, uint64(v)) }
// Get can be used to get a label for the key from a label.Map.
func (k *UInt16) Get(lm label.Map) uint16 {
if t := lm.Find(k); t.Valid() {
return k.From(t)
}
return 0
}
// From can be used to get a value from a Label.
func (k *UInt16) From(t label.Label) uint16 { return uint16(t.Unpack64()) }
// UInt32 represents a key
type UInt32 struct {
name string
description string
}
// NewUInt32 creates a new Key for uint32 values.
func NewUInt32(name, description string) *UInt32 {
return &UInt32{name: name, description: description}
}
func (k *UInt32) Name() string { return k.name }
func (k *UInt32) Description() string { return k.description }
func (k *UInt32) Format(w io.Writer, buf []byte, l label.Label) {
w.Write(strconv.AppendUint(buf, uint64(k.From(l)), 10))
}
// Of creates a new Label with this key and the supplied value.
func (k *UInt32) Of(v uint32) label.Label { return label.Of64(k, uint64(v)) }
// Get can be used to get a label for the key from a label.Map.
func (k *UInt32) Get(lm label.Map) uint32 {
if t := lm.Find(k); t.Valid() {
return k.From(t)
}
return 0
}
// From can be used to get a value from a Label.
func (k *UInt32) From(t label.Label) uint32 { return uint32(t.Unpack64()) }
// UInt64 represents a key
type UInt64 struct {
name string
description string
}
// NewUInt64 creates a new Key for uint64 values.
func NewUInt64(name, description string) *UInt64 {
return &UInt64{name: name, description: description}
}
func (k *UInt64) Name() string { return k.name }
func (k *UInt64) Description() string { return k.description }
func (k *UInt64) Format(w io.Writer, buf []byte, l label.Label) {
w.Write(strconv.AppendUint(buf, k.From(l), 10))
}
// Of creates a new Label with this key and the supplied value.
func (k *UInt64) Of(v uint64) label.Label { return label.Of64(k, v) }
// Get can be used to get a label for the key from a label.Map.
func (k *UInt64) Get(lm label.Map) uint64 {
if t := lm.Find(k); t.Valid() {
return k.From(t)
}
return 0
}
// From can be used to get a value from a Label.
func (k *UInt64) From(t label.Label) uint64 { return t.Unpack64() }
// Float32 represents a key
type Float32 struct {
name string
description string
}
// NewFloat32 creates a new Key for float32 values.
func NewFloat32(name, description string) *Float32 {
return &Float32{name: name, description: description}
}
func (k *Float32) Name() string { return k.name }
func (k *Float32) Description() string { return k.description }
func (k *Float32) Format(w io.Writer, buf []byte, l label.Label) {
w.Write(strconv.AppendFloat(buf, float64(k.From(l)), 'E', -1, 32))
}
// Of creates a new Label with this key and the supplied value.
func (k *Float32) Of(v float32) label.Label {
return label.Of64(k, uint64(math.Float32bits(v)))
}
// Get can be used to get a label for the key from a label.Map.
func (k *Float32) Get(lm label.Map) float32 {
if t := lm.Find(k); t.Valid() {
return k.From(t)
}
return 0
}
// From can be used to get a value from a Label.
func (k *Float32) From(t label.Label) float32 {
return math.Float32frombits(uint32(t.Unpack64()))
}
// Float64 represents a key
type Float64 struct {
name string
description string
}
// NewFloat64 creates a new Key for int64 values.
func NewFloat64(name, description string) *Float64 {
return &Float64{name: name, description: description}
}
func (k *Float64) Name() string { return k.name }
func (k *Float64) Description() string { return k.description }
func (k *Float64) Format(w io.Writer, buf []byte, l label.Label) {
w.Write(strconv.AppendFloat(buf, k.From(l), 'E', -1, 64))
}
// Of creates a new Label with this key and the supplied value.
func (k *Float64) Of(v float64) label.Label {
return label.Of64(k, math.Float64bits(v))
}
// Get can be used to get a label for the key from a label.Map.
func (k *Float64) Get(lm label.Map) float64 {
if t := lm.Find(k); t.Valid() {
return k.From(t)
}
return 0
}
// From can be used to get a value from a Label.
func (k *Float64) From(t label.Label) float64 {
return math.Float64frombits(t.Unpack64())
}
// String represents a key
type String struct {
name string
description string
}
// NewString creates a new Key for int64 values.
func NewString(name, description string) *String {
return &String{name: name, description: description}
}
func (k *String) Name() string { return k.name }
func (k *String) Description() string { return k.description }
func (k *String) Format(w io.Writer, buf []byte, l label.Label) {
w.Write(strconv.AppendQuote(buf, k.From(l)))
}
// Of creates a new Label with this key and the supplied value.
func (k *String) Of(v string) label.Label { return label.OfString(k, v) }
// Get can be used to get a label for the key from a label.Map.
func (k *String) Get(lm label.Map) string {
if t := lm.Find(k); t.Valid() {
return k.From(t)
}
return ""
}
// From can be used to get a value from a Label.
func (k *String) From(t label.Label) string { return t.UnpackString() }
// Boolean represents a key
type Boolean struct {
name string
description string
}
// NewBoolean creates a new Key for bool values.
func NewBoolean(name, description string) *Boolean {
return &Boolean{name: name, description: description}
}
func (k *Boolean) Name() string { return k.name }
func (k *Boolean) Description() string { return k.description }
func (k *Boolean) Format(w io.Writer, buf []byte, l label.Label) {
w.Write(strconv.AppendBool(buf, k.From(l)))
}
// Of creates a new Label with this key and the supplied value.
func (k *Boolean) Of(v bool) label.Label {
if v {
return label.Of64(k, 1)
}
return label.Of64(k, 0)
}
// Get can be used to get a label for the key from a label.Map.
func (k *Boolean) Get(lm label.Map) bool {
if t := lm.Find(k); t.Valid() {
return k.From(t)
}
return false
}
// From can be used to get a value from a Label.
func (k *Boolean) From(t label.Label) bool { return t.Unpack64() > 0 }
// Error represents a key
type Error struct {
name string
description string
}
// NewError creates a new Key for int64 values.
func NewError(name, description string) *Error {
return &Error{name: name, description: description}
}
func (k *Error) Name() string { return k.name }
func (k *Error) Description() string { return k.description }
func (k *Error) Format(w io.Writer, buf []byte, l label.Label) {
io.WriteString(w, k.From(l).Error())
}
// Of creates a new Label with this key and the supplied value.
func (k *Error) Of(v error) label.Label { return label.OfValue(k, v) }
// Get can be used to get a label for the key from a label.Map.
func (k *Error) Get(lm label.Map) error {
if t := lm.Find(k); t.Valid() {
return k.From(t)
}
return nil
}
// From can be used to get a value from a Label.
func (k *Error) From(t label.Label) error {
err, _ := t.UnpackValue().(error)
return err
}
@@ -0,0 +1,22 @@
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package keys
var (
// Msg is a key used to add message strings to label lists.
Msg = NewString("message", "a readable message")
// Label is a key used to indicate an event adds labels to the context.
Label = NewTag("label", "a label context marker")
// Start is used for things like traces that have a name.
Start = NewString("start", "span start")
// Metric is a key used to indicate an event records metrics.
End = NewTag("end", "a span end marker")
// Metric is a key used to indicate an event records metrics.
Detach = NewTag("detach", "a span detach marker")
// Err is a key used to add error values to label lists.
Err = NewError("error", "an error that occurred")
// Metric is a key used to indicate an event records metrics.
Metric = NewTag("metric", "a metric event marker")
)
@@ -0,0 +1,21 @@
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package keys
import (
"sort"
"strings"
)
// Join returns a canonical join of the keys in S:
// a sorted comma-separated string list.
func Join[S ~[]T, T ~string](s S) string {
strs := make([]string, 0, len(s))
for _, v := range s {
strs = append(strs, string(v))
}
sort.Strings(strs)
return strings.Join(strs, ",")
}
@@ -0,0 +1,29 @@
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package keys
import "testing"
func TestJoin(t *testing.T) {
type T string
type S []T
tests := []struct {
data S
want string
}{
{S{"a", "b", "c"}, "a,b,c"},
{S{"b", "a", "c"}, "a,b,c"},
{S{"c", "a", "b"}, "a,b,c"},
{nil, ""},
{S{}, ""},
}
for _, test := range tests {
if got := Join(test.data); got != test.want {
t.Errorf("Join(%v) = %q, want %q", test.data, got, test.want)
}
}
}
@@ -0,0 +1,215 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package label
import (
"fmt"
"io"
"reflect"
"unsafe"
)
// Key is used as the identity of a Label.
// Keys are intended to be compared by pointer only, the name should be unique
// for communicating with external systems, but it is not required or enforced.
type Key interface {
// Name returns the key name.
Name() string
// Description returns a string that can be used to describe the value.
Description() string
// Format is used in formatting to append the value of the label to the
// supplied buffer.
// The formatter may use the supplied buf as a scratch area to avoid
// allocations.
Format(w io.Writer, buf []byte, l Label)
}
// Label holds a key and value pair.
// It is normally used when passing around lists of labels.
type Label struct {
key Key
packed uint64
untyped interface{}
}
// Map is the interface to a collection of Labels indexed by key.
type Map interface {
// Find returns the label that matches the supplied key.
Find(key Key) Label
}
// List is the interface to something that provides an iterable
// list of labels.
// Iteration should start from 0 and continue until Valid returns false.
type List interface {
// Valid returns true if the index is within range for the list.
// It does not imply the label at that index will itself be valid.
Valid(index int) bool
// Label returns the label at the given index.
Label(index int) Label
}
// list implements LabelList for a list of Labels.
type list struct {
labels []Label
}
// filter wraps a LabelList filtering out specific labels.
type filter struct {
keys []Key
underlying List
}
// listMap implements LabelMap for a simple list of labels.
type listMap struct {
labels []Label
}
// mapChain implements LabelMap for a list of underlying LabelMap.
type mapChain struct {
maps []Map
}
// OfValue creates a new label from the key and value.
// This method is for implementing new key types, label creation should
// normally be done with the Of method of the key.
func OfValue(k Key, value interface{}) Label { return Label{key: k, untyped: value} }
// UnpackValue assumes the label was built using LabelOfValue and returns the value
// that was passed to that constructor.
// This method is for implementing new key types, for type safety normal
// access should be done with the From method of the key.
func (t Label) UnpackValue() interface{} { return t.untyped }
// Of64 creates a new label from a key and a uint64. This is often
// used for non uint64 values that can be packed into a uint64.
// This method is for implementing new key types, label creation should
// normally be done with the Of method of the key.
func Of64(k Key, v uint64) Label { return Label{key: k, packed: v} }
// Unpack64 assumes the label was built using LabelOf64 and returns the value that
// was passed to that constructor.
// This method is for implementing new key types, for type safety normal
// access should be done with the From method of the key.
func (t Label) Unpack64() uint64 { return t.packed }
type stringptr unsafe.Pointer
// OfString creates a new label from a key and a string.
// This method is for implementing new key types, label creation should
// normally be done with the Of method of the key.
func OfString(k Key, v string) Label {
hdr := (*reflect.StringHeader)(unsafe.Pointer(&v))
return Label{
key: k,
packed: uint64(hdr.Len),
untyped: stringptr(hdr.Data),
}
}
// UnpackString assumes the label was built using LabelOfString and returns the
// value that was passed to that constructor.
// This method is for implementing new key types, for type safety normal
// access should be done with the From method of the key.
func (t Label) UnpackString() string {
var v string
hdr := (*reflect.StringHeader)(unsafe.Pointer(&v))
hdr.Data = uintptr(t.untyped.(stringptr))
hdr.Len = int(t.packed)
return v
}
// Valid returns true if the Label is a valid one (it has a key).
func (t Label) Valid() bool { return t.key != nil }
// Key returns the key of this Label.
func (t Label) Key() Key { return t.key }
// Format is used for debug printing of labels.
func (t Label) Format(f fmt.State, r rune) {
if !t.Valid() {
io.WriteString(f, `nil`)
return
}
io.WriteString(f, t.Key().Name())
io.WriteString(f, "=")
var buf [128]byte
t.Key().Format(f, buf[:0], t)
}
func (l *list) Valid(index int) bool {
return index >= 0 && index < len(l.labels)
}
func (l *list) Label(index int) Label {
return l.labels[index]
}
func (f *filter) Valid(index int) bool {
return f.underlying.Valid(index)
}
func (f *filter) Label(index int) Label {
l := f.underlying.Label(index)
for _, f := range f.keys {
if l.Key() == f {
return Label{}
}
}
return l
}
func (lm listMap) Find(key Key) Label {
for _, l := range lm.labels {
if l.Key() == key {
return l
}
}
return Label{}
}
func (c mapChain) Find(key Key) Label {
for _, src := range c.maps {
l := src.Find(key)
if l.Valid() {
return l
}
}
return Label{}
}
var emptyList = &list{}
func NewList(labels ...Label) List {
if len(labels) == 0 {
return emptyList
}
return &list{labels: labels}
}
func Filter(l List, keys ...Key) List {
if len(keys) == 0 {
return l
}
return &filter{keys: keys, underlying: l}
}
func NewMap(labels ...Label) Map {
return listMap{labels: labels}
}
func MergeMaps(srcs ...Map) Map {
var nonNil []Map
for _, src := range srcs {
if src != nil {
nonNil = append(nonNil, src)
}
}
if len(nonNil) == 1 {
return nonNil[0]
}
return mapChain{maps: nonNil}
}
@@ -0,0 +1,285 @@
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package label_test
import (
"bytes"
"fmt"
"runtime"
"testing"
"unsafe"
"golang.org/x/tools/internal/event/keys"
"golang.org/x/tools/internal/event/label"
)
var (
AKey = keys.NewString("A", "")
BKey = keys.NewString("B", "")
CKey = keys.NewString("C", "")
A = AKey.Of("a")
B = BKey.Of("b")
C = CKey.Of("c")
all = []label.Label{A, B, C}
)
func TestList(t *testing.T) {
for _, test := range []struct {
name string
labels []label.Label
expect string
}{{
name: "empty",
}, {
name: "single",
labels: []label.Label{A},
expect: `A="a"`,
}, {
name: "invalid",
labels: []label.Label{{}},
expect: ``,
}, {
name: "two",
labels: []label.Label{A, B},
expect: `A="a", B="b"`,
}, {
name: "three",
labels: []label.Label{A, B, C},
expect: `A="a", B="b", C="c"`,
}, {
name: "missing A",
labels: []label.Label{{}, B, C},
expect: `B="b", C="c"`,
}, {
name: "missing B",
labels: []label.Label{A, {}, C},
expect: `A="a", C="c"`,
}, {
name: "missing C",
labels: []label.Label{A, B, {}},
expect: `A="a", B="b"`,
}, {
name: "missing AB",
labels: []label.Label{{}, {}, C},
expect: `C="c"`,
}, {
name: "missing AC",
labels: []label.Label{{}, B, {}},
expect: `B="b"`,
}, {
name: "missing BC",
labels: []label.Label{A, {}, {}},
expect: `A="a"`,
}} {
t.Run(test.name, func(t *testing.T) {
got := printList(label.NewList(test.labels...))
if got != test.expect {
t.Errorf("got %q want %q", got, test.expect)
}
})
}
}
func TestFilter(t *testing.T) {
for _, test := range []struct {
name string
labels []label.Label
filters []label.Key
expect string
}{{
name: "no filters",
labels: all,
expect: `A="a", B="b", C="c"`,
}, {
name: "no labels",
filters: []label.Key{AKey},
expect: ``,
}, {
name: "filter A",
labels: all,
filters: []label.Key{AKey},
expect: `B="b", C="c"`,
}, {
name: "filter B",
labels: all,
filters: []label.Key{BKey},
expect: `A="a", C="c"`,
}, {
name: "filter C",
labels: all,
filters: []label.Key{CKey},
expect: `A="a", B="b"`,
}, {
name: "filter AC",
labels: all,
filters: []label.Key{AKey, CKey},
expect: `B="b"`,
}} {
t.Run(test.name, func(t *testing.T) {
labels := label.NewList(test.labels...)
got := printList(label.Filter(labels, test.filters...))
if got != test.expect {
t.Errorf("got %q want %q", got, test.expect)
}
})
}
}
func TestMap(t *testing.T) {
for _, test := range []struct {
name string
labels []label.Label
keys []label.Key
expect string
}{{
name: "no labels",
keys: []label.Key{AKey},
expect: `nil`,
}, {
name: "match A",
labels: all,
keys: []label.Key{AKey},
expect: `A="a"`,
}, {
name: "match B",
labels: all,
keys: []label.Key{BKey},
expect: `B="b"`,
}, {
name: "match C",
labels: all,
keys: []label.Key{CKey},
expect: `C="c"`,
}, {
name: "match ABC",
labels: all,
keys: []label.Key{AKey, BKey, CKey},
expect: `A="a", B="b", C="c"`,
}, {
name: "missing A",
labels: []label.Label{{}, B, C},
keys: []label.Key{AKey, BKey, CKey},
expect: `nil, B="b", C="c"`,
}, {
name: "missing B",
labels: []label.Label{A, {}, C},
keys: []label.Key{AKey, BKey, CKey},
expect: `A="a", nil, C="c"`,
}, {
name: "missing C",
labels: []label.Label{A, B, {}},
keys: []label.Key{AKey, BKey, CKey},
expect: `A="a", B="b", nil`,
}} {
t.Run(test.name, func(t *testing.T) {
lm := label.NewMap(test.labels...)
got := printMap(lm, test.keys)
if got != test.expect {
t.Errorf("got %q want %q", got, test.expect)
}
})
}
}
func TestMapMerge(t *testing.T) {
for _, test := range []struct {
name string
maps []label.Map
keys []label.Key
expect string
}{{
name: "no maps",
keys: []label.Key{AKey},
expect: `nil`,
}, {
name: "one map",
maps: []label.Map{label.NewMap(all...)},
keys: []label.Key{AKey},
expect: `A="a"`,
}, {
name: "invalid map",
maps: []label.Map{label.NewMap()},
keys: []label.Key{AKey},
expect: `nil`,
}, {
name: "two maps",
maps: []label.Map{label.NewMap(B, C), label.NewMap(A)},
keys: []label.Key{AKey, BKey, CKey},
expect: `A="a", B="b", C="c"`,
}, {
name: "invalid start map",
maps: []label.Map{label.NewMap(), label.NewMap(B, C)},
keys: []label.Key{AKey, BKey, CKey},
expect: `nil, B="b", C="c"`,
}, {
name: "invalid mid map",
maps: []label.Map{label.NewMap(A), label.NewMap(), label.NewMap(C)},
keys: []label.Key{AKey, BKey, CKey},
expect: `A="a", nil, C="c"`,
}, {
name: "invalid end map",
maps: []label.Map{label.NewMap(A, B), label.NewMap()},
keys: []label.Key{AKey, BKey, CKey},
expect: `A="a", B="b", nil`,
}, {
name: "three maps one nil",
maps: []label.Map{label.NewMap(A), label.NewMap(B), nil},
keys: []label.Key{AKey, BKey, CKey},
expect: `A="a", B="b", nil`,
}, {
name: "two maps one nil",
maps: []label.Map{label.NewMap(A, B), nil},
keys: []label.Key{AKey, BKey, CKey},
expect: `A="a", B="b", nil`,
}} {
t.Run(test.name, func(t *testing.T) {
tagMap := label.MergeMaps(test.maps...)
got := printMap(tagMap, test.keys)
if got != test.expect {
t.Errorf("got %q want %q", got, test.expect)
}
})
}
}
func printList(list label.List) string {
buf := &bytes.Buffer{}
for index := 0; list.Valid(index); index++ {
l := list.Label(index)
if !l.Valid() {
continue
}
if buf.Len() > 0 {
buf.WriteString(", ")
}
fmt.Fprint(buf, l)
}
return buf.String()
}
func printMap(lm label.Map, keys []label.Key) string {
buf := &bytes.Buffer{}
for _, key := range keys {
if buf.Len() > 0 {
buf.WriteString(", ")
}
fmt.Fprint(buf, lm.Find(key))
}
return buf.String()
}
func TestAttemptedStringCorruption(t *testing.T) {
defer func() {
r := recover()
if _, ok := r.(*runtime.TypeAssertionError); !ok {
t.Fatalf("wanted to recover TypeAssertionError, got %T", r)
}
}()
var x uint64 = 12390
p := unsafe.Pointer(&x)
l := label.OfValue(AKey, p)
_ = l.UnpackString()
}