whatcanGOwrong
This commit is contained in:
@@ -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: ×tamp,
|
||||
},
|
||||
}
|
||||
case *metric.Float64Data:
|
||||
timestamp := convertTimestamp(d.EndTime)
|
||||
return []*wire.Point{
|
||||
{
|
||||
Value: wire.PointDoubleValue{
|
||||
DoubleValue: d.Rows[i],
|
||||
},
|
||||
Timestamp: ×tamp,
|
||||
},
|
||||
}
|
||||
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: ×tamp,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// 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"`
|
||||
}
|
||||
+80
@@ -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()
|
||||
}
|
||||
Reference in New Issue
Block a user